sparkecoder 0.1.24 → 0.1.26
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 +3 -4
- package/dist/agent/index.js +670 -470
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +1990 -1022
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +145 -122
- package/dist/db/index.js +276 -646
- package/dist/db/index.js.map +1 -1
- package/dist/{index-CwQ-PrZw.d.ts → index-CWO1mYcx.d.ts} +87 -16
- package/dist/index.d.ts +6 -9
- package/dist/index.js +1026 -888
- package/dist/index.js.map +1 -1
- package/dist/schema-NcQknWCg.d.ts +295 -0
- package/dist/{search-S0REHtvA.d.ts → search-DApagzr3.d.ts} +5 -5
- package/dist/server/index.js +1026 -888
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +38 -4
- package/dist/tools/index.js +569 -291
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -7
- 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/dist/schema-BqpMl6XE.d.ts +0 -1614
- /package/web/.next/standalone/web/.next/static/{static/vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → static/gnPGpNaKfNwMKujKqMYAv}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → static/gnPGpNaKfNwMKujKqMYAv}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → static/gnPGpNaKfNwMKujKqMYAv}/_ssgManifest.js +0 -0
- /package/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_buildManifest.js +0 -0
- /package/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{vMB9qfM9a7tfGQAYWRMr8 → gnPGpNaKfNwMKujKqMYAv}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __export = (target, all) => {
|
|
|
10
10
|
|
|
11
11
|
// src/config/types.ts
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, SparkcoderConfigSchema;
|
|
13
|
+
var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
|
|
14
14
|
var init_types = __esm({
|
|
15
15
|
"src/config/types.ts"() {
|
|
16
16
|
"use strict";
|
|
@@ -35,6 +35,53 @@ var init_types = __esm({
|
|
|
35
35
|
skillsDirectory: z.string().optional(),
|
|
36
36
|
maxContextChars: z.number().optional().default(2e5)
|
|
37
37
|
});
|
|
38
|
+
VectorGatewayConfigSchema = z.object({
|
|
39
|
+
// Redis URL for Vector Gateway (or use VECTOR_REDIS_URL env var)
|
|
40
|
+
redisUrl: z.string().optional(),
|
|
41
|
+
// HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
|
|
42
|
+
httpUrl: z.string().optional(),
|
|
43
|
+
// Embedding model to use (default: text-embedding-3-small)
|
|
44
|
+
embeddingModel: z.string().default("gemini-embedding-001"),
|
|
45
|
+
// Custom namespace override (auto-generated from git remote if not set)
|
|
46
|
+
namespace: z.string().optional(),
|
|
47
|
+
// File patterns to include in indexing
|
|
48
|
+
include: z.array(z.string()).optional().default([
|
|
49
|
+
"**/*.ts",
|
|
50
|
+
"**/*.tsx",
|
|
51
|
+
"**/*.js",
|
|
52
|
+
"**/*.jsx",
|
|
53
|
+
"**/*.py",
|
|
54
|
+
"**/*.go",
|
|
55
|
+
"**/*.rs",
|
|
56
|
+
"**/*.java",
|
|
57
|
+
"**/*.md",
|
|
58
|
+
"**/*.mdx",
|
|
59
|
+
"**/*.txt"
|
|
60
|
+
]),
|
|
61
|
+
// File patterns to exclude from indexing
|
|
62
|
+
exclude: z.array(z.string()).optional().default([
|
|
63
|
+
"**/node_modules/**",
|
|
64
|
+
"**/dist/**",
|
|
65
|
+
"**/build/**",
|
|
66
|
+
"**/.git/**",
|
|
67
|
+
"**/.next/**",
|
|
68
|
+
"**/*.min.js",
|
|
69
|
+
"**/*.bundle.js",
|
|
70
|
+
"**/pnpm-lock.yaml",
|
|
71
|
+
"**/package-lock.json",
|
|
72
|
+
"**/yarn.lock",
|
|
73
|
+
"**/.test-workspace/**",
|
|
74
|
+
"**/.semantic-test-workspace/**",
|
|
75
|
+
"**/.semantic-integration-test/**"
|
|
76
|
+
])
|
|
77
|
+
}).optional();
|
|
78
|
+
RemoteServerConfigSchema = z.object({
|
|
79
|
+
// URL of the remote server (e.g., https://agent.sparkecode.com)
|
|
80
|
+
url: z.string().url().optional(),
|
|
81
|
+
// Auth key for the remote server (auto-generated on first use if not set)
|
|
82
|
+
// Can also be set via SPARKECODER_AUTH_KEY env var
|
|
83
|
+
authKey: z.string().optional()
|
|
84
|
+
}).optional();
|
|
38
85
|
SparkcoderConfigSchema = z.object({
|
|
39
86
|
// Default model to use (Vercel AI Gateway format)
|
|
40
87
|
defaultModel: z.string().default("anthropic/claude-opus-4-5"),
|
|
@@ -68,8 +115,13 @@ var init_types = __esm({
|
|
|
68
115
|
// If not set, defaults to http://{host}:{port}
|
|
69
116
|
publicUrl: z.string().url().optional()
|
|
70
117
|
}).default({ port: 3141, host: "127.0.0.1" }),
|
|
71
|
-
// Database path
|
|
72
|
-
databasePath: z.string().optional().default("./sparkecoder.db")
|
|
118
|
+
// Database path (used for local SQLite - ignored if remoteServer is configured)
|
|
119
|
+
databasePath: z.string().optional().default("./sparkecoder.db"),
|
|
120
|
+
// Remote server configuration (for centralized storage)
|
|
121
|
+
// If configured, uses remote MongoDB instead of local SQLite
|
|
122
|
+
remoteServer: RemoteServerConfigSchema,
|
|
123
|
+
// Vector Gateway configuration for semantic search
|
|
124
|
+
vectorGateway: VectorGatewayConfigSchema
|
|
73
125
|
});
|
|
74
126
|
}
|
|
75
127
|
});
|
|
@@ -398,7 +450,7 @@ var init_skills = __esm({
|
|
|
398
450
|
import {
|
|
399
451
|
streamText as streamText2,
|
|
400
452
|
generateText as generateText3,
|
|
401
|
-
tool as
|
|
453
|
+
tool as tool10,
|
|
402
454
|
stepCountIs as stepCountIs2
|
|
403
455
|
} from "ai";
|
|
404
456
|
|
|
@@ -419,730 +471,345 @@ var SUBAGENT_MODELS = {
|
|
|
419
471
|
};
|
|
420
472
|
|
|
421
473
|
// src/agent/index.ts
|
|
422
|
-
import { z as
|
|
423
|
-
import { nanoid as
|
|
424
|
-
|
|
425
|
-
// src/db/index.ts
|
|
426
|
-
import Database from "better-sqlite3";
|
|
427
|
-
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
428
|
-
import { eq, desc, and, sql } from "drizzle-orm";
|
|
429
|
-
import { nanoid } from "nanoid";
|
|
430
|
-
|
|
431
|
-
// src/db/schema.ts
|
|
432
|
-
var schema_exports = {};
|
|
433
|
-
__export(schema_exports, {
|
|
434
|
-
activeStreams: () => activeStreams,
|
|
435
|
-
checkpoints: () => checkpoints,
|
|
436
|
-
fileBackups: () => fileBackups,
|
|
437
|
-
loadedSkills: () => loadedSkills,
|
|
438
|
-
messages: () => messages,
|
|
439
|
-
sessions: () => sessions,
|
|
440
|
-
subagentExecutions: () => subagentExecutions,
|
|
441
|
-
terminals: () => terminals,
|
|
442
|
-
todoItems: () => todoItems,
|
|
443
|
-
toolExecutions: () => toolExecutions
|
|
444
|
-
});
|
|
445
|
-
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
446
|
-
var sessions = sqliteTable("sessions", {
|
|
447
|
-
id: text("id").primaryKey(),
|
|
448
|
-
name: text("name"),
|
|
449
|
-
workingDirectory: text("working_directory").notNull(),
|
|
450
|
-
model: text("model").notNull(),
|
|
451
|
-
status: text("status", { enum: ["active", "waiting", "completed", "error"] }).notNull().default("active"),
|
|
452
|
-
config: text("config", { mode: "json" }).$type(),
|
|
453
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
454
|
-
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
455
|
-
});
|
|
456
|
-
var messages = sqliteTable("messages", {
|
|
457
|
-
id: text("id").primaryKey(),
|
|
458
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
459
|
-
// Store the entire ModelMessage as JSON (role + content)
|
|
460
|
-
modelMessage: text("model_message", { mode: "json" }).$type().notNull(),
|
|
461
|
-
// Sequence number within session to maintain exact ordering
|
|
462
|
-
sequence: integer("sequence").notNull().default(0),
|
|
463
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
464
|
-
});
|
|
465
|
-
var toolExecutions = sqliteTable("tool_executions", {
|
|
466
|
-
id: text("id").primaryKey(),
|
|
467
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
468
|
-
messageId: text("message_id").references(() => messages.id, { onDelete: "cascade" }),
|
|
469
|
-
toolName: text("tool_name").notNull(),
|
|
470
|
-
toolCallId: text("tool_call_id").notNull(),
|
|
471
|
-
input: text("input", { mode: "json" }),
|
|
472
|
-
output: text("output", { mode: "json" }),
|
|
473
|
-
status: text("status", { enum: ["pending", "approved", "rejected", "completed", "error"] }).notNull().default("pending"),
|
|
474
|
-
requiresApproval: integer("requires_approval", { mode: "boolean" }).notNull().default(false),
|
|
475
|
-
error: text("error"),
|
|
476
|
-
startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
477
|
-
completedAt: integer("completed_at", { mode: "timestamp" })
|
|
478
|
-
});
|
|
479
|
-
var todoItems = sqliteTable("todo_items", {
|
|
480
|
-
id: text("id").primaryKey(),
|
|
481
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
482
|
-
content: text("content").notNull(),
|
|
483
|
-
status: text("status", { enum: ["pending", "in_progress", "completed", "cancelled"] }).notNull().default("pending"),
|
|
484
|
-
order: integer("order").notNull().default(0),
|
|
485
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
486
|
-
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
487
|
-
});
|
|
488
|
-
var loadedSkills = sqliteTable("loaded_skills", {
|
|
489
|
-
id: text("id").primaryKey(),
|
|
490
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
491
|
-
skillName: text("skill_name").notNull(),
|
|
492
|
-
loadedAt: integer("loaded_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
493
|
-
});
|
|
494
|
-
var terminals = sqliteTable("terminals", {
|
|
495
|
-
id: text("id").primaryKey(),
|
|
496
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
497
|
-
name: text("name"),
|
|
498
|
-
// Optional friendly name (e.g., "dev-server")
|
|
499
|
-
command: text("command").notNull(),
|
|
500
|
-
// The command that was run
|
|
501
|
-
cwd: text("cwd").notNull(),
|
|
502
|
-
// Working directory
|
|
503
|
-
pid: integer("pid"),
|
|
504
|
-
// Process ID (null if not running)
|
|
505
|
-
status: text("status", { enum: ["running", "stopped", "error"] }).notNull().default("running"),
|
|
506
|
-
exitCode: integer("exit_code"),
|
|
507
|
-
// Exit code if stopped
|
|
508
|
-
error: text("error"),
|
|
509
|
-
// Error message if status is 'error'
|
|
510
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
511
|
-
stoppedAt: integer("stopped_at", { mode: "timestamp" })
|
|
512
|
-
});
|
|
513
|
-
var activeStreams = sqliteTable("active_streams", {
|
|
514
|
-
id: text("id").primaryKey(),
|
|
515
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
516
|
-
streamId: text("stream_id").notNull().unique(),
|
|
517
|
-
// Unique stream identifier
|
|
518
|
-
status: text("status", { enum: ["active", "finished", "error"] }).notNull().default("active"),
|
|
519
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
520
|
-
finishedAt: integer("finished_at", { mode: "timestamp" })
|
|
521
|
-
});
|
|
522
|
-
var checkpoints = sqliteTable("checkpoints", {
|
|
523
|
-
id: text("id").primaryKey(),
|
|
524
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
525
|
-
// The message sequence number this checkpoint was created BEFORE
|
|
526
|
-
// (i.e., the state before this user message was processed)
|
|
527
|
-
messageSequence: integer("message_sequence").notNull(),
|
|
528
|
-
// Optional git commit hash if in a git repo
|
|
529
|
-
gitHead: text("git_head"),
|
|
530
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
531
|
-
});
|
|
532
|
-
var fileBackups = sqliteTable("file_backups", {
|
|
533
|
-
id: text("id").primaryKey(),
|
|
534
|
-
checkpointId: text("checkpoint_id").notNull().references(() => checkpoints.id, { onDelete: "cascade" }),
|
|
535
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
536
|
-
// Relative path from working directory
|
|
537
|
-
filePath: text("file_path").notNull(),
|
|
538
|
-
// Original content (null means file didn't exist before)
|
|
539
|
-
originalContent: text("original_content"),
|
|
540
|
-
// Whether the file existed before this checkpoint
|
|
541
|
-
existed: integer("existed", { mode: "boolean" }).notNull().default(true),
|
|
542
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
543
|
-
});
|
|
544
|
-
var subagentExecutions = sqliteTable("subagent_executions", {
|
|
545
|
-
id: text("id").primaryKey(),
|
|
546
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
547
|
-
toolCallId: text("tool_call_id").notNull(),
|
|
548
|
-
// The tool call that spawned this subagent
|
|
549
|
-
subagentType: text("subagent_type").notNull(),
|
|
550
|
-
// e.g., 'search', 'analyze', etc.
|
|
551
|
-
task: text("task").notNull(),
|
|
552
|
-
// The task/query given to the subagent
|
|
553
|
-
model: text("model").notNull(),
|
|
554
|
-
// The model used (e.g., 'gemini-2.0-flash')
|
|
555
|
-
status: text("status", { enum: ["running", "completed", "error", "cancelled"] }).notNull().default("running"),
|
|
556
|
-
// Steps taken by the subagent (stored as JSON array)
|
|
557
|
-
steps: text("steps", { mode: "json" }).$type().default([]),
|
|
558
|
-
// Final result/output
|
|
559
|
-
result: text("result", { mode: "json" }),
|
|
560
|
-
error: text("error"),
|
|
561
|
-
startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
562
|
-
completedAt: integer("completed_at", { mode: "timestamp" })
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// src/db/index.ts
|
|
566
|
-
var db = null;
|
|
567
|
-
var sqlite = null;
|
|
568
|
-
function initDatabase(dbPath) {
|
|
569
|
-
sqlite = new Database(dbPath);
|
|
570
|
-
sqlite.pragma("journal_mode = WAL");
|
|
571
|
-
db = drizzle(sqlite, { schema: schema_exports });
|
|
572
|
-
sqlite.exec(`
|
|
573
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
574
|
-
id TEXT PRIMARY KEY,
|
|
575
|
-
name TEXT,
|
|
576
|
-
working_directory TEXT NOT NULL,
|
|
577
|
-
model TEXT NOT NULL,
|
|
578
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
579
|
-
config TEXT,
|
|
580
|
-
created_at INTEGER NOT NULL,
|
|
581
|
-
updated_at INTEGER NOT NULL
|
|
582
|
-
);
|
|
583
|
-
|
|
584
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
585
|
-
id TEXT PRIMARY KEY,
|
|
586
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
587
|
-
model_message TEXT NOT NULL,
|
|
588
|
-
sequence INTEGER NOT NULL DEFAULT 0,
|
|
589
|
-
created_at INTEGER NOT NULL
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
CREATE TABLE IF NOT EXISTS tool_executions (
|
|
593
|
-
id TEXT PRIMARY KEY,
|
|
594
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
595
|
-
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
|
596
|
-
tool_name TEXT NOT NULL,
|
|
597
|
-
tool_call_id TEXT NOT NULL,
|
|
598
|
-
input TEXT,
|
|
599
|
-
output TEXT,
|
|
600
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
601
|
-
requires_approval INTEGER NOT NULL DEFAULT 0,
|
|
602
|
-
error TEXT,
|
|
603
|
-
started_at INTEGER NOT NULL,
|
|
604
|
-
completed_at INTEGER
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
CREATE TABLE IF NOT EXISTS todo_items (
|
|
608
|
-
id TEXT PRIMARY KEY,
|
|
609
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
610
|
-
content TEXT NOT NULL,
|
|
611
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
612
|
-
"order" INTEGER NOT NULL DEFAULT 0,
|
|
613
|
-
created_at INTEGER NOT NULL,
|
|
614
|
-
updated_at INTEGER NOT NULL
|
|
615
|
-
);
|
|
616
|
-
|
|
617
|
-
CREATE TABLE IF NOT EXISTS loaded_skills (
|
|
618
|
-
id TEXT PRIMARY KEY,
|
|
619
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
620
|
-
skill_name TEXT NOT NULL,
|
|
621
|
-
loaded_at INTEGER NOT NULL
|
|
622
|
-
);
|
|
623
|
-
|
|
624
|
-
CREATE TABLE IF NOT EXISTS terminals (
|
|
625
|
-
id TEXT PRIMARY KEY,
|
|
626
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
627
|
-
name TEXT,
|
|
628
|
-
command TEXT NOT NULL,
|
|
629
|
-
cwd TEXT NOT NULL,
|
|
630
|
-
pid INTEGER,
|
|
631
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
632
|
-
exit_code INTEGER,
|
|
633
|
-
error TEXT,
|
|
634
|
-
created_at INTEGER NOT NULL,
|
|
635
|
-
stopped_at INTEGER
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
-- Table for tracking active streams (for resumable streams)
|
|
639
|
-
CREATE TABLE IF NOT EXISTS active_streams (
|
|
640
|
-
id TEXT PRIMARY KEY,
|
|
641
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
642
|
-
stream_id TEXT NOT NULL UNIQUE,
|
|
643
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
644
|
-
created_at INTEGER NOT NULL,
|
|
645
|
-
finished_at INTEGER
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
-- Checkpoints table - created before each user message
|
|
649
|
-
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
650
|
-
id TEXT PRIMARY KEY,
|
|
651
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
652
|
-
message_sequence INTEGER NOT NULL,
|
|
653
|
-
git_head TEXT,
|
|
654
|
-
created_at INTEGER NOT NULL
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
-- File backups table - stores original file content
|
|
658
|
-
CREATE TABLE IF NOT EXISTS file_backups (
|
|
659
|
-
id TEXT PRIMARY KEY,
|
|
660
|
-
checkpoint_id TEXT NOT NULL REFERENCES checkpoints(id) ON DELETE CASCADE,
|
|
661
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
662
|
-
file_path TEXT NOT NULL,
|
|
663
|
-
original_content TEXT,
|
|
664
|
-
existed INTEGER NOT NULL DEFAULT 1,
|
|
665
|
-
created_at INTEGER NOT NULL
|
|
666
|
-
);
|
|
667
|
-
|
|
668
|
-
-- Subagent executions table - tracks subagent runs
|
|
669
|
-
CREATE TABLE IF NOT EXISTS subagent_executions (
|
|
670
|
-
id TEXT PRIMARY KEY,
|
|
671
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
672
|
-
tool_call_id TEXT NOT NULL,
|
|
673
|
-
subagent_type TEXT NOT NULL,
|
|
674
|
-
task TEXT NOT NULL,
|
|
675
|
-
model TEXT NOT NULL,
|
|
676
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
677
|
-
steps TEXT DEFAULT '[]',
|
|
678
|
-
result TEXT,
|
|
679
|
-
error TEXT,
|
|
680
|
-
started_at INTEGER NOT NULL,
|
|
681
|
-
completed_at INTEGER
|
|
682
|
-
);
|
|
474
|
+
import { z as z11 } from "zod";
|
|
475
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
683
476
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);
|
|
691
|
-
CREATE INDEX IF NOT EXISTS idx_file_backups_checkpoint ON file_backups(checkpoint_id);
|
|
692
|
-
CREATE INDEX IF NOT EXISTS idx_file_backups_session ON file_backups(session_id);
|
|
693
|
-
CREATE INDEX IF NOT EXISTS idx_subagent_executions_session ON subagent_executions(session_id);
|
|
694
|
-
CREATE INDEX IF NOT EXISTS idx_subagent_executions_tool_call ON subagent_executions(tool_call_id);
|
|
695
|
-
`);
|
|
696
|
-
return db;
|
|
477
|
+
// src/db/remote.ts
|
|
478
|
+
var remoteServerUrl = null;
|
|
479
|
+
var authKey = null;
|
|
480
|
+
function initRemoteDatabase(serverUrl, key) {
|
|
481
|
+
remoteServerUrl = serverUrl.replace(/\/$/, "");
|
|
482
|
+
authKey = key;
|
|
697
483
|
}
|
|
698
|
-
function
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
return db;
|
|
484
|
+
function closeRemoteDatabase() {
|
|
485
|
+
remoteServerUrl = null;
|
|
486
|
+
authKey = null;
|
|
703
487
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
488
|
+
var DATE_FIELDS = ["createdAt", "updatedAt", "startedAt", "completedAt", "stoppedAt", "finishedAt", "loadedAt", "indexedAt", "lastFullIndex", "lastIncrementalIndex"];
|
|
489
|
+
function parseDates(obj) {
|
|
490
|
+
if (obj === null || obj === void 0) return obj;
|
|
491
|
+
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
492
|
+
if (typeof obj !== "object") return obj;
|
|
493
|
+
const result = { ...obj };
|
|
494
|
+
for (const key of Object.keys(result)) {
|
|
495
|
+
if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
|
|
496
|
+
result[key] = new Date(result[key]);
|
|
497
|
+
} else if (typeof result[key] === "object") {
|
|
498
|
+
result[key] = parseDates(result[key]);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
async function api(path, options = {}) {
|
|
504
|
+
if (!remoteServerUrl || !authKey) {
|
|
505
|
+
throw new Error("Remote database not initialized");
|
|
506
|
+
}
|
|
507
|
+
const url = `${remoteServerUrl}/db${path}`;
|
|
508
|
+
const init = {
|
|
509
|
+
method: options.method || "GET",
|
|
510
|
+
headers: {
|
|
511
|
+
"Content-Type": "application/json",
|
|
512
|
+
"Authorization": `Bearer ${authKey}`
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
if (options.body) {
|
|
516
|
+
init.body = JSON.stringify(options.body);
|
|
709
517
|
}
|
|
518
|
+
const response = await fetch(url, init);
|
|
519
|
+
if (!response.ok) {
|
|
520
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
521
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
522
|
+
}
|
|
523
|
+
const text = await response.text();
|
|
524
|
+
if (!text || text === "null") {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
return parseDates(JSON.parse(text));
|
|
710
528
|
}
|
|
711
|
-
var
|
|
529
|
+
var remoteSessionQueries = {
|
|
712
530
|
create(data) {
|
|
713
|
-
|
|
714
|
-
const now = /* @__PURE__ */ new Date();
|
|
715
|
-
const result = getDb().insert(sessions).values({
|
|
716
|
-
id,
|
|
717
|
-
...data,
|
|
718
|
-
createdAt: now,
|
|
719
|
-
updatedAt: now
|
|
720
|
-
}).returning().get();
|
|
721
|
-
return result;
|
|
531
|
+
return api("/sessions", { method: "POST", body: data });
|
|
722
532
|
},
|
|
723
533
|
getById(id) {
|
|
724
|
-
return
|
|
534
|
+
return api(`/sessions/${id}`).catch(() => void 0);
|
|
725
535
|
},
|
|
726
536
|
list(limit = 50, offset = 0) {
|
|
727
|
-
return
|
|
537
|
+
return api(`/sessions?limit=${limit}&offset=${offset}`);
|
|
728
538
|
},
|
|
729
539
|
updateStatus(id, status) {
|
|
730
|
-
return
|
|
540
|
+
return api(`/sessions/${id}`, { method: "PATCH", body: { status } });
|
|
731
541
|
},
|
|
732
542
|
updateModel(id, model) {
|
|
733
|
-
return
|
|
543
|
+
return api(`/sessions/${id}`, { method: "PATCH", body: { model } });
|
|
734
544
|
},
|
|
735
545
|
update(id, updates) {
|
|
736
|
-
return
|
|
546
|
+
return api(`/sessions/${id}`, { method: "PATCH", body: updates });
|
|
737
547
|
},
|
|
738
548
|
delete(id) {
|
|
739
|
-
|
|
740
|
-
return result.changes > 0;
|
|
549
|
+
return api(`/sessions/${id}`, { method: "DELETE" }).then((r) => r?.success ?? false);
|
|
741
550
|
}
|
|
742
551
|
};
|
|
743
|
-
var
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
getNextSequence(sessionId) {
|
|
748
|
-
const result = getDb().select({ maxSeq: sql`COALESCE(MAX(sequence), -1)` }).from(messages).where(eq(messages.sessionId, sessionId)).get();
|
|
749
|
-
return (result?.maxSeq ?? -1) + 1;
|
|
552
|
+
var remoteMessageQueries = {
|
|
553
|
+
async getNextSequence(sessionId) {
|
|
554
|
+
const result = await api(`/messages/session/${sessionId}/next-sequence`);
|
|
555
|
+
return result.nextSequence;
|
|
750
556
|
},
|
|
751
|
-
/**
|
|
752
|
-
* Create a single message from a ModelMessage
|
|
753
|
-
*/
|
|
754
557
|
create(sessionId, modelMessage) {
|
|
755
|
-
|
|
756
|
-
const sequence = this.getNextSequence(sessionId);
|
|
757
|
-
const result = getDb().insert(messages).values({
|
|
758
|
-
id,
|
|
759
|
-
sessionId,
|
|
760
|
-
modelMessage,
|
|
761
|
-
sequence,
|
|
762
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
763
|
-
}).returning().get();
|
|
764
|
-
return result;
|
|
558
|
+
return api("/messages", { method: "POST", body: { sessionId, modelMessage } });
|
|
765
559
|
},
|
|
766
|
-
/**
|
|
767
|
-
* Add multiple ModelMessages at once (from response.messages)
|
|
768
|
-
* Maintains insertion order via sequence numbers
|
|
769
|
-
*/
|
|
770
560
|
addMany(sessionId, modelMessages) {
|
|
771
|
-
|
|
772
|
-
let sequence = this.getNextSequence(sessionId);
|
|
773
|
-
for (const msg of modelMessages) {
|
|
774
|
-
const id = nanoid();
|
|
775
|
-
const result = getDb().insert(messages).values({
|
|
776
|
-
id,
|
|
777
|
-
sessionId,
|
|
778
|
-
modelMessage: msg,
|
|
779
|
-
sequence,
|
|
780
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
781
|
-
}).returning().get();
|
|
782
|
-
results.push(result);
|
|
783
|
-
sequence++;
|
|
784
|
-
}
|
|
785
|
-
return results;
|
|
561
|
+
return api("/messages/batch", { method: "POST", body: { sessionId, modelMessages } });
|
|
786
562
|
},
|
|
787
|
-
/**
|
|
788
|
-
* Get all messages for a session as ModelMessage[]
|
|
789
|
-
* Ordered by sequence to maintain exact insertion order
|
|
790
|
-
*/
|
|
791
563
|
getBySession(sessionId) {
|
|
792
|
-
return
|
|
564
|
+
return api(`/messages/session/${sessionId}`);
|
|
793
565
|
},
|
|
794
|
-
/**
|
|
795
|
-
* Get ModelMessages directly (for passing to AI SDK)
|
|
796
|
-
*/
|
|
797
566
|
getModelMessages(sessionId) {
|
|
798
|
-
|
|
799
|
-
return messages2.map((m) => m.modelMessage);
|
|
567
|
+
return api(`/messages/session/${sessionId}/model-messages`);
|
|
800
568
|
},
|
|
801
|
-
getRecentBySession(sessionId, limit = 50) {
|
|
802
|
-
|
|
569
|
+
async getRecentBySession(sessionId, limit = 50) {
|
|
570
|
+
const messages = await api(`/messages/session/${sessionId}`);
|
|
571
|
+
return messages.slice(-limit);
|
|
803
572
|
},
|
|
804
|
-
countBySession(sessionId) {
|
|
805
|
-
const result =
|
|
806
|
-
return result
|
|
573
|
+
async countBySession(sessionId) {
|
|
574
|
+
const result = await api(`/messages/session/${sessionId}/count`);
|
|
575
|
+
return result.count;
|
|
807
576
|
},
|
|
808
|
-
deleteBySession(sessionId) {
|
|
809
|
-
const result =
|
|
810
|
-
return result.
|
|
577
|
+
async deleteBySession(sessionId) {
|
|
578
|
+
const result = await api(`/messages/session/${sessionId}`, { method: "DELETE" });
|
|
579
|
+
return result.deleted;
|
|
811
580
|
},
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
and(
|
|
819
|
-
eq(messages.sessionId, sessionId),
|
|
820
|
-
sql`sequence >= ${fromSequence}`
|
|
821
|
-
)
|
|
822
|
-
).run();
|
|
823
|
-
return result.changes;
|
|
581
|
+
async deleteFromSequence(sessionId, fromSequence) {
|
|
582
|
+
const result = await api(
|
|
583
|
+
`/messages/session/${sessionId}/from-sequence/${fromSequence}`,
|
|
584
|
+
{ method: "DELETE" }
|
|
585
|
+
);
|
|
586
|
+
return result.deleted;
|
|
824
587
|
}
|
|
825
588
|
};
|
|
826
|
-
var
|
|
589
|
+
var remoteToolExecutionQueries = {
|
|
827
590
|
create(data) {
|
|
828
|
-
|
|
829
|
-
const result = getDb().insert(toolExecutions).values({
|
|
830
|
-
id,
|
|
831
|
-
...data,
|
|
832
|
-
startedAt: /* @__PURE__ */ new Date()
|
|
833
|
-
}).returning().get();
|
|
834
|
-
return result;
|
|
591
|
+
return api("/tool-executions", { method: "POST", body: data });
|
|
835
592
|
},
|
|
836
593
|
getById(id) {
|
|
837
|
-
return
|
|
594
|
+
return api(`/tool-executions/${id}`).catch(() => void 0);
|
|
838
595
|
},
|
|
839
596
|
getByToolCallId(toolCallId) {
|
|
840
|
-
return
|
|
597
|
+
return api(`/tool-executions/by-tool-call-id/${toolCallId}`).catch(() => void 0);
|
|
841
598
|
},
|
|
842
599
|
getPendingApprovals(sessionId) {
|
|
843
|
-
return
|
|
844
|
-
and(
|
|
845
|
-
eq(toolExecutions.sessionId, sessionId),
|
|
846
|
-
eq(toolExecutions.status, "pending"),
|
|
847
|
-
eq(toolExecutions.requiresApproval, true)
|
|
848
|
-
)
|
|
849
|
-
).all();
|
|
600
|
+
return api(`/tool-executions/session/${sessionId}/pending`);
|
|
850
601
|
},
|
|
851
602
|
approve(id) {
|
|
852
|
-
return
|
|
603
|
+
return api(`/tool-executions/${id}`, { method: "PATCH", body: { status: "approved" } });
|
|
853
604
|
},
|
|
854
605
|
reject(id) {
|
|
855
|
-
return
|
|
606
|
+
return api(`/tool-executions/${id}`, { method: "PATCH", body: { status: "rejected" } });
|
|
856
607
|
},
|
|
857
608
|
complete(id, output, error) {
|
|
858
|
-
return
|
|
859
|
-
|
|
860
|
-
output,
|
|
861
|
-
|
|
862
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
863
|
-
}).where(eq(toolExecutions.id, id)).returning().get();
|
|
609
|
+
return api(`/tool-executions/${id}`, {
|
|
610
|
+
method: "PATCH",
|
|
611
|
+
body: { status: error ? "error" : "completed", output, error }
|
|
612
|
+
});
|
|
864
613
|
},
|
|
865
614
|
getBySession(sessionId) {
|
|
866
|
-
return
|
|
615
|
+
return api(`/tool-executions/session/${sessionId}`);
|
|
867
616
|
},
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
eq(toolExecutions.sessionId, sessionId),
|
|
876
|
-
sql`started_at > ${afterTime.getTime()}`
|
|
877
|
-
)
|
|
878
|
-
).run();
|
|
879
|
-
return result.changes;
|
|
617
|
+
async deleteAfterTime(sessionId, afterTime) {
|
|
618
|
+
const timestamp = afterTime instanceof Date ? afterTime.getTime() : new Date(afterTime).getTime();
|
|
619
|
+
const result = await api(
|
|
620
|
+
`/tool-executions/session/${sessionId}/after/${timestamp}`,
|
|
621
|
+
{ method: "DELETE" }
|
|
622
|
+
);
|
|
623
|
+
return result.deleted;
|
|
880
624
|
}
|
|
881
625
|
};
|
|
882
|
-
var
|
|
626
|
+
var remoteTodoQueries = {
|
|
883
627
|
create(data) {
|
|
884
|
-
|
|
885
|
-
const now = /* @__PURE__ */ new Date();
|
|
886
|
-
const result = getDb().insert(todoItems).values({
|
|
887
|
-
id,
|
|
888
|
-
...data,
|
|
889
|
-
createdAt: now,
|
|
890
|
-
updatedAt: now
|
|
891
|
-
}).returning().get();
|
|
892
|
-
return result;
|
|
628
|
+
return api("/todos", { method: "POST", body: data });
|
|
893
629
|
},
|
|
894
630
|
createMany(sessionId, items) {
|
|
895
|
-
|
|
896
|
-
const values = items.map((item, index) => ({
|
|
897
|
-
id: nanoid(),
|
|
898
|
-
sessionId,
|
|
899
|
-
content: item.content,
|
|
900
|
-
order: item.order ?? index,
|
|
901
|
-
createdAt: now,
|
|
902
|
-
updatedAt: now
|
|
903
|
-
}));
|
|
904
|
-
return getDb().insert(todoItems).values(values).returning().all();
|
|
631
|
+
return api("/todos/batch", { method: "POST", body: { sessionId, items } });
|
|
905
632
|
},
|
|
906
633
|
getBySession(sessionId) {
|
|
907
|
-
return
|
|
634
|
+
return api(`/todos/session/${sessionId}`);
|
|
908
635
|
},
|
|
909
636
|
updateStatus(id, status) {
|
|
910
|
-
return
|
|
637
|
+
return api(`/todos/${id}`, { method: "PATCH", body: { status } });
|
|
911
638
|
},
|
|
912
|
-
delete(id) {
|
|
913
|
-
const result =
|
|
914
|
-
return result
|
|
639
|
+
async delete(id) {
|
|
640
|
+
const result = await api(`/todos/${id}`, { method: "DELETE" });
|
|
641
|
+
return result?.success ?? false;
|
|
915
642
|
},
|
|
916
|
-
clearSession(sessionId) {
|
|
917
|
-
const result =
|
|
918
|
-
return result.
|
|
643
|
+
async clearSession(sessionId) {
|
|
644
|
+
const result = await api(`/todos/session/${sessionId}`, { method: "DELETE" });
|
|
645
|
+
return result.deleted;
|
|
919
646
|
}
|
|
920
647
|
};
|
|
921
|
-
var
|
|
648
|
+
var remoteSkillQueries = {
|
|
922
649
|
load(sessionId, skillName) {
|
|
923
|
-
|
|
924
|
-
const result = getDb().insert(loadedSkills).values({
|
|
925
|
-
id,
|
|
926
|
-
sessionId,
|
|
927
|
-
skillName,
|
|
928
|
-
loadedAt: /* @__PURE__ */ new Date()
|
|
929
|
-
}).returning().get();
|
|
930
|
-
return result;
|
|
650
|
+
return api("/skills", { method: "POST", body: { sessionId, skillName } });
|
|
931
651
|
},
|
|
932
652
|
getBySession(sessionId) {
|
|
933
|
-
return
|
|
653
|
+
return api(`/skills/session/${sessionId}`);
|
|
934
654
|
},
|
|
935
|
-
isLoaded(sessionId, skillName) {
|
|
936
|
-
const result =
|
|
937
|
-
|
|
938
|
-
eq(loadedSkills.sessionId, sessionId),
|
|
939
|
-
eq(loadedSkills.skillName, skillName)
|
|
940
|
-
)
|
|
941
|
-
).get();
|
|
942
|
-
return !!result;
|
|
655
|
+
async isLoaded(sessionId, skillName) {
|
|
656
|
+
const result = await api(`/skills/session/${sessionId}/is-loaded/${skillName}`);
|
|
657
|
+
return result.isLoaded;
|
|
943
658
|
}
|
|
944
659
|
};
|
|
945
|
-
var
|
|
660
|
+
var remoteActiveStreamQueries = {
|
|
946
661
|
create(sessionId, streamId) {
|
|
947
|
-
|
|
948
|
-
const result = getDb().insert(activeStreams).values({
|
|
949
|
-
id,
|
|
950
|
-
sessionId,
|
|
951
|
-
streamId,
|
|
952
|
-
status: "active",
|
|
953
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
954
|
-
}).returning().get();
|
|
955
|
-
return result;
|
|
662
|
+
return api("/streams", { method: "POST", body: { sessionId, streamId } });
|
|
956
663
|
},
|
|
957
664
|
getBySessionId(sessionId) {
|
|
958
|
-
return
|
|
959
|
-
and(
|
|
960
|
-
eq(activeStreams.sessionId, sessionId),
|
|
961
|
-
eq(activeStreams.status, "active")
|
|
962
|
-
)
|
|
963
|
-
).get();
|
|
665
|
+
return api(`/streams/session/${sessionId}`).then((r) => r ?? void 0);
|
|
964
666
|
},
|
|
965
667
|
getByStreamId(streamId) {
|
|
966
|
-
return
|
|
668
|
+
return api(`/streams/by-stream-id/${streamId}`).catch(() => void 0);
|
|
967
669
|
},
|
|
968
670
|
finish(streamId) {
|
|
969
|
-
return
|
|
671
|
+
return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "finished" } });
|
|
970
672
|
},
|
|
971
673
|
markError(streamId) {
|
|
972
|
-
return
|
|
674
|
+
return api(`/streams/by-stream-id/${streamId}`, { method: "PATCH", body: { status: "error" } });
|
|
973
675
|
},
|
|
974
|
-
deleteBySession(sessionId) {
|
|
975
|
-
const result =
|
|
976
|
-
return result.
|
|
676
|
+
async deleteBySession(sessionId) {
|
|
677
|
+
const result = await api(`/streams/session/${sessionId}`, { method: "DELETE" });
|
|
678
|
+
return result.deleted;
|
|
977
679
|
}
|
|
978
680
|
};
|
|
979
|
-
var
|
|
681
|
+
var remoteCheckpointQueries = {
|
|
980
682
|
create(data) {
|
|
981
|
-
|
|
982
|
-
const result = getDb().insert(checkpoints).values({
|
|
983
|
-
id,
|
|
984
|
-
sessionId: data.sessionId,
|
|
985
|
-
messageSequence: data.messageSequence,
|
|
986
|
-
gitHead: data.gitHead,
|
|
987
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
988
|
-
}).returning().get();
|
|
989
|
-
return result;
|
|
683
|
+
return api("/checkpoints", { method: "POST", body: data });
|
|
990
684
|
},
|
|
991
685
|
getById(id) {
|
|
992
|
-
return
|
|
686
|
+
return api(`/checkpoints/${id}`).catch(() => void 0);
|
|
993
687
|
},
|
|
994
688
|
getBySession(sessionId) {
|
|
995
|
-
return
|
|
689
|
+
return api(`/checkpoints/session/${sessionId}`);
|
|
996
690
|
},
|
|
997
691
|
getByMessageSequence(sessionId, messageSequence) {
|
|
998
|
-
return
|
|
999
|
-
and(
|
|
1000
|
-
eq(checkpoints.sessionId, sessionId),
|
|
1001
|
-
eq(checkpoints.messageSequence, messageSequence)
|
|
1002
|
-
)
|
|
1003
|
-
).get();
|
|
692
|
+
return api(`/checkpoints/session/${sessionId}/by-sequence/${messageSequence}`).then((r) => r ?? void 0);
|
|
1004
693
|
},
|
|
1005
694
|
getLatest(sessionId) {
|
|
1006
|
-
return
|
|
695
|
+
return api(`/checkpoints/session/${sessionId}/latest`).then((r) => r ?? void 0);
|
|
1007
696
|
},
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
and(
|
|
1015
|
-
eq(checkpoints.sessionId, sessionId),
|
|
1016
|
-
sql`message_sequence > ${messageSequence}`
|
|
1017
|
-
)
|
|
1018
|
-
).run();
|
|
1019
|
-
return result.changes;
|
|
697
|
+
async deleteAfterSequence(sessionId, messageSequence) {
|
|
698
|
+
const result = await api(
|
|
699
|
+
`/checkpoints/session/${sessionId}/after-sequence/${messageSequence}`,
|
|
700
|
+
{ method: "DELETE" }
|
|
701
|
+
);
|
|
702
|
+
return result.deleted;
|
|
1020
703
|
},
|
|
1021
|
-
deleteBySession(sessionId) {
|
|
1022
|
-
const result =
|
|
1023
|
-
return result.
|
|
704
|
+
async deleteBySession(sessionId) {
|
|
705
|
+
const result = await api(`/checkpoints/session/${sessionId}`, { method: "DELETE" });
|
|
706
|
+
return result.deleted;
|
|
1024
707
|
}
|
|
1025
708
|
};
|
|
1026
|
-
var
|
|
709
|
+
var remoteFileBackupQueries = {
|
|
1027
710
|
create(data) {
|
|
1028
|
-
|
|
1029
|
-
const result = getDb().insert(fileBackups).values({
|
|
1030
|
-
id,
|
|
1031
|
-
checkpointId: data.checkpointId,
|
|
1032
|
-
sessionId: data.sessionId,
|
|
1033
|
-
filePath: data.filePath,
|
|
1034
|
-
originalContent: data.originalContent,
|
|
1035
|
-
existed: data.existed,
|
|
1036
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
1037
|
-
}).returning().get();
|
|
1038
|
-
return result;
|
|
711
|
+
return api("/file-backups", { method: "POST", body: data });
|
|
1039
712
|
},
|
|
1040
713
|
getByCheckpoint(checkpointId) {
|
|
1041
|
-
return
|
|
714
|
+
return api(`/file-backups/checkpoint/${checkpointId}`);
|
|
1042
715
|
},
|
|
1043
716
|
getBySession(sessionId) {
|
|
1044
|
-
return
|
|
717
|
+
return api(`/file-backups/session/${sessionId}`);
|
|
1045
718
|
},
|
|
1046
|
-
/**
|
|
1047
|
-
* Get all file backups from a given checkpoint sequence onwards (inclusive)
|
|
1048
|
-
* (Used when reverting - need to restore these files)
|
|
1049
|
-
*
|
|
1050
|
-
* When reverting to checkpoint X, we need backups from checkpoint X and all later ones
|
|
1051
|
-
* because checkpoint X's backups represent the state BEFORE processing message X.
|
|
1052
|
-
*/
|
|
1053
719
|
getFromSequence(sessionId, messageSequence) {
|
|
1054
|
-
|
|
1055
|
-
and(
|
|
1056
|
-
eq(checkpoints.sessionId, sessionId),
|
|
1057
|
-
sql`message_sequence >= ${messageSequence}`
|
|
1058
|
-
)
|
|
1059
|
-
).all();
|
|
1060
|
-
if (checkpointsFrom.length === 0) {
|
|
1061
|
-
return [];
|
|
1062
|
-
}
|
|
1063
|
-
const checkpointIds = checkpointsFrom.map((c) => c.id);
|
|
1064
|
-
const allBackups = [];
|
|
1065
|
-
for (const cpId of checkpointIds) {
|
|
1066
|
-
const backups = getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, cpId)).all();
|
|
1067
|
-
allBackups.push(...backups);
|
|
1068
|
-
}
|
|
1069
|
-
return allBackups;
|
|
720
|
+
return api(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);
|
|
1070
721
|
},
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
and(
|
|
1077
|
-
eq(fileBackups.checkpointId, checkpointId),
|
|
1078
|
-
eq(fileBackups.filePath, filePath)
|
|
1079
|
-
)
|
|
1080
|
-
).get();
|
|
1081
|
-
return !!result;
|
|
722
|
+
async hasBackup(checkpointId, filePath) {
|
|
723
|
+
const result = await api(
|
|
724
|
+
`/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`
|
|
725
|
+
);
|
|
726
|
+
return result.hasBackup;
|
|
1082
727
|
},
|
|
1083
|
-
deleteBySession(sessionId) {
|
|
1084
|
-
const result =
|
|
1085
|
-
return result.
|
|
728
|
+
async deleteBySession(sessionId) {
|
|
729
|
+
const result = await api(`/file-backups/session/${sessionId}`, { method: "DELETE" });
|
|
730
|
+
return result.deleted;
|
|
1086
731
|
}
|
|
1087
732
|
};
|
|
1088
|
-
var
|
|
733
|
+
var remoteSubagentQueries = {
|
|
1089
734
|
create(data) {
|
|
1090
|
-
|
|
1091
|
-
const result = getDb().insert(subagentExecutions).values({
|
|
1092
|
-
id,
|
|
1093
|
-
sessionId: data.sessionId,
|
|
1094
|
-
toolCallId: data.toolCallId,
|
|
1095
|
-
subagentType: data.subagentType,
|
|
1096
|
-
task: data.task,
|
|
1097
|
-
model: data.model,
|
|
1098
|
-
status: "running",
|
|
1099
|
-
steps: [],
|
|
1100
|
-
startedAt: /* @__PURE__ */ new Date()
|
|
1101
|
-
}).returning().get();
|
|
1102
|
-
return result;
|
|
735
|
+
return api("/subagents", { method: "POST", body: data });
|
|
1103
736
|
},
|
|
1104
737
|
getById(id) {
|
|
1105
|
-
return
|
|
738
|
+
return api(`/subagents/${id}`).catch(() => void 0);
|
|
1106
739
|
},
|
|
1107
740
|
getByToolCallId(toolCallId) {
|
|
1108
|
-
return
|
|
741
|
+
return api(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => void 0);
|
|
1109
742
|
},
|
|
1110
743
|
getBySession(sessionId) {
|
|
1111
|
-
return
|
|
744
|
+
return api(`/subagents/session/${sessionId}`);
|
|
1112
745
|
},
|
|
1113
746
|
addStep(id, step) {
|
|
1114
|
-
|
|
1115
|
-
if (!existing) return void 0;
|
|
1116
|
-
const currentSteps = existing.steps || [];
|
|
1117
|
-
const newSteps = [...currentSteps, step];
|
|
1118
|
-
return getDb().update(subagentExecutions).set({ steps: newSteps }).where(eq(subagentExecutions.id, id)).returning().get();
|
|
747
|
+
return api(`/subagents/${id}/add-step`, { method: "POST", body: { step } }).catch(() => void 0);
|
|
1119
748
|
},
|
|
1120
749
|
complete(id, result) {
|
|
1121
|
-
return
|
|
1122
|
-
status: "completed",
|
|
1123
|
-
result,
|
|
1124
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
1125
|
-
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
750
|
+
return api(`/subagents/${id}`, { method: "PATCH", body: { status: "completed", result } }).catch(() => void 0);
|
|
1126
751
|
},
|
|
1127
752
|
markError(id, error) {
|
|
1128
|
-
return
|
|
1129
|
-
status: "error",
|
|
1130
|
-
error,
|
|
1131
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
1132
|
-
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
753
|
+
return api(`/subagents/${id}`, { method: "PATCH", body: { status: "error", error } }).catch(() => void 0);
|
|
1133
754
|
},
|
|
1134
755
|
cancel(id) {
|
|
1135
|
-
return
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
756
|
+
return api(`/subagents/${id}`, { method: "PATCH", body: { status: "cancelled" } }).catch(() => void 0);
|
|
757
|
+
},
|
|
758
|
+
async deleteBySession(sessionId) {
|
|
759
|
+
const result = await api(`/subagents/session/${sessionId}`, { method: "DELETE" });
|
|
760
|
+
return result.deleted;
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
var remoteIndexStatusQueries = {
|
|
764
|
+
upsert(_db, data) {
|
|
765
|
+
return api("/index-status", {
|
|
766
|
+
method: "POST",
|
|
767
|
+
body: {
|
|
768
|
+
...data,
|
|
769
|
+
lastFullIndex: data.lastFullIndex?.toISOString(),
|
|
770
|
+
lastIncrementalIndex: data.lastIncrementalIndex?.toISOString()
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
},
|
|
774
|
+
get(_db, namespace) {
|
|
775
|
+
return api(`/index-status/namespace/${namespace}`).then((r) => r ?? void 0);
|
|
776
|
+
},
|
|
777
|
+
async delete(_db, namespace) {
|
|
778
|
+
const result = await api(`/index-status/namespace/${namespace}`, { method: "DELETE" });
|
|
779
|
+
return result?.success ?? false;
|
|
1139
780
|
},
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
return result.changes;
|
|
781
|
+
list(_db) {
|
|
782
|
+
return api("/index-status");
|
|
1143
783
|
}
|
|
1144
784
|
};
|
|
1145
785
|
|
|
786
|
+
// src/db/index.ts
|
|
787
|
+
var initialized = false;
|
|
788
|
+
function initDatabase(config) {
|
|
789
|
+
initRemoteDatabase(config.url, config.authKey);
|
|
790
|
+
initialized = true;
|
|
791
|
+
}
|
|
792
|
+
function getDb() {
|
|
793
|
+
if (!initialized) {
|
|
794
|
+
throw new Error("Database not initialized. Call initDatabase first.");
|
|
795
|
+
}
|
|
796
|
+
return {};
|
|
797
|
+
}
|
|
798
|
+
function closeDatabase() {
|
|
799
|
+
closeRemoteDatabase();
|
|
800
|
+
initialized = false;
|
|
801
|
+
}
|
|
802
|
+
var sessionQueries = remoteSessionQueries;
|
|
803
|
+
var messageQueries = remoteMessageQueries;
|
|
804
|
+
var toolExecutionQueries = remoteToolExecutionQueries;
|
|
805
|
+
var todoQueries = remoteTodoQueries;
|
|
806
|
+
var skillQueries = remoteSkillQueries;
|
|
807
|
+
var activeStreamQueries = remoteActiveStreamQueries;
|
|
808
|
+
var checkpointQueries = remoteCheckpointQueries;
|
|
809
|
+
var fileBackupQueries = remoteFileBackupQueries;
|
|
810
|
+
var subagentQueries = remoteSubagentQueries;
|
|
811
|
+
var indexStatusQueries = remoteIndexStatusQueries;
|
|
812
|
+
|
|
1146
813
|
// src/config/index.ts
|
|
1147
814
|
init_types();
|
|
1148
815
|
init_types();
|
|
@@ -1292,6 +959,48 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
1292
959
|
const appDataDir = ensureAppDataDirectory();
|
|
1293
960
|
resolvedDatabasePath = join(appDataDir, "sparkecoder.db");
|
|
1294
961
|
}
|
|
962
|
+
const resolvedVectorGateway = {
|
|
963
|
+
redisUrl: process.env.VECTOR_REDIS_URL || config.vectorGateway?.redisUrl || null,
|
|
964
|
+
httpUrl: process.env.VECTOR_HTTP_URL || config.vectorGateway?.httpUrl || null,
|
|
965
|
+
embeddingModel: process.env.VECTOR_EMBEDDING_MODEL || config.vectorGateway?.embeddingModel || "gemini-embedding-001",
|
|
966
|
+
namespace: config.vectorGateway?.namespace || null,
|
|
967
|
+
include: config.vectorGateway?.include || [
|
|
968
|
+
"**/*.ts",
|
|
969
|
+
"**/*.tsx",
|
|
970
|
+
"**/*.js",
|
|
971
|
+
"**/*.jsx",
|
|
972
|
+
"**/*.py",
|
|
973
|
+
"**/*.go",
|
|
974
|
+
"**/*.rs",
|
|
975
|
+
"**/*.java",
|
|
976
|
+
"**/*.md",
|
|
977
|
+
"**/*.mdx",
|
|
978
|
+
"**/*.txt"
|
|
979
|
+
],
|
|
980
|
+
exclude: config.vectorGateway?.exclude || [
|
|
981
|
+
"**/node_modules/**",
|
|
982
|
+
"**/dist/**",
|
|
983
|
+
"**/build/**",
|
|
984
|
+
"**/.git/**",
|
|
985
|
+
"**/.next/**",
|
|
986
|
+
"**/*.min.js",
|
|
987
|
+
"**/*.bundle.js",
|
|
988
|
+
"**/pnpm-lock.yaml",
|
|
989
|
+
"**/package-lock.json",
|
|
990
|
+
"**/yarn.lock",
|
|
991
|
+
"**/.test-workspace/**",
|
|
992
|
+
"**/.semantic-test-workspace/**",
|
|
993
|
+
"**/.semantic-integration-test/**"
|
|
994
|
+
]
|
|
995
|
+
};
|
|
996
|
+
const DEFAULT_REMOTE_URL = "https://agent-remote-server.sparkecode.com";
|
|
997
|
+
const remoteUrl = process.env.SPARKECODER_REMOTE_URL || config.remoteServer?.url || DEFAULT_REMOTE_URL;
|
|
998
|
+
const remoteAuthKey = process.env.SPARKECODER_AUTH_KEY || config.remoteServer?.authKey || loadStoredAuthKey();
|
|
999
|
+
const resolvedRemoteServer = {
|
|
1000
|
+
url: remoteUrl,
|
|
1001
|
+
authKey: remoteAuthKey,
|
|
1002
|
+
isConfigured: !!remoteUrl && !!remoteAuthKey
|
|
1003
|
+
};
|
|
1295
1004
|
const resolved = {
|
|
1296
1005
|
...config,
|
|
1297
1006
|
server: {
|
|
@@ -1302,7 +1011,9 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
1302
1011
|
resolvedWorkingDirectory,
|
|
1303
1012
|
resolvedSkillsDirectories,
|
|
1304
1013
|
resolvedDatabasePath,
|
|
1305
|
-
discoveredSkills: discovered
|
|
1014
|
+
discoveredSkills: discovered,
|
|
1015
|
+
resolvedVectorGateway,
|
|
1016
|
+
resolvedRemoteServer
|
|
1306
1017
|
};
|
|
1307
1018
|
cachedConfig = resolved;
|
|
1308
1019
|
return resolved;
|
|
@@ -1327,6 +1038,55 @@ function requiresApproval(toolName, sessionConfig) {
|
|
|
1327
1038
|
}
|
|
1328
1039
|
return false;
|
|
1329
1040
|
}
|
|
1041
|
+
var AUTH_KEY_FILE = "auth-key.json";
|
|
1042
|
+
function loadStoredAuthKey() {
|
|
1043
|
+
const keysPath = join(getAppDataDirectory(), AUTH_KEY_FILE);
|
|
1044
|
+
if (!existsSync(keysPath)) {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
const content = readFileSync(keysPath, "utf-8");
|
|
1049
|
+
const data = JSON.parse(content);
|
|
1050
|
+
return data.authKey || null;
|
|
1051
|
+
} catch {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function saveAuthKey(authKey3, userId) {
|
|
1056
|
+
const appDir = ensureAppDataDirectory();
|
|
1057
|
+
const keysPath = join(appDir, AUTH_KEY_FILE);
|
|
1058
|
+
const data = {
|
|
1059
|
+
authKey: authKey3,
|
|
1060
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1061
|
+
userId
|
|
1062
|
+
};
|
|
1063
|
+
writeFileSync(keysPath, JSON.stringify(data, null, 2), { mode: 384 });
|
|
1064
|
+
}
|
|
1065
|
+
async function registerWithRemoteServer(serverUrl, name) {
|
|
1066
|
+
const response = await fetch(`${serverUrl}/auth/register`, {
|
|
1067
|
+
method: "POST",
|
|
1068
|
+
headers: { "Content-Type": "application/json" },
|
|
1069
|
+
body: JSON.stringify({ name: name || `CLI ${(/* @__PURE__ */ new Date()).toISOString()}` })
|
|
1070
|
+
});
|
|
1071
|
+
if (!response.ok) {
|
|
1072
|
+
const error = await response.json().catch(() => ({}));
|
|
1073
|
+
throw new Error(error.error || `Failed to register: HTTP ${response.status}`);
|
|
1074
|
+
}
|
|
1075
|
+
const data = await response.json();
|
|
1076
|
+
saveAuthKey(data.authKey, data.userId);
|
|
1077
|
+
return data;
|
|
1078
|
+
}
|
|
1079
|
+
async function ensureRemoteAuthKey(serverUrl) {
|
|
1080
|
+
if (process.env.SPARKECODER_AUTH_KEY) {
|
|
1081
|
+
return process.env.SPARKECODER_AUTH_KEY;
|
|
1082
|
+
}
|
|
1083
|
+
const storedKey = loadStoredAuthKey();
|
|
1084
|
+
if (storedKey) {
|
|
1085
|
+
return storedKey;
|
|
1086
|
+
}
|
|
1087
|
+
const { authKey: authKey3 } = await registerWithRemoteServer(serverUrl);
|
|
1088
|
+
return authKey3;
|
|
1089
|
+
}
|
|
1330
1090
|
var API_KEYS_FILE = "api-keys.json";
|
|
1331
1091
|
var PROVIDER_ENV_MAP = {
|
|
1332
1092
|
anthropic: "ANTHROPIC_API_KEY",
|
|
@@ -1440,8 +1200,8 @@ function truncateOutput(output, maxChars = MAX_OUTPUT_CHARS) {
|
|
|
1440
1200
|
|
|
1441
1201
|
` + output.slice(-halfMax);
|
|
1442
1202
|
}
|
|
1443
|
-
function calculateContextSize(
|
|
1444
|
-
return
|
|
1203
|
+
function calculateContextSize(messages) {
|
|
1204
|
+
return messages.reduce((total, msg) => {
|
|
1445
1205
|
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
1446
1206
|
return total + content.length;
|
|
1447
1207
|
}, 0);
|
|
@@ -1470,7 +1230,7 @@ import { promisify } from "util";
|
|
|
1470
1230
|
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
1471
1231
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1472
1232
|
import { join as join2 } from "path";
|
|
1473
|
-
import { nanoid
|
|
1233
|
+
import { nanoid } from "nanoid";
|
|
1474
1234
|
var execAsync = promisify(exec);
|
|
1475
1235
|
var SESSION_PREFIX = "spark_";
|
|
1476
1236
|
var LOG_BASE_DIR = "sessions";
|
|
@@ -1490,7 +1250,7 @@ async function isTmuxAvailable() {
|
|
|
1490
1250
|
}
|
|
1491
1251
|
}
|
|
1492
1252
|
function generateTerminalId() {
|
|
1493
|
-
return "t" +
|
|
1253
|
+
return "t" + nanoid(9);
|
|
1494
1254
|
}
|
|
1495
1255
|
function getSessionName(terminalId) {
|
|
1496
1256
|
return `${SESSION_PREFIX}${terminalId}`;
|
|
@@ -1718,7 +1478,7 @@ async function getMeta(terminalId, workingDirectory, sessionId) {
|
|
|
1718
1478
|
}
|
|
1719
1479
|
async function listSessionTerminals(sessionId, workingDirectory) {
|
|
1720
1480
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
1721
|
-
const
|
|
1481
|
+
const terminals2 = [];
|
|
1722
1482
|
try {
|
|
1723
1483
|
const { readdir: readdir5 } = await import("fs/promises");
|
|
1724
1484
|
const entries = await readdir5(terminalsDir, { withFileTypes: true });
|
|
@@ -1726,13 +1486,13 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
1726
1486
|
if (entry.isDirectory()) {
|
|
1727
1487
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
1728
1488
|
if (meta) {
|
|
1729
|
-
|
|
1489
|
+
terminals2.push(meta);
|
|
1730
1490
|
}
|
|
1731
1491
|
}
|
|
1732
1492
|
}
|
|
1733
1493
|
} catch {
|
|
1734
1494
|
}
|
|
1735
|
-
return
|
|
1495
|
+
return terminals2;
|
|
1736
1496
|
}
|
|
1737
1497
|
async function sendInput(terminalId, input, options = {}) {
|
|
1738
1498
|
const session = getSessionName(terminalId);
|
|
@@ -2147,7 +1907,7 @@ function getCheckpointManager(sessionId, workingDirectory) {
|
|
|
2147
1907
|
}
|
|
2148
1908
|
async function createCheckpoint(sessionId, workingDirectory, messageSequence) {
|
|
2149
1909
|
const gitHead = await getGitHead(workingDirectory);
|
|
2150
|
-
const checkpoint = checkpointQueries.create({
|
|
1910
|
+
const checkpoint = await checkpointQueries.create({
|
|
2151
1911
|
sessionId,
|
|
2152
1912
|
messageSequence,
|
|
2153
1913
|
gitHead
|
|
@@ -2164,7 +1924,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
|
|
|
2164
1924
|
}
|
|
2165
1925
|
const absolutePath = resolve3(workingDirectory, filePath);
|
|
2166
1926
|
const relativePath = relative2(workingDirectory, absolutePath);
|
|
2167
|
-
if (fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
|
|
1927
|
+
if (await fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
|
|
2168
1928
|
return null;
|
|
2169
1929
|
}
|
|
2170
1930
|
let originalContent = null;
|
|
@@ -2177,7 +1937,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
|
|
|
2177
1937
|
console.warn(`[checkpoint] Failed to read file for backup: ${error.message}`);
|
|
2178
1938
|
}
|
|
2179
1939
|
}
|
|
2180
|
-
const backup = fileBackupQueries.create({
|
|
1940
|
+
const backup = await fileBackupQueries.create({
|
|
2181
1941
|
checkpointId: manager.currentCheckpointId,
|
|
2182
1942
|
sessionId,
|
|
2183
1943
|
filePath: relativePath,
|
|
@@ -2187,7 +1947,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
|
|
|
2187
1947
|
return backup;
|
|
2188
1948
|
}
|
|
2189
1949
|
async function revertToCheckpoint(sessionId, checkpointId) {
|
|
2190
|
-
const session = sessionQueries.getById(sessionId);
|
|
1950
|
+
const session = await sessionQueries.getById(sessionId);
|
|
2191
1951
|
if (!session) {
|
|
2192
1952
|
return {
|
|
2193
1953
|
success: false,
|
|
@@ -2198,7 +1958,7 @@ async function revertToCheckpoint(sessionId, checkpointId) {
|
|
|
2198
1958
|
error: "Session not found"
|
|
2199
1959
|
};
|
|
2200
1960
|
}
|
|
2201
|
-
const checkpoint = checkpointQueries.getById(checkpointId);
|
|
1961
|
+
const checkpoint = await checkpointQueries.getById(checkpointId);
|
|
2202
1962
|
if (!checkpoint || checkpoint.sessionId !== sessionId) {
|
|
2203
1963
|
return {
|
|
2204
1964
|
success: false,
|
|
@@ -2210,7 +1970,7 @@ async function revertToCheckpoint(sessionId, checkpointId) {
|
|
|
2210
1970
|
};
|
|
2211
1971
|
}
|
|
2212
1972
|
const workingDirectory = session.workingDirectory;
|
|
2213
|
-
const backupsToRevert = fileBackupQueries.getFromSequence(sessionId, checkpoint.messageSequence);
|
|
1973
|
+
const backupsToRevert = await fileBackupQueries.getFromSequence(sessionId, checkpoint.messageSequence);
|
|
2214
1974
|
const fileToEarliestBackup = /* @__PURE__ */ new Map();
|
|
2215
1975
|
for (const backup of backupsToRevert) {
|
|
2216
1976
|
if (!fileToEarliestBackup.has(backup.filePath)) {
|
|
@@ -2239,9 +1999,9 @@ async function revertToCheckpoint(sessionId, checkpointId) {
|
|
|
2239
1999
|
console.error(`Failed to restore ${filePath}: ${error.message}`);
|
|
2240
2000
|
}
|
|
2241
2001
|
}
|
|
2242
|
-
const messagesDeleted = messageQueries.deleteFromSequence(sessionId, checkpoint.messageSequence);
|
|
2243
|
-
toolExecutionQueries.deleteAfterTime(sessionId, checkpoint.createdAt);
|
|
2244
|
-
const checkpointsDeleted = checkpointQueries.deleteAfterSequence(sessionId, checkpoint.messageSequence);
|
|
2002
|
+
const messagesDeleted = await messageQueries.deleteFromSequence(sessionId, checkpoint.messageSequence);
|
|
2003
|
+
await toolExecutionQueries.deleteAfterTime(sessionId, checkpoint.createdAt);
|
|
2004
|
+
const checkpointsDeleted = await checkpointQueries.deleteAfterSequence(sessionId, checkpoint.messageSequence);
|
|
2245
2005
|
const manager = getCheckpointManager(sessionId, workingDirectory);
|
|
2246
2006
|
manager.currentCheckpointId = checkpoint.id;
|
|
2247
2007
|
return {
|
|
@@ -2252,16 +2012,16 @@ async function revertToCheckpoint(sessionId, checkpointId) {
|
|
|
2252
2012
|
checkpointsDeleted
|
|
2253
2013
|
};
|
|
2254
2014
|
}
|
|
2255
|
-
function getCheckpoints(sessionId) {
|
|
2015
|
+
async function getCheckpoints(sessionId) {
|
|
2256
2016
|
return checkpointQueries.getBySession(sessionId);
|
|
2257
2017
|
}
|
|
2258
2018
|
async function getSessionDiff(sessionId) {
|
|
2259
|
-
const session = sessionQueries.getById(sessionId);
|
|
2019
|
+
const session = await sessionQueries.getById(sessionId);
|
|
2260
2020
|
if (!session) {
|
|
2261
2021
|
return { files: [] };
|
|
2262
2022
|
}
|
|
2263
2023
|
const workingDirectory = session.workingDirectory;
|
|
2264
|
-
const allBackups = fileBackupQueries.getBySession(sessionId);
|
|
2024
|
+
const allBackups = await fileBackupQueries.getBySession(sessionId);
|
|
2265
2025
|
const fileToOriginalBackup = /* @__PURE__ */ new Map();
|
|
2266
2026
|
for (const backup of allBackups) {
|
|
2267
2027
|
if (!fileToOriginalBackup.has(backup.filePath)) {
|
|
@@ -3048,7 +2808,7 @@ Best practices:
|
|
|
3048
2808
|
error: "No items provided. Include at least one todo item."
|
|
3049
2809
|
};
|
|
3050
2810
|
}
|
|
3051
|
-
const created = todoQueries.createMany(options.sessionId, items);
|
|
2811
|
+
const created = await todoQueries.createMany(options.sessionId, items);
|
|
3052
2812
|
return {
|
|
3053
2813
|
success: true,
|
|
3054
2814
|
action: "add",
|
|
@@ -3057,7 +2817,7 @@ Best practices:
|
|
|
3057
2817
|
};
|
|
3058
2818
|
}
|
|
3059
2819
|
case "list": {
|
|
3060
|
-
const todos = todoQueries.getBySession(options.sessionId);
|
|
2820
|
+
const todos = await todoQueries.getBySession(options.sessionId);
|
|
3061
2821
|
const stats = {
|
|
3062
2822
|
total: todos.length,
|
|
3063
2823
|
pending: todos.filter((t) => t.status === "pending").length,
|
|
@@ -3085,7 +2845,7 @@ Best practices:
|
|
|
3085
2845
|
error: 'status is required for "mark" action'
|
|
3086
2846
|
};
|
|
3087
2847
|
}
|
|
3088
|
-
const updated = todoQueries.updateStatus(todoId, status);
|
|
2848
|
+
const updated = await todoQueries.updateStatus(todoId, status);
|
|
3089
2849
|
if (!updated) {
|
|
3090
2850
|
return {
|
|
3091
2851
|
success: false,
|
|
@@ -3099,7 +2859,7 @@ Best practices:
|
|
|
3099
2859
|
};
|
|
3100
2860
|
}
|
|
3101
2861
|
case "clear": {
|
|
3102
|
-
const count = todoQueries.clearSession(options.sessionId);
|
|
2862
|
+
const count = await todoQueries.clearSession(options.sessionId);
|
|
3103
2863
|
return {
|
|
3104
2864
|
success: true,
|
|
3105
2865
|
action: "clear",
|
|
@@ -3173,7 +2933,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
3173
2933
|
error: 'skillName is required for "load" action'
|
|
3174
2934
|
};
|
|
3175
2935
|
}
|
|
3176
|
-
if (skillQueries.isLoaded(options.sessionId, skillName)) {
|
|
2936
|
+
if (await skillQueries.isLoaded(options.sessionId, skillName)) {
|
|
3177
2937
|
return {
|
|
3178
2938
|
success: false,
|
|
3179
2939
|
error: `Skill "${skillName}" is already loaded in this session`
|
|
@@ -3188,7 +2948,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
3188
2948
|
availableSkills: allSkills.map((s) => s.name)
|
|
3189
2949
|
};
|
|
3190
2950
|
}
|
|
3191
|
-
skillQueries.load(options.sessionId, skillName);
|
|
2951
|
+
await skillQueries.load(options.sessionId, skillName);
|
|
3192
2952
|
return {
|
|
3193
2953
|
success: true,
|
|
3194
2954
|
action: "load",
|
|
@@ -3416,7 +3176,7 @@ import {
|
|
|
3416
3176
|
generateText,
|
|
3417
3177
|
stepCountIs
|
|
3418
3178
|
} from "ai";
|
|
3419
|
-
import { nanoid as
|
|
3179
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
3420
3180
|
var Subagent = class {
|
|
3421
3181
|
/** Model to use (defaults to gemini-2.0-flash) */
|
|
3422
3182
|
model;
|
|
@@ -3429,8 +3189,8 @@ var Subagent = class {
|
|
|
3429
3189
|
* Parse the final result from the subagent's output.
|
|
3430
3190
|
* Override this to structure the result for your subagent type.
|
|
3431
3191
|
*/
|
|
3432
|
-
parseResult(
|
|
3433
|
-
return { text
|
|
3192
|
+
parseResult(text, steps) {
|
|
3193
|
+
return { text, steps };
|
|
3434
3194
|
}
|
|
3435
3195
|
/**
|
|
3436
3196
|
* Run the subagent with streaming progress updates
|
|
@@ -3438,7 +3198,7 @@ var Subagent = class {
|
|
|
3438
3198
|
async run(options) {
|
|
3439
3199
|
const { task, sessionId, toolCallId, onProgress, abortSignal } = options;
|
|
3440
3200
|
const steps = [];
|
|
3441
|
-
const execution = subagentQueries.create({
|
|
3201
|
+
const execution = await subagentQueries.create({
|
|
3442
3202
|
sessionId,
|
|
3443
3203
|
toolCallId,
|
|
3444
3204
|
subagentType: this.type,
|
|
@@ -3447,12 +3207,12 @@ var Subagent = class {
|
|
|
3447
3207
|
});
|
|
3448
3208
|
const addStep = async (step) => {
|
|
3449
3209
|
const fullStep = {
|
|
3450
|
-
id:
|
|
3210
|
+
id: nanoid2(8),
|
|
3451
3211
|
timestamp: Date.now(),
|
|
3452
3212
|
...step
|
|
3453
3213
|
};
|
|
3454
3214
|
steps.push(fullStep);
|
|
3455
|
-
subagentQueries.addStep(execution.id, fullStep);
|
|
3215
|
+
await subagentQueries.addStep(execution.id, fullStep);
|
|
3456
3216
|
await onProgress?.({
|
|
3457
3217
|
type: "step",
|
|
3458
3218
|
subagentId: execution.id,
|
|
@@ -3522,7 +3282,7 @@ var Subagent = class {
|
|
|
3522
3282
|
}
|
|
3523
3283
|
});
|
|
3524
3284
|
const parsedResult = this.parseResult(result.text, steps);
|
|
3525
|
-
subagentQueries.complete(execution.id, parsedResult);
|
|
3285
|
+
await subagentQueries.complete(execution.id, parsedResult);
|
|
3526
3286
|
await onProgress?.({
|
|
3527
3287
|
type: "complete",
|
|
3528
3288
|
subagentId: execution.id,
|
|
@@ -3537,7 +3297,7 @@ var Subagent = class {
|
|
|
3537
3297
|
};
|
|
3538
3298
|
} catch (error) {
|
|
3539
3299
|
const errorMessage = error.message || "Unknown error";
|
|
3540
|
-
subagentQueries.markError(execution.id, errorMessage);
|
|
3300
|
+
await subagentQueries.markError(execution.id, errorMessage);
|
|
3541
3301
|
await onProgress?.({
|
|
3542
3302
|
type: "error",
|
|
3543
3303
|
subagentId: execution.id,
|
|
@@ -3847,7 +3607,7 @@ Keep your responses concise and focused on actionable information.`;
|
|
|
3847
3607
|
})
|
|
3848
3608
|
};
|
|
3849
3609
|
}
|
|
3850
|
-
parseResult(
|
|
3610
|
+
parseResult(text, steps) {
|
|
3851
3611
|
const findings = [];
|
|
3852
3612
|
let filesSearched = 0;
|
|
3853
3613
|
let matchCount = 0;
|
|
@@ -3891,7 +3651,7 @@ Keep your responses concise and focused on actionable information.`;
|
|
|
3891
3651
|
const query = steps.length > 0 ? steps.find((s) => s.type === "text")?.content || "" : "";
|
|
3892
3652
|
return {
|
|
3893
3653
|
query,
|
|
3894
|
-
summary:
|
|
3654
|
+
summary: text,
|
|
3895
3655
|
findings: findings.slice(0, 20),
|
|
3896
3656
|
// Limit findings
|
|
3897
3657
|
filesSearched,
|
|
@@ -4022,9 +3782,362 @@ Context: ${context}` : query;
|
|
|
4022
3782
|
});
|
|
4023
3783
|
}
|
|
4024
3784
|
|
|
3785
|
+
// src/tools/semantic-search.ts
|
|
3786
|
+
import { tool as tool9 } from "ai";
|
|
3787
|
+
import { z as z10 } from "zod";
|
|
3788
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
|
|
3789
|
+
import { join as join5 } from "path";
|
|
3790
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
3791
|
+
|
|
3792
|
+
// src/semantic/namespace.ts
|
|
3793
|
+
import { execSync } from "child_process";
|
|
3794
|
+
function getGitRemoteUrl(workingDirectory) {
|
|
3795
|
+
try {
|
|
3796
|
+
const result = execSync("git remote get-url origin", {
|
|
3797
|
+
cwd: workingDirectory,
|
|
3798
|
+
encoding: "utf-8",
|
|
3799
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3800
|
+
});
|
|
3801
|
+
return result.trim();
|
|
3802
|
+
} catch {
|
|
3803
|
+
return null;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
function parseGitRemoteUrl(url) {
|
|
3807
|
+
const cleanUrl = url.replace(/\.git$/, "");
|
|
3808
|
+
const sshMatch = cleanUrl.match(/git@[^:]+:([^/]+)\/(.+)$/);
|
|
3809
|
+
if (sshMatch) {
|
|
3810
|
+
return { org: sshMatch[1], repo: sshMatch[2] };
|
|
3811
|
+
}
|
|
3812
|
+
const httpsMatch = cleanUrl.match(/https?:\/\/[^/]+\/([^/]+)\/(.+)$/);
|
|
3813
|
+
if (httpsMatch) {
|
|
3814
|
+
return { org: httpsMatch[1], repo: httpsMatch[2] };
|
|
3815
|
+
}
|
|
3816
|
+
const sshProtoMatch = cleanUrl.match(/ssh:\/\/[^/]+\/([^/]+)\/(.+)$/);
|
|
3817
|
+
if (sshProtoMatch) {
|
|
3818
|
+
return { org: sshProtoMatch[1], repo: sshProtoMatch[2] };
|
|
3819
|
+
}
|
|
3820
|
+
return null;
|
|
3821
|
+
}
|
|
3822
|
+
function sanitizeForNamespace(str) {
|
|
3823
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
3824
|
+
}
|
|
3825
|
+
async function getRepoNamespace(workingDirectory, configuredNamespace) {
|
|
3826
|
+
if (configuredNamespace) {
|
|
3827
|
+
return configuredNamespace;
|
|
3828
|
+
}
|
|
3829
|
+
const remoteUrl = getGitRemoteUrl(workingDirectory);
|
|
3830
|
+
if (!remoteUrl) {
|
|
3831
|
+
return null;
|
|
3832
|
+
}
|
|
3833
|
+
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
3834
|
+
if (!parsed) {
|
|
3835
|
+
return null;
|
|
3836
|
+
}
|
|
3837
|
+
const org = sanitizeForNamespace(parsed.org);
|
|
3838
|
+
const repo = sanitizeForNamespace(parsed.repo);
|
|
3839
|
+
return `sparkecoder_${org}_${repo}`;
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
// src/semantic/hasher.ts
|
|
3843
|
+
import { createHash } from "crypto";
|
|
3844
|
+
|
|
3845
|
+
// src/semantic/chunker.ts
|
|
3846
|
+
import { extname as extname6, basename as basename2 } from "path";
|
|
3847
|
+
|
|
3848
|
+
// src/semantic/client.ts
|
|
3849
|
+
var remoteServerUrl2 = null;
|
|
3850
|
+
var authKey2 = null;
|
|
3851
|
+
function initVectorClient(serverUrl, key) {
|
|
3852
|
+
remoteServerUrl2 = serverUrl.replace(/\/$/, "");
|
|
3853
|
+
authKey2 = key;
|
|
3854
|
+
}
|
|
3855
|
+
function isVectorClientConfigured() {
|
|
3856
|
+
return !!remoteServerUrl2 && !!authKey2;
|
|
3857
|
+
}
|
|
3858
|
+
async function vectorApi(path, options = {}) {
|
|
3859
|
+
if (!remoteServerUrl2 || !authKey2) {
|
|
3860
|
+
throw new Error("Vector client not initialized - remote server not configured");
|
|
3861
|
+
}
|
|
3862
|
+
const url = `${remoteServerUrl2}/vectors${path}`;
|
|
3863
|
+
const init = {
|
|
3864
|
+
method: options.method || "GET",
|
|
3865
|
+
headers: {
|
|
3866
|
+
"Content-Type": "application/json",
|
|
3867
|
+
"Authorization": `Bearer ${authKey2}`
|
|
3868
|
+
}
|
|
3869
|
+
};
|
|
3870
|
+
if (options.body) {
|
|
3871
|
+
init.body = JSON.stringify(options.body);
|
|
3872
|
+
}
|
|
3873
|
+
const response = await fetch(url, init);
|
|
3874
|
+
if (!response.ok) {
|
|
3875
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
3876
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
3877
|
+
}
|
|
3878
|
+
return response.json();
|
|
3879
|
+
}
|
|
3880
|
+
var remoteVectorClient = {
|
|
3881
|
+
embeddings: {
|
|
3882
|
+
/**
|
|
3883
|
+
* Create embeddings and store in vector DB
|
|
3884
|
+
*/
|
|
3885
|
+
async createAndWait(texts, options) {
|
|
3886
|
+
return vectorApi("/embed", {
|
|
3887
|
+
method: "POST",
|
|
3888
|
+
body: {
|
|
3889
|
+
texts,
|
|
3890
|
+
namespace: options.namespace,
|
|
3891
|
+
embeddingModel: options.embeddingModel
|
|
3892
|
+
}
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
},
|
|
3896
|
+
search: {
|
|
3897
|
+
/**
|
|
3898
|
+
* Query vectors using semantic search
|
|
3899
|
+
*/
|
|
3900
|
+
async queryAndWait(query, options) {
|
|
3901
|
+
return vectorApi("/search", {
|
|
3902
|
+
method: "POST",
|
|
3903
|
+
body: {
|
|
3904
|
+
query,
|
|
3905
|
+
namespace: options.namespace,
|
|
3906
|
+
topK: options.topK || 10,
|
|
3907
|
+
embeddingModel: options.embeddingModel
|
|
3908
|
+
}
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
},
|
|
3912
|
+
/**
|
|
3913
|
+
* Delete a namespace (if supported)
|
|
3914
|
+
*/
|
|
3915
|
+
async deleteNamespace(namespace) {
|
|
3916
|
+
await vectorApi(`/namespace/${encodeURIComponent(namespace)}`, {
|
|
3917
|
+
method: "DELETE"
|
|
3918
|
+
});
|
|
3919
|
+
},
|
|
3920
|
+
/**
|
|
3921
|
+
* Close client (no-op for HTTP client)
|
|
3922
|
+
*/
|
|
3923
|
+
async close() {
|
|
3924
|
+
}
|
|
3925
|
+
};
|
|
3926
|
+
function getVectorClient() {
|
|
3927
|
+
if (!isVectorClientConfigured()) {
|
|
3928
|
+
try {
|
|
3929
|
+
const config = getConfig();
|
|
3930
|
+
if (config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey) {
|
|
3931
|
+
initVectorClient(config.resolvedRemoteServer.url, config.resolvedRemoteServer.authKey);
|
|
3932
|
+
} else {
|
|
3933
|
+
return null;
|
|
3934
|
+
}
|
|
3935
|
+
} catch {
|
|
3936
|
+
return null;
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
return remoteVectorClient;
|
|
3940
|
+
}
|
|
3941
|
+
async function closeVectorClient() {
|
|
3942
|
+
}
|
|
3943
|
+
function isVectorGatewayConfigured() {
|
|
3944
|
+
try {
|
|
3945
|
+
const config = getConfig();
|
|
3946
|
+
return !!(config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey);
|
|
3947
|
+
} catch {
|
|
3948
|
+
return false;
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
function getEmbeddingModel() {
|
|
3952
|
+
try {
|
|
3953
|
+
const config = getConfig();
|
|
3954
|
+
return config.resolvedVectorGateway.embeddingModel;
|
|
3955
|
+
} catch {
|
|
3956
|
+
return "gemini-embedding-001";
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// src/semantic/indexer.ts
|
|
3961
|
+
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
3962
|
+
import { relative as relative7 } from "path";
|
|
3963
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
3964
|
+
var MAX_FILE_SIZE3 = 1024 * 1024;
|
|
3965
|
+
async function getIndexStatus(workingDirectory) {
|
|
3966
|
+
const config = getConfig();
|
|
3967
|
+
const namespace = await getRepoNamespace(
|
|
3968
|
+
workingDirectory,
|
|
3969
|
+
config.resolvedVectorGateway.namespace
|
|
3970
|
+
);
|
|
3971
|
+
const isConfigured = !!config.resolvedVectorGateway.redisUrl;
|
|
3972
|
+
if (!namespace) {
|
|
3973
|
+
return {
|
|
3974
|
+
namespace: "",
|
|
3975
|
+
totalChunks: 0,
|
|
3976
|
+
lastFullIndex: null,
|
|
3977
|
+
lastIncrementalIndex: null,
|
|
3978
|
+
isConfigured
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
try {
|
|
3982
|
+
const db = getDb();
|
|
3983
|
+
const status = await indexStatusQueries.get(db, namespace);
|
|
3984
|
+
if (!status) {
|
|
3985
|
+
return {
|
|
3986
|
+
namespace,
|
|
3987
|
+
totalChunks: 0,
|
|
3988
|
+
lastFullIndex: null,
|
|
3989
|
+
lastIncrementalIndex: null,
|
|
3990
|
+
isConfigured
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
namespace,
|
|
3995
|
+
totalChunks: status.totalChunks ?? 0,
|
|
3996
|
+
lastFullIndex: status.lastFullIndex ?? null,
|
|
3997
|
+
lastIncrementalIndex: status.lastIncrementalIndex ?? null,
|
|
3998
|
+
isConfigured
|
|
3999
|
+
};
|
|
4000
|
+
} catch {
|
|
4001
|
+
return {
|
|
4002
|
+
namespace,
|
|
4003
|
+
totalChunks: 0,
|
|
4004
|
+
lastFullIndex: null,
|
|
4005
|
+
lastIncrementalIndex: null,
|
|
4006
|
+
isConfigured
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
async function checkIndexExists(workingDirectory) {
|
|
4011
|
+
const status = await getIndexStatus(workingDirectory);
|
|
4012
|
+
return status.totalChunks > 0;
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
// src/tools/semantic-search.ts
|
|
4016
|
+
var semanticSearchInputSchema = z10.object({
|
|
4017
|
+
query: z10.string().describe("Natural language search query describing what you want to find"),
|
|
4018
|
+
topK: z10.number().optional().default(10).describe("Number of results to return (default: 10, max: 50)"),
|
|
4019
|
+
filePattern: z10.string().optional().describe('Filter results by file glob pattern (e.g., "*.ts", "src/**/*.py")'),
|
|
4020
|
+
language: z10.string().optional().describe('Filter by programming language (e.g., "typescript", "python")')
|
|
4021
|
+
});
|
|
4022
|
+
function createSemanticSearchTool(options) {
|
|
4023
|
+
return tool9({
|
|
4024
|
+
description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.
|
|
4025
|
+
|
|
4026
|
+
Use this tool when:
|
|
4027
|
+
- You need to understand how something works in the codebase
|
|
4028
|
+
- You're looking for code related to a concept (e.g., "authentication", "database queries")
|
|
4029
|
+
- You want to find implementations of features
|
|
4030
|
+
- The user asks "where is X?" or "how does Y work?"
|
|
4031
|
+
|
|
4032
|
+
This tool requires the repository to be indexed first with 'sparkecoder index'.
|
|
4033
|
+
|
|
4034
|
+
Returns matching code snippets with file paths, line numbers, and relevance scores.`,
|
|
4035
|
+
inputSchema: semanticSearchInputSchema,
|
|
4036
|
+
execute: async ({
|
|
4037
|
+
query,
|
|
4038
|
+
topK,
|
|
4039
|
+
filePattern,
|
|
4040
|
+
language
|
|
4041
|
+
}) => {
|
|
4042
|
+
const startTime = Date.now();
|
|
4043
|
+
try {
|
|
4044
|
+
const config = getConfig();
|
|
4045
|
+
const namespace = await getRepoNamespace(
|
|
4046
|
+
options.workingDirectory,
|
|
4047
|
+
config.resolvedVectorGateway.namespace
|
|
4048
|
+
);
|
|
4049
|
+
if (!namespace) {
|
|
4050
|
+
return {
|
|
4051
|
+
success: false,
|
|
4052
|
+
error: "Repository namespace not found. Ensure this is a git repository with a remote configured."
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
const client = getVectorClient();
|
|
4056
|
+
if (!client) {
|
|
4057
|
+
return {
|
|
4058
|
+
success: false,
|
|
4059
|
+
error: 'Vector Gateway not configured. Run "sparkecoder index --status" for setup instructions.'
|
|
4060
|
+
};
|
|
4061
|
+
}
|
|
4062
|
+
try {
|
|
4063
|
+
const limitedTopK = Math.min(Math.max(1, topK), 50);
|
|
4064
|
+
const embeddingModel = getEmbeddingModel();
|
|
4065
|
+
const result = await client.search.queryAndWait(query, {
|
|
4066
|
+
namespace,
|
|
4067
|
+
topK: limitedTopK * 2,
|
|
4068
|
+
includeMetadata: true,
|
|
4069
|
+
embeddingModel
|
|
4070
|
+
});
|
|
4071
|
+
const matches = [];
|
|
4072
|
+
for (const match of result.matches) {
|
|
4073
|
+
const metadata = match.metadata;
|
|
4074
|
+
if (!metadata) continue;
|
|
4075
|
+
const filePath = metadata.filePath;
|
|
4076
|
+
const startLine = metadata.startLine;
|
|
4077
|
+
const endLine = metadata.endLine;
|
|
4078
|
+
const matchLanguage = metadata.language;
|
|
4079
|
+
const symbolName = metadata.symbolName;
|
|
4080
|
+
if (filePattern) {
|
|
4081
|
+
const matchesPattern = minimatch3(filePath, filePattern, { dot: true });
|
|
4082
|
+
if (!matchesPattern) continue;
|
|
4083
|
+
}
|
|
4084
|
+
if (language && matchLanguage !== language.toLowerCase()) {
|
|
4085
|
+
continue;
|
|
4086
|
+
}
|
|
4087
|
+
const fullPath = join5(options.workingDirectory, filePath);
|
|
4088
|
+
if (!existsSync11(fullPath)) {
|
|
4089
|
+
continue;
|
|
4090
|
+
}
|
|
4091
|
+
let snippet = "";
|
|
4092
|
+
try {
|
|
4093
|
+
const content = readFileSync4(fullPath, "utf-8");
|
|
4094
|
+
const lines = content.split("\n");
|
|
4095
|
+
const snippetLines = lines.slice(
|
|
4096
|
+
Math.max(0, startLine - 1),
|
|
4097
|
+
Math.min(lines.length, endLine)
|
|
4098
|
+
);
|
|
4099
|
+
snippet = snippetLines.join("\n");
|
|
4100
|
+
if (snippet.length > 500) {
|
|
4101
|
+
snippet = snippet.slice(0, 500) + "\n... (truncated)";
|
|
4102
|
+
}
|
|
4103
|
+
} catch {
|
|
4104
|
+
}
|
|
4105
|
+
matches.push({
|
|
4106
|
+
filePath,
|
|
4107
|
+
startLine,
|
|
4108
|
+
endLine,
|
|
4109
|
+
score: match.score,
|
|
4110
|
+
snippet,
|
|
4111
|
+
symbolName,
|
|
4112
|
+
language: matchLanguage
|
|
4113
|
+
});
|
|
4114
|
+
if (matches.length >= limitedTopK) {
|
|
4115
|
+
break;
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
return {
|
|
4119
|
+
success: true,
|
|
4120
|
+
query,
|
|
4121
|
+
matches,
|
|
4122
|
+
totalMatches: matches.length,
|
|
4123
|
+
duration: Date.now() - startTime
|
|
4124
|
+
};
|
|
4125
|
+
} finally {
|
|
4126
|
+
await closeVectorClient();
|
|
4127
|
+
}
|
|
4128
|
+
} catch (error) {
|
|
4129
|
+
return {
|
|
4130
|
+
success: false,
|
|
4131
|
+
error: `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`
|
|
4132
|
+
};
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4025
4138
|
// src/tools/index.ts
|
|
4026
|
-
function createTools(options) {
|
|
4027
|
-
|
|
4139
|
+
async function createTools(options) {
|
|
4140
|
+
const tools = {
|
|
4028
4141
|
bash: createBashTool({
|
|
4029
4142
|
workingDirectory: options.workingDirectory,
|
|
4030
4143
|
sessionId: options.sessionId,
|
|
@@ -4056,6 +4169,20 @@ function createTools(options) {
|
|
|
4056
4169
|
onProgress: options.onSearchProgress
|
|
4057
4170
|
})
|
|
4058
4171
|
};
|
|
4172
|
+
if (options.enableSemanticSearch !== false) {
|
|
4173
|
+
try {
|
|
4174
|
+
if (isVectorGatewayConfigured()) {
|
|
4175
|
+
const hasIndex = await checkIndexExists(options.workingDirectory);
|
|
4176
|
+
if (hasIndex) {
|
|
4177
|
+
tools.semantic_search = createSemanticSearchTool({
|
|
4178
|
+
workingDirectory: options.workingDirectory
|
|
4179
|
+
});
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
} catch {
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
return tools;
|
|
4059
4186
|
}
|
|
4060
4187
|
|
|
4061
4188
|
// src/agent/context.ts
|
|
@@ -4108,7 +4235,7 @@ async function buildSystemPrompt(options) {
|
|
|
4108
4235
|
const skills = await loadAllSkills2(skillsDirectories);
|
|
4109
4236
|
onDemandSkillsContext = formatSkillsForContext(skills);
|
|
4110
4237
|
}
|
|
4111
|
-
const todos = todoQueries.getBySession(sessionId);
|
|
4238
|
+
const todos = await todoQueries.getBySession(sessionId);
|
|
4112
4239
|
const todosContext = formatTodosForContext(todos);
|
|
4113
4240
|
const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
4114
4241
|
const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
|
|
@@ -4350,7 +4477,7 @@ var ContextManager = class {
|
|
|
4350
4477
|
* Returns ModelMessage[] that can be passed directly to streamText/generateText
|
|
4351
4478
|
*/
|
|
4352
4479
|
async getMessages() {
|
|
4353
|
-
let modelMessages = messageQueries.getModelMessages(this.sessionId);
|
|
4480
|
+
let modelMessages = await messageQueries.getModelMessages(this.sessionId);
|
|
4354
4481
|
const contextSize = calculateContextSize(modelMessages);
|
|
4355
4482
|
if (this.autoSummarize && contextSize > this.maxContextChars) {
|
|
4356
4483
|
modelMessages = await this.summarizeContext(modelMessages);
|
|
@@ -4370,13 +4497,13 @@ ${this.summary}`
|
|
|
4370
4497
|
/**
|
|
4371
4498
|
* Summarize older messages to reduce context size
|
|
4372
4499
|
*/
|
|
4373
|
-
async summarizeContext(
|
|
4374
|
-
if (
|
|
4375
|
-
return
|
|
4500
|
+
async summarizeContext(messages) {
|
|
4501
|
+
if (messages.length <= this.keepRecentMessages) {
|
|
4502
|
+
return messages;
|
|
4376
4503
|
}
|
|
4377
|
-
const splitIndex =
|
|
4378
|
-
const oldMessages =
|
|
4379
|
-
const recentMessages =
|
|
4504
|
+
const splitIndex = messages.length - this.keepRecentMessages;
|
|
4505
|
+
const oldMessages = messages.slice(0, splitIndex);
|
|
4506
|
+
const recentMessages = messages.slice(splitIndex);
|
|
4380
4507
|
const historyText = oldMessages.map((msg) => {
|
|
4381
4508
|
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
4382
4509
|
return `[${msg.role}]: ${content}`;
|
|
@@ -4400,36 +4527,36 @@ ${this.summary}`
|
|
|
4400
4527
|
* Add a user message to the context
|
|
4401
4528
|
* Content can be a string or an array of content parts (for messages with images/files)
|
|
4402
4529
|
*/
|
|
4403
|
-
addUserMessage(content) {
|
|
4530
|
+
async addUserMessage(content) {
|
|
4404
4531
|
const userMessage = {
|
|
4405
4532
|
role: "user",
|
|
4406
4533
|
content
|
|
4407
4534
|
};
|
|
4408
|
-
messageQueries.create(this.sessionId, userMessage);
|
|
4535
|
+
await messageQueries.create(this.sessionId, userMessage);
|
|
4409
4536
|
}
|
|
4410
4537
|
/**
|
|
4411
4538
|
* Add response messages from AI SDK directly
|
|
4412
4539
|
* This is the preferred method - use result.response.messages from streamText/generateText
|
|
4413
4540
|
*/
|
|
4414
|
-
addResponseMessages(
|
|
4415
|
-
messageQueries.addMany(this.sessionId,
|
|
4541
|
+
async addResponseMessages(messages) {
|
|
4542
|
+
await messageQueries.addMany(this.sessionId, messages);
|
|
4416
4543
|
}
|
|
4417
4544
|
/**
|
|
4418
4545
|
* Get current context statistics
|
|
4419
4546
|
*/
|
|
4420
|
-
getStats() {
|
|
4421
|
-
const
|
|
4547
|
+
async getStats() {
|
|
4548
|
+
const messages = await messageQueries.getModelMessages(this.sessionId);
|
|
4422
4549
|
return {
|
|
4423
|
-
messageCount:
|
|
4424
|
-
contextChars: calculateContextSize(
|
|
4550
|
+
messageCount: messages.length,
|
|
4551
|
+
contextChars: calculateContextSize(messages),
|
|
4425
4552
|
hasSummary: this.summary !== null
|
|
4426
4553
|
};
|
|
4427
4554
|
}
|
|
4428
4555
|
/**
|
|
4429
4556
|
* Clear all messages in the context
|
|
4430
4557
|
*/
|
|
4431
|
-
clear() {
|
|
4432
|
-
messageQueries.deleteBySession(this.sessionId);
|
|
4558
|
+
async clear() {
|
|
4559
|
+
await messageQueries.deleteBySession(this.sessionId);
|
|
4433
4560
|
this.summary = null;
|
|
4434
4561
|
}
|
|
4435
4562
|
};
|
|
@@ -4449,7 +4576,7 @@ var Agent = class _Agent {
|
|
|
4449
4576
|
/**
|
|
4450
4577
|
* Create tools with optional progress callbacks
|
|
4451
4578
|
*/
|
|
4452
|
-
createToolsWithCallbacks(options) {
|
|
4579
|
+
async createToolsWithCallbacks(options) {
|
|
4453
4580
|
const config = getConfig();
|
|
4454
4581
|
return createTools({
|
|
4455
4582
|
sessionId: this.session.id,
|
|
@@ -4467,13 +4594,13 @@ var Agent = class _Agent {
|
|
|
4467
4594
|
const config = getConfig();
|
|
4468
4595
|
let session;
|
|
4469
4596
|
if (options.sessionId) {
|
|
4470
|
-
const existing = sessionQueries.getById(options.sessionId);
|
|
4597
|
+
const existing = await sessionQueries.getById(options.sessionId);
|
|
4471
4598
|
if (!existing) {
|
|
4472
4599
|
throw new Error(`Session not found: ${options.sessionId}`);
|
|
4473
4600
|
}
|
|
4474
4601
|
session = existing;
|
|
4475
4602
|
} else {
|
|
4476
|
-
session = sessionQueries.create({
|
|
4603
|
+
session = await sessionQueries.create({
|
|
4477
4604
|
name: options.name,
|
|
4478
4605
|
workingDirectory: options.workingDirectory || config.resolvedWorkingDirectory,
|
|
4479
4606
|
model: options.model || config.defaultModel,
|
|
@@ -4486,7 +4613,7 @@ var Agent = class _Agent {
|
|
|
4486
4613
|
keepRecentMessages: config.context?.keepRecentMessages || 10,
|
|
4487
4614
|
autoSummarize: config.context?.autoSummarize ?? true
|
|
4488
4615
|
});
|
|
4489
|
-
const tools = createTools({
|
|
4616
|
+
const tools = await createTools({
|
|
4490
4617
|
sessionId: session.id,
|
|
4491
4618
|
workingDirectory: session.workingDirectory,
|
|
4492
4619
|
skillsDirectories: config.resolvedSkillsDirectories
|
|
@@ -4562,7 +4689,7 @@ ${prompt}` });
|
|
|
4562
4689
|
if (!options.skipSaveUserMessage) {
|
|
4563
4690
|
this.context.addUserMessage(userContent);
|
|
4564
4691
|
}
|
|
4565
|
-
sessionQueries.updateStatus(this.session.id, "active");
|
|
4692
|
+
await sessionQueries.updateStatus(this.session.id, "active");
|
|
4566
4693
|
const systemPrompt = await buildSystemPrompt({
|
|
4567
4694
|
workingDirectory: this.session.workingDirectory,
|
|
4568
4695
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -4571,14 +4698,14 @@ ${prompt}` });
|
|
|
4571
4698
|
// TODO: Pass activeFiles from client for glob matching
|
|
4572
4699
|
activeFiles: []
|
|
4573
4700
|
});
|
|
4574
|
-
const
|
|
4575
|
-
const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
4701
|
+
const messages = await this.context.getMessages();
|
|
4702
|
+
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
4576
4703
|
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
4577
4704
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
4578
4705
|
const stream = streamText2({
|
|
4579
4706
|
model: resolveModel(this.session.model),
|
|
4580
4707
|
system: systemPrompt,
|
|
4581
|
-
messages
|
|
4708
|
+
messages,
|
|
4582
4709
|
tools: wrappedTools,
|
|
4583
4710
|
stopWhen: stepCountIs2(500),
|
|
4584
4711
|
// Forward abort signal if provided
|
|
@@ -4626,14 +4753,14 @@ ${prompt}` });
|
|
|
4626
4753
|
discoveredSkills: config.discoveredSkills,
|
|
4627
4754
|
activeFiles: []
|
|
4628
4755
|
});
|
|
4629
|
-
const
|
|
4630
|
-
const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
4756
|
+
const messages = await this.context.getMessages();
|
|
4757
|
+
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
4631
4758
|
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
4632
4759
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
4633
4760
|
const result = await generateText3({
|
|
4634
4761
|
model: resolveModel(this.session.model),
|
|
4635
4762
|
system: systemPrompt,
|
|
4636
|
-
messages
|
|
4763
|
+
messages,
|
|
4637
4764
|
tools: wrappedTools,
|
|
4638
4765
|
stopWhen: stepCountIs2(500),
|
|
4639
4766
|
// Enable extended thinking/reasoning for models that support it
|
|
@@ -4666,11 +4793,11 @@ ${prompt}` });
|
|
|
4666
4793
|
wrappedTools[name] = originalTool;
|
|
4667
4794
|
continue;
|
|
4668
4795
|
}
|
|
4669
|
-
wrappedTools[name] =
|
|
4796
|
+
wrappedTools[name] = tool10({
|
|
4670
4797
|
description: originalTool.description || "",
|
|
4671
|
-
inputSchema: originalTool.inputSchema ||
|
|
4798
|
+
inputSchema: originalTool.inputSchema || z11.object({}),
|
|
4672
4799
|
execute: async (input, toolOptions) => {
|
|
4673
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
4800
|
+
const toolCallId = toolOptions.toolCallId || nanoid3();
|
|
4674
4801
|
const execution = toolExecutionQueries.create({
|
|
4675
4802
|
sessionId: this.session.id,
|
|
4676
4803
|
toolName: name,
|
|
@@ -4679,19 +4806,20 @@ ${prompt}` });
|
|
|
4679
4806
|
requiresApproval: true,
|
|
4680
4807
|
status: "pending"
|
|
4681
4808
|
});
|
|
4682
|
-
this.pendingApprovals.set(toolCallId, execution);
|
|
4683
|
-
options.onApprovalRequired?.(execution);
|
|
4684
|
-
sessionQueries.updateStatus(this.session.id, "waiting");
|
|
4809
|
+
this.pendingApprovals.set(toolCallId, await execution);
|
|
4810
|
+
options.onApprovalRequired?.(await execution);
|
|
4811
|
+
await sessionQueries.updateStatus(this.session.id, "waiting");
|
|
4685
4812
|
const approved = await new Promise((resolve10) => {
|
|
4686
4813
|
approvalResolvers.set(toolCallId, { resolve: resolve10, sessionId: this.session.id });
|
|
4687
4814
|
});
|
|
4688
4815
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
4689
4816
|
approvalResolvers.delete(toolCallId);
|
|
4690
4817
|
this.pendingApprovals.delete(toolCallId);
|
|
4818
|
+
const exec6 = await execution;
|
|
4691
4819
|
if (!approved) {
|
|
4692
4820
|
const reason = resolverData?.reason || "User rejected the tool execution";
|
|
4693
|
-
toolExecutionQueries.reject(
|
|
4694
|
-
sessionQueries.updateStatus(this.session.id, "active");
|
|
4821
|
+
await toolExecutionQueries.reject(exec6.id);
|
|
4822
|
+
await sessionQueries.updateStatus(this.session.id, "active");
|
|
4695
4823
|
return {
|
|
4696
4824
|
status: "rejected",
|
|
4697
4825
|
toolCallId,
|
|
@@ -4700,14 +4828,14 @@ ${prompt}` });
|
|
|
4700
4828
|
message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
|
|
4701
4829
|
};
|
|
4702
4830
|
}
|
|
4703
|
-
toolExecutionQueries.approve(
|
|
4704
|
-
sessionQueries.updateStatus(this.session.id, "active");
|
|
4831
|
+
await toolExecutionQueries.approve(exec6.id);
|
|
4832
|
+
await sessionQueries.updateStatus(this.session.id, "active");
|
|
4705
4833
|
try {
|
|
4706
4834
|
const result = await originalTool.execute(input, toolOptions);
|
|
4707
|
-
toolExecutionQueries.complete(
|
|
4835
|
+
await toolExecutionQueries.complete(exec6.id, result);
|
|
4708
4836
|
return result;
|
|
4709
4837
|
} catch (error) {
|
|
4710
|
-
toolExecutionQueries.complete(
|
|
4838
|
+
await toolExecutionQueries.complete(exec6.id, null, error.message);
|
|
4711
4839
|
throw error;
|
|
4712
4840
|
}
|
|
4713
4841
|
}
|
|
@@ -4730,36 +4858,36 @@ ${prompt}` });
|
|
|
4730
4858
|
resolver.resolve(true);
|
|
4731
4859
|
return { approved: true };
|
|
4732
4860
|
}
|
|
4733
|
-
const pendingFromDb = toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
4861
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
4734
4862
|
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
4735
4863
|
if (!execution) {
|
|
4736
4864
|
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
4737
4865
|
}
|
|
4738
|
-
toolExecutionQueries.approve(execution.id);
|
|
4866
|
+
await toolExecutionQueries.approve(execution.id);
|
|
4739
4867
|
return { approved: true };
|
|
4740
4868
|
}
|
|
4741
4869
|
/**
|
|
4742
4870
|
* Reject a pending tool execution
|
|
4743
4871
|
*/
|
|
4744
|
-
reject(toolCallId, reason) {
|
|
4872
|
+
async reject(toolCallId, reason) {
|
|
4745
4873
|
const resolver = approvalResolvers.get(toolCallId);
|
|
4746
4874
|
if (resolver) {
|
|
4747
4875
|
resolver.reason = reason;
|
|
4748
4876
|
resolver.resolve(false);
|
|
4749
4877
|
return { rejected: true };
|
|
4750
4878
|
}
|
|
4751
|
-
const pendingFromDb = toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
4879
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
4752
4880
|
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
4753
4881
|
if (!execution) {
|
|
4754
4882
|
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
4755
4883
|
}
|
|
4756
|
-
toolExecutionQueries.reject(execution.id);
|
|
4884
|
+
await toolExecutionQueries.reject(execution.id);
|
|
4757
4885
|
return { rejected: true };
|
|
4758
4886
|
}
|
|
4759
4887
|
/**
|
|
4760
4888
|
* Get pending approvals
|
|
4761
4889
|
*/
|
|
4762
|
-
getPendingApprovals() {
|
|
4890
|
+
async getPendingApprovals() {
|
|
4763
4891
|
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
4764
4892
|
}
|
|
4765
4893
|
/**
|
|
@@ -4782,8 +4910,8 @@ import { Hono as Hono5 } from "hono";
|
|
|
4782
4910
|
import { serve } from "@hono/node-server";
|
|
4783
4911
|
import { cors } from "hono/cors";
|
|
4784
4912
|
import { logger } from "hono/logger";
|
|
4785
|
-
import { existsSync as
|
|
4786
|
-
import { resolve as resolve9, dirname as dirname7, join as
|
|
4913
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
4914
|
+
import { resolve as resolve9, dirname as dirname7, join as join9 } from "path";
|
|
4787
4915
|
import { spawn as spawn2 } from "child_process";
|
|
4788
4916
|
import { createServer as createNetServer } from "net";
|
|
4789
4917
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -4791,35 +4919,35 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
4791
4919
|
// src/server/routes/sessions.ts
|
|
4792
4920
|
import { Hono } from "hono";
|
|
4793
4921
|
import { zValidator } from "@hono/zod-validator";
|
|
4794
|
-
import { z as
|
|
4795
|
-
import { existsSync as
|
|
4922
|
+
import { z as z12 } from "zod";
|
|
4923
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
4796
4924
|
import { readdir as readdir4 } from "fs/promises";
|
|
4797
|
-
import { join as
|
|
4798
|
-
import { nanoid as
|
|
4799
|
-
var
|
|
4800
|
-
var createSessionSchema =
|
|
4801
|
-
name:
|
|
4802
|
-
workingDirectory:
|
|
4803
|
-
model:
|
|
4804
|
-
toolApprovals:
|
|
4925
|
+
import { join as join6, basename as basename3, extname as extname7, relative as relative8 } from "path";
|
|
4926
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
4927
|
+
var sessions = new Hono();
|
|
4928
|
+
var createSessionSchema = z12.object({
|
|
4929
|
+
name: z12.string().optional(),
|
|
4930
|
+
workingDirectory: z12.string().optional(),
|
|
4931
|
+
model: z12.string().optional(),
|
|
4932
|
+
toolApprovals: z12.record(z12.string(), z12.boolean()).optional()
|
|
4805
4933
|
});
|
|
4806
|
-
var paginationQuerySchema =
|
|
4807
|
-
limit:
|
|
4808
|
-
offset:
|
|
4934
|
+
var paginationQuerySchema = z12.object({
|
|
4935
|
+
limit: z12.string().optional(),
|
|
4936
|
+
offset: z12.string().optional()
|
|
4809
4937
|
});
|
|
4810
|
-
var messagesQuerySchema =
|
|
4811
|
-
limit:
|
|
4938
|
+
var messagesQuerySchema = z12.object({
|
|
4939
|
+
limit: z12.string().optional()
|
|
4812
4940
|
});
|
|
4813
|
-
|
|
4941
|
+
sessions.get(
|
|
4814
4942
|
"/",
|
|
4815
4943
|
zValidator("query", paginationQuerySchema),
|
|
4816
4944
|
async (c) => {
|
|
4817
4945
|
const query = c.req.valid("query");
|
|
4818
4946
|
const limit = parseInt(query.limit || "50");
|
|
4819
4947
|
const offset = parseInt(query.offset || "0");
|
|
4820
|
-
const allSessions = sessionQueries.list(limit, offset);
|
|
4821
|
-
const sessionsWithStreamInfo = allSessions.map((s) => {
|
|
4822
|
-
const activeStream = activeStreamQueries.getBySessionId(s.id);
|
|
4948
|
+
const allSessions = await sessionQueries.list(limit, offset);
|
|
4949
|
+
const sessionsWithStreamInfo = await Promise.all(allSessions.map(async (s) => {
|
|
4950
|
+
const activeStream = await activeStreamQueries.getBySessionId(s.id);
|
|
4823
4951
|
return {
|
|
4824
4952
|
id: s.id,
|
|
4825
4953
|
name: s.name,
|
|
@@ -4831,7 +4959,7 @@ sessions2.get(
|
|
|
4831
4959
|
createdAt: s.createdAt.toISOString(),
|
|
4832
4960
|
updatedAt: s.updatedAt.toISOString()
|
|
4833
4961
|
};
|
|
4834
|
-
});
|
|
4962
|
+
}));
|
|
4835
4963
|
return c.json({
|
|
4836
4964
|
sessions: sessionsWithStreamInfo,
|
|
4837
4965
|
count: allSessions.length,
|
|
@@ -4840,7 +4968,7 @@ sessions2.get(
|
|
|
4840
4968
|
});
|
|
4841
4969
|
}
|
|
4842
4970
|
);
|
|
4843
|
-
|
|
4971
|
+
sessions.post(
|
|
4844
4972
|
"/",
|
|
4845
4973
|
zValidator("json", createSessionSchema),
|
|
4846
4974
|
async (c) => {
|
|
@@ -4863,9 +4991,9 @@ sessions2.post(
|
|
|
4863
4991
|
}, 201);
|
|
4864
4992
|
}
|
|
4865
4993
|
);
|
|
4866
|
-
|
|
4994
|
+
sessions.get("/:id", async (c) => {
|
|
4867
4995
|
const id = c.req.param("id");
|
|
4868
|
-
const session = sessionQueries.getById(id);
|
|
4996
|
+
const session = await sessionQueries.getById(id);
|
|
4869
4997
|
if (!session) {
|
|
4870
4998
|
return c.json({ error: "Session not found" }, 404);
|
|
4871
4999
|
}
|
|
@@ -4873,8 +5001,8 @@ sessions2.get("/:id", async (c) => {
|
|
|
4873
5001
|
const agent = await Agent.create({ sessionId: id });
|
|
4874
5002
|
return agent.getContextStats();
|
|
4875
5003
|
})();
|
|
4876
|
-
const todos = todoQueries.getBySession(id);
|
|
4877
|
-
const pendingApprovals = toolExecutionQueries.getPendingApprovals(id);
|
|
5004
|
+
const todos = await todoQueries.getBySession(id);
|
|
5005
|
+
const pendingApprovals = await toolExecutionQueries.getPendingApprovals(id);
|
|
4878
5006
|
return c.json({
|
|
4879
5007
|
id: session.id,
|
|
4880
5008
|
name: session.name,
|
|
@@ -4899,37 +5027,37 @@ sessions2.get("/:id", async (c) => {
|
|
|
4899
5027
|
}))
|
|
4900
5028
|
});
|
|
4901
5029
|
});
|
|
4902
|
-
|
|
5030
|
+
sessions.get(
|
|
4903
5031
|
"/:id/messages",
|
|
4904
5032
|
zValidator("query", messagesQuerySchema),
|
|
4905
5033
|
async (c) => {
|
|
4906
5034
|
const id = c.req.param("id");
|
|
4907
5035
|
const query = c.req.valid("query");
|
|
4908
5036
|
const limit = parseInt(query.limit || "100");
|
|
4909
|
-
const session = sessionQueries.getById(id);
|
|
5037
|
+
const session = await sessionQueries.getById(id);
|
|
4910
5038
|
if (!session) {
|
|
4911
5039
|
return c.json({ error: "Session not found" }, 404);
|
|
4912
5040
|
}
|
|
4913
|
-
const
|
|
5041
|
+
const messages = await messageQueries.getRecentBySession(id, limit);
|
|
4914
5042
|
return c.json({
|
|
4915
5043
|
sessionId: id,
|
|
4916
|
-
messages:
|
|
5044
|
+
messages: messages.map((m) => ({
|
|
4917
5045
|
id: m.id,
|
|
4918
5046
|
...m.modelMessage,
|
|
4919
5047
|
// Spread the AI SDK ModelMessage (role, content)
|
|
4920
5048
|
createdAt: m.createdAt.toISOString()
|
|
4921
5049
|
})),
|
|
4922
|
-
count:
|
|
5050
|
+
count: messages.length
|
|
4923
5051
|
});
|
|
4924
5052
|
}
|
|
4925
5053
|
);
|
|
4926
|
-
|
|
5054
|
+
sessions.get("/:id/tools", async (c) => {
|
|
4927
5055
|
const id = c.req.param("id");
|
|
4928
|
-
const session = sessionQueries.getById(id);
|
|
5056
|
+
const session = await sessionQueries.getById(id);
|
|
4929
5057
|
if (!session) {
|
|
4930
5058
|
return c.json({ error: "Session not found" }, 404);
|
|
4931
5059
|
}
|
|
4932
|
-
const executions = toolExecutionQueries.getBySession(id);
|
|
5060
|
+
const executions = await toolExecutionQueries.getBySession(id);
|
|
4933
5061
|
return c.json({
|
|
4934
5062
|
sessionId: id,
|
|
4935
5063
|
executions: executions.map((e) => ({
|
|
@@ -4947,18 +5075,18 @@ sessions2.get("/:id/tools", async (c) => {
|
|
|
4947
5075
|
count: executions.length
|
|
4948
5076
|
});
|
|
4949
5077
|
});
|
|
4950
|
-
var updateSessionSchema =
|
|
4951
|
-
model:
|
|
4952
|
-
name:
|
|
4953
|
-
toolApprovals:
|
|
5078
|
+
var updateSessionSchema = z12.object({
|
|
5079
|
+
model: z12.string().optional(),
|
|
5080
|
+
name: z12.string().optional(),
|
|
5081
|
+
toolApprovals: z12.record(z12.string(), z12.boolean()).optional()
|
|
4954
5082
|
});
|
|
4955
|
-
|
|
5083
|
+
sessions.patch(
|
|
4956
5084
|
"/:id",
|
|
4957
5085
|
zValidator("json", updateSessionSchema),
|
|
4958
5086
|
async (c) => {
|
|
4959
5087
|
const id = c.req.param("id");
|
|
4960
5088
|
const body = c.req.valid("json");
|
|
4961
|
-
const session = sessionQueries.getById(id);
|
|
5089
|
+
const session = await sessionQueries.getById(id);
|
|
4962
5090
|
if (!session) {
|
|
4963
5091
|
return c.json({ error: "Session not found" }, 404);
|
|
4964
5092
|
}
|
|
@@ -4976,7 +5104,7 @@ sessions2.patch(
|
|
|
4976
5104
|
}
|
|
4977
5105
|
};
|
|
4978
5106
|
}
|
|
4979
|
-
const updatedSession = Object.keys(updates).length > 0 ? sessionQueries.update(id, updates) || session : session;
|
|
5107
|
+
const updatedSession = Object.keys(updates).length > 0 ? await sessionQueries.update(id, updates) || session : session;
|
|
4980
5108
|
return c.json({
|
|
4981
5109
|
id: updatedSession.id,
|
|
4982
5110
|
name: updatedSession.name,
|
|
@@ -4988,10 +5116,10 @@ sessions2.patch(
|
|
|
4988
5116
|
});
|
|
4989
5117
|
}
|
|
4990
5118
|
);
|
|
4991
|
-
|
|
5119
|
+
sessions.delete("/:id", async (c) => {
|
|
4992
5120
|
const id = c.req.param("id");
|
|
4993
5121
|
try {
|
|
4994
|
-
const session = sessionQueries.getById(id);
|
|
5122
|
+
const session = await sessionQueries.getById(id);
|
|
4995
5123
|
if (session) {
|
|
4996
5124
|
const terminalIds = await listSessions();
|
|
4997
5125
|
for (const tid of terminalIds) {
|
|
@@ -5004,29 +5132,29 @@ sessions2.delete("/:id", async (c) => {
|
|
|
5004
5132
|
} catch (e) {
|
|
5005
5133
|
}
|
|
5006
5134
|
clearCheckpointManager(id);
|
|
5007
|
-
const deleted = sessionQueries.delete(id);
|
|
5135
|
+
const deleted = await sessionQueries.delete(id);
|
|
5008
5136
|
if (!deleted) {
|
|
5009
5137
|
return c.json({ error: "Session not found" }, 404);
|
|
5010
5138
|
}
|
|
5011
5139
|
return c.json({ success: true, id });
|
|
5012
5140
|
});
|
|
5013
|
-
|
|
5141
|
+
sessions.post("/:id/clear", async (c) => {
|
|
5014
5142
|
const id = c.req.param("id");
|
|
5015
|
-
const session = sessionQueries.getById(id);
|
|
5143
|
+
const session = await sessionQueries.getById(id);
|
|
5016
5144
|
if (!session) {
|
|
5017
5145
|
return c.json({ error: "Session not found" }, 404);
|
|
5018
5146
|
}
|
|
5019
5147
|
const agent = await Agent.create({ sessionId: id });
|
|
5020
|
-
agent.clearContext();
|
|
5148
|
+
await agent.clearContext();
|
|
5021
5149
|
return c.json({ success: true, sessionId: id });
|
|
5022
5150
|
});
|
|
5023
|
-
|
|
5151
|
+
sessions.get("/:id/todos", async (c) => {
|
|
5024
5152
|
const id = c.req.param("id");
|
|
5025
|
-
const session = sessionQueries.getById(id);
|
|
5153
|
+
const session = await sessionQueries.getById(id);
|
|
5026
5154
|
if (!session) {
|
|
5027
5155
|
return c.json({ error: "Session not found" }, 404);
|
|
5028
5156
|
}
|
|
5029
|
-
const todos = todoQueries.getBySession(id);
|
|
5157
|
+
const todos = await todoQueries.getBySession(id);
|
|
5030
5158
|
const pending = todos.filter((t) => t.status === "pending");
|
|
5031
5159
|
const inProgress = todos.filter((t) => t.status === "in_progress");
|
|
5032
5160
|
const completed = todos.filter((t) => t.status === "completed");
|
|
@@ -5055,32 +5183,32 @@ sessions2.get("/:id/todos", async (c) => {
|
|
|
5055
5183
|
} : null
|
|
5056
5184
|
});
|
|
5057
5185
|
});
|
|
5058
|
-
|
|
5186
|
+
sessions.get("/:id/checkpoints", async (c) => {
|
|
5059
5187
|
const id = c.req.param("id");
|
|
5060
|
-
const session = sessionQueries.getById(id);
|
|
5188
|
+
const session = await sessionQueries.getById(id);
|
|
5061
5189
|
if (!session) {
|
|
5062
5190
|
return c.json({ error: "Session not found" }, 404);
|
|
5063
5191
|
}
|
|
5064
|
-
const
|
|
5192
|
+
const checkpoints = await getCheckpoints(id);
|
|
5065
5193
|
return c.json({
|
|
5066
5194
|
sessionId: id,
|
|
5067
|
-
checkpoints:
|
|
5195
|
+
checkpoints: checkpoints.map((cp) => ({
|
|
5068
5196
|
id: cp.id,
|
|
5069
5197
|
messageSequence: cp.messageSequence,
|
|
5070
5198
|
gitHead: cp.gitHead,
|
|
5071
5199
|
createdAt: cp.createdAt.toISOString()
|
|
5072
5200
|
})),
|
|
5073
|
-
count:
|
|
5201
|
+
count: checkpoints.length
|
|
5074
5202
|
});
|
|
5075
5203
|
});
|
|
5076
|
-
|
|
5204
|
+
sessions.post("/:id/revert/:checkpointId", async (c) => {
|
|
5077
5205
|
const sessionId = c.req.param("id");
|
|
5078
5206
|
const checkpointId = c.req.param("checkpointId");
|
|
5079
|
-
const session = sessionQueries.getById(sessionId);
|
|
5207
|
+
const session = await sessionQueries.getById(sessionId);
|
|
5080
5208
|
if (!session) {
|
|
5081
5209
|
return c.json({ error: "Session not found" }, 404);
|
|
5082
5210
|
}
|
|
5083
|
-
const activeStream = activeStreamQueries.getBySessionId(sessionId);
|
|
5211
|
+
const activeStream = await activeStreamQueries.getBySessionId(sessionId);
|
|
5084
5212
|
if (activeStream) {
|
|
5085
5213
|
return c.json({
|
|
5086
5214
|
error: "Cannot revert while a stream is active. Stop the stream first.",
|
|
@@ -5101,9 +5229,9 @@ sessions2.post("/:id/revert/:checkpointId", async (c) => {
|
|
|
5101
5229
|
checkpointsDeleted: result.checkpointsDeleted
|
|
5102
5230
|
});
|
|
5103
5231
|
});
|
|
5104
|
-
|
|
5232
|
+
sessions.get("/:id/diff", async (c) => {
|
|
5105
5233
|
const id = c.req.param("id");
|
|
5106
|
-
const session = sessionQueries.getById(id);
|
|
5234
|
+
const session = await sessionQueries.getById(id);
|
|
5107
5235
|
if (!session) {
|
|
5108
5236
|
return c.json({ error: "Session not found" }, 404);
|
|
5109
5237
|
}
|
|
@@ -5127,10 +5255,10 @@ sessions2.get("/:id/diff", async (c) => {
|
|
|
5127
5255
|
}
|
|
5128
5256
|
});
|
|
5129
5257
|
});
|
|
5130
|
-
|
|
5258
|
+
sessions.get("/:id/diff/:filePath", async (c) => {
|
|
5131
5259
|
const sessionId = c.req.param("id");
|
|
5132
5260
|
const filePath = decodeURIComponent(c.req.param("filePath"));
|
|
5133
|
-
const session = sessionQueries.getById(sessionId);
|
|
5261
|
+
const session = await sessionQueries.getById(sessionId);
|
|
5134
5262
|
if (!session) {
|
|
5135
5263
|
return c.json({ error: "Session not found" }, 404);
|
|
5136
5264
|
}
|
|
@@ -5149,29 +5277,29 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
5149
5277
|
});
|
|
5150
5278
|
function getAttachmentsDir(sessionId) {
|
|
5151
5279
|
const appDataDir = getAppDataDirectory();
|
|
5152
|
-
return
|
|
5280
|
+
return join6(appDataDir, "attachments", sessionId);
|
|
5153
5281
|
}
|
|
5154
5282
|
function ensureAttachmentsDir(sessionId) {
|
|
5155
5283
|
const dir = getAttachmentsDir(sessionId);
|
|
5156
|
-
if (!
|
|
5284
|
+
if (!existsSync12(dir)) {
|
|
5157
5285
|
mkdirSync3(dir, { recursive: true });
|
|
5158
5286
|
}
|
|
5159
5287
|
return dir;
|
|
5160
5288
|
}
|
|
5161
|
-
|
|
5289
|
+
sessions.get("/:id/attachments", async (c) => {
|
|
5162
5290
|
const sessionId = c.req.param("id");
|
|
5163
|
-
const session = sessionQueries.getById(sessionId);
|
|
5291
|
+
const session = await sessionQueries.getById(sessionId);
|
|
5164
5292
|
if (!session) {
|
|
5165
5293
|
return c.json({ error: "Session not found" }, 404);
|
|
5166
5294
|
}
|
|
5167
5295
|
const dir = getAttachmentsDir(sessionId);
|
|
5168
|
-
if (!
|
|
5296
|
+
if (!existsSync12(dir)) {
|
|
5169
5297
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
5170
5298
|
}
|
|
5171
5299
|
const files = readdirSync(dir);
|
|
5172
5300
|
const attachments = files.map((filename) => {
|
|
5173
|
-
const filePath =
|
|
5174
|
-
const stats =
|
|
5301
|
+
const filePath = join6(dir, filename);
|
|
5302
|
+
const stats = statSync2(filePath);
|
|
5175
5303
|
return {
|
|
5176
5304
|
id: filename.split("_")[0],
|
|
5177
5305
|
// Extract the nanoid prefix
|
|
@@ -5187,9 +5315,9 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
5187
5315
|
count: attachments.length
|
|
5188
5316
|
});
|
|
5189
5317
|
});
|
|
5190
|
-
|
|
5318
|
+
sessions.post("/:id/attachments", async (c) => {
|
|
5191
5319
|
const sessionId = c.req.param("id");
|
|
5192
|
-
const session = sessionQueries.getById(sessionId);
|
|
5320
|
+
const session = await sessionQueries.getById(sessionId);
|
|
5193
5321
|
if (!session) {
|
|
5194
5322
|
return c.json({ error: "Session not found" }, 404);
|
|
5195
5323
|
}
|
|
@@ -5202,10 +5330,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
5202
5330
|
return c.json({ error: "No file provided" }, 400);
|
|
5203
5331
|
}
|
|
5204
5332
|
const dir = ensureAttachmentsDir(sessionId);
|
|
5205
|
-
const id =
|
|
5206
|
-
const ext =
|
|
5207
|
-
const safeFilename = `${id}_${
|
|
5208
|
-
const filePath =
|
|
5333
|
+
const id = nanoid4(10);
|
|
5334
|
+
const ext = extname7(file.name) || "";
|
|
5335
|
+
const safeFilename = `${id}_${basename3(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
5336
|
+
const filePath = join6(dir, safeFilename);
|
|
5209
5337
|
const arrayBuffer = await file.arrayBuffer();
|
|
5210
5338
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
5211
5339
|
return c.json({
|
|
@@ -5228,10 +5356,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
5228
5356
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
5229
5357
|
}
|
|
5230
5358
|
const dir = ensureAttachmentsDir(sessionId);
|
|
5231
|
-
const id =
|
|
5232
|
-
const ext =
|
|
5233
|
-
const safeFilename = `${id}_${
|
|
5234
|
-
const filePath =
|
|
5359
|
+
const id = nanoid4(10);
|
|
5360
|
+
const ext = extname7(body.filename) || "";
|
|
5361
|
+
const safeFilename = `${id}_${basename3(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
5362
|
+
const filePath = join6(dir, safeFilename);
|
|
5235
5363
|
let base64Data = body.data;
|
|
5236
5364
|
if (base64Data.includes(",")) {
|
|
5237
5365
|
base64Data = base64Data.split(",")[1];
|
|
@@ -5252,15 +5380,15 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
5252
5380
|
return c.json({ error: "Failed to upload file" }, 500);
|
|
5253
5381
|
}
|
|
5254
5382
|
});
|
|
5255
|
-
|
|
5383
|
+
sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
5256
5384
|
const sessionId = c.req.param("id");
|
|
5257
5385
|
const attachmentId = c.req.param("attachmentId");
|
|
5258
|
-
const session = sessionQueries.getById(sessionId);
|
|
5386
|
+
const session = await sessionQueries.getById(sessionId);
|
|
5259
5387
|
if (!session) {
|
|
5260
5388
|
return c.json({ error: "Session not found" }, 404);
|
|
5261
5389
|
}
|
|
5262
5390
|
const dir = getAttachmentsDir(sessionId);
|
|
5263
|
-
if (!
|
|
5391
|
+
if (!existsSync12(dir)) {
|
|
5264
5392
|
return c.json({ error: "Attachment not found" }, 404);
|
|
5265
5393
|
}
|
|
5266
5394
|
const files = readdirSync(dir);
|
|
@@ -5268,14 +5396,14 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
5268
5396
|
if (!file) {
|
|
5269
5397
|
return c.json({ error: "Attachment not found" }, 404);
|
|
5270
5398
|
}
|
|
5271
|
-
const filePath =
|
|
5399
|
+
const filePath = join6(dir, file);
|
|
5272
5400
|
unlinkSync(filePath);
|
|
5273
5401
|
return c.json({ success: true, id: attachmentId });
|
|
5274
5402
|
});
|
|
5275
|
-
var filesQuerySchema =
|
|
5276
|
-
query:
|
|
5403
|
+
var filesQuerySchema = z12.object({
|
|
5404
|
+
query: z12.string().optional(),
|
|
5277
5405
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
5278
|
-
limit:
|
|
5406
|
+
limit: z12.string().optional()
|
|
5279
5407
|
// Max results (default 50)
|
|
5280
5408
|
});
|
|
5281
5409
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -5351,15 +5479,15 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
5351
5479
|
const entries = await readdir4(currentDir, { withFileTypes: true });
|
|
5352
5480
|
for (const entry of entries) {
|
|
5353
5481
|
if (results.length >= limit * 2) break;
|
|
5354
|
-
const fullPath =
|
|
5355
|
-
const relativePath =
|
|
5482
|
+
const fullPath = join6(currentDir, entry.name);
|
|
5483
|
+
const relativePath = relative8(baseDir, fullPath);
|
|
5356
5484
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
5357
5485
|
continue;
|
|
5358
5486
|
}
|
|
5359
5487
|
if (entry.name.startsWith(".")) {
|
|
5360
5488
|
continue;
|
|
5361
5489
|
}
|
|
5362
|
-
const ext =
|
|
5490
|
+
const ext = extname7(entry.name).toLowerCase();
|
|
5363
5491
|
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
5364
5492
|
continue;
|
|
5365
5493
|
}
|
|
@@ -5387,19 +5515,19 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
5387
5515
|
}
|
|
5388
5516
|
return results;
|
|
5389
5517
|
}
|
|
5390
|
-
|
|
5518
|
+
sessions.get(
|
|
5391
5519
|
"/:id/files",
|
|
5392
5520
|
zValidator("query", filesQuerySchema),
|
|
5393
5521
|
async (c) => {
|
|
5394
5522
|
const sessionId = c.req.param("id");
|
|
5395
5523
|
const { query = "", limit: limitStr = "50" } = c.req.valid("query");
|
|
5396
5524
|
const limit = Math.min(parseInt(limitStr) || 50, 100);
|
|
5397
|
-
const session = sessionQueries.getById(sessionId);
|
|
5525
|
+
const session = await sessionQueries.getById(sessionId);
|
|
5398
5526
|
if (!session) {
|
|
5399
5527
|
return c.json({ error: "Session not found" }, 404);
|
|
5400
5528
|
}
|
|
5401
5529
|
const workingDirectory = session.workingDirectory;
|
|
5402
|
-
if (!
|
|
5530
|
+
if (!existsSync12(workingDirectory)) {
|
|
5403
5531
|
return c.json({
|
|
5404
5532
|
sessionId,
|
|
5405
5533
|
workingDirectory,
|
|
@@ -5452,9 +5580,9 @@ sessions2.get(
|
|
|
5452
5580
|
// src/server/routes/agents.ts
|
|
5453
5581
|
import { Hono as Hono2 } from "hono";
|
|
5454
5582
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
5455
|
-
import { z as
|
|
5456
|
-
import { existsSync as
|
|
5457
|
-
import { join as
|
|
5583
|
+
import { z as z13 } from "zod";
|
|
5584
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
5585
|
+
import { join as join7 } from "path";
|
|
5458
5586
|
|
|
5459
5587
|
// src/server/resumable-stream.ts
|
|
5460
5588
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -5529,7 +5657,7 @@ var streamContext = createResumableStreamContext({
|
|
|
5529
5657
|
});
|
|
5530
5658
|
|
|
5531
5659
|
// src/server/routes/agents.ts
|
|
5532
|
-
import { nanoid as
|
|
5660
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
5533
5661
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
5534
5662
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
5535
5663
|
var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
|
|
@@ -5597,39 +5725,39 @@ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId,
|
|
|
5597
5725
|
}
|
|
5598
5726
|
}
|
|
5599
5727
|
var agents = new Hono2();
|
|
5600
|
-
var attachmentSchema =
|
|
5601
|
-
type:
|
|
5602
|
-
data:
|
|
5728
|
+
var attachmentSchema = z13.object({
|
|
5729
|
+
type: z13.enum(["image", "file"]),
|
|
5730
|
+
data: z13.string(),
|
|
5603
5731
|
// base64 data URL or raw base64
|
|
5604
|
-
mediaType:
|
|
5605
|
-
filename:
|
|
5732
|
+
mediaType: z13.string().optional(),
|
|
5733
|
+
filename: z13.string().optional()
|
|
5606
5734
|
});
|
|
5607
|
-
var runPromptSchema =
|
|
5608
|
-
prompt:
|
|
5735
|
+
var runPromptSchema = z13.object({
|
|
5736
|
+
prompt: z13.string(),
|
|
5609
5737
|
// Can be empty if attachments are provided
|
|
5610
|
-
attachments:
|
|
5738
|
+
attachments: z13.array(attachmentSchema).optional()
|
|
5611
5739
|
}).refine(
|
|
5612
5740
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
5613
5741
|
{ message: "Either prompt or attachments must be provided" }
|
|
5614
5742
|
);
|
|
5615
|
-
var quickStartSchema =
|
|
5616
|
-
prompt:
|
|
5617
|
-
name:
|
|
5618
|
-
workingDirectory:
|
|
5619
|
-
model:
|
|
5620
|
-
toolApprovals:
|
|
5743
|
+
var quickStartSchema = z13.object({
|
|
5744
|
+
prompt: z13.string().min(1),
|
|
5745
|
+
name: z13.string().optional(),
|
|
5746
|
+
workingDirectory: z13.string().optional(),
|
|
5747
|
+
model: z13.string().optional(),
|
|
5748
|
+
toolApprovals: z13.record(z13.string(), z13.boolean()).optional()
|
|
5621
5749
|
});
|
|
5622
|
-
var rejectSchema =
|
|
5623
|
-
reason:
|
|
5750
|
+
var rejectSchema = z13.object({
|
|
5751
|
+
reason: z13.string().optional()
|
|
5624
5752
|
}).optional();
|
|
5625
5753
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
5626
5754
|
function getAttachmentsDirectory(sessionId) {
|
|
5627
5755
|
const appDataDir = getAppDataDirectory();
|
|
5628
|
-
return
|
|
5756
|
+
return join7(appDataDir, "attachments", sessionId);
|
|
5629
5757
|
}
|
|
5630
5758
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
5631
5759
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
5632
|
-
if (!
|
|
5760
|
+
if (!existsSync13(attachmentsDir)) {
|
|
5633
5761
|
mkdirSync4(attachmentsDir, { recursive: true });
|
|
5634
5762
|
}
|
|
5635
5763
|
let filename = attachment.filename;
|
|
@@ -5641,7 +5769,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
5641
5769
|
if (base64Data.includes(",")) {
|
|
5642
5770
|
base64Data = base64Data.split(",")[1];
|
|
5643
5771
|
}
|
|
5644
|
-
const filePath =
|
|
5772
|
+
const filePath = join7(attachmentsDir, filename);
|
|
5645
5773
|
const buffer = Buffer.from(base64Data, "base64");
|
|
5646
5774
|
writeFileSync3(filePath, buffer);
|
|
5647
5775
|
return filePath;
|
|
@@ -5903,7 +6031,7 @@ ${prompt}` });
|
|
|
5903
6031
|
await writeSSE(JSON.stringify({ type: "finish" }));
|
|
5904
6032
|
}
|
|
5905
6033
|
try {
|
|
5906
|
-
activeStreamQueries.finish(streamId);
|
|
6034
|
+
await activeStreamQueries.finish(streamId);
|
|
5907
6035
|
} catch {
|
|
5908
6036
|
}
|
|
5909
6037
|
} catch (error) {
|
|
@@ -5913,7 +6041,7 @@ ${prompt}` });
|
|
|
5913
6041
|
console.error("Agent error:", error);
|
|
5914
6042
|
await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
|
|
5915
6043
|
try {
|
|
5916
|
-
activeStreamQueries.markError(streamId);
|
|
6044
|
+
await activeStreamQueries.markError(streamId);
|
|
5917
6045
|
} catch {
|
|
5918
6046
|
}
|
|
5919
6047
|
}
|
|
@@ -5932,11 +6060,11 @@ agents.post(
|
|
|
5932
6060
|
async (c) => {
|
|
5933
6061
|
const id = c.req.param("id");
|
|
5934
6062
|
const { prompt, attachments } = c.req.valid("json");
|
|
5935
|
-
const session = sessionQueries.getById(id);
|
|
6063
|
+
const session = await sessionQueries.getById(id);
|
|
5936
6064
|
if (!session) {
|
|
5937
6065
|
return c.json({ error: "Session not found" }, 404);
|
|
5938
6066
|
}
|
|
5939
|
-
const nextSequence = messageQueries.getNextSequence(id);
|
|
6067
|
+
const nextSequence = await messageQueries.getNextSequence(id);
|
|
5940
6068
|
await createCheckpoint(id, session.workingDirectory, nextSequence);
|
|
5941
6069
|
let userMessageContent;
|
|
5942
6070
|
const streamAttachments = attachments;
|
|
@@ -5993,9 +6121,9 @@ ${prompt}` });
|
|
|
5993
6121
|
} else {
|
|
5994
6122
|
userMessageContent = prompt;
|
|
5995
6123
|
}
|
|
5996
|
-
messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
5997
|
-
const streamId = `stream_${id}_${
|
|
5998
|
-
activeStreamQueries.create(id, streamId);
|
|
6124
|
+
await messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
6125
|
+
const streamId = `stream_${id}_${nanoid5(10)}`;
|
|
6126
|
+
await activeStreamQueries.create(id, streamId);
|
|
5999
6127
|
const stream = await streamContext.resumableStream(
|
|
6000
6128
|
streamId,
|
|
6001
6129
|
createAgentStreamProducer(id, prompt, streamId, streamAttachments)
|
|
@@ -6019,13 +6147,13 @@ agents.get("/:id/watch", async (c) => {
|
|
|
6019
6147
|
const sessionId = c.req.param("id");
|
|
6020
6148
|
const resumeAt = c.req.query("resumeAt");
|
|
6021
6149
|
const explicitStreamId = c.req.query("streamId");
|
|
6022
|
-
const session = sessionQueries.getById(sessionId);
|
|
6150
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6023
6151
|
if (!session) {
|
|
6024
6152
|
return c.json({ error: "Session not found" }, 404);
|
|
6025
6153
|
}
|
|
6026
6154
|
let streamId = explicitStreamId;
|
|
6027
6155
|
if (!streamId) {
|
|
6028
|
-
const activeStream = activeStreamQueries.getBySessionId(sessionId);
|
|
6156
|
+
const activeStream = await activeStreamQueries.getBySessionId(sessionId);
|
|
6029
6157
|
if (!activeStream) {
|
|
6030
6158
|
return c.json({ error: "No active stream for this session", hint: "Start a new run with POST /agents/:id/run" }, 404);
|
|
6031
6159
|
}
|
|
@@ -6055,11 +6183,11 @@ agents.get("/:id/watch", async (c) => {
|
|
|
6055
6183
|
});
|
|
6056
6184
|
agents.get("/:id/stream", async (c) => {
|
|
6057
6185
|
const sessionId = c.req.param("id");
|
|
6058
|
-
const session = sessionQueries.getById(sessionId);
|
|
6186
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6059
6187
|
if (!session) {
|
|
6060
6188
|
return c.json({ error: "Session not found" }, 404);
|
|
6061
6189
|
}
|
|
6062
|
-
const activeStream = activeStreamQueries.getBySessionId(sessionId);
|
|
6190
|
+
const activeStream = await activeStreamQueries.getBySessionId(sessionId);
|
|
6063
6191
|
return c.json({
|
|
6064
6192
|
sessionId,
|
|
6065
6193
|
hasActiveStream: !!activeStream,
|
|
@@ -6077,7 +6205,7 @@ agents.post(
|
|
|
6077
6205
|
async (c) => {
|
|
6078
6206
|
const id = c.req.param("id");
|
|
6079
6207
|
const { prompt } = c.req.valid("json");
|
|
6080
|
-
const session = sessionQueries.getById(id);
|
|
6208
|
+
const session = await sessionQueries.getById(id);
|
|
6081
6209
|
if (!session) {
|
|
6082
6210
|
return c.json({ error: "Session not found" }, 404);
|
|
6083
6211
|
}
|
|
@@ -6097,7 +6225,7 @@ agents.post(
|
|
|
6097
6225
|
agents.post("/:id/approve/:toolCallId", async (c) => {
|
|
6098
6226
|
const sessionId = c.req.param("id");
|
|
6099
6227
|
const toolCallId = c.req.param("toolCallId");
|
|
6100
|
-
const session = sessionQueries.getById(sessionId);
|
|
6228
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6101
6229
|
if (!session) {
|
|
6102
6230
|
return c.json({ error: "Session not found" }, 404);
|
|
6103
6231
|
}
|
|
@@ -6120,7 +6248,7 @@ agents.post(
|
|
|
6120
6248
|
const sessionId = c.req.param("id");
|
|
6121
6249
|
const toolCallId = c.req.param("toolCallId");
|
|
6122
6250
|
const body = c.req.valid("json");
|
|
6123
|
-
const session = sessionQueries.getById(sessionId);
|
|
6251
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6124
6252
|
if (!session) {
|
|
6125
6253
|
return c.json({ error: "Session not found" }, 404);
|
|
6126
6254
|
}
|
|
@@ -6139,11 +6267,11 @@ agents.post(
|
|
|
6139
6267
|
);
|
|
6140
6268
|
agents.get("/:id/approvals", async (c) => {
|
|
6141
6269
|
const sessionId = c.req.param("id");
|
|
6142
|
-
const session = sessionQueries.getById(sessionId);
|
|
6270
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6143
6271
|
if (!session) {
|
|
6144
6272
|
return c.json({ error: "Session not found" }, 404);
|
|
6145
6273
|
}
|
|
6146
|
-
const pendingApprovals = toolExecutionQueries.getPendingApprovals(sessionId);
|
|
6274
|
+
const pendingApprovals = await toolExecutionQueries.getPendingApprovals(sessionId);
|
|
6147
6275
|
return c.json({
|
|
6148
6276
|
sessionId,
|
|
6149
6277
|
pendingApprovals: pendingApprovals.map((p) => ({
|
|
@@ -6158,11 +6286,11 @@ agents.get("/:id/approvals", async (c) => {
|
|
|
6158
6286
|
});
|
|
6159
6287
|
agents.post("/:id/abort", async (c) => {
|
|
6160
6288
|
const sessionId = c.req.param("id");
|
|
6161
|
-
const session = sessionQueries.getById(sessionId);
|
|
6289
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6162
6290
|
if (!session) {
|
|
6163
6291
|
return c.json({ error: "Session not found" }, 404);
|
|
6164
6292
|
}
|
|
6165
|
-
const activeStream = activeStreamQueries.getBySessionId(sessionId);
|
|
6293
|
+
const activeStream = await activeStreamQueries.getBySessionId(sessionId);
|
|
6166
6294
|
if (!activeStream) {
|
|
6167
6295
|
return c.json({ error: "No active stream for this session" }, 404);
|
|
6168
6296
|
}
|
|
@@ -6191,9 +6319,9 @@ agents.post(
|
|
|
6191
6319
|
sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
|
|
6192
6320
|
});
|
|
6193
6321
|
const session = agent.getSession();
|
|
6194
|
-
const streamId = `stream_${session.id}_${
|
|
6322
|
+
const streamId = `stream_${session.id}_${nanoid5(10)}`;
|
|
6195
6323
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
6196
|
-
activeStreamQueries.create(session.id, streamId);
|
|
6324
|
+
await activeStreamQueries.create(session.id, streamId);
|
|
6197
6325
|
const createQuickStreamProducer = () => {
|
|
6198
6326
|
const { readable, writable } = new TransformStream();
|
|
6199
6327
|
const writer = writable.getWriter();
|
|
@@ -6363,14 +6491,14 @@ agents.post(
|
|
|
6363
6491
|
} else {
|
|
6364
6492
|
await writeSSE(JSON.stringify({ type: "finish" }));
|
|
6365
6493
|
}
|
|
6366
|
-
activeStreamQueries.finish(streamId);
|
|
6494
|
+
await activeStreamQueries.finish(streamId);
|
|
6367
6495
|
} catch (error) {
|
|
6368
6496
|
if (error.name === "AbortError" || error.message?.includes("aborted")) {
|
|
6369
6497
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
6370
6498
|
} else {
|
|
6371
6499
|
console.error("Agent error:", error);
|
|
6372
6500
|
await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
|
|
6373
|
-
activeStreamQueries.markError(streamId);
|
|
6501
|
+
await activeStreamQueries.markError(streamId);
|
|
6374
6502
|
}
|
|
6375
6503
|
} finally {
|
|
6376
6504
|
cleanupAbortController();
|
|
@@ -6404,23 +6532,23 @@ agents.post(
|
|
|
6404
6532
|
// src/server/routes/health.ts
|
|
6405
6533
|
import { Hono as Hono3 } from "hono";
|
|
6406
6534
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
6407
|
-
import { z as
|
|
6408
|
-
import { readFileSync as
|
|
6535
|
+
import { z as z14 } from "zod";
|
|
6536
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
6409
6537
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6410
|
-
import { dirname as dirname6, join as
|
|
6538
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
6411
6539
|
var __filename = fileURLToPath2(import.meta.url);
|
|
6412
6540
|
var __dirname = dirname6(__filename);
|
|
6413
6541
|
var possiblePaths = [
|
|
6414
|
-
|
|
6542
|
+
join8(__dirname, "../../package.json"),
|
|
6415
6543
|
// From dist/server (bundled)
|
|
6416
|
-
|
|
6544
|
+
join8(__dirname, "../../../package.json")
|
|
6417
6545
|
// From dist/server/routes or src/server/routes
|
|
6418
6546
|
];
|
|
6419
6547
|
var currentVersion = "0.0.0";
|
|
6420
6548
|
var packageName = "sparkecoder";
|
|
6421
6549
|
for (const packageJsonPath of possiblePaths) {
|
|
6422
6550
|
try {
|
|
6423
|
-
const packageJson = JSON.parse(
|
|
6551
|
+
const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
6424
6552
|
if (packageJson.name === "sparkecoder" || packageJson.name?.startsWith("sparkecoder")) {
|
|
6425
6553
|
currentVersion = packageJson.version || "0.0.0";
|
|
6426
6554
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -6510,9 +6638,9 @@ health.get("/api-keys", async (c) => {
|
|
|
6510
6638
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
6511
6639
|
});
|
|
6512
6640
|
});
|
|
6513
|
-
var setApiKeySchema =
|
|
6514
|
-
provider:
|
|
6515
|
-
apiKey:
|
|
6641
|
+
var setApiKeySchema = z14.object({
|
|
6642
|
+
provider: z14.string(),
|
|
6643
|
+
apiKey: z14.string().min(1)
|
|
6516
6644
|
});
|
|
6517
6645
|
health.post(
|
|
6518
6646
|
"/api-keys",
|
|
@@ -6551,20 +6679,20 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
6551
6679
|
// src/server/routes/terminals.ts
|
|
6552
6680
|
import { Hono as Hono4 } from "hono";
|
|
6553
6681
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
6554
|
-
import { z as
|
|
6555
|
-
var
|
|
6556
|
-
var spawnSchema =
|
|
6557
|
-
command:
|
|
6558
|
-
cwd:
|
|
6559
|
-
name:
|
|
6682
|
+
import { z as z15 } from "zod";
|
|
6683
|
+
var terminals = new Hono4();
|
|
6684
|
+
var spawnSchema = z15.object({
|
|
6685
|
+
command: z15.string(),
|
|
6686
|
+
cwd: z15.string().optional(),
|
|
6687
|
+
name: z15.string().optional()
|
|
6560
6688
|
});
|
|
6561
|
-
|
|
6689
|
+
terminals.post(
|
|
6562
6690
|
"/:sessionId/terminals",
|
|
6563
6691
|
zValidator4("json", spawnSchema),
|
|
6564
6692
|
async (c) => {
|
|
6565
6693
|
const sessionId = c.req.param("sessionId");
|
|
6566
6694
|
const body = c.req.valid("json");
|
|
6567
|
-
const session = sessionQueries.getById(sessionId);
|
|
6695
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6568
6696
|
if (!session) {
|
|
6569
6697
|
return c.json({ error: "Session not found" }, 404);
|
|
6570
6698
|
}
|
|
@@ -6588,9 +6716,9 @@ terminals2.post(
|
|
|
6588
6716
|
}, 201);
|
|
6589
6717
|
}
|
|
6590
6718
|
);
|
|
6591
|
-
|
|
6719
|
+
terminals.get("/:sessionId/terminals", async (c) => {
|
|
6592
6720
|
const sessionId = c.req.param("sessionId");
|
|
6593
|
-
const session = sessionQueries.getById(sessionId);
|
|
6721
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6594
6722
|
if (!session) {
|
|
6595
6723
|
return c.json({ error: "Session not found" }, 404);
|
|
6596
6724
|
}
|
|
@@ -6615,10 +6743,10 @@ terminals2.get("/:sessionId/terminals", async (c) => {
|
|
|
6615
6743
|
running: terminalList.filter((t) => t.status === "running").length
|
|
6616
6744
|
});
|
|
6617
6745
|
});
|
|
6618
|
-
|
|
6746
|
+
terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
6619
6747
|
const sessionId = c.req.param("sessionId");
|
|
6620
6748
|
const terminalId = c.req.param("terminalId");
|
|
6621
|
-
const session = sessionQueries.getById(sessionId);
|
|
6749
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6622
6750
|
if (!session) {
|
|
6623
6751
|
return c.json({ error: "Session not found" }, 404);
|
|
6624
6752
|
}
|
|
@@ -6637,17 +6765,17 @@ terminals2.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
6637
6765
|
// We don't track exit codes in tmux mode
|
|
6638
6766
|
});
|
|
6639
6767
|
});
|
|
6640
|
-
var logsQuerySchema =
|
|
6641
|
-
tail:
|
|
6768
|
+
var logsQuerySchema = z15.object({
|
|
6769
|
+
tail: z15.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
6642
6770
|
});
|
|
6643
|
-
|
|
6771
|
+
terminals.get(
|
|
6644
6772
|
"/:sessionId/terminals/:terminalId/logs",
|
|
6645
6773
|
zValidator4("query", logsQuerySchema),
|
|
6646
6774
|
async (c) => {
|
|
6647
6775
|
const sessionId = c.req.param("sessionId");
|
|
6648
6776
|
const terminalId = c.req.param("terminalId");
|
|
6649
6777
|
const query = c.req.valid("query");
|
|
6650
|
-
const session = sessionQueries.getById(sessionId);
|
|
6778
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6651
6779
|
if (!session) {
|
|
6652
6780
|
return c.json({ error: "Session not found" }, 404);
|
|
6653
6781
|
}
|
|
@@ -6662,10 +6790,10 @@ terminals2.get(
|
|
|
6662
6790
|
});
|
|
6663
6791
|
}
|
|
6664
6792
|
);
|
|
6665
|
-
var killSchema =
|
|
6666
|
-
signal:
|
|
6793
|
+
var killSchema = z15.object({
|
|
6794
|
+
signal: z15.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
6667
6795
|
});
|
|
6668
|
-
|
|
6796
|
+
terminals.post(
|
|
6669
6797
|
"/:sessionId/terminals/:terminalId/kill",
|
|
6670
6798
|
zValidator4("json", killSchema.optional()),
|
|
6671
6799
|
async (c) => {
|
|
@@ -6677,10 +6805,10 @@ terminals2.post(
|
|
|
6677
6805
|
return c.json({ success: true, message: "Terminal killed" });
|
|
6678
6806
|
}
|
|
6679
6807
|
);
|
|
6680
|
-
var writeSchema =
|
|
6681
|
-
input:
|
|
6808
|
+
var writeSchema = z15.object({
|
|
6809
|
+
input: z15.string()
|
|
6682
6810
|
});
|
|
6683
|
-
|
|
6811
|
+
terminals.post(
|
|
6684
6812
|
"/:sessionId/terminals/:terminalId/write",
|
|
6685
6813
|
zValidator4("json", writeSchema),
|
|
6686
6814
|
async (c) => {
|
|
@@ -6697,9 +6825,9 @@ terminals2.post(
|
|
|
6697
6825
|
return c.json({ success: true, written: body.input.length });
|
|
6698
6826
|
}
|
|
6699
6827
|
);
|
|
6700
|
-
|
|
6828
|
+
terminals.post("/:sessionId/terminals/kill-all", async (c) => {
|
|
6701
6829
|
const sessionId = c.req.param("sessionId");
|
|
6702
|
-
const session = sessionQueries.getById(sessionId);
|
|
6830
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6703
6831
|
if (!session) {
|
|
6704
6832
|
return c.json({ error: "Session not found" }, 404);
|
|
6705
6833
|
}
|
|
@@ -6714,13 +6842,13 @@ terminals2.post("/:sessionId/terminals/kill-all", async (c) => {
|
|
|
6714
6842
|
}
|
|
6715
6843
|
return c.json({ success: true, killed });
|
|
6716
6844
|
});
|
|
6717
|
-
|
|
6845
|
+
terminals.get("/stream/:terminalId", async (c) => {
|
|
6718
6846
|
const terminalId = c.req.param("terminalId");
|
|
6719
|
-
const
|
|
6847
|
+
const sessions2 = await sessionQueries.list();
|
|
6720
6848
|
let terminalMeta = null;
|
|
6721
6849
|
let workingDirectory = process.cwd();
|
|
6722
6850
|
let foundSessionId;
|
|
6723
|
-
for (const session of
|
|
6851
|
+
for (const session of sessions2) {
|
|
6724
6852
|
terminalMeta = await getMeta(terminalId, session.workingDirectory, session.id);
|
|
6725
6853
|
if (terminalMeta) {
|
|
6726
6854
|
workingDirectory = session.workingDirectory;
|
|
@@ -6729,7 +6857,7 @@ terminals2.get("/stream/:terminalId", async (c) => {
|
|
|
6729
6857
|
}
|
|
6730
6858
|
}
|
|
6731
6859
|
if (!terminalMeta) {
|
|
6732
|
-
for (const session of
|
|
6860
|
+
for (const session of sessions2) {
|
|
6733
6861
|
terminalMeta = await getMeta(terminalId, session.workingDirectory);
|
|
6734
6862
|
if (terminalMeta) {
|
|
6735
6863
|
workingDirectory = session.workingDirectory;
|
|
@@ -6799,10 +6927,10 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
6799
6927
|
}
|
|
6800
6928
|
);
|
|
6801
6929
|
});
|
|
6802
|
-
|
|
6930
|
+
terminals.get("/:sessionId/terminals/:terminalId/stream", async (c) => {
|
|
6803
6931
|
const sessionId = c.req.param("sessionId");
|
|
6804
6932
|
const terminalId = c.req.param("terminalId");
|
|
6805
|
-
const session = sessionQueries.getById(sessionId);
|
|
6933
|
+
const session = await sessionQueries.getById(sessionId);
|
|
6806
6934
|
if (!session) {
|
|
6807
6935
|
return c.json({ error: "Session not found" }, 404);
|
|
6808
6936
|
}
|
|
@@ -6945,11 +7073,11 @@ function getWebDirectory() {
|
|
|
6945
7073
|
try {
|
|
6946
7074
|
const currentDir = dirname7(fileURLToPath3(import.meta.url));
|
|
6947
7075
|
const webDir = resolve9(currentDir, "..", "web");
|
|
6948
|
-
if (
|
|
7076
|
+
if (existsSync14(webDir) && existsSync14(join9(webDir, "package.json"))) {
|
|
6949
7077
|
return webDir;
|
|
6950
7078
|
}
|
|
6951
7079
|
const altWebDir = resolve9(currentDir, "..", "..", "web");
|
|
6952
|
-
if (
|
|
7080
|
+
if (existsSync14(altWebDir) && existsSync14(join9(altWebDir, "package.json"))) {
|
|
6953
7081
|
return altWebDir;
|
|
6954
7082
|
}
|
|
6955
7083
|
return null;
|
|
@@ -7007,23 +7135,23 @@ async function findWebPort(preferredPort) {
|
|
|
7007
7135
|
return { port: preferredPort, alreadyRunning: false };
|
|
7008
7136
|
}
|
|
7009
7137
|
function hasProductionBuild(webDir) {
|
|
7010
|
-
const buildIdPath =
|
|
7011
|
-
return
|
|
7138
|
+
const buildIdPath = join9(webDir, ".next", "BUILD_ID");
|
|
7139
|
+
return existsSync14(buildIdPath);
|
|
7012
7140
|
}
|
|
7013
7141
|
function hasSourceFiles(webDir) {
|
|
7014
|
-
const appDir =
|
|
7015
|
-
const pagesDir =
|
|
7016
|
-
const rootAppDir =
|
|
7017
|
-
const rootPagesDir =
|
|
7018
|
-
return
|
|
7142
|
+
const appDir = join9(webDir, "src", "app");
|
|
7143
|
+
const pagesDir = join9(webDir, "src", "pages");
|
|
7144
|
+
const rootAppDir = join9(webDir, "app");
|
|
7145
|
+
const rootPagesDir = join9(webDir, "pages");
|
|
7146
|
+
return existsSync14(appDir) || existsSync14(pagesDir) || existsSync14(rootAppDir) || existsSync14(rootPagesDir);
|
|
7019
7147
|
}
|
|
7020
7148
|
function getStandaloneServerPath(webDir) {
|
|
7021
7149
|
const possiblePaths2 = [
|
|
7022
|
-
|
|
7023
|
-
|
|
7150
|
+
join9(webDir, ".next", "standalone", "server.js"),
|
|
7151
|
+
join9(webDir, ".next", "standalone", "web", "server.js")
|
|
7024
7152
|
];
|
|
7025
7153
|
for (const serverPath of possiblePaths2) {
|
|
7026
|
-
if (
|
|
7154
|
+
if (existsSync14(serverPath)) {
|
|
7027
7155
|
return serverPath;
|
|
7028
7156
|
}
|
|
7029
7157
|
}
|
|
@@ -7063,13 +7191,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
7063
7191
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
7064
7192
|
return { process: null, port: actualPort };
|
|
7065
7193
|
}
|
|
7066
|
-
const usePnpm =
|
|
7067
|
-
const useNpm = !usePnpm &&
|
|
7194
|
+
const usePnpm = existsSync14(join9(webDir, "pnpm-lock.yaml"));
|
|
7195
|
+
const useNpm = !usePnpm && existsSync14(join9(webDir, "package-lock.json"));
|
|
7068
7196
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
7069
7197
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
7070
7198
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
7071
7199
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
7072
|
-
const runtimeConfigPath =
|
|
7200
|
+
const runtimeConfigPath = join9(webDir, "runtime-config.json");
|
|
7073
7201
|
try {
|
|
7074
7202
|
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
7075
7203
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -7205,10 +7333,10 @@ async function createApp(options = {}) {
|
|
|
7205
7333
|
app.use("*", logger());
|
|
7206
7334
|
}
|
|
7207
7335
|
app.route("/health", health);
|
|
7208
|
-
app.route("/sessions",
|
|
7336
|
+
app.route("/sessions", sessions);
|
|
7209
7337
|
app.route("/agents", agents);
|
|
7210
|
-
app.route("/sessions",
|
|
7211
|
-
app.route("/terminals",
|
|
7338
|
+
app.route("/sessions", terminals);
|
|
7339
|
+
app.route("/terminals", terminals);
|
|
7212
7340
|
app.get("/openapi.json", async (c) => {
|
|
7213
7341
|
return c.json(generateOpenAPISpec());
|
|
7214
7342
|
});
|
|
@@ -7261,11 +7389,21 @@ async function startServer(options = {}) {
|
|
|
7261
7389
|
if (options.workingDirectory) {
|
|
7262
7390
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
7263
7391
|
}
|
|
7264
|
-
if (!
|
|
7392
|
+
if (!existsSync14(config.resolvedWorkingDirectory)) {
|
|
7265
7393
|
mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
|
|
7266
7394
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
7267
7395
|
}
|
|
7268
|
-
|
|
7396
|
+
if (!config.resolvedRemoteServer.url) {
|
|
7397
|
+
throw new Error("Remote server not configured. Set REMOTE_SERVER_URL environment variable or remoteServer.url in config");
|
|
7398
|
+
}
|
|
7399
|
+
let authKey3 = config.resolvedRemoteServer.authKey;
|
|
7400
|
+
if (!authKey3) {
|
|
7401
|
+
if (!options.quiet) console.log("\u{1F4E1} Registering with remote server...");
|
|
7402
|
+
authKey3 = await ensureRemoteAuthKey(config.resolvedRemoteServer.url);
|
|
7403
|
+
if (!options.quiet) console.log("\u2713 Registered with remote server");
|
|
7404
|
+
}
|
|
7405
|
+
initDatabase({ url: config.resolvedRemoteServer.url, authKey: authKey3 });
|
|
7406
|
+
if (!options.quiet) console.log(`\u{1F4E1} Using remote database: ${config.resolvedRemoteServer.url}`);
|
|
7269
7407
|
const port = options.port || config.server.port;
|
|
7270
7408
|
const host = options.host || config.server.host || "0.0.0.0";
|
|
7271
7409
|
const publicUrl = options.publicUrl || config.server.publicUrl;
|
|
@@ -7298,8 +7436,8 @@ async function startServer(options = {}) {
|
|
|
7298
7436
|
}
|
|
7299
7437
|
function stopServer() {
|
|
7300
7438
|
stopWebUI();
|
|
7301
|
-
listSessions().then(async (
|
|
7302
|
-
for (const id of
|
|
7439
|
+
listSessions().then(async (sessions2) => {
|
|
7440
|
+
for (const id of sessions2) {
|
|
7303
7441
|
await killTerminal(id);
|
|
7304
7442
|
}
|
|
7305
7443
|
}).catch(() => {
|