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/tools/index.js
CHANGED
|
@@ -55,6 +55,53 @@ var SessionConfigSchema = z.object({
|
|
|
55
55
|
skillsDirectory: z.string().optional(),
|
|
56
56
|
maxContextChars: z.number().optional().default(2e5)
|
|
57
57
|
});
|
|
58
|
+
var VectorGatewayConfigSchema = z.object({
|
|
59
|
+
// Redis URL for Vector Gateway (or use VECTOR_REDIS_URL env var)
|
|
60
|
+
redisUrl: z.string().optional(),
|
|
61
|
+
// HTTP URL for database operations (or use VECTOR_HTTP_URL env var)
|
|
62
|
+
httpUrl: z.string().optional(),
|
|
63
|
+
// Embedding model to use (default: text-embedding-3-small)
|
|
64
|
+
embeddingModel: z.string().default("gemini-embedding-001"),
|
|
65
|
+
// Custom namespace override (auto-generated from git remote if not set)
|
|
66
|
+
namespace: z.string().optional(),
|
|
67
|
+
// File patterns to include in indexing
|
|
68
|
+
include: z.array(z.string()).optional().default([
|
|
69
|
+
"**/*.ts",
|
|
70
|
+
"**/*.tsx",
|
|
71
|
+
"**/*.js",
|
|
72
|
+
"**/*.jsx",
|
|
73
|
+
"**/*.py",
|
|
74
|
+
"**/*.go",
|
|
75
|
+
"**/*.rs",
|
|
76
|
+
"**/*.java",
|
|
77
|
+
"**/*.md",
|
|
78
|
+
"**/*.mdx",
|
|
79
|
+
"**/*.txt"
|
|
80
|
+
]),
|
|
81
|
+
// File patterns to exclude from indexing
|
|
82
|
+
exclude: z.array(z.string()).optional().default([
|
|
83
|
+
"**/node_modules/**",
|
|
84
|
+
"**/dist/**",
|
|
85
|
+
"**/build/**",
|
|
86
|
+
"**/.git/**",
|
|
87
|
+
"**/.next/**",
|
|
88
|
+
"**/*.min.js",
|
|
89
|
+
"**/*.bundle.js",
|
|
90
|
+
"**/pnpm-lock.yaml",
|
|
91
|
+
"**/package-lock.json",
|
|
92
|
+
"**/yarn.lock",
|
|
93
|
+
"**/.test-workspace/**",
|
|
94
|
+
"**/.semantic-test-workspace/**",
|
|
95
|
+
"**/.semantic-integration-test/**"
|
|
96
|
+
])
|
|
97
|
+
}).optional();
|
|
98
|
+
var RemoteServerConfigSchema = z.object({
|
|
99
|
+
// URL of the remote server (e.g., https://agent.sparkecode.com)
|
|
100
|
+
url: z.string().url().optional(),
|
|
101
|
+
// Auth key for the remote server (auto-generated on first use if not set)
|
|
102
|
+
// Can also be set via SPARKECODER_AUTH_KEY env var
|
|
103
|
+
authKey: z.string().optional()
|
|
104
|
+
}).optional();
|
|
58
105
|
var SparkcoderConfigSchema = z.object({
|
|
59
106
|
// Default model to use (Vercel AI Gateway format)
|
|
60
107
|
defaultModel: z.string().default("anthropic/claude-opus-4-5"),
|
|
@@ -88,8 +135,13 @@ var SparkcoderConfigSchema = z.object({
|
|
|
88
135
|
// If not set, defaults to http://{host}:{port}
|
|
89
136
|
publicUrl: z.string().url().optional()
|
|
90
137
|
}).default({ port: 3141, host: "127.0.0.1" }),
|
|
91
|
-
// Database path
|
|
92
|
-
databasePath: z.string().optional().default("./sparkecoder.db")
|
|
138
|
+
// Database path (used for local SQLite - ignored if remoteServer is configured)
|
|
139
|
+
databasePath: z.string().optional().default("./sparkecoder.db"),
|
|
140
|
+
// Remote server configuration (for centralized storage)
|
|
141
|
+
// If configured, uses remote MongoDB instead of local SQLite
|
|
142
|
+
remoteServer: RemoteServerConfigSchema,
|
|
143
|
+
// Vector Gateway configuration for semantic search
|
|
144
|
+
vectorGateway: VectorGatewayConfigSchema
|
|
93
145
|
});
|
|
94
146
|
|
|
95
147
|
// src/config/index.ts
|
|
@@ -104,6 +156,13 @@ function getAppDataDirectory() {
|
|
|
104
156
|
return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
|
|
105
157
|
}
|
|
106
158
|
}
|
|
159
|
+
var cachedConfig = null;
|
|
160
|
+
function getConfig() {
|
|
161
|
+
if (!cachedConfig) {
|
|
162
|
+
throw new Error("Config not loaded. Call loadConfig first.");
|
|
163
|
+
}
|
|
164
|
+
return cachedConfig;
|
|
165
|
+
}
|
|
107
166
|
var PROVIDER_ENV_MAP = {
|
|
108
167
|
anthropic: "ANTHROPIC_API_KEY",
|
|
109
168
|
openai: "OPENAI_API_KEY",
|
|
@@ -716,323 +775,174 @@ import { resolve as resolve3, relative as relative2, dirname as dirname2 } from
|
|
|
716
775
|
import { exec as exec3 } from "child_process";
|
|
717
776
|
import { promisify as promisify3 } from "util";
|
|
718
777
|
|
|
719
|
-
// src/db/
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
735
|
-
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
736
|
-
});
|
|
737
|
-
var messages = sqliteTable("messages", {
|
|
738
|
-
id: text("id").primaryKey(),
|
|
739
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
740
|
-
// Store the entire ModelMessage as JSON (role + content)
|
|
741
|
-
modelMessage: text("model_message", { mode: "json" }).$type().notNull(),
|
|
742
|
-
// Sequence number within session to maintain exact ordering
|
|
743
|
-
sequence: integer("sequence").notNull().default(0),
|
|
744
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
745
|
-
});
|
|
746
|
-
var toolExecutions = sqliteTable("tool_executions", {
|
|
747
|
-
id: text("id").primaryKey(),
|
|
748
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
749
|
-
messageId: text("message_id").references(() => messages.id, { onDelete: "cascade" }),
|
|
750
|
-
toolName: text("tool_name").notNull(),
|
|
751
|
-
toolCallId: text("tool_call_id").notNull(),
|
|
752
|
-
input: text("input", { mode: "json" }),
|
|
753
|
-
output: text("output", { mode: "json" }),
|
|
754
|
-
status: text("status", { enum: ["pending", "approved", "rejected", "completed", "error"] }).notNull().default("pending"),
|
|
755
|
-
requiresApproval: integer("requires_approval", { mode: "boolean" }).notNull().default(false),
|
|
756
|
-
error: text("error"),
|
|
757
|
-
startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
758
|
-
completedAt: integer("completed_at", { mode: "timestamp" })
|
|
759
|
-
});
|
|
760
|
-
var todoItems = sqliteTable("todo_items", {
|
|
761
|
-
id: text("id").primaryKey(),
|
|
762
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
763
|
-
content: text("content").notNull(),
|
|
764
|
-
status: text("status", { enum: ["pending", "in_progress", "completed", "cancelled"] }).notNull().default("pending"),
|
|
765
|
-
order: integer("order").notNull().default(0),
|
|
766
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
767
|
-
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
768
|
-
});
|
|
769
|
-
var loadedSkills = sqliteTable("loaded_skills", {
|
|
770
|
-
id: text("id").primaryKey(),
|
|
771
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
772
|
-
skillName: text("skill_name").notNull(),
|
|
773
|
-
loadedAt: integer("loaded_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
774
|
-
});
|
|
775
|
-
var terminals = sqliteTable("terminals", {
|
|
776
|
-
id: text("id").primaryKey(),
|
|
777
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
778
|
-
name: text("name"),
|
|
779
|
-
// Optional friendly name (e.g., "dev-server")
|
|
780
|
-
command: text("command").notNull(),
|
|
781
|
-
// The command that was run
|
|
782
|
-
cwd: text("cwd").notNull(),
|
|
783
|
-
// Working directory
|
|
784
|
-
pid: integer("pid"),
|
|
785
|
-
// Process ID (null if not running)
|
|
786
|
-
status: text("status", { enum: ["running", "stopped", "error"] }).notNull().default("running"),
|
|
787
|
-
exitCode: integer("exit_code"),
|
|
788
|
-
// Exit code if stopped
|
|
789
|
-
error: text("error"),
|
|
790
|
-
// Error message if status is 'error'
|
|
791
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
792
|
-
stoppedAt: integer("stopped_at", { mode: "timestamp" })
|
|
793
|
-
});
|
|
794
|
-
var activeStreams = sqliteTable("active_streams", {
|
|
795
|
-
id: text("id").primaryKey(),
|
|
796
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
797
|
-
streamId: text("stream_id").notNull().unique(),
|
|
798
|
-
// Unique stream identifier
|
|
799
|
-
status: text("status", { enum: ["active", "finished", "error"] }).notNull().default("active"),
|
|
800
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
801
|
-
finishedAt: integer("finished_at", { mode: "timestamp" })
|
|
802
|
-
});
|
|
803
|
-
var checkpoints = sqliteTable("checkpoints", {
|
|
804
|
-
id: text("id").primaryKey(),
|
|
805
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
806
|
-
// The message sequence number this checkpoint was created BEFORE
|
|
807
|
-
// (i.e., the state before this user message was processed)
|
|
808
|
-
messageSequence: integer("message_sequence").notNull(),
|
|
809
|
-
// Optional git commit hash if in a git repo
|
|
810
|
-
gitHead: text("git_head"),
|
|
811
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
812
|
-
});
|
|
813
|
-
var fileBackups = sqliteTable("file_backups", {
|
|
814
|
-
id: text("id").primaryKey(),
|
|
815
|
-
checkpointId: text("checkpoint_id").notNull().references(() => checkpoints.id, { onDelete: "cascade" }),
|
|
816
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
817
|
-
// Relative path from working directory
|
|
818
|
-
filePath: text("file_path").notNull(),
|
|
819
|
-
// Original content (null means file didn't exist before)
|
|
820
|
-
originalContent: text("original_content"),
|
|
821
|
-
// Whether the file existed before this checkpoint
|
|
822
|
-
existed: integer("existed", { mode: "boolean" }).notNull().default(true),
|
|
823
|
-
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
824
|
-
});
|
|
825
|
-
var subagentExecutions = sqliteTable("subagent_executions", {
|
|
826
|
-
id: text("id").primaryKey(),
|
|
827
|
-
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
828
|
-
toolCallId: text("tool_call_id").notNull(),
|
|
829
|
-
// The tool call that spawned this subagent
|
|
830
|
-
subagentType: text("subagent_type").notNull(),
|
|
831
|
-
// e.g., 'search', 'analyze', etc.
|
|
832
|
-
task: text("task").notNull(),
|
|
833
|
-
// The task/query given to the subagent
|
|
834
|
-
model: text("model").notNull(),
|
|
835
|
-
// The model used (e.g., 'gemini-2.0-flash')
|
|
836
|
-
status: text("status", { enum: ["running", "completed", "error", "cancelled"] }).notNull().default("running"),
|
|
837
|
-
// Steps taken by the subagent (stored as JSON array)
|
|
838
|
-
steps: text("steps", { mode: "json" }).$type().default([]),
|
|
839
|
-
// Final result/output
|
|
840
|
-
result: text("result", { mode: "json" }),
|
|
841
|
-
error: text("error"),
|
|
842
|
-
startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
843
|
-
completedAt: integer("completed_at", { mode: "timestamp" })
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
// src/db/index.ts
|
|
847
|
-
var db = null;
|
|
848
|
-
function getDb() {
|
|
849
|
-
if (!db) {
|
|
850
|
-
throw new Error("Database not initialized. Call initDatabase first.");
|
|
778
|
+
// src/db/remote.ts
|
|
779
|
+
var remoteServerUrl = null;
|
|
780
|
+
var authKey = null;
|
|
781
|
+
var DATE_FIELDS = ["createdAt", "updatedAt", "startedAt", "completedAt", "stoppedAt", "finishedAt", "loadedAt", "indexedAt", "lastFullIndex", "lastIncrementalIndex"];
|
|
782
|
+
function parseDates(obj) {
|
|
783
|
+
if (obj === null || obj === void 0) return obj;
|
|
784
|
+
if (Array.isArray(obj)) return obj.map(parseDates);
|
|
785
|
+
if (typeof obj !== "object") return obj;
|
|
786
|
+
const result = { ...obj };
|
|
787
|
+
for (const key of Object.keys(result)) {
|
|
788
|
+
if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
|
|
789
|
+
result[key] = new Date(result[key]);
|
|
790
|
+
} else if (typeof result[key] === "object") {
|
|
791
|
+
result[key] = parseDates(result[key]);
|
|
792
|
+
}
|
|
851
793
|
}
|
|
852
|
-
return
|
|
794
|
+
return result;
|
|
853
795
|
}
|
|
854
|
-
|
|
796
|
+
async function api(path, options = {}) {
|
|
797
|
+
if (!remoteServerUrl || !authKey) {
|
|
798
|
+
throw new Error("Remote database not initialized");
|
|
799
|
+
}
|
|
800
|
+
const url = `${remoteServerUrl}/db${path}`;
|
|
801
|
+
const init = {
|
|
802
|
+
method: options.method || "GET",
|
|
803
|
+
headers: {
|
|
804
|
+
"Content-Type": "application/json",
|
|
805
|
+
"Authorization": `Bearer ${authKey}`
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
if (options.body) {
|
|
809
|
+
init.body = JSON.stringify(options.body);
|
|
810
|
+
}
|
|
811
|
+
const response = await fetch(url, init);
|
|
812
|
+
if (!response.ok) {
|
|
813
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
814
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
815
|
+
}
|
|
816
|
+
const text = await response.text();
|
|
817
|
+
if (!text || text === "null") {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
return parseDates(JSON.parse(text));
|
|
821
|
+
}
|
|
822
|
+
var remoteTodoQueries = {
|
|
855
823
|
create(data) {
|
|
856
|
-
|
|
857
|
-
const now = /* @__PURE__ */ new Date();
|
|
858
|
-
const result = getDb().insert(todoItems).values({
|
|
859
|
-
id,
|
|
860
|
-
...data,
|
|
861
|
-
createdAt: now,
|
|
862
|
-
updatedAt: now
|
|
863
|
-
}).returning().get();
|
|
864
|
-
return result;
|
|
824
|
+
return api("/todos", { method: "POST", body: data });
|
|
865
825
|
},
|
|
866
826
|
createMany(sessionId, items) {
|
|
867
|
-
|
|
868
|
-
const values = items.map((item, index) => ({
|
|
869
|
-
id: nanoid2(),
|
|
870
|
-
sessionId,
|
|
871
|
-
content: item.content,
|
|
872
|
-
order: item.order ?? index,
|
|
873
|
-
createdAt: now,
|
|
874
|
-
updatedAt: now
|
|
875
|
-
}));
|
|
876
|
-
return getDb().insert(todoItems).values(values).returning().all();
|
|
827
|
+
return api("/todos/batch", { method: "POST", body: { sessionId, items } });
|
|
877
828
|
},
|
|
878
829
|
getBySession(sessionId) {
|
|
879
|
-
return
|
|
830
|
+
return api(`/todos/session/${sessionId}`);
|
|
880
831
|
},
|
|
881
832
|
updateStatus(id, status) {
|
|
882
|
-
return
|
|
833
|
+
return api(`/todos/${id}`, { method: "PATCH", body: { status } });
|
|
883
834
|
},
|
|
884
|
-
delete(id) {
|
|
885
|
-
const result =
|
|
886
|
-
return result
|
|
835
|
+
async delete(id) {
|
|
836
|
+
const result = await api(`/todos/${id}`, { method: "DELETE" });
|
|
837
|
+
return result?.success ?? false;
|
|
887
838
|
},
|
|
888
|
-
clearSession(sessionId) {
|
|
889
|
-
const result =
|
|
890
|
-
return result.
|
|
839
|
+
async clearSession(sessionId) {
|
|
840
|
+
const result = await api(`/todos/session/${sessionId}`, { method: "DELETE" });
|
|
841
|
+
return result.deleted;
|
|
891
842
|
}
|
|
892
843
|
};
|
|
893
|
-
var
|
|
844
|
+
var remoteSkillQueries = {
|
|
894
845
|
load(sessionId, skillName) {
|
|
895
|
-
|
|
896
|
-
const result = getDb().insert(loadedSkills).values({
|
|
897
|
-
id,
|
|
898
|
-
sessionId,
|
|
899
|
-
skillName,
|
|
900
|
-
loadedAt: /* @__PURE__ */ new Date()
|
|
901
|
-
}).returning().get();
|
|
902
|
-
return result;
|
|
846
|
+
return api("/skills", { method: "POST", body: { sessionId, skillName } });
|
|
903
847
|
},
|
|
904
848
|
getBySession(sessionId) {
|
|
905
|
-
return
|
|
849
|
+
return api(`/skills/session/${sessionId}`);
|
|
906
850
|
},
|
|
907
|
-
isLoaded(sessionId, skillName) {
|
|
908
|
-
const result =
|
|
909
|
-
|
|
910
|
-
eq(loadedSkills.sessionId, sessionId),
|
|
911
|
-
eq(loadedSkills.skillName, skillName)
|
|
912
|
-
)
|
|
913
|
-
).get();
|
|
914
|
-
return !!result;
|
|
851
|
+
async isLoaded(sessionId, skillName) {
|
|
852
|
+
const result = await api(`/skills/session/${sessionId}/is-loaded/${skillName}`);
|
|
853
|
+
return result.isLoaded;
|
|
915
854
|
}
|
|
916
855
|
};
|
|
917
|
-
var
|
|
856
|
+
var remoteFileBackupQueries = {
|
|
918
857
|
create(data) {
|
|
919
|
-
|
|
920
|
-
const result = getDb().insert(fileBackups).values({
|
|
921
|
-
id,
|
|
922
|
-
checkpointId: data.checkpointId,
|
|
923
|
-
sessionId: data.sessionId,
|
|
924
|
-
filePath: data.filePath,
|
|
925
|
-
originalContent: data.originalContent,
|
|
926
|
-
existed: data.existed,
|
|
927
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
928
|
-
}).returning().get();
|
|
929
|
-
return result;
|
|
858
|
+
return api("/file-backups", { method: "POST", body: data });
|
|
930
859
|
},
|
|
931
860
|
getByCheckpoint(checkpointId) {
|
|
932
|
-
return
|
|
861
|
+
return api(`/file-backups/checkpoint/${checkpointId}`);
|
|
933
862
|
},
|
|
934
863
|
getBySession(sessionId) {
|
|
935
|
-
return
|
|
864
|
+
return api(`/file-backups/session/${sessionId}`);
|
|
936
865
|
},
|
|
937
|
-
/**
|
|
938
|
-
* Get all file backups from a given checkpoint sequence onwards (inclusive)
|
|
939
|
-
* (Used when reverting - need to restore these files)
|
|
940
|
-
*
|
|
941
|
-
* When reverting to checkpoint X, we need backups from checkpoint X and all later ones
|
|
942
|
-
* because checkpoint X's backups represent the state BEFORE processing message X.
|
|
943
|
-
*/
|
|
944
866
|
getFromSequence(sessionId, messageSequence) {
|
|
945
|
-
|
|
946
|
-
and(
|
|
947
|
-
eq(checkpoints.sessionId, sessionId),
|
|
948
|
-
sql`message_sequence >= ${messageSequence}`
|
|
949
|
-
)
|
|
950
|
-
).all();
|
|
951
|
-
if (checkpointsFrom.length === 0) {
|
|
952
|
-
return [];
|
|
953
|
-
}
|
|
954
|
-
const checkpointIds = checkpointsFrom.map((c) => c.id);
|
|
955
|
-
const allBackups = [];
|
|
956
|
-
for (const cpId of checkpointIds) {
|
|
957
|
-
const backups = getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, cpId)).all();
|
|
958
|
-
allBackups.push(...backups);
|
|
959
|
-
}
|
|
960
|
-
return allBackups;
|
|
867
|
+
return api(`/file-backups/session/${sessionId}/from-sequence/${messageSequence}`);
|
|
961
868
|
},
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
and(
|
|
968
|
-
eq(fileBackups.checkpointId, checkpointId),
|
|
969
|
-
eq(fileBackups.filePath, filePath)
|
|
970
|
-
)
|
|
971
|
-
).get();
|
|
972
|
-
return !!result;
|
|
869
|
+
async hasBackup(checkpointId, filePath) {
|
|
870
|
+
const result = await api(
|
|
871
|
+
`/file-backups/checkpoint/${checkpointId}/has-backup/${encodeURIComponent(filePath)}`
|
|
872
|
+
);
|
|
873
|
+
return result.hasBackup;
|
|
973
874
|
},
|
|
974
|
-
deleteBySession(sessionId) {
|
|
975
|
-
const result =
|
|
976
|
-
return result.
|
|
875
|
+
async deleteBySession(sessionId) {
|
|
876
|
+
const result = await api(`/file-backups/session/${sessionId}`, { method: "DELETE" });
|
|
877
|
+
return result.deleted;
|
|
977
878
|
}
|
|
978
879
|
};
|
|
979
|
-
var
|
|
880
|
+
var remoteSubagentQueries = {
|
|
980
881
|
create(data) {
|
|
981
|
-
|
|
982
|
-
const result = getDb().insert(subagentExecutions).values({
|
|
983
|
-
id,
|
|
984
|
-
sessionId: data.sessionId,
|
|
985
|
-
toolCallId: data.toolCallId,
|
|
986
|
-
subagentType: data.subagentType,
|
|
987
|
-
task: data.task,
|
|
988
|
-
model: data.model,
|
|
989
|
-
status: "running",
|
|
990
|
-
steps: [],
|
|
991
|
-
startedAt: /* @__PURE__ */ new Date()
|
|
992
|
-
}).returning().get();
|
|
993
|
-
return result;
|
|
882
|
+
return api("/subagents", { method: "POST", body: data });
|
|
994
883
|
},
|
|
995
884
|
getById(id) {
|
|
996
|
-
return
|
|
885
|
+
return api(`/subagents/${id}`).catch(() => void 0);
|
|
997
886
|
},
|
|
998
887
|
getByToolCallId(toolCallId) {
|
|
999
|
-
return
|
|
888
|
+
return api(`/subagents/by-tool-call-id/${toolCallId}`).catch(() => void 0);
|
|
1000
889
|
},
|
|
1001
890
|
getBySession(sessionId) {
|
|
1002
|
-
return
|
|
891
|
+
return api(`/subagents/session/${sessionId}`);
|
|
1003
892
|
},
|
|
1004
893
|
addStep(id, step) {
|
|
1005
|
-
|
|
1006
|
-
if (!existing) return void 0;
|
|
1007
|
-
const currentSteps = existing.steps || [];
|
|
1008
|
-
const newSteps = [...currentSteps, step];
|
|
1009
|
-
return getDb().update(subagentExecutions).set({ steps: newSteps }).where(eq(subagentExecutions.id, id)).returning().get();
|
|
894
|
+
return api(`/subagents/${id}/add-step`, { method: "POST", body: { step } }).catch(() => void 0);
|
|
1010
895
|
},
|
|
1011
896
|
complete(id, result) {
|
|
1012
|
-
return
|
|
1013
|
-
status: "completed",
|
|
1014
|
-
result,
|
|
1015
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
1016
|
-
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
897
|
+
return api(`/subagents/${id}`, { method: "PATCH", body: { status: "completed", result } }).catch(() => void 0);
|
|
1017
898
|
},
|
|
1018
899
|
markError(id, error) {
|
|
1019
|
-
return
|
|
1020
|
-
status: "error",
|
|
1021
|
-
error,
|
|
1022
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
1023
|
-
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
900
|
+
return api(`/subagents/${id}`, { method: "PATCH", body: { status: "error", error } }).catch(() => void 0);
|
|
1024
901
|
},
|
|
1025
902
|
cancel(id) {
|
|
1026
|
-
return
|
|
1027
|
-
status: "cancelled",
|
|
1028
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
1029
|
-
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
903
|
+
return api(`/subagents/${id}`, { method: "PATCH", body: { status: "cancelled" } }).catch(() => void 0);
|
|
1030
904
|
},
|
|
1031
|
-
deleteBySession(sessionId) {
|
|
1032
|
-
const result =
|
|
1033
|
-
return result.
|
|
905
|
+
async deleteBySession(sessionId) {
|
|
906
|
+
const result = await api(`/subagents/session/${sessionId}`, { method: "DELETE" });
|
|
907
|
+
return result.deleted;
|
|
1034
908
|
}
|
|
1035
909
|
};
|
|
910
|
+
var remoteIndexStatusQueries = {
|
|
911
|
+
upsert(_db, data) {
|
|
912
|
+
return api("/index-status", {
|
|
913
|
+
method: "POST",
|
|
914
|
+
body: {
|
|
915
|
+
...data,
|
|
916
|
+
lastFullIndex: data.lastFullIndex?.toISOString(),
|
|
917
|
+
lastIncrementalIndex: data.lastIncrementalIndex?.toISOString()
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
},
|
|
921
|
+
get(_db, namespace) {
|
|
922
|
+
return api(`/index-status/namespace/${namespace}`).then((r) => r ?? void 0);
|
|
923
|
+
},
|
|
924
|
+
async delete(_db, namespace) {
|
|
925
|
+
const result = await api(`/index-status/namespace/${namespace}`, { method: "DELETE" });
|
|
926
|
+
return result?.success ?? false;
|
|
927
|
+
},
|
|
928
|
+
list(_db) {
|
|
929
|
+
return api("/index-status");
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
// src/db/index.ts
|
|
934
|
+
var initialized = false;
|
|
935
|
+
function getDb() {
|
|
936
|
+
if (!initialized) {
|
|
937
|
+
throw new Error("Database not initialized. Call initDatabase first.");
|
|
938
|
+
}
|
|
939
|
+
return {};
|
|
940
|
+
}
|
|
941
|
+
var todoQueries = remoteTodoQueries;
|
|
942
|
+
var skillQueries = remoteSkillQueries;
|
|
943
|
+
var fileBackupQueries = remoteFileBackupQueries;
|
|
944
|
+
var subagentQueries = remoteSubagentQueries;
|
|
945
|
+
var indexStatusQueries = remoteIndexStatusQueries;
|
|
1036
946
|
|
|
1037
947
|
// src/checkpoints/index.ts
|
|
1038
948
|
var execAsync3 = promisify3(exec3);
|
|
@@ -1057,7 +967,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
|
|
|
1057
967
|
}
|
|
1058
968
|
const absolutePath = resolve3(workingDirectory, filePath);
|
|
1059
969
|
const relativePath = relative2(workingDirectory, absolutePath);
|
|
1060
|
-
if (fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
|
|
970
|
+
if (await fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
|
|
1061
971
|
return null;
|
|
1062
972
|
}
|
|
1063
973
|
let originalContent = null;
|
|
@@ -1070,7 +980,7 @@ async function backupFile(sessionId, workingDirectory, filePath) {
|
|
|
1070
980
|
console.warn(`[checkpoint] Failed to read file for backup: ${error.message}`);
|
|
1071
981
|
}
|
|
1072
982
|
}
|
|
1073
|
-
const backup = fileBackupQueries.create({
|
|
983
|
+
const backup = await fileBackupQueries.create({
|
|
1074
984
|
checkpointId: manager.currentCheckpointId,
|
|
1075
985
|
sessionId,
|
|
1076
986
|
filePath: relativePath,
|
|
@@ -1827,7 +1737,7 @@ Best practices:
|
|
|
1827
1737
|
error: "No items provided. Include at least one todo item."
|
|
1828
1738
|
};
|
|
1829
1739
|
}
|
|
1830
|
-
const created = todoQueries.createMany(options.sessionId, items);
|
|
1740
|
+
const created = await todoQueries.createMany(options.sessionId, items);
|
|
1831
1741
|
return {
|
|
1832
1742
|
success: true,
|
|
1833
1743
|
action: "add",
|
|
@@ -1836,7 +1746,7 @@ Best practices:
|
|
|
1836
1746
|
};
|
|
1837
1747
|
}
|
|
1838
1748
|
case "list": {
|
|
1839
|
-
const todos = todoQueries.getBySession(options.sessionId);
|
|
1749
|
+
const todos = await todoQueries.getBySession(options.sessionId);
|
|
1840
1750
|
const stats = {
|
|
1841
1751
|
total: todos.length,
|
|
1842
1752
|
pending: todos.filter((t) => t.status === "pending").length,
|
|
@@ -1864,7 +1774,7 @@ Best practices:
|
|
|
1864
1774
|
error: 'status is required for "mark" action'
|
|
1865
1775
|
};
|
|
1866
1776
|
}
|
|
1867
|
-
const updated = todoQueries.updateStatus(todoId, status);
|
|
1777
|
+
const updated = await todoQueries.updateStatus(todoId, status);
|
|
1868
1778
|
if (!updated) {
|
|
1869
1779
|
return {
|
|
1870
1780
|
success: false,
|
|
@@ -1878,7 +1788,7 @@ Best practices:
|
|
|
1878
1788
|
};
|
|
1879
1789
|
}
|
|
1880
1790
|
case "clear": {
|
|
1881
|
-
const count = todoQueries.clearSession(options.sessionId);
|
|
1791
|
+
const count = await todoQueries.clearSession(options.sessionId);
|
|
1882
1792
|
return {
|
|
1883
1793
|
success: true,
|
|
1884
1794
|
action: "clear",
|
|
@@ -2130,7 +2040,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
2130
2040
|
error: 'skillName is required for "load" action'
|
|
2131
2041
|
};
|
|
2132
2042
|
}
|
|
2133
|
-
if (skillQueries.isLoaded(options.sessionId, skillName)) {
|
|
2043
|
+
if (await skillQueries.isLoaded(options.sessionId, skillName)) {
|
|
2134
2044
|
return {
|
|
2135
2045
|
success: false,
|
|
2136
2046
|
error: `Skill "${skillName}" is already loaded in this session`
|
|
@@ -2145,7 +2055,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
2145
2055
|
availableSkills: allSkills.map((s) => s.name)
|
|
2146
2056
|
};
|
|
2147
2057
|
}
|
|
2148
|
-
skillQueries.load(options.sessionId, skillName);
|
|
2058
|
+
await skillQueries.load(options.sessionId, skillName);
|
|
2149
2059
|
return {
|
|
2150
2060
|
success: true,
|
|
2151
2061
|
action: "load",
|
|
@@ -2373,7 +2283,7 @@ import {
|
|
|
2373
2283
|
generateText,
|
|
2374
2284
|
stepCountIs
|
|
2375
2285
|
} from "ai";
|
|
2376
|
-
import { nanoid as
|
|
2286
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
2377
2287
|
|
|
2378
2288
|
// src/agent/model.ts
|
|
2379
2289
|
import { gateway } from "@ai-sdk/gateway";
|
|
@@ -2399,8 +2309,8 @@ var Subagent = class {
|
|
|
2399
2309
|
* Parse the final result from the subagent's output.
|
|
2400
2310
|
* Override this to structure the result for your subagent type.
|
|
2401
2311
|
*/
|
|
2402
|
-
parseResult(
|
|
2403
|
-
return { text
|
|
2312
|
+
parseResult(text, steps) {
|
|
2313
|
+
return { text, steps };
|
|
2404
2314
|
}
|
|
2405
2315
|
/**
|
|
2406
2316
|
* Run the subagent with streaming progress updates
|
|
@@ -2408,7 +2318,7 @@ var Subagent = class {
|
|
|
2408
2318
|
async run(options) {
|
|
2409
2319
|
const { task, sessionId, toolCallId, onProgress, abortSignal } = options;
|
|
2410
2320
|
const steps = [];
|
|
2411
|
-
const execution = subagentQueries.create({
|
|
2321
|
+
const execution = await subagentQueries.create({
|
|
2412
2322
|
sessionId,
|
|
2413
2323
|
toolCallId,
|
|
2414
2324
|
subagentType: this.type,
|
|
@@ -2417,12 +2327,12 @@ var Subagent = class {
|
|
|
2417
2327
|
});
|
|
2418
2328
|
const addStep = async (step) => {
|
|
2419
2329
|
const fullStep = {
|
|
2420
|
-
id:
|
|
2330
|
+
id: nanoid2(8),
|
|
2421
2331
|
timestamp: Date.now(),
|
|
2422
2332
|
...step
|
|
2423
2333
|
};
|
|
2424
2334
|
steps.push(fullStep);
|
|
2425
|
-
subagentQueries.addStep(execution.id, fullStep);
|
|
2335
|
+
await subagentQueries.addStep(execution.id, fullStep);
|
|
2426
2336
|
await onProgress?.({
|
|
2427
2337
|
type: "step",
|
|
2428
2338
|
subagentId: execution.id,
|
|
@@ -2492,7 +2402,7 @@ var Subagent = class {
|
|
|
2492
2402
|
}
|
|
2493
2403
|
});
|
|
2494
2404
|
const parsedResult = this.parseResult(result.text, steps);
|
|
2495
|
-
subagentQueries.complete(execution.id, parsedResult);
|
|
2405
|
+
await subagentQueries.complete(execution.id, parsedResult);
|
|
2496
2406
|
await onProgress?.({
|
|
2497
2407
|
type: "complete",
|
|
2498
2408
|
subagentId: execution.id,
|
|
@@ -2507,7 +2417,7 @@ var Subagent = class {
|
|
|
2507
2417
|
};
|
|
2508
2418
|
} catch (error) {
|
|
2509
2419
|
const errorMessage = error.message || "Unknown error";
|
|
2510
|
-
subagentQueries.markError(execution.id, errorMessage);
|
|
2420
|
+
await subagentQueries.markError(execution.id, errorMessage);
|
|
2511
2421
|
await onProgress?.({
|
|
2512
2422
|
type: "error",
|
|
2513
2423
|
subagentId: execution.id,
|
|
@@ -2817,7 +2727,7 @@ Keep your responses concise and focused on actionable information.`;
|
|
|
2817
2727
|
})
|
|
2818
2728
|
};
|
|
2819
2729
|
}
|
|
2820
|
-
parseResult(
|
|
2730
|
+
parseResult(text, steps) {
|
|
2821
2731
|
const findings = [];
|
|
2822
2732
|
let filesSearched = 0;
|
|
2823
2733
|
let matchCount = 0;
|
|
@@ -2861,7 +2771,7 @@ Keep your responses concise and focused on actionable information.`;
|
|
|
2861
2771
|
const query = steps.length > 0 ? steps.find((s) => s.type === "text")?.content || "" : "";
|
|
2862
2772
|
return {
|
|
2863
2773
|
query,
|
|
2864
|
-
summary:
|
|
2774
|
+
summary: text,
|
|
2865
2775
|
findings: findings.slice(0, 20),
|
|
2866
2776
|
// Limit findings
|
|
2867
2777
|
filesSearched,
|
|
@@ -2992,9 +2902,362 @@ Context: ${context}` : query;
|
|
|
2992
2902
|
});
|
|
2993
2903
|
}
|
|
2994
2904
|
|
|
2905
|
+
// src/tools/semantic-search.ts
|
|
2906
|
+
import { tool as tool9 } from "ai";
|
|
2907
|
+
import { z as z10 } from "zod";
|
|
2908
|
+
import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
|
|
2909
|
+
import { join as join5 } from "path";
|
|
2910
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
2911
|
+
|
|
2912
|
+
// src/semantic/namespace.ts
|
|
2913
|
+
import { execSync } from "child_process";
|
|
2914
|
+
function getGitRemoteUrl(workingDirectory) {
|
|
2915
|
+
try {
|
|
2916
|
+
const result = execSync("git remote get-url origin", {
|
|
2917
|
+
cwd: workingDirectory,
|
|
2918
|
+
encoding: "utf-8",
|
|
2919
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2920
|
+
});
|
|
2921
|
+
return result.trim();
|
|
2922
|
+
} catch {
|
|
2923
|
+
return null;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
function parseGitRemoteUrl(url) {
|
|
2927
|
+
const cleanUrl = url.replace(/\.git$/, "");
|
|
2928
|
+
const sshMatch = cleanUrl.match(/git@[^:]+:([^/]+)\/(.+)$/);
|
|
2929
|
+
if (sshMatch) {
|
|
2930
|
+
return { org: sshMatch[1], repo: sshMatch[2] };
|
|
2931
|
+
}
|
|
2932
|
+
const httpsMatch = cleanUrl.match(/https?:\/\/[^/]+\/([^/]+)\/(.+)$/);
|
|
2933
|
+
if (httpsMatch) {
|
|
2934
|
+
return { org: httpsMatch[1], repo: httpsMatch[2] };
|
|
2935
|
+
}
|
|
2936
|
+
const sshProtoMatch = cleanUrl.match(/ssh:\/\/[^/]+\/([^/]+)\/(.+)$/);
|
|
2937
|
+
if (sshProtoMatch) {
|
|
2938
|
+
return { org: sshProtoMatch[1], repo: sshProtoMatch[2] };
|
|
2939
|
+
}
|
|
2940
|
+
return null;
|
|
2941
|
+
}
|
|
2942
|
+
function sanitizeForNamespace(str) {
|
|
2943
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
2944
|
+
}
|
|
2945
|
+
async function getRepoNamespace(workingDirectory, configuredNamespace) {
|
|
2946
|
+
if (configuredNamespace) {
|
|
2947
|
+
return configuredNamespace;
|
|
2948
|
+
}
|
|
2949
|
+
const remoteUrl = getGitRemoteUrl(workingDirectory);
|
|
2950
|
+
if (!remoteUrl) {
|
|
2951
|
+
return null;
|
|
2952
|
+
}
|
|
2953
|
+
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
2954
|
+
if (!parsed) {
|
|
2955
|
+
return null;
|
|
2956
|
+
}
|
|
2957
|
+
const org = sanitizeForNamespace(parsed.org);
|
|
2958
|
+
const repo = sanitizeForNamespace(parsed.repo);
|
|
2959
|
+
return `sparkecoder_${org}_${repo}`;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// src/semantic/hasher.ts
|
|
2963
|
+
import { createHash } from "crypto";
|
|
2964
|
+
|
|
2965
|
+
// src/semantic/chunker.ts
|
|
2966
|
+
import { extname as extname6, basename as basename2 } from "path";
|
|
2967
|
+
|
|
2968
|
+
// src/semantic/client.ts
|
|
2969
|
+
var remoteServerUrl2 = null;
|
|
2970
|
+
var authKey2 = null;
|
|
2971
|
+
function initVectorClient(serverUrl, key) {
|
|
2972
|
+
remoteServerUrl2 = serverUrl.replace(/\/$/, "");
|
|
2973
|
+
authKey2 = key;
|
|
2974
|
+
}
|
|
2975
|
+
function isVectorClientConfigured() {
|
|
2976
|
+
return !!remoteServerUrl2 && !!authKey2;
|
|
2977
|
+
}
|
|
2978
|
+
async function vectorApi(path, options = {}) {
|
|
2979
|
+
if (!remoteServerUrl2 || !authKey2) {
|
|
2980
|
+
throw new Error("Vector client not initialized - remote server not configured");
|
|
2981
|
+
}
|
|
2982
|
+
const url = `${remoteServerUrl2}/vectors${path}`;
|
|
2983
|
+
const init = {
|
|
2984
|
+
method: options.method || "GET",
|
|
2985
|
+
headers: {
|
|
2986
|
+
"Content-Type": "application/json",
|
|
2987
|
+
"Authorization": `Bearer ${authKey2}`
|
|
2988
|
+
}
|
|
2989
|
+
};
|
|
2990
|
+
if (options.body) {
|
|
2991
|
+
init.body = JSON.stringify(options.body);
|
|
2992
|
+
}
|
|
2993
|
+
const response = await fetch(url, init);
|
|
2994
|
+
if (!response.ok) {
|
|
2995
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
2996
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
2997
|
+
}
|
|
2998
|
+
return response.json();
|
|
2999
|
+
}
|
|
3000
|
+
var remoteVectorClient = {
|
|
3001
|
+
embeddings: {
|
|
3002
|
+
/**
|
|
3003
|
+
* Create embeddings and store in vector DB
|
|
3004
|
+
*/
|
|
3005
|
+
async createAndWait(texts, options) {
|
|
3006
|
+
return vectorApi("/embed", {
|
|
3007
|
+
method: "POST",
|
|
3008
|
+
body: {
|
|
3009
|
+
texts,
|
|
3010
|
+
namespace: options.namespace,
|
|
3011
|
+
embeddingModel: options.embeddingModel
|
|
3012
|
+
}
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
},
|
|
3016
|
+
search: {
|
|
3017
|
+
/**
|
|
3018
|
+
* Query vectors using semantic search
|
|
3019
|
+
*/
|
|
3020
|
+
async queryAndWait(query, options) {
|
|
3021
|
+
return vectorApi("/search", {
|
|
3022
|
+
method: "POST",
|
|
3023
|
+
body: {
|
|
3024
|
+
query,
|
|
3025
|
+
namespace: options.namespace,
|
|
3026
|
+
topK: options.topK || 10,
|
|
3027
|
+
embeddingModel: options.embeddingModel
|
|
3028
|
+
}
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
},
|
|
3032
|
+
/**
|
|
3033
|
+
* Delete a namespace (if supported)
|
|
3034
|
+
*/
|
|
3035
|
+
async deleteNamespace(namespace) {
|
|
3036
|
+
await vectorApi(`/namespace/${encodeURIComponent(namespace)}`, {
|
|
3037
|
+
method: "DELETE"
|
|
3038
|
+
});
|
|
3039
|
+
},
|
|
3040
|
+
/**
|
|
3041
|
+
* Close client (no-op for HTTP client)
|
|
3042
|
+
*/
|
|
3043
|
+
async close() {
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
function getVectorClient() {
|
|
3047
|
+
if (!isVectorClientConfigured()) {
|
|
3048
|
+
try {
|
|
3049
|
+
const config = getConfig();
|
|
3050
|
+
if (config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey) {
|
|
3051
|
+
initVectorClient(config.resolvedRemoteServer.url, config.resolvedRemoteServer.authKey);
|
|
3052
|
+
} else {
|
|
3053
|
+
return null;
|
|
3054
|
+
}
|
|
3055
|
+
} catch {
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
return remoteVectorClient;
|
|
3060
|
+
}
|
|
3061
|
+
async function closeVectorClient() {
|
|
3062
|
+
}
|
|
3063
|
+
function isVectorGatewayConfigured() {
|
|
3064
|
+
try {
|
|
3065
|
+
const config = getConfig();
|
|
3066
|
+
return !!(config.resolvedRemoteServer.url && config.resolvedRemoteServer.authKey);
|
|
3067
|
+
} catch {
|
|
3068
|
+
return false;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
function getEmbeddingModel() {
|
|
3072
|
+
try {
|
|
3073
|
+
const config = getConfig();
|
|
3074
|
+
return config.resolvedVectorGateway.embeddingModel;
|
|
3075
|
+
} catch {
|
|
3076
|
+
return "gemini-embedding-001";
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// src/semantic/indexer.ts
|
|
3081
|
+
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
3082
|
+
import { relative as relative7 } from "path";
|
|
3083
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
3084
|
+
var MAX_FILE_SIZE3 = 1024 * 1024;
|
|
3085
|
+
async function getIndexStatus(workingDirectory) {
|
|
3086
|
+
const config = getConfig();
|
|
3087
|
+
const namespace = await getRepoNamespace(
|
|
3088
|
+
workingDirectory,
|
|
3089
|
+
config.resolvedVectorGateway.namespace
|
|
3090
|
+
);
|
|
3091
|
+
const isConfigured = !!config.resolvedVectorGateway.redisUrl;
|
|
3092
|
+
if (!namespace) {
|
|
3093
|
+
return {
|
|
3094
|
+
namespace: "",
|
|
3095
|
+
totalChunks: 0,
|
|
3096
|
+
lastFullIndex: null,
|
|
3097
|
+
lastIncrementalIndex: null,
|
|
3098
|
+
isConfigured
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
try {
|
|
3102
|
+
const db = getDb();
|
|
3103
|
+
const status = await indexStatusQueries.get(db, namespace);
|
|
3104
|
+
if (!status) {
|
|
3105
|
+
return {
|
|
3106
|
+
namespace,
|
|
3107
|
+
totalChunks: 0,
|
|
3108
|
+
lastFullIndex: null,
|
|
3109
|
+
lastIncrementalIndex: null,
|
|
3110
|
+
isConfigured
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
return {
|
|
3114
|
+
namespace,
|
|
3115
|
+
totalChunks: status.totalChunks ?? 0,
|
|
3116
|
+
lastFullIndex: status.lastFullIndex ?? null,
|
|
3117
|
+
lastIncrementalIndex: status.lastIncrementalIndex ?? null,
|
|
3118
|
+
isConfigured
|
|
3119
|
+
};
|
|
3120
|
+
} catch {
|
|
3121
|
+
return {
|
|
3122
|
+
namespace,
|
|
3123
|
+
totalChunks: 0,
|
|
3124
|
+
lastFullIndex: null,
|
|
3125
|
+
lastIncrementalIndex: null,
|
|
3126
|
+
isConfigured
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
async function checkIndexExists(workingDirectory) {
|
|
3131
|
+
const status = await getIndexStatus(workingDirectory);
|
|
3132
|
+
return status.totalChunks > 0;
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
// src/tools/semantic-search.ts
|
|
3136
|
+
var semanticSearchInputSchema = z10.object({
|
|
3137
|
+
query: z10.string().describe("Natural language search query describing what you want to find"),
|
|
3138
|
+
topK: z10.number().optional().default(10).describe("Number of results to return (default: 10, max: 50)"),
|
|
3139
|
+
filePattern: z10.string().optional().describe('Filter results by file glob pattern (e.g., "*.ts", "src/**/*.py")'),
|
|
3140
|
+
language: z10.string().optional().describe('Filter by programming language (e.g., "typescript", "python")')
|
|
3141
|
+
});
|
|
3142
|
+
function createSemanticSearchTool(options) {
|
|
3143
|
+
return tool9({
|
|
3144
|
+
description: `Search the codebase using semantic similarity. This tool finds code by understanding its meaning, not just matching text.
|
|
3145
|
+
|
|
3146
|
+
Use this tool when:
|
|
3147
|
+
- You need to understand how something works in the codebase
|
|
3148
|
+
- You're looking for code related to a concept (e.g., "authentication", "database queries")
|
|
3149
|
+
- You want to find implementations of features
|
|
3150
|
+
- The user asks "where is X?" or "how does Y work?"
|
|
3151
|
+
|
|
3152
|
+
This tool requires the repository to be indexed first with 'sparkecoder index'.
|
|
3153
|
+
|
|
3154
|
+
Returns matching code snippets with file paths, line numbers, and relevance scores.`,
|
|
3155
|
+
inputSchema: semanticSearchInputSchema,
|
|
3156
|
+
execute: async ({
|
|
3157
|
+
query,
|
|
3158
|
+
topK,
|
|
3159
|
+
filePattern,
|
|
3160
|
+
language
|
|
3161
|
+
}) => {
|
|
3162
|
+
const startTime = Date.now();
|
|
3163
|
+
try {
|
|
3164
|
+
const config = getConfig();
|
|
3165
|
+
const namespace = await getRepoNamespace(
|
|
3166
|
+
options.workingDirectory,
|
|
3167
|
+
config.resolvedVectorGateway.namespace
|
|
3168
|
+
);
|
|
3169
|
+
if (!namespace) {
|
|
3170
|
+
return {
|
|
3171
|
+
success: false,
|
|
3172
|
+
error: "Repository namespace not found. Ensure this is a git repository with a remote configured."
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
const client = getVectorClient();
|
|
3176
|
+
if (!client) {
|
|
3177
|
+
return {
|
|
3178
|
+
success: false,
|
|
3179
|
+
error: 'Vector Gateway not configured. Run "sparkecoder index --status" for setup instructions.'
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
try {
|
|
3183
|
+
const limitedTopK = Math.min(Math.max(1, topK), 50);
|
|
3184
|
+
const embeddingModel = getEmbeddingModel();
|
|
3185
|
+
const result = await client.search.queryAndWait(query, {
|
|
3186
|
+
namespace,
|
|
3187
|
+
topK: limitedTopK * 2,
|
|
3188
|
+
includeMetadata: true,
|
|
3189
|
+
embeddingModel
|
|
3190
|
+
});
|
|
3191
|
+
const matches = [];
|
|
3192
|
+
for (const match of result.matches) {
|
|
3193
|
+
const metadata = match.metadata;
|
|
3194
|
+
if (!metadata) continue;
|
|
3195
|
+
const filePath = metadata.filePath;
|
|
3196
|
+
const startLine = metadata.startLine;
|
|
3197
|
+
const endLine = metadata.endLine;
|
|
3198
|
+
const matchLanguage = metadata.language;
|
|
3199
|
+
const symbolName = metadata.symbolName;
|
|
3200
|
+
if (filePattern) {
|
|
3201
|
+
const matchesPattern = minimatch3(filePath, filePattern, { dot: true });
|
|
3202
|
+
if (!matchesPattern) continue;
|
|
3203
|
+
}
|
|
3204
|
+
if (language && matchLanguage !== language.toLowerCase()) {
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
const fullPath = join5(options.workingDirectory, filePath);
|
|
3208
|
+
if (!existsSync11(fullPath)) {
|
|
3209
|
+
continue;
|
|
3210
|
+
}
|
|
3211
|
+
let snippet = "";
|
|
3212
|
+
try {
|
|
3213
|
+
const content = readFileSync4(fullPath, "utf-8");
|
|
3214
|
+
const lines = content.split("\n");
|
|
3215
|
+
const snippetLines = lines.slice(
|
|
3216
|
+
Math.max(0, startLine - 1),
|
|
3217
|
+
Math.min(lines.length, endLine)
|
|
3218
|
+
);
|
|
3219
|
+
snippet = snippetLines.join("\n");
|
|
3220
|
+
if (snippet.length > 500) {
|
|
3221
|
+
snippet = snippet.slice(0, 500) + "\n... (truncated)";
|
|
3222
|
+
}
|
|
3223
|
+
} catch {
|
|
3224
|
+
}
|
|
3225
|
+
matches.push({
|
|
3226
|
+
filePath,
|
|
3227
|
+
startLine,
|
|
3228
|
+
endLine,
|
|
3229
|
+
score: match.score,
|
|
3230
|
+
snippet,
|
|
3231
|
+
symbolName,
|
|
3232
|
+
language: matchLanguage
|
|
3233
|
+
});
|
|
3234
|
+
if (matches.length >= limitedTopK) {
|
|
3235
|
+
break;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
return {
|
|
3239
|
+
success: true,
|
|
3240
|
+
query,
|
|
3241
|
+
matches,
|
|
3242
|
+
totalMatches: matches.length,
|
|
3243
|
+
duration: Date.now() - startTime
|
|
3244
|
+
};
|
|
3245
|
+
} finally {
|
|
3246
|
+
await closeVectorClient();
|
|
3247
|
+
}
|
|
3248
|
+
} catch (error) {
|
|
3249
|
+
return {
|
|
3250
|
+
success: false,
|
|
3251
|
+
error: `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
|
|
2995
3258
|
// src/tools/index.ts
|
|
2996
|
-
function createTools(options) {
|
|
2997
|
-
|
|
3259
|
+
async function createTools(options) {
|
|
3260
|
+
const tools = {
|
|
2998
3261
|
bash: createBashTool({
|
|
2999
3262
|
workingDirectory: options.workingDirectory,
|
|
3000
3263
|
sessionId: options.sessionId,
|
|
@@ -3026,6 +3289,20 @@ function createTools(options) {
|
|
|
3026
3289
|
onProgress: options.onSearchProgress
|
|
3027
3290
|
})
|
|
3028
3291
|
};
|
|
3292
|
+
if (options.enableSemanticSearch !== false) {
|
|
3293
|
+
try {
|
|
3294
|
+
if (isVectorGatewayConfigured()) {
|
|
3295
|
+
const hasIndex = await checkIndexExists(options.workingDirectory);
|
|
3296
|
+
if (hasIndex) {
|
|
3297
|
+
tools.semantic_search = createSemanticSearchTool({
|
|
3298
|
+
workingDirectory: options.workingDirectory
|
|
3299
|
+
});
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
} catch {
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
return tools;
|
|
3029
3306
|
}
|
|
3030
3307
|
export {
|
|
3031
3308
|
createBashTool,
|
|
@@ -3033,6 +3310,7 @@ export {
|
|
|
3033
3310
|
createLoadSkillTool,
|
|
3034
3311
|
createReadFileTool,
|
|
3035
3312
|
createSearchTool,
|
|
3313
|
+
createSemanticSearchTool,
|
|
3036
3314
|
createTodoTool,
|
|
3037
3315
|
createTools,
|
|
3038
3316
|
createWriteFileTool
|