sparkecoder 0.1.59 → 0.1.61
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 +4 -3
- package/dist/agent/index.js +662 -203
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +906 -129
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +3 -2
- package/dist/db/index.js.map +1 -1
- package/dist/{index-Csad1Nx4.d.ts → index-DuGtMaAJ.d.ts} +85 -4
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1302 -711
- package/dist/index.js.map +1 -1
- package/dist/schema-C7Mm4Ykn.d.ts +1410 -0
- package/dist/{search-BETuS1vh.d.ts → search-C_IFImt1.d.ts} +3 -3
- package/dist/server/index.js +692 -101
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/browser.md +143 -0
- package/dist/skills/default/code-review.md +122 -0
- package/dist/skills/default/debugging.md +105 -0
- package/dist/skills/default/refactoring.md +197 -0
- package/dist/tools/index.d.ts +54 -4
- package/dist/tools/index.js +304 -29
- package/dist/tools/index.js.map +1 -1
- package/package.json +6 -1
- package/src/skills/default/browser.md +143 -0
- package/src/skills/default/code-review.md +122 -0
- package/src/skills/default/debugging.md +105 -0
- package/src/skills/default/refactoring.md +197 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- 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/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- 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/embed/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_19289e11._.js → 2374f_03d22c16._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b4b86c1f._.js → 2374f_0d0b1fb9._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4858a1ea._.js → 2374f_3f7d6d28._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_51385fed._.js → 2374f_6166d14d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_40e35a02._.js → 2374f_67b9525f._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_fb95e3c9._.js → 2374f_6b436efc._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2f0d9f6f._.js → 2374f_784d851c._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_35475cbe._.js → 2374f_976b2ded._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_7db22cde._.js → 2374f_98eba491._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_90b8e4fb._.js → 2374f_99b7b533._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_4666c827._.js → 2374f_a1a36483._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_076f03ec._.js → 2374f_b98835eb._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b17fce11._.js → 2374f_e19eaecc._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d8b9ce38._.js → 2374f_e3aec189._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7775f784._.js → [root-of-the-server]__4f176a16._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d59f831d._.js +15 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_645f4b90._.js → web_693f514e._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_a42a2651._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_dc6ce793._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
- package/web/.next/standalone/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
- package/web/.next/standalone/web/.next/static/chunks/95436454a7559b0d.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/a751ca474cc46212.js +5 -0
- package/web/.next/standalone/web/.next/static/static/chunks/0358a0e7a40cfb93.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/1ecde4c0d426a635.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/20e0fa99c7a1c1fc.js +13 -0
- package/web/.next/standalone/web/.next/static/static/chunks/36688a049d72e8ab.js +5 -0
- package/web/.next/standalone/web/.next/static/static/chunks/95436454a7559b0d.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/a751ca474cc46212.js +5 -0
- package/web/.next/standalone/web/src/components/ai-elements/complete-task-tool.tsx +126 -0
- package/web/.next/standalone/web/src/components/chat-interface.tsx +68 -3
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +18 -1
- package/web/.next/standalone/web/src/lib/api.ts +12 -0
- package/web/.next/static/chunks/0358a0e7a40cfb93.css +1 -0
- package/web/.next/static/chunks/1ecde4c0d426a635.js +1 -0
- package/web/.next/static/chunks/20e0fa99c7a1c1fc.js +13 -0
- package/web/.next/static/chunks/36688a049d72e8ab.js +5 -0
- package/web/.next/static/chunks/95436454a7559b0d.js +7 -0
- package/web/.next/static/chunks/a751ca474cc46212.js +5 -0
- package/dist/schema-NcQknWCg.d.ts +0 -295
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__bd396152._.js +0 -15
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_9c9f0e3b._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_d08270f7._.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
- package/web/.next/standalone/web/.next/static/chunks/3f295b6960943c38.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/631b023d37a08635.js +0 -13
- package/web/.next/standalone/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
- package/web/.next/standalone/web/.next/static/chunks/e97212fcc8221479.js +0 -5
- package/web/.next/standalone/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/2868b007ce5163fc.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/3f295b6960943c38.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/631b023d37a08635.js +0 -13
- package/web/.next/standalone/web/.next/static/static/chunks/a2b4737b190d1b54.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/e97212fcc8221479.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/f6e47c8a9766ce91.js +0 -7
- package/web/.next/static/chunks/2868b007ce5163fc.css +0 -1
- package/web/.next/static/chunks/3f295b6960943c38.js +0 -1
- package/web/.next/static/chunks/631b023d37a08635.js +0 -13
- package/web/.next/static/chunks/a2b4737b190d1b54.js +0 -5
- package/web/.next/static/chunks/e97212fcc8221479.js +0 -5
- package/web/.next/static/chunks/f6e47c8a9766ce91.js +0 -7
- /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
- /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_buildManifest.js +0 -0
- /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → 8zJH-RqrUQ3scBGbdaCmn}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -404,7 +404,7 @@ var init_db = __esm({
|
|
|
404
404
|
|
|
405
405
|
// src/config/types.ts
|
|
406
406
|
import { z } from "zod";
|
|
407
|
-
var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
|
|
407
|
+
var ToolApprovalConfigSchema, SkillMetadataSchema, TaskConfigSchema, SessionConfigSchema, VectorGatewayConfigSchema, RemoteServerConfigSchema, SparkcoderConfigSchema;
|
|
408
408
|
var init_types = __esm({
|
|
409
409
|
"src/config/types.ts"() {
|
|
410
410
|
"use strict";
|
|
@@ -423,11 +423,22 @@ var init_types = __esm({
|
|
|
423
423
|
// Glob patterns - auto-inject when working with matching files
|
|
424
424
|
globs: z.array(z.string()).optional().default([])
|
|
425
425
|
});
|
|
426
|
+
TaskConfigSchema = z.object({
|
|
427
|
+
enabled: z.boolean(),
|
|
428
|
+
outputSchema: z.record(z.string(), z.unknown()),
|
|
429
|
+
webhookUrl: z.string().url().optional(),
|
|
430
|
+
maxIterations: z.number().optional(),
|
|
431
|
+
status: z.enum(["running", "completed", "failed"]),
|
|
432
|
+
result: z.unknown().optional(),
|
|
433
|
+
error: z.string().optional(),
|
|
434
|
+
iterations: z.number().optional()
|
|
435
|
+
});
|
|
426
436
|
SessionConfigSchema = z.object({
|
|
427
437
|
toolApprovals: z.record(z.string(), z.boolean()).optional(),
|
|
428
438
|
approvalWebhook: z.string().url().optional(),
|
|
429
439
|
skillsDirectory: z.string().optional(),
|
|
430
|
-
maxContextChars: z.number().optional().default(2e5)
|
|
440
|
+
maxContextChars: z.number().optional().default(2e5),
|
|
441
|
+
task: TaskConfigSchema.optional()
|
|
431
442
|
});
|
|
432
443
|
VectorGatewayConfigSchema = z.object({
|
|
433
444
|
// Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
|
|
@@ -558,8 +569,15 @@ function discoverSkillDirectories(workingDir) {
|
|
|
558
569
|
if (existsSync(agentsMd)) {
|
|
559
570
|
agentsMdPath = agentsMd;
|
|
560
571
|
}
|
|
561
|
-
const
|
|
562
|
-
|
|
572
|
+
const baseDir = dirname(import.meta.url.replace("file://", ""));
|
|
573
|
+
const builtInCandidates = [
|
|
574
|
+
resolve(baseDir, "../skills/default"),
|
|
575
|
+
// dev: src/config → src/skills/default
|
|
576
|
+
resolve(baseDir, "./skills/default")
|
|
577
|
+
// prod: dist/ → dist/skills/default
|
|
578
|
+
];
|
|
579
|
+
const builtInSkillsDir = builtInCandidates.find((p) => existsSync(p));
|
|
580
|
+
if (builtInSkillsDir) {
|
|
563
581
|
onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
|
|
564
582
|
allDirectories.push(builtInSkillsDir);
|
|
565
583
|
}
|
|
@@ -728,6 +746,9 @@ function getConfig() {
|
|
|
728
746
|
}
|
|
729
747
|
function requiresApproval(toolName, sessionConfig) {
|
|
730
748
|
const config = getConfig();
|
|
749
|
+
if (sessionConfig?.toolApprovals?.["*"] !== void 0) {
|
|
750
|
+
return sessionConfig.toolApprovals["*"];
|
|
751
|
+
}
|
|
731
752
|
if (sessionConfig?.toolApprovals?.[toolName] !== void 0) {
|
|
732
753
|
return sessionConfig.toolApprovals[toolName];
|
|
733
754
|
}
|
|
@@ -938,7 +959,7 @@ __export(skills_exports, {
|
|
|
938
959
|
loadSkillsFromDirectory: () => loadSkillsFromDirectory
|
|
939
960
|
});
|
|
940
961
|
import { readFile as readFile6, readdir } from "fs/promises";
|
|
941
|
-
import { resolve as resolve6, basename, extname as
|
|
962
|
+
import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
|
|
942
963
|
import { existsSync as existsSync8 } from "fs";
|
|
943
964
|
import { minimatch } from "minimatch";
|
|
944
965
|
function parseSkillFrontmatter(content) {
|
|
@@ -1009,7 +1030,7 @@ function parseSkillFrontmatter(content) {
|
|
|
1009
1030
|
}
|
|
1010
1031
|
}
|
|
1011
1032
|
function getSkillNameFromPath(filePath) {
|
|
1012
|
-
return basename(filePath,
|
|
1033
|
+
return basename(filePath, extname4(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1013
1034
|
}
|
|
1014
1035
|
async function loadSkillsFromDirectory(directory, options = {}) {
|
|
1015
1036
|
const {
|
|
@@ -1334,9 +1355,9 @@ var init_hasher = __esm({
|
|
|
1334
1355
|
});
|
|
1335
1356
|
|
|
1336
1357
|
// src/semantic/chunker.ts
|
|
1337
|
-
import { extname as
|
|
1358
|
+
import { extname as extname6, basename as basename2 } from "path";
|
|
1338
1359
|
function detectLanguage(filePath) {
|
|
1339
|
-
const ext =
|
|
1360
|
+
const ext = extname6(filePath).toLowerCase();
|
|
1340
1361
|
return LANGUAGE_MAP[ext] || "unknown";
|
|
1341
1362
|
}
|
|
1342
1363
|
function supportsSemanticChunking(language) {
|
|
@@ -2411,7 +2432,7 @@ import { createInterface } from "readline";
|
|
|
2411
2432
|
|
|
2412
2433
|
// src/server/index.ts
|
|
2413
2434
|
import "dotenv/config";
|
|
2414
|
-
import { Hono as
|
|
2435
|
+
import { Hono as Hono6 } from "hono";
|
|
2415
2436
|
import { serve } from "@hono/node-server";
|
|
2416
2437
|
import { cors } from "hono/cors";
|
|
2417
2438
|
import { logger } from "hono/logger";
|
|
@@ -2425,28 +2446,186 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
2425
2446
|
init_db();
|
|
2426
2447
|
import { Hono } from "hono";
|
|
2427
2448
|
import { zValidator } from "@hono/zod-validator";
|
|
2428
|
-
import { z as
|
|
2449
|
+
import { z as z14 } from "zod";
|
|
2429
2450
|
import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
2430
2451
|
import { readdir as readdir5 } from "fs/promises";
|
|
2431
|
-
import { join as join5, basename as basename4, extname as
|
|
2452
|
+
import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
|
|
2432
2453
|
import { nanoid as nanoid4 } from "nanoid";
|
|
2433
2454
|
|
|
2434
2455
|
// src/agent/index.ts
|
|
2435
2456
|
import {
|
|
2436
2457
|
streamText as streamText2,
|
|
2437
2458
|
generateText as generateText3,
|
|
2438
|
-
tool as
|
|
2459
|
+
tool as tool12,
|
|
2439
2460
|
stepCountIs as stepCountIs2
|
|
2440
2461
|
} from "ai";
|
|
2441
2462
|
|
|
2442
2463
|
// src/agent/model.ts
|
|
2443
2464
|
import { gateway } from "@ai-sdk/gateway";
|
|
2465
|
+
|
|
2466
|
+
// src/agent/remote-model.ts
|
|
2467
|
+
function serializePrompt(prompt) {
|
|
2468
|
+
return prompt.map((msg) => {
|
|
2469
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
2470
|
+
return {
|
|
2471
|
+
...msg,
|
|
2472
|
+
content: msg.content.map((part) => {
|
|
2473
|
+
if (part.type === "file" && part.data instanceof Uint8Array) {
|
|
2474
|
+
return {
|
|
2475
|
+
...part,
|
|
2476
|
+
data: Buffer.from(part.data).toString("base64"),
|
|
2477
|
+
_base64: true
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
return part;
|
|
2481
|
+
})
|
|
2482
|
+
};
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
function deserializeValue(value) {
|
|
2486
|
+
if (value && typeof value === "object") {
|
|
2487
|
+
if (value.__uint8array && typeof value.data === "string") {
|
|
2488
|
+
return Buffer.from(value.data, "base64");
|
|
2489
|
+
}
|
|
2490
|
+
if (Array.isArray(value)) {
|
|
2491
|
+
return value.map(deserializeValue);
|
|
2492
|
+
}
|
|
2493
|
+
const result = {};
|
|
2494
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2495
|
+
result[k] = deserializeValue(v);
|
|
2496
|
+
}
|
|
2497
|
+
return result;
|
|
2498
|
+
}
|
|
2499
|
+
return value;
|
|
2500
|
+
}
|
|
2501
|
+
function prepareOptions(options) {
|
|
2502
|
+
const { abortSignal, ...rest } = options;
|
|
2503
|
+
return {
|
|
2504
|
+
...rest,
|
|
2505
|
+
prompt: serializePrompt(options.prompt)
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
function createRemoteModel(modelId, config) {
|
|
2509
|
+
const baseUrl = config.url.replace(/\/$/, "");
|
|
2510
|
+
const headers = {
|
|
2511
|
+
"Content-Type": "application/json",
|
|
2512
|
+
"Authorization": `Bearer ${config.authKey}`
|
|
2513
|
+
};
|
|
2514
|
+
return {
|
|
2515
|
+
specificationVersion: "v3",
|
|
2516
|
+
provider: "remote-proxy",
|
|
2517
|
+
modelId,
|
|
2518
|
+
supportedUrls: {},
|
|
2519
|
+
async doGenerate(options) {
|
|
2520
|
+
const res = await fetch(`${baseUrl}/inference/generate`, {
|
|
2521
|
+
method: "POST",
|
|
2522
|
+
headers,
|
|
2523
|
+
body: JSON.stringify({
|
|
2524
|
+
modelId,
|
|
2525
|
+
options: prepareOptions(options)
|
|
2526
|
+
}),
|
|
2527
|
+
signal: options.abortSignal
|
|
2528
|
+
});
|
|
2529
|
+
if (!res.ok) {
|
|
2530
|
+
const err = await res.json().catch(() => ({}));
|
|
2531
|
+
throw new Error(
|
|
2532
|
+
`Remote inference failed (${res.status}): ${err.error || res.statusText}`
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
const result = await res.json();
|
|
2536
|
+
return deserializeValue(result);
|
|
2537
|
+
},
|
|
2538
|
+
async doStream(options) {
|
|
2539
|
+
const res = await fetch(`${baseUrl}/inference/stream`, {
|
|
2540
|
+
method: "POST",
|
|
2541
|
+
headers,
|
|
2542
|
+
body: JSON.stringify({
|
|
2543
|
+
modelId,
|
|
2544
|
+
options: prepareOptions(options)
|
|
2545
|
+
}),
|
|
2546
|
+
signal: options.abortSignal
|
|
2547
|
+
});
|
|
2548
|
+
if (!res.ok) {
|
|
2549
|
+
const err = await res.json().catch(() => ({}));
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
`Remote inference failed (${res.status}): ${err.error || res.statusText}`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
const reader = res.body.getReader();
|
|
2555
|
+
const decoder = new TextDecoder();
|
|
2556
|
+
let buffer = "";
|
|
2557
|
+
const stream = new ReadableStream({
|
|
2558
|
+
async pull(controller) {
|
|
2559
|
+
while (true) {
|
|
2560
|
+
const { done, value } = await reader.read();
|
|
2561
|
+
if (done) {
|
|
2562
|
+
if (buffer.trim()) {
|
|
2563
|
+
try {
|
|
2564
|
+
const parsed = deserializeValue(JSON.parse(buffer.trim()));
|
|
2565
|
+
if (parsed.type === "error") {
|
|
2566
|
+
controller.error(new Error(parsed.error));
|
|
2567
|
+
} else {
|
|
2568
|
+
controller.enqueue(parsed);
|
|
2569
|
+
}
|
|
2570
|
+
} catch {
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
controller.close();
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2577
|
+
const lines = buffer.split("\n");
|
|
2578
|
+
buffer = lines.pop() || "";
|
|
2579
|
+
for (const line of lines) {
|
|
2580
|
+
if (!line.trim()) continue;
|
|
2581
|
+
try {
|
|
2582
|
+
const parsed = deserializeValue(JSON.parse(line));
|
|
2583
|
+
if (parsed.type === "error") {
|
|
2584
|
+
controller.error(new Error(parsed.error));
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
controller.enqueue(parsed);
|
|
2588
|
+
} catch {
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
},
|
|
2593
|
+
cancel() {
|
|
2594
|
+
reader.cancel();
|
|
2595
|
+
}
|
|
2596
|
+
});
|
|
2597
|
+
const responseHeaders = {};
|
|
2598
|
+
res.headers.forEach((v, k) => {
|
|
2599
|
+
if (k.startsWith("x-upstream-")) {
|
|
2600
|
+
responseHeaders[k.replace("x-upstream-", "")] = v;
|
|
2601
|
+
}
|
|
2602
|
+
});
|
|
2603
|
+
return {
|
|
2604
|
+
stream,
|
|
2605
|
+
response: Object.keys(responseHeaders).length > 0 ? { headers: responseHeaders } : void 0
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// src/agent/model.ts
|
|
2612
|
+
init_config();
|
|
2444
2613
|
var ANTHROPIC_PREFIX = "anthropic/";
|
|
2445
2614
|
function isAnthropicModel(modelId) {
|
|
2446
2615
|
const normalized = modelId.trim().toLowerCase();
|
|
2447
2616
|
return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
|
|
2448
2617
|
}
|
|
2449
2618
|
function resolveModel(modelId) {
|
|
2619
|
+
try {
|
|
2620
|
+
const config = getConfig();
|
|
2621
|
+
if (config.resolvedRemoteServer.isConfigured) {
|
|
2622
|
+
return createRemoteModel(modelId.trim(), {
|
|
2623
|
+
url: config.resolvedRemoteServer.url,
|
|
2624
|
+
authKey: config.resolvedRemoteServer.authKey
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
} catch {
|
|
2628
|
+
}
|
|
2450
2629
|
return gateway(modelId.trim());
|
|
2451
2630
|
}
|
|
2452
2631
|
var SUBAGENT_MODELS = {
|
|
@@ -2458,7 +2637,7 @@ var SUBAGENT_MODELS = {
|
|
|
2458
2637
|
// src/agent/index.ts
|
|
2459
2638
|
init_db();
|
|
2460
2639
|
init_config();
|
|
2461
|
-
import { z as
|
|
2640
|
+
import { z as z13 } from "zod";
|
|
2462
2641
|
import { nanoid as nanoid3 } from "nanoid";
|
|
2463
2642
|
|
|
2464
2643
|
// src/tools/bash.ts
|
|
@@ -3039,26 +3218,41 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
3039
3218
|
import { tool as tool2 } from "ai";
|
|
3040
3219
|
import { z as z3 } from "zod";
|
|
3041
3220
|
import { readFile as readFile2, stat } from "fs/promises";
|
|
3042
|
-
import { resolve as resolve2, relative, isAbsolute } from "path";
|
|
3221
|
+
import { resolve as resolve2, relative, isAbsolute, extname } from "path";
|
|
3043
3222
|
import { existsSync as existsSync3 } from "fs";
|
|
3044
3223
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
3224
|
+
var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
|
|
3045
3225
|
var MAX_OUTPUT_CHARS3 = 5e4;
|
|
3226
|
+
var IMAGE_EXTENSIONS = {
|
|
3227
|
+
".png": "image/png",
|
|
3228
|
+
".jpg": "image/jpeg",
|
|
3229
|
+
".jpeg": "image/jpeg",
|
|
3230
|
+
".gif": "image/gif",
|
|
3231
|
+
".webp": "image/webp"
|
|
3232
|
+
};
|
|
3233
|
+
function isImageFile(filePath) {
|
|
3234
|
+
return extname(filePath).toLowerCase() in IMAGE_EXTENSIONS;
|
|
3235
|
+
}
|
|
3236
|
+
function getImageMediaType(filePath) {
|
|
3237
|
+
return IMAGE_EXTENSIONS[extname(filePath).toLowerCase()] || "image/png";
|
|
3238
|
+
}
|
|
3046
3239
|
var readFileInputSchema = z3.object({
|
|
3047
|
-
path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute."),
|
|
3048
|
-
startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed)"),
|
|
3049
|
-
endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive)")
|
|
3240
|
+
path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute. Supports text files and images (png, jpg, jpeg, gif, webp)."),
|
|
3241
|
+
startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed). Only for text files."),
|
|
3242
|
+
endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive). Only for text files.")
|
|
3050
3243
|
});
|
|
3051
3244
|
function createReadFileTool(options) {
|
|
3052
3245
|
return tool2({
|
|
3053
3246
|
description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.
|
|
3054
|
-
|
|
3055
|
-
|
|
3247
|
+
Supports text files (automatically truncated if large) and image files (png, jpg, jpeg, gif, webp).
|
|
3248
|
+
For images, the file contents are returned as visual data you can see and analyze.
|
|
3249
|
+
Use this to understand existing code, check file contents, view screenshots, or gather context.`,
|
|
3056
3250
|
inputSchema: readFileInputSchema,
|
|
3057
|
-
execute: async ({ path, startLine, endLine }) => {
|
|
3251
|
+
execute: async ({ path: filePath, startLine, endLine }) => {
|
|
3058
3252
|
try {
|
|
3059
|
-
const absolutePath = isAbsolute(
|
|
3253
|
+
const absolutePath = isAbsolute(filePath) ? filePath : resolve2(options.workingDirectory, filePath);
|
|
3060
3254
|
const relativePath = relative(options.workingDirectory, absolutePath);
|
|
3061
|
-
if (relativePath.startsWith("..") && !isAbsolute(
|
|
3255
|
+
if (relativePath.startsWith("..") && !isAbsolute(filePath)) {
|
|
3062
3256
|
return {
|
|
3063
3257
|
success: false,
|
|
3064
3258
|
error: "Path escapes the working directory. Use an absolute path if intentional.",
|
|
@@ -3068,22 +3262,43 @@ Use this to understand existing code, check file contents, or gather context.`,
|
|
|
3068
3262
|
if (!existsSync3(absolutePath)) {
|
|
3069
3263
|
return {
|
|
3070
3264
|
success: false,
|
|
3071
|
-
error: `File not found: ${
|
|
3265
|
+
error: `File not found: ${filePath}`,
|
|
3072
3266
|
content: null
|
|
3073
3267
|
};
|
|
3074
3268
|
}
|
|
3075
3269
|
const stats = await stat(absolutePath);
|
|
3076
|
-
if (stats.
|
|
3270
|
+
if (stats.isDirectory()) {
|
|
3077
3271
|
return {
|
|
3078
3272
|
success: false,
|
|
3079
|
-
error:
|
|
3273
|
+
error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
|
|
3080
3274
|
content: null
|
|
3081
3275
|
};
|
|
3082
3276
|
}
|
|
3083
|
-
if (
|
|
3277
|
+
if (isImageFile(absolutePath)) {
|
|
3278
|
+
if (stats.size > MAX_IMAGE_SIZE) {
|
|
3279
|
+
return {
|
|
3280
|
+
success: false,
|
|
3281
|
+
error: `Image is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_IMAGE_SIZE / 1024 / 1024}MB.`,
|
|
3282
|
+
content: null
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
const buffer = await readFile2(absolutePath);
|
|
3286
|
+
const base64 = buffer.toString("base64");
|
|
3287
|
+
const mediaType = getImageMediaType(absolutePath);
|
|
3288
|
+
return {
|
|
3289
|
+
success: true,
|
|
3290
|
+
path: absolutePath,
|
|
3291
|
+
relativePath: relative(options.workingDirectory, absolutePath),
|
|
3292
|
+
content: `[Image: ${relativePath} (${mediaType}, ${(stats.size / 1024).toFixed(1)}KB)]`,
|
|
3293
|
+
mediaType,
|
|
3294
|
+
imageData: base64,
|
|
3295
|
+
sizeBytes: stats.size
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
3084
3299
|
return {
|
|
3085
3300
|
success: false,
|
|
3086
|
-
error:
|
|
3301
|
+
error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
|
|
3087
3302
|
content: null
|
|
3088
3303
|
};
|
|
3089
3304
|
}
|
|
@@ -3099,9 +3314,7 @@ Use this to understand existing code, check file contents, or gather context.`,
|
|
|
3099
3314
|
content: null
|
|
3100
3315
|
};
|
|
3101
3316
|
}
|
|
3102
|
-
content = lines.slice(start, end).join("\n");
|
|
3103
|
-
const lineNumbers = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
|
|
3104
|
-
content = lineNumbers;
|
|
3317
|
+
content = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
|
|
3105
3318
|
}
|
|
3106
3319
|
const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS3);
|
|
3107
3320
|
const wasTruncated = truncatedContent.length < content.length;
|
|
@@ -3128,6 +3341,19 @@ Use this to understand existing code, check file contents, or gather context.`,
|
|
|
3128
3341
|
content: null
|
|
3129
3342
|
};
|
|
3130
3343
|
}
|
|
3344
|
+
},
|
|
3345
|
+
toModelOutput: ({ output }) => {
|
|
3346
|
+
if (output && typeof output === "object" && "imageData" in output && output.imageData) {
|
|
3347
|
+
const result = output;
|
|
3348
|
+
return {
|
|
3349
|
+
type: "content",
|
|
3350
|
+
value: [
|
|
3351
|
+
{ type: "text", text: result.content },
|
|
3352
|
+
{ type: "image-data", data: result.imageData, mediaType: result.mediaType }
|
|
3353
|
+
]
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output };
|
|
3131
3357
|
}
|
|
3132
3358
|
});
|
|
3133
3359
|
}
|
|
@@ -3328,7 +3554,7 @@ function clearCheckpointManager(sessionId) {
|
|
|
3328
3554
|
}
|
|
3329
3555
|
|
|
3330
3556
|
// src/lsp/index.ts
|
|
3331
|
-
import { extname as
|
|
3557
|
+
import { extname as extname3, dirname as dirname4 } from "path";
|
|
3332
3558
|
|
|
3333
3559
|
// src/lsp/servers.ts
|
|
3334
3560
|
import { spawn } from "child_process";
|
|
@@ -3451,9 +3677,9 @@ import {
|
|
|
3451
3677
|
import { pathToFileURL, fileURLToPath } from "url";
|
|
3452
3678
|
import { readFile as readFile4 } from "fs/promises";
|
|
3453
3679
|
import { existsSync as existsSync6 } from "fs";
|
|
3454
|
-
import { extname, normalize } from "path";
|
|
3680
|
+
import { extname as extname2, normalize } from "path";
|
|
3455
3681
|
function getLanguageId(filePath) {
|
|
3456
|
-
const ext =
|
|
3682
|
+
const ext = extname2(filePath).toLowerCase();
|
|
3457
3683
|
const map = {
|
|
3458
3684
|
".ts": "typescript",
|
|
3459
3685
|
".tsx": "typescriptreact",
|
|
@@ -3841,7 +4067,7 @@ var state = {
|
|
|
3841
4067
|
};
|
|
3842
4068
|
async function getClientForFile(filePath) {
|
|
3843
4069
|
const normalized = normalizePath(filePath);
|
|
3844
|
-
const ext =
|
|
4070
|
+
const ext = extname3(normalized);
|
|
3845
4071
|
const serverDef = getServerForExtension(ext);
|
|
3846
4072
|
if (!serverDef) {
|
|
3847
4073
|
return null;
|
|
@@ -3934,7 +4160,7 @@ async function formatDiagnosticsOutput(filePath, options = {}) {
|
|
|
3934
4160
|
return formatDiagnosticsForAgent(filePath, diagnostics, options);
|
|
3935
4161
|
}
|
|
3936
4162
|
function isSupported(filePath) {
|
|
3937
|
-
const ext =
|
|
4163
|
+
const ext = extname3(filePath);
|
|
3938
4164
|
return getServerForExtension(ext) !== null;
|
|
3939
4165
|
}
|
|
3940
4166
|
|
|
@@ -4361,7 +4587,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
4361
4587
|
// src/tools/linter.ts
|
|
4362
4588
|
import { tool as tool6 } from "ai";
|
|
4363
4589
|
import { z as z7 } from "zod";
|
|
4364
|
-
import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as
|
|
4590
|
+
import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
|
|
4365
4591
|
import { existsSync as existsSync9 } from "fs";
|
|
4366
4592
|
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
4367
4593
|
var linterInputSchema = z7.object({
|
|
@@ -4384,7 +4610,7 @@ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
|
|
|
4384
4610
|
}
|
|
4385
4611
|
await walk(fullPath);
|
|
4386
4612
|
} else if (entry.isFile()) {
|
|
4387
|
-
const ext =
|
|
4613
|
+
const ext = extname5(entry.name);
|
|
4388
4614
|
if (supportedExtensions.includes(ext)) {
|
|
4389
4615
|
files.push(fullPath);
|
|
4390
4616
|
}
|
|
@@ -5721,6 +5947,59 @@ Context: ${context}` : query;
|
|
|
5721
5947
|
|
|
5722
5948
|
// src/tools/index.ts
|
|
5723
5949
|
init_semantic_search();
|
|
5950
|
+
|
|
5951
|
+
// src/tools/task.ts
|
|
5952
|
+
import { tool as tool11 } from "ai";
|
|
5953
|
+
import { z as z12 } from "zod";
|
|
5954
|
+
import Ajv from "ajv";
|
|
5955
|
+
var ajv = new Ajv({ allErrors: true });
|
|
5956
|
+
function createCompleteTaskTool(options) {
|
|
5957
|
+
const validate = ajv.compile(options.outputSchema);
|
|
5958
|
+
return tool11({
|
|
5959
|
+
description: "Call this tool when you have completed the task. Pass the result as a JSON object matching the required output schema. If the result does not match the schema, you will receive validation errors and should fix and retry.",
|
|
5960
|
+
inputSchema: z12.object({
|
|
5961
|
+
result: z12.record(z12.string(), z12.unknown()).describe("The task result as a JSON object matching the output schema")
|
|
5962
|
+
}),
|
|
5963
|
+
execute: async (input) => {
|
|
5964
|
+
const valid = validate(input.result);
|
|
5965
|
+
if (!valid) {
|
|
5966
|
+
const errors = validate.errors?.map((e) => ({
|
|
5967
|
+
path: e.instancePath || "/",
|
|
5968
|
+
message: e.message,
|
|
5969
|
+
params: e.params
|
|
5970
|
+
}));
|
|
5971
|
+
return {
|
|
5972
|
+
status: "validation_error",
|
|
5973
|
+
message: "The result does not match the required output schema. Fix the errors and call complete_task again.",
|
|
5974
|
+
errors,
|
|
5975
|
+
expectedSchema: options.outputSchema
|
|
5976
|
+
};
|
|
5977
|
+
}
|
|
5978
|
+
options.onComplete({ status: "completed", result: input.result });
|
|
5979
|
+
return {
|
|
5980
|
+
status: "completed",
|
|
5981
|
+
message: "Task completed successfully."
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5984
|
+
});
|
|
5985
|
+
}
|
|
5986
|
+
function createTaskFailedTool(options) {
|
|
5987
|
+
return tool11({
|
|
5988
|
+
description: "Call this tool if you are unable to complete the task. Provide a clear reason explaining why the task cannot be completed.",
|
|
5989
|
+
inputSchema: z12.object({
|
|
5990
|
+
reason: z12.string().describe("Explanation of why the task cannot be completed")
|
|
5991
|
+
}),
|
|
5992
|
+
execute: async (input) => {
|
|
5993
|
+
options.onComplete({ status: "failed", error: input.reason });
|
|
5994
|
+
return {
|
|
5995
|
+
status: "failed",
|
|
5996
|
+
message: `Task marked as failed: ${input.reason}`
|
|
5997
|
+
};
|
|
5998
|
+
}
|
|
5999
|
+
});
|
|
6000
|
+
}
|
|
6001
|
+
|
|
6002
|
+
// src/tools/index.ts
|
|
5724
6003
|
init_semantic();
|
|
5725
6004
|
init_semantic_search();
|
|
5726
6005
|
async function createTools(options) {
|
|
@@ -5772,6 +6051,10 @@ async function createTools(options) {
|
|
|
5772
6051
|
} catch {
|
|
5773
6052
|
}
|
|
5774
6053
|
}
|
|
6054
|
+
if (options.taskTools) {
|
|
6055
|
+
tools.complete_task = createCompleteTaskTool(options.taskTools);
|
|
6056
|
+
tools.task_failed = createTaskFailedTool(options.taskTools);
|
|
6057
|
+
}
|
|
5775
6058
|
return tools;
|
|
5776
6059
|
}
|
|
5777
6060
|
|
|
@@ -6089,6 +6372,30 @@ function formatTodosForContext(todos) {
|
|
|
6089
6372
|
}
|
|
6090
6373
|
return lines.join("\n");
|
|
6091
6374
|
}
|
|
6375
|
+
function buildTaskPromptAddendum(outputSchema) {
|
|
6376
|
+
return `
|
|
6377
|
+
## Task Mode
|
|
6378
|
+
|
|
6379
|
+
You are running in **task mode**. You have been given a specific task to complete autonomously.
|
|
6380
|
+
|
|
6381
|
+
### Rules
|
|
6382
|
+
1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
|
|
6383
|
+
2. Keep working until the task is fully complete.
|
|
6384
|
+
3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
|
|
6385
|
+
4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
|
|
6386
|
+
5. Do NOT stop without calling one of these two tools.
|
|
6387
|
+
|
|
6388
|
+
### Output Schema
|
|
6389
|
+
The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
|
|
6390
|
+
\`\`\`json
|
|
6391
|
+
${JSON.stringify(outputSchema, null, 2)}
|
|
6392
|
+
\`\`\`
|
|
6393
|
+
|
|
6394
|
+
### Completion Tools
|
|
6395
|
+
- **\`complete_task({ result: ... })\`** \u2014 Call when the task is done. The result is validated against the schema above. If validation fails you will get errors back \u2014 fix and retry.
|
|
6396
|
+
- **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
|
|
6397
|
+
`;
|
|
6398
|
+
}
|
|
6092
6399
|
function createSummaryPrompt(conversationHistory) {
|
|
6093
6400
|
return `Please provide a concise summary of the following conversation history. Focus on:
|
|
6094
6401
|
1. The main task or goal being worked on
|
|
@@ -6350,6 +6657,25 @@ ${this.summary}`
|
|
|
6350
6657
|
}
|
|
6351
6658
|
};
|
|
6352
6659
|
|
|
6660
|
+
// src/utils/webhook.ts
|
|
6661
|
+
async function sendWebhook(url, event) {
|
|
6662
|
+
try {
|
|
6663
|
+
const controller = new AbortController();
|
|
6664
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
6665
|
+
await fetch(url, {
|
|
6666
|
+
method: "POST",
|
|
6667
|
+
headers: {
|
|
6668
|
+
"Content-Type": "application/json",
|
|
6669
|
+
"X-SparkECoder-Event": event.type
|
|
6670
|
+
},
|
|
6671
|
+
body: JSON.stringify(event),
|
|
6672
|
+
signal: controller.signal
|
|
6673
|
+
});
|
|
6674
|
+
clearTimeout(timeout);
|
|
6675
|
+
} catch {
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
|
|
6353
6679
|
// src/agent/index.ts
|
|
6354
6680
|
var approvalResolvers = /* @__PURE__ */ new Map();
|
|
6355
6681
|
var Agent = class _Agent {
|
|
@@ -6569,6 +6895,145 @@ ${prompt}` });
|
|
|
6569
6895
|
steps: result.steps
|
|
6570
6896
|
};
|
|
6571
6897
|
}
|
|
6898
|
+
/**
|
|
6899
|
+
* Run the agent in task mode — loops autonomously until the agent calls
|
|
6900
|
+
* complete_task or task_failed (or hits maxIterations).
|
|
6901
|
+
* All tools run without approval. Webhook events are fired throughout.
|
|
6902
|
+
*/
|
|
6903
|
+
async runTask(options) {
|
|
6904
|
+
const config = getConfig();
|
|
6905
|
+
const maxIterations = options.taskConfig.maxIterations ?? 50;
|
|
6906
|
+
const webhookUrl = options.taskConfig.webhookUrl;
|
|
6907
|
+
const fireWebhook = (type, data) => {
|
|
6908
|
+
if (!webhookUrl) return;
|
|
6909
|
+
sendWebhook(webhookUrl, {
|
|
6910
|
+
type,
|
|
6911
|
+
taskId: this.session.id,
|
|
6912
|
+
sessionId: this.session.id,
|
|
6913
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6914
|
+
data
|
|
6915
|
+
});
|
|
6916
|
+
};
|
|
6917
|
+
const completion = { signal: null };
|
|
6918
|
+
const onComplete = (signal) => {
|
|
6919
|
+
completion.signal = signal;
|
|
6920
|
+
};
|
|
6921
|
+
const taskTools = await createTools({
|
|
6922
|
+
sessionId: this.session.id,
|
|
6923
|
+
workingDirectory: this.session.workingDirectory,
|
|
6924
|
+
skillsDirectories: config.resolvedSkillsDirectories,
|
|
6925
|
+
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
6926
|
+
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
6927
|
+
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0,
|
|
6928
|
+
taskTools: {
|
|
6929
|
+
outputSchema: options.taskConfig.outputSchema,
|
|
6930
|
+
onComplete
|
|
6931
|
+
}
|
|
6932
|
+
});
|
|
6933
|
+
const baseSystemPrompt = await buildSystemPrompt({
|
|
6934
|
+
workingDirectory: this.session.workingDirectory,
|
|
6935
|
+
skillsDirectories: config.resolvedSkillsDirectories,
|
|
6936
|
+
sessionId: this.session.id,
|
|
6937
|
+
discoveredSkills: config.discoveredSkills,
|
|
6938
|
+
activeFiles: []
|
|
6939
|
+
});
|
|
6940
|
+
const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
|
|
6941
|
+
const systemPrompt = `${baseSystemPrompt}
|
|
6942
|
+
|
|
6943
|
+
${taskAddendum}`;
|
|
6944
|
+
fireWebhook("task.started", { prompt: options.prompt });
|
|
6945
|
+
this.context.addUserMessage(options.prompt);
|
|
6946
|
+
let iteration = 0;
|
|
6947
|
+
while (iteration < maxIterations) {
|
|
6948
|
+
iteration++;
|
|
6949
|
+
if (options.abortSignal?.aborted) {
|
|
6950
|
+
const cancelError = "Task was cancelled";
|
|
6951
|
+
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
6952
|
+
return { status: "failed", error: cancelError, iterations: iteration };
|
|
6953
|
+
}
|
|
6954
|
+
const messages = await this.context.getMessages();
|
|
6955
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
6956
|
+
const result = await generateText3({
|
|
6957
|
+
model: resolveModel(this.session.model),
|
|
6958
|
+
system: systemPrompt,
|
|
6959
|
+
messages,
|
|
6960
|
+
tools: taskTools,
|
|
6961
|
+
stopWhen: stepCountIs2(500),
|
|
6962
|
+
abortSignal: options.abortSignal,
|
|
6963
|
+
providerOptions: useAnthropic ? {
|
|
6964
|
+
anthropic: {
|
|
6965
|
+
thinking: { type: "enabled", budgetTokens: 1e4 }
|
|
6966
|
+
}
|
|
6967
|
+
} : void 0,
|
|
6968
|
+
onStepFinish: (step) => {
|
|
6969
|
+
options.onStepFinish?.(step);
|
|
6970
|
+
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
6971
|
+
}
|
|
6972
|
+
});
|
|
6973
|
+
const responseMessages = result.response.messages;
|
|
6974
|
+
this.context.addResponseMessages(responseMessages);
|
|
6975
|
+
if (result.text) {
|
|
6976
|
+
options.onText?.(result.text);
|
|
6977
|
+
fireWebhook("task.message", { iteration, text: result.text });
|
|
6978
|
+
}
|
|
6979
|
+
for (const step of result.steps) {
|
|
6980
|
+
if (step.toolCalls) {
|
|
6981
|
+
for (const tc of step.toolCalls) {
|
|
6982
|
+
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.args });
|
|
6983
|
+
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.args });
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
if (step.toolResults) {
|
|
6987
|
+
for (const tr of step.toolResults) {
|
|
6988
|
+
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.result });
|
|
6989
|
+
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.result });
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
if (completion.signal) {
|
|
6994
|
+
const sig = completion.signal;
|
|
6995
|
+
const finalStatus = sig.status;
|
|
6996
|
+
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
6997
|
+
fireWebhook(eventType, {
|
|
6998
|
+
status: finalStatus,
|
|
6999
|
+
result: sig.result,
|
|
7000
|
+
error: sig.error,
|
|
7001
|
+
iterations: iteration
|
|
7002
|
+
});
|
|
7003
|
+
const updatedTask2 = {
|
|
7004
|
+
...options.taskConfig,
|
|
7005
|
+
status: finalStatus,
|
|
7006
|
+
result: sig.result,
|
|
7007
|
+
error: sig.error,
|
|
7008
|
+
iterations: iteration
|
|
7009
|
+
};
|
|
7010
|
+
await sessionQueries.update(this.session.id, {
|
|
7011
|
+
config: { ...this.session.config, task: updatedTask2 }
|
|
7012
|
+
});
|
|
7013
|
+
return {
|
|
7014
|
+
status: finalStatus,
|
|
7015
|
+
result: sig.result,
|
|
7016
|
+
error: sig.error,
|
|
7017
|
+
iterations: iteration
|
|
7018
|
+
};
|
|
7019
|
+
}
|
|
7020
|
+
this.context.addUserMessage(
|
|
7021
|
+
"Continue working on the task. When done, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason."
|
|
7022
|
+
);
|
|
7023
|
+
}
|
|
7024
|
+
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
7025
|
+
fireWebhook("task.failed", { status: "failed", error: timeoutError, iterations: iteration });
|
|
7026
|
+
const updatedTask = {
|
|
7027
|
+
...options.taskConfig,
|
|
7028
|
+
status: "failed",
|
|
7029
|
+
error: timeoutError,
|
|
7030
|
+
iterations: iteration
|
|
7031
|
+
};
|
|
7032
|
+
await sessionQueries.update(this.session.id, {
|
|
7033
|
+
config: { ...this.session.config, task: updatedTask }
|
|
7034
|
+
});
|
|
7035
|
+
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
7036
|
+
}
|
|
6572
7037
|
/**
|
|
6573
7038
|
* Wrap tools to add approval checking
|
|
6574
7039
|
*/
|
|
@@ -6582,9 +7047,9 @@ ${prompt}` });
|
|
|
6582
7047
|
wrappedTools[name] = originalTool;
|
|
6583
7048
|
continue;
|
|
6584
7049
|
}
|
|
6585
|
-
wrappedTools[name] =
|
|
7050
|
+
wrappedTools[name] = tool12({
|
|
6586
7051
|
description: originalTool.description || "",
|
|
6587
|
-
inputSchema: originalTool.inputSchema ||
|
|
7052
|
+
inputSchema: originalTool.inputSchema || z13.object({}),
|
|
6588
7053
|
execute: async (input, toolOptions) => {
|
|
6589
7054
|
const toolCallId = toolOptions.toolCallId || nanoid3();
|
|
6590
7055
|
const execution = toolExecutionQueries.create({
|
|
@@ -6725,18 +7190,18 @@ function cleanupPendingInputs() {
|
|
|
6725
7190
|
}
|
|
6726
7191
|
}
|
|
6727
7192
|
}
|
|
6728
|
-
var createSessionSchema =
|
|
6729
|
-
name:
|
|
6730
|
-
workingDirectory:
|
|
6731
|
-
model:
|
|
6732
|
-
toolApprovals:
|
|
7193
|
+
var createSessionSchema = z14.object({
|
|
7194
|
+
name: z14.string().optional(),
|
|
7195
|
+
workingDirectory: z14.string().optional(),
|
|
7196
|
+
model: z14.string().optional(),
|
|
7197
|
+
toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
|
|
6733
7198
|
});
|
|
6734
|
-
var paginationQuerySchema =
|
|
6735
|
-
limit:
|
|
6736
|
-
offset:
|
|
7199
|
+
var paginationQuerySchema = z14.object({
|
|
7200
|
+
limit: z14.string().optional(),
|
|
7201
|
+
offset: z14.string().optional()
|
|
6737
7202
|
});
|
|
6738
|
-
var messagesQuerySchema =
|
|
6739
|
-
limit:
|
|
7203
|
+
var messagesQuerySchema = z14.object({
|
|
7204
|
+
limit: z14.string().optional()
|
|
6740
7205
|
});
|
|
6741
7206
|
sessions.get(
|
|
6742
7207
|
"/",
|
|
@@ -6875,10 +7340,10 @@ sessions.get("/:id/tools", async (c) => {
|
|
|
6875
7340
|
count: executions.length
|
|
6876
7341
|
});
|
|
6877
7342
|
});
|
|
6878
|
-
var updateSessionSchema =
|
|
6879
|
-
model:
|
|
6880
|
-
name:
|
|
6881
|
-
toolApprovals:
|
|
7343
|
+
var updateSessionSchema = z14.object({
|
|
7344
|
+
model: z14.string().optional(),
|
|
7345
|
+
name: z14.string().optional(),
|
|
7346
|
+
toolApprovals: z14.record(z14.string(), z14.boolean()).optional()
|
|
6882
7347
|
});
|
|
6883
7348
|
sessions.patch(
|
|
6884
7349
|
"/:id",
|
|
@@ -6948,8 +7413,8 @@ sessions.post("/:id/clear", async (c) => {
|
|
|
6948
7413
|
await agent.clearContext();
|
|
6949
7414
|
return c.json({ success: true, sessionId: id });
|
|
6950
7415
|
});
|
|
6951
|
-
var pendingInputSchema =
|
|
6952
|
-
text:
|
|
7416
|
+
var pendingInputSchema = z14.object({
|
|
7417
|
+
text: z14.string()
|
|
6953
7418
|
});
|
|
6954
7419
|
sessions.post(
|
|
6955
7420
|
"/:id/pending-input",
|
|
@@ -6980,13 +7445,13 @@ sessions.get("/:id/pending-input", async (c) => {
|
|
|
6980
7445
|
createdAt: pending.createdAt.toISOString()
|
|
6981
7446
|
});
|
|
6982
7447
|
});
|
|
6983
|
-
var devtoolsContextSchema =
|
|
6984
|
-
url:
|
|
6985
|
-
path:
|
|
6986
|
-
pageName:
|
|
6987
|
-
screenWidth:
|
|
6988
|
-
screenHeight:
|
|
6989
|
-
devicePixelRatio:
|
|
7448
|
+
var devtoolsContextSchema = z14.object({
|
|
7449
|
+
url: z14.string(),
|
|
7450
|
+
path: z14.string(),
|
|
7451
|
+
pageName: z14.string().optional(),
|
|
7452
|
+
screenWidth: z14.number().optional(),
|
|
7453
|
+
screenHeight: z14.number().optional(),
|
|
7454
|
+
devicePixelRatio: z14.number().optional()
|
|
6990
7455
|
});
|
|
6991
7456
|
sessions.post(
|
|
6992
7457
|
"/:id/devtools-context",
|
|
@@ -7208,7 +7673,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
7208
7673
|
}
|
|
7209
7674
|
const dir = ensureAttachmentsDir(sessionId);
|
|
7210
7675
|
const id = nanoid4(10);
|
|
7211
|
-
const ext =
|
|
7676
|
+
const ext = extname7(file.name) || "";
|
|
7212
7677
|
const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7213
7678
|
const filePath = join5(dir, safeFilename);
|
|
7214
7679
|
const arrayBuffer = await file.arrayBuffer();
|
|
@@ -7234,7 +7699,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
7234
7699
|
}
|
|
7235
7700
|
const dir = ensureAttachmentsDir(sessionId);
|
|
7236
7701
|
const id = nanoid4(10);
|
|
7237
|
-
const ext =
|
|
7702
|
+
const ext = extname7(body.filename) || "";
|
|
7238
7703
|
const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
7239
7704
|
const filePath = join5(dir, safeFilename);
|
|
7240
7705
|
let base64Data = body.data;
|
|
@@ -7277,10 +7742,10 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
7277
7742
|
unlinkSync(filePath);
|
|
7278
7743
|
return c.json({ success: true, id: attachmentId });
|
|
7279
7744
|
});
|
|
7280
|
-
var filesQuerySchema =
|
|
7281
|
-
query:
|
|
7745
|
+
var filesQuerySchema = z14.object({
|
|
7746
|
+
query: z14.string().optional(),
|
|
7282
7747
|
// Filter query (e.g., "src/com" to match "src/components")
|
|
7283
|
-
limit:
|
|
7748
|
+
limit: z14.string().optional()
|
|
7284
7749
|
// Max results (default 50)
|
|
7285
7750
|
});
|
|
7286
7751
|
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -7364,7 +7829,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
7364
7829
|
if (entry.name.startsWith(".")) {
|
|
7365
7830
|
continue;
|
|
7366
7831
|
}
|
|
7367
|
-
const ext =
|
|
7832
|
+
const ext = extname7(entry.name).toLowerCase();
|
|
7368
7833
|
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
7369
7834
|
continue;
|
|
7370
7835
|
}
|
|
@@ -7458,7 +7923,7 @@ sessions.get(
|
|
|
7458
7923
|
init_db();
|
|
7459
7924
|
import { Hono as Hono2 } from "hono";
|
|
7460
7925
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
7461
|
-
import { z as
|
|
7926
|
+
import { z as z15 } from "zod";
|
|
7462
7927
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7463
7928
|
import { join as join6 } from "path";
|
|
7464
7929
|
init_config();
|
|
@@ -7623,30 +8088,30 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
|
|
|
7623
8088
|
${prompt}`;
|
|
7624
8089
|
}
|
|
7625
8090
|
var agents = new Hono2();
|
|
7626
|
-
var attachmentSchema =
|
|
7627
|
-
type:
|
|
7628
|
-
data:
|
|
8091
|
+
var attachmentSchema = z15.object({
|
|
8092
|
+
type: z15.enum(["image", "file"]),
|
|
8093
|
+
data: z15.string(),
|
|
7629
8094
|
// base64 data URL or raw base64
|
|
7630
|
-
mediaType:
|
|
7631
|
-
filename:
|
|
8095
|
+
mediaType: z15.string().optional(),
|
|
8096
|
+
filename: z15.string().optional()
|
|
7632
8097
|
});
|
|
7633
|
-
var runPromptSchema =
|
|
7634
|
-
prompt:
|
|
8098
|
+
var runPromptSchema = z15.object({
|
|
8099
|
+
prompt: z15.string(),
|
|
7635
8100
|
// Can be empty if attachments are provided
|
|
7636
|
-
attachments:
|
|
8101
|
+
attachments: z15.array(attachmentSchema).optional()
|
|
7637
8102
|
}).refine(
|
|
7638
8103
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
7639
8104
|
{ message: "Either prompt or attachments must be provided" }
|
|
7640
8105
|
);
|
|
7641
|
-
var quickStartSchema =
|
|
7642
|
-
prompt:
|
|
7643
|
-
name:
|
|
7644
|
-
workingDirectory:
|
|
7645
|
-
model:
|
|
7646
|
-
toolApprovals:
|
|
8106
|
+
var quickStartSchema = z15.object({
|
|
8107
|
+
prompt: z15.string().min(1),
|
|
8108
|
+
name: z15.string().optional(),
|
|
8109
|
+
workingDirectory: z15.string().optional(),
|
|
8110
|
+
model: z15.string().optional(),
|
|
8111
|
+
toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
|
|
7647
8112
|
});
|
|
7648
|
-
var rejectSchema =
|
|
7649
|
-
reason:
|
|
8113
|
+
var rejectSchema = z15.object({
|
|
8114
|
+
reason: z15.string().optional()
|
|
7650
8115
|
}).optional();
|
|
7651
8116
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
7652
8117
|
function getAttachmentsDirectory(sessionId) {
|
|
@@ -8076,7 +8541,7 @@ agents.get("/:id/watch", async (c) => {
|
|
|
8076
8541
|
"Cache-Control": "no-cache",
|
|
8077
8542
|
"Connection": "keep-alive",
|
|
8078
8543
|
"x-vercel-ai-ui-message-stream": "v1",
|
|
8079
|
-
"x-stream-id": streamId
|
|
8544
|
+
"x-stream-id": streamId ?? ""
|
|
8080
8545
|
}
|
|
8081
8546
|
});
|
|
8082
8547
|
});
|
|
@@ -8434,7 +8899,7 @@ agents.post(
|
|
|
8434
8899
|
init_config();
|
|
8435
8900
|
import { Hono as Hono3 } from "hono";
|
|
8436
8901
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
8437
|
-
import { z as
|
|
8902
|
+
import { z as z16 } from "zod";
|
|
8438
8903
|
import { readFileSync as readFileSync5 } from "fs";
|
|
8439
8904
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8440
8905
|
import { dirname as dirname6, join as join7 } from "path";
|
|
@@ -8544,9 +9009,9 @@ health.get("/api-keys", async (c) => {
|
|
|
8544
9009
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
8545
9010
|
});
|
|
8546
9011
|
});
|
|
8547
|
-
var setApiKeySchema =
|
|
8548
|
-
provider:
|
|
8549
|
-
apiKey:
|
|
9012
|
+
var setApiKeySchema = z16.object({
|
|
9013
|
+
provider: z16.string(),
|
|
9014
|
+
apiKey: z16.string().min(1)
|
|
8550
9015
|
});
|
|
8551
9016
|
health.post(
|
|
8552
9017
|
"/api-keys",
|
|
@@ -8585,13 +9050,13 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
8585
9050
|
// src/server/routes/terminals.ts
|
|
8586
9051
|
import { Hono as Hono4 } from "hono";
|
|
8587
9052
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
8588
|
-
import { z as
|
|
9053
|
+
import { z as z17 } from "zod";
|
|
8589
9054
|
init_db();
|
|
8590
9055
|
var terminals = new Hono4();
|
|
8591
|
-
var spawnSchema =
|
|
8592
|
-
command:
|
|
8593
|
-
cwd:
|
|
8594
|
-
name:
|
|
9056
|
+
var spawnSchema = z17.object({
|
|
9057
|
+
command: z17.string(),
|
|
9058
|
+
cwd: z17.string().optional(),
|
|
9059
|
+
name: z17.string().optional()
|
|
8595
9060
|
});
|
|
8596
9061
|
terminals.post(
|
|
8597
9062
|
"/:sessionId/terminals",
|
|
@@ -8672,8 +9137,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
8672
9137
|
// We don't track exit codes in tmux mode
|
|
8673
9138
|
});
|
|
8674
9139
|
});
|
|
8675
|
-
var logsQuerySchema =
|
|
8676
|
-
tail:
|
|
9140
|
+
var logsQuerySchema = z17.object({
|
|
9141
|
+
tail: z17.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
8677
9142
|
});
|
|
8678
9143
|
terminals.get(
|
|
8679
9144
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -8697,8 +9162,8 @@ terminals.get(
|
|
|
8697
9162
|
});
|
|
8698
9163
|
}
|
|
8699
9164
|
);
|
|
8700
|
-
var killSchema =
|
|
8701
|
-
signal:
|
|
9165
|
+
var killSchema = z17.object({
|
|
9166
|
+
signal: z17.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
8702
9167
|
});
|
|
8703
9168
|
terminals.post(
|
|
8704
9169
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -8712,8 +9177,8 @@ terminals.post(
|
|
|
8712
9177
|
return c.json({ success: true, message: "Terminal killed" });
|
|
8713
9178
|
}
|
|
8714
9179
|
);
|
|
8715
|
-
var writeSchema =
|
|
8716
|
-
input:
|
|
9180
|
+
var writeSchema = z17.object({
|
|
9181
|
+
input: z17.string()
|
|
8717
9182
|
});
|
|
8718
9183
|
terminals.post(
|
|
8719
9184
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -8894,6 +9359,131 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
8894
9359
|
);
|
|
8895
9360
|
});
|
|
8896
9361
|
|
|
9362
|
+
// src/server/routes/tasks.ts
|
|
9363
|
+
init_db();
|
|
9364
|
+
import { Hono as Hono5 } from "hono";
|
|
9365
|
+
import { zValidator as zValidator5 } from "@hono/zod-validator";
|
|
9366
|
+
import { z as z18 } from "zod";
|
|
9367
|
+
init_config();
|
|
9368
|
+
var tasks = new Hono5();
|
|
9369
|
+
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
9370
|
+
var createTaskSchema = z18.object({
|
|
9371
|
+
prompt: z18.string().min(1),
|
|
9372
|
+
outputSchema: z18.record(z18.string(), z18.unknown()),
|
|
9373
|
+
webhookUrl: z18.string().url().optional(),
|
|
9374
|
+
model: z18.string().optional(),
|
|
9375
|
+
workingDirectory: z18.string().optional(),
|
|
9376
|
+
name: z18.string().optional(),
|
|
9377
|
+
maxIterations: z18.number().int().min(1).max(500).optional()
|
|
9378
|
+
});
|
|
9379
|
+
tasks.post(
|
|
9380
|
+
"/",
|
|
9381
|
+
zValidator5("json", createTaskSchema),
|
|
9382
|
+
async (c) => {
|
|
9383
|
+
const body = c.req.valid("json");
|
|
9384
|
+
const config = getConfig();
|
|
9385
|
+
const taskConfig = {
|
|
9386
|
+
enabled: true,
|
|
9387
|
+
outputSchema: body.outputSchema,
|
|
9388
|
+
webhookUrl: body.webhookUrl,
|
|
9389
|
+
maxIterations: body.maxIterations ?? 50,
|
|
9390
|
+
status: "running"
|
|
9391
|
+
};
|
|
9392
|
+
const agent = await Agent.create({
|
|
9393
|
+
name: body.name || "Task",
|
|
9394
|
+
workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
|
|
9395
|
+
model: body.model || config.defaultModel,
|
|
9396
|
+
sessionConfig: {
|
|
9397
|
+
toolApprovals: { bash: false, write_file: false, read_file: false },
|
|
9398
|
+
task: taskConfig
|
|
9399
|
+
}
|
|
9400
|
+
});
|
|
9401
|
+
const taskId = agent.sessionId;
|
|
9402
|
+
const abortController = new AbortController();
|
|
9403
|
+
taskAbortControllers.set(taskId, abortController);
|
|
9404
|
+
(async () => {
|
|
9405
|
+
try {
|
|
9406
|
+
await agent.runTask({
|
|
9407
|
+
prompt: body.prompt,
|
|
9408
|
+
taskConfig,
|
|
9409
|
+
abortSignal: abortController.signal
|
|
9410
|
+
});
|
|
9411
|
+
} catch (err) {
|
|
9412
|
+
if (err.name === "AbortError" || abortController.signal.aborted) {
|
|
9413
|
+
console.log(`[TASK] Task ${taskId} was cancelled`);
|
|
9414
|
+
} else {
|
|
9415
|
+
console.error(`[TASK] Error in task ${taskId}:`, err.message);
|
|
9416
|
+
const failedTask = {
|
|
9417
|
+
...taskConfig,
|
|
9418
|
+
status: "failed",
|
|
9419
|
+
error: err.message || "Unknown error"
|
|
9420
|
+
};
|
|
9421
|
+
await sessionQueries.update(taskId, {
|
|
9422
|
+
config: {
|
|
9423
|
+
toolApprovals: { bash: false, write_file: false, read_file: false },
|
|
9424
|
+
task: failedTask
|
|
9425
|
+
}
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
} finally {
|
|
9429
|
+
taskAbortControllers.delete(taskId);
|
|
9430
|
+
}
|
|
9431
|
+
})();
|
|
9432
|
+
return c.json({ taskId, status: "running" }, 201);
|
|
9433
|
+
}
|
|
9434
|
+
);
|
|
9435
|
+
tasks.get("/:id", async (c) => {
|
|
9436
|
+
const id = c.req.param("id");
|
|
9437
|
+
const session = await sessionQueries.getById(id);
|
|
9438
|
+
if (!session) {
|
|
9439
|
+
return c.json({ error: "Task not found" }, 404);
|
|
9440
|
+
}
|
|
9441
|
+
const task = session.config?.task;
|
|
9442
|
+
if (!task?.enabled) {
|
|
9443
|
+
return c.json({ error: "Session is not a task" }, 400);
|
|
9444
|
+
}
|
|
9445
|
+
return c.json({
|
|
9446
|
+
taskId: id,
|
|
9447
|
+
status: task.status,
|
|
9448
|
+
result: task.result,
|
|
9449
|
+
error: task.error,
|
|
9450
|
+
iterations: task.iterations,
|
|
9451
|
+
model: session.model,
|
|
9452
|
+
name: session.name,
|
|
9453
|
+
createdAt: session.createdAt.toISOString(),
|
|
9454
|
+
updatedAt: session.updatedAt.toISOString()
|
|
9455
|
+
});
|
|
9456
|
+
});
|
|
9457
|
+
tasks.post("/:id/cancel", async (c) => {
|
|
9458
|
+
const id = c.req.param("id");
|
|
9459
|
+
const session = await sessionQueries.getById(id);
|
|
9460
|
+
if (!session) {
|
|
9461
|
+
return c.json({ error: "Task not found" }, 404);
|
|
9462
|
+
}
|
|
9463
|
+
const task = session.config?.task;
|
|
9464
|
+
if (!task?.enabled) {
|
|
9465
|
+
return c.json({ error: "Session is not a task" }, 400);
|
|
9466
|
+
}
|
|
9467
|
+
if (task.status !== "running") {
|
|
9468
|
+
return c.json({ error: `Task is already ${task.status}` }, 400);
|
|
9469
|
+
}
|
|
9470
|
+
const abortController = taskAbortControllers.get(id);
|
|
9471
|
+
if (abortController) {
|
|
9472
|
+
abortController.abort();
|
|
9473
|
+
taskAbortControllers.delete(id);
|
|
9474
|
+
}
|
|
9475
|
+
const cancelledTask = {
|
|
9476
|
+
...task,
|
|
9477
|
+
status: "failed",
|
|
9478
|
+
error: "Task cancelled by user"
|
|
9479
|
+
};
|
|
9480
|
+
await sessionQueries.update(id, {
|
|
9481
|
+
config: { ...session.config, task: cancelledTask }
|
|
9482
|
+
});
|
|
9483
|
+
return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
|
|
9484
|
+
});
|
|
9485
|
+
var tasks_default = tasks;
|
|
9486
|
+
|
|
8897
9487
|
// src/server/index.ts
|
|
8898
9488
|
init_config();
|
|
8899
9489
|
init_db();
|
|
@@ -8974,6 +9564,43 @@ async function checkDependencies(options = {}) {
|
|
|
8974
9564
|
}
|
|
8975
9565
|
return true;
|
|
8976
9566
|
}
|
|
9567
|
+
async function checkAgentBrowser() {
|
|
9568
|
+
try {
|
|
9569
|
+
const { stdout } = await execAsync5("agent-browser --version", { timeout: 1e4 });
|
|
9570
|
+
const version = stdout.trim();
|
|
9571
|
+
return { available: true, version };
|
|
9572
|
+
} catch {
|
|
9573
|
+
return {
|
|
9574
|
+
available: false,
|
|
9575
|
+
error: "agent-browser is not installed globally",
|
|
9576
|
+
installInstructions: "Install agent-browser globally:\n npm install -g agent-browser\n agent-browser install"
|
|
9577
|
+
};
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
async function tryInstallAgentBrowser(options = {}) {
|
|
9581
|
+
try {
|
|
9582
|
+
if (!options.quiet) {
|
|
9583
|
+
console.log("\u{1F4E6} Installing agent-browser globally...");
|
|
9584
|
+
}
|
|
9585
|
+
await execAsync5("npm install -g agent-browser", { timeout: 12e4 });
|
|
9586
|
+
try {
|
|
9587
|
+
if (!options.quiet) {
|
|
9588
|
+
console.log("\u{1F4E6} Installing Chromium for browser automation...");
|
|
9589
|
+
}
|
|
9590
|
+
await execAsync5("agent-browser install", { timeout: 12e4 });
|
|
9591
|
+
} catch {
|
|
9592
|
+
}
|
|
9593
|
+
if (!options.quiet) {
|
|
9594
|
+
console.log("\u2705 agent-browser installed successfully");
|
|
9595
|
+
}
|
|
9596
|
+
return true;
|
|
9597
|
+
} catch (error) {
|
|
9598
|
+
if (!options.quiet) {
|
|
9599
|
+
console.error(`Failed to install agent-browser: ${error.message}`);
|
|
9600
|
+
}
|
|
9601
|
+
return false;
|
|
9602
|
+
}
|
|
9603
|
+
}
|
|
8977
9604
|
async function tryAutoInstallTmux() {
|
|
8978
9605
|
const os2 = platform2();
|
|
8979
9606
|
try {
|
|
@@ -9018,16 +9645,20 @@ async function tryAutoInstallTmux() {
|
|
|
9018
9645
|
async function ensureDependencies(options = {}) {
|
|
9019
9646
|
const { autoInstall = false, quiet = false } = options;
|
|
9020
9647
|
const tmuxCheck = await checkTmux();
|
|
9021
|
-
if (tmuxCheck.available) {
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9648
|
+
if (!tmuxCheck.available) {
|
|
9649
|
+
if (autoInstall) {
|
|
9650
|
+
const installed = await tryAutoInstallTmux();
|
|
9651
|
+
if (!installed) {
|
|
9652
|
+
await checkDependencies({ quiet, exitOnFailure: true });
|
|
9653
|
+
}
|
|
9654
|
+
} else {
|
|
9655
|
+
await checkDependencies({ quiet, exitOnFailure: true });
|
|
9028
9656
|
}
|
|
9029
9657
|
}
|
|
9030
|
-
|
|
9658
|
+
const browserCheck = await checkAgentBrowser();
|
|
9659
|
+
if (!browserCheck.available) {
|
|
9660
|
+
await tryInstallAgentBrowser({ quiet });
|
|
9661
|
+
}
|
|
9031
9662
|
}
|
|
9032
9663
|
|
|
9033
9664
|
// src/server/index.ts
|
|
@@ -9285,7 +9916,7 @@ function stopWebUI() {
|
|
|
9285
9916
|
}
|
|
9286
9917
|
}
|
|
9287
9918
|
async function createApp(options = {}) {
|
|
9288
|
-
const app = new
|
|
9919
|
+
const app = new Hono6();
|
|
9289
9920
|
app.use("*", cors({
|
|
9290
9921
|
origin: "*",
|
|
9291
9922
|
// Allow all origins
|
|
@@ -9303,6 +9934,7 @@ async function createApp(options = {}) {
|
|
|
9303
9934
|
app.route("/agents", agents);
|
|
9304
9935
|
app.route("/sessions", terminals);
|
|
9305
9936
|
app.route("/terminals", terminals);
|
|
9937
|
+
app.route("/tasks", tasks_default);
|
|
9306
9938
|
app.get("/openapi.json", async (c) => {
|
|
9307
9939
|
return c.json(generateOpenAPISpec());
|
|
9308
9940
|
});
|
|
@@ -9872,7 +10504,7 @@ function generateOpenAPISpec() {
|
|
|
9872
10504
|
init_config();
|
|
9873
10505
|
init_semantic();
|
|
9874
10506
|
init_db();
|
|
9875
|
-
import { writeFileSync as writeFileSync5, existsSync as existsSync16 } from "fs";
|
|
10507
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync16 } from "fs";
|
|
9876
10508
|
import { resolve as resolve11, join as join9 } from "path";
|
|
9877
10509
|
async function apiRequest(baseUrl, path, options = {}) {
|
|
9878
10510
|
const url = `${baseUrl}${path}`;
|
|
@@ -9912,9 +10544,15 @@ function promptApproval(rl, toolName, input) {
|
|
|
9912
10544
|
const inputStr = JSON.stringify(input);
|
|
9913
10545
|
const truncatedInput = inputStr.length > 100 ? inputStr.slice(0, 100) + "..." : inputStr;
|
|
9914
10546
|
console.log(chalk.dim(` Command: ${truncatedInput}`));
|
|
9915
|
-
rl.question(chalk.yellow(` Approve? [y/n]: `), (answer) => {
|
|
9916
|
-
const
|
|
9917
|
-
|
|
10547
|
+
rl.question(chalk.yellow(` Approve? [y/n/a(lways)]: `), (answer) => {
|
|
10548
|
+
const lower = answer.toLowerCase().trim();
|
|
10549
|
+
if (lower === "a" || lower === "always") {
|
|
10550
|
+
resolve12("always");
|
|
10551
|
+
} else if (lower.startsWith("y")) {
|
|
10552
|
+
resolve12("approve");
|
|
10553
|
+
} else {
|
|
10554
|
+
resolve12("reject");
|
|
10555
|
+
}
|
|
9918
10556
|
});
|
|
9919
10557
|
});
|
|
9920
10558
|
}
|
|
@@ -9976,14 +10614,27 @@ async function consumeSSEStream(response, options = {}) {
|
|
|
9976
10614
|
}
|
|
9977
10615
|
if (event.type === "data-approval-required") {
|
|
9978
10616
|
const approval = event.data;
|
|
9979
|
-
|
|
10617
|
+
if (options.skipApprovals && options.baseUrl && options.sessionId) {
|
|
10618
|
+
console.log(chalk.dim(` \u2713 Auto-approved: ${approval.toolName}`));
|
|
10619
|
+
await apiRequest(options.baseUrl, `/agents/${options.sessionId}/approve/${approval.toolCallId}`, { method: "POST" });
|
|
10620
|
+
} else if (options.interactive && options.baseUrl && options.sessionId && options.readline) {
|
|
10621
|
+
console.log(chalk.yellow(`
|
|
9980
10622
|
\u26A0\uFE0F Approval required for: ${approval.toolName}`));
|
|
9981
|
-
|
|
9982
|
-
const
|
|
9983
|
-
const endpoint = approved ? "approve" : "reject";
|
|
10623
|
+
const result = await promptApproval(options.readline, approval.toolName, approval.input);
|
|
10624
|
+
const endpoint = result === "reject" ? "reject" : "approve";
|
|
9984
10625
|
const apiResponse = await apiRequest(options.baseUrl, `/agents/${options.sessionId}/${endpoint}/${approval.toolCallId}`, { method: "POST" });
|
|
9985
10626
|
if (apiResponse.ok) {
|
|
9986
|
-
|
|
10627
|
+
if (result === "always") {
|
|
10628
|
+
await apiRequest(options.baseUrl, `/sessions/${options.sessionId}`, {
|
|
10629
|
+
method: "PATCH",
|
|
10630
|
+
body: { toolApprovals: { [approval.toolName]: false } }
|
|
10631
|
+
});
|
|
10632
|
+
console.log(chalk.green(` \u2713 Always allowed \u2014 ${approval.toolName} won't ask again this session`));
|
|
10633
|
+
} else if (result === "approve") {
|
|
10634
|
+
console.log(chalk.green(" \u2713 Approved"));
|
|
10635
|
+
} else {
|
|
10636
|
+
console.log(chalk.red(" \u2717 Rejected"));
|
|
10637
|
+
}
|
|
9987
10638
|
} else {
|
|
9988
10639
|
console.log(chalk.red(` Failed to ${endpoint}: ${apiResponse.statusText}`));
|
|
9989
10640
|
}
|
|
@@ -10122,6 +10773,7 @@ async function runChat(options) {
|
|
|
10122
10773
|
output: process.stdout
|
|
10123
10774
|
});
|
|
10124
10775
|
let sessionId;
|
|
10776
|
+
const skipApprovals = !!options.dangerouslySkipApprovals;
|
|
10125
10777
|
if (options.session) {
|
|
10126
10778
|
const response = await apiRequest(baseUrl, `/sessions/${options.session}`);
|
|
10127
10779
|
if (!response.ok) {
|
|
@@ -10131,6 +10783,12 @@ async function runChat(options) {
|
|
|
10131
10783
|
const session = await response.json();
|
|
10132
10784
|
sessionId = session.id;
|
|
10133
10785
|
console.log(chalk.dim(`Resuming session: ${session.name || sessionId}`));
|
|
10786
|
+
if (skipApprovals) {
|
|
10787
|
+
await apiRequest(baseUrl, `/sessions/${sessionId}`, {
|
|
10788
|
+
method: "PATCH",
|
|
10789
|
+
body: { toolApprovals: { "*": false } }
|
|
10790
|
+
});
|
|
10791
|
+
}
|
|
10134
10792
|
const streamInfo = await getActiveStream(baseUrl, sessionId);
|
|
10135
10793
|
if (streamInfo.hasActiveStream && streamInfo.streamId) {
|
|
10136
10794
|
console.log(chalk.cyan(`
|
|
@@ -10141,7 +10799,7 @@ async function runChat(options) {
|
|
|
10141
10799
|
try {
|
|
10142
10800
|
const watchResponse = await fetch(`${baseUrl}/agents/${sessionId}/watch?streamId=${streamInfo.streamId}`);
|
|
10143
10801
|
if (watchResponse.ok) {
|
|
10144
|
-
await consumeSSEStream(watchResponse, { interactive: true, baseUrl, sessionId, readline: rl });
|
|
10802
|
+
await consumeSSEStream(watchResponse, { interactive: true, skipApprovals, baseUrl, sessionId, readline: rl });
|
|
10145
10803
|
}
|
|
10146
10804
|
} catch (err) {
|
|
10147
10805
|
console.log(chalk.dim("Stream ended or connection lost."));
|
|
@@ -10150,13 +10808,17 @@ async function runChat(options) {
|
|
|
10150
10808
|
}
|
|
10151
10809
|
} else {
|
|
10152
10810
|
const config = loadConfig(options.config, options.workingDir);
|
|
10811
|
+
const sessionBody = {
|
|
10812
|
+
name: options.name || "CLI Chat",
|
|
10813
|
+
workingDirectory: options.workingDir || config.resolvedWorkingDirectory,
|
|
10814
|
+
model: options.model || config.defaultModel
|
|
10815
|
+
};
|
|
10816
|
+
if (skipApprovals) {
|
|
10817
|
+
sessionBody.toolApprovals = { "*": false };
|
|
10818
|
+
}
|
|
10153
10819
|
const response = await apiRequest(baseUrl, "/sessions", {
|
|
10154
10820
|
method: "POST",
|
|
10155
|
-
body:
|
|
10156
|
-
name: options.name || "CLI Chat",
|
|
10157
|
-
workingDirectory: options.workingDir || config.resolvedWorkingDirectory,
|
|
10158
|
-
model: options.model || config.defaultModel
|
|
10159
|
-
}
|
|
10821
|
+
body: sessionBody
|
|
10160
10822
|
});
|
|
10161
10823
|
if (!response.ok) {
|
|
10162
10824
|
const error = await response.json();
|
|
@@ -10172,6 +10834,9 @@ async function runChat(options) {
|
|
|
10172
10834
|
console.log("");
|
|
10173
10835
|
console.log(chalk.bold.cyan("\u{1F436} SparkECoder"));
|
|
10174
10836
|
console.log(chalk.dim(`Working directory: ${workingDir}`));
|
|
10837
|
+
if (skipApprovals) {
|
|
10838
|
+
console.log(chalk.yellow("\u26A1 All tool approvals disabled (--dangerously-skip-approvals)"));
|
|
10839
|
+
}
|
|
10175
10840
|
console.log(chalk.dim("Commands: /quit, /clear, /session, /tools, /help"));
|
|
10176
10841
|
console.log("");
|
|
10177
10842
|
const prompt = () => {
|
|
@@ -10200,6 +10865,7 @@ async function runChat(options) {
|
|
|
10200
10865
|
console.log(chalk.dim(" /approve <id> - Approve pending tool call"));
|
|
10201
10866
|
console.log(chalk.dim(" /reject <id> - Reject pending tool call"));
|
|
10202
10867
|
console.log(chalk.dim(" /approvals - List pending approvals"));
|
|
10868
|
+
console.log(chalk.dim(" /allow <tool> - Always allow a tool (skip approval)"));
|
|
10203
10869
|
console.log(chalk.dim(" /help, /? - Show this help"));
|
|
10204
10870
|
prompt();
|
|
10205
10871
|
return;
|
|
@@ -10211,7 +10877,7 @@ async function runChat(options) {
|
|
|
10211
10877
|
try {
|
|
10212
10878
|
const watchResponse = await fetch(`${baseUrl}/agents/${sessionId}/watch?streamId=${streamInfo.streamId}`);
|
|
10213
10879
|
if (watchResponse.ok) {
|
|
10214
|
-
await consumeSSEStream(watchResponse, { interactive: true, baseUrl, sessionId, readline: rl });
|
|
10880
|
+
await consumeSSEStream(watchResponse, { interactive: true, skipApprovals, baseUrl, sessionId, readline: rl });
|
|
10215
10881
|
} else {
|
|
10216
10882
|
console.log(chalk.dim("Failed to connect to stream."));
|
|
10217
10883
|
}
|
|
@@ -10277,6 +10943,26 @@ async function runChat(options) {
|
|
|
10277
10943
|
prompt();
|
|
10278
10944
|
return;
|
|
10279
10945
|
}
|
|
10946
|
+
if (cmd.startsWith("/allow ")) {
|
|
10947
|
+
const toolName = trimmed.slice(7).trim();
|
|
10948
|
+
if (!toolName) {
|
|
10949
|
+
console.log(chalk.yellow("Usage: /allow <toolName> (e.g., /allow bash)"));
|
|
10950
|
+
prompt();
|
|
10951
|
+
return;
|
|
10952
|
+
}
|
|
10953
|
+
const response = await apiRequest(baseUrl, `/sessions/${sessionId}`, {
|
|
10954
|
+
method: "PATCH",
|
|
10955
|
+
body: { toolApprovals: { [toolName]: false } }
|
|
10956
|
+
});
|
|
10957
|
+
if (response.ok) {
|
|
10958
|
+
console.log(chalk.green(`\u2713 ${toolName} will no longer require approval this session`));
|
|
10959
|
+
} else {
|
|
10960
|
+
const error = await response.json();
|
|
10961
|
+
console.log(chalk.red(`Failed: ${error.error || "Unknown error"}`));
|
|
10962
|
+
}
|
|
10963
|
+
prompt();
|
|
10964
|
+
return;
|
|
10965
|
+
}
|
|
10280
10966
|
if (cmd === "/clear") {
|
|
10281
10967
|
const response = await apiRequest(baseUrl, `/sessions/${sessionId}/clear`, { method: "POST" });
|
|
10282
10968
|
if (response.ok) {
|
|
@@ -10353,7 +11039,7 @@ async function runChat(options) {
|
|
|
10353
11039
|
const error = await response.json();
|
|
10354
11040
|
throw new Error(error.error || `HTTP ${response.status}`);
|
|
10355
11041
|
}
|
|
10356
|
-
await consumeSSEStream(response, { interactive: true, baseUrl, sessionId, readline: rl });
|
|
11042
|
+
await consumeSSEStream(response, { interactive: true, skipApprovals, baseUrl, sessionId, readline: rl });
|
|
10357
11043
|
} catch (error) {
|
|
10358
11044
|
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
10359
11045
|
console.log(chalk.red(`
|
|
@@ -10382,7 +11068,7 @@ Unexpected error: ${outerError.message}`));
|
|
|
10382
11068
|
}
|
|
10383
11069
|
}
|
|
10384
11070
|
var program = new Command();
|
|
10385
|
-
program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
|
|
11071
|
+
program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
|
|
10386
11072
|
await runChat(options);
|
|
10387
11073
|
});
|
|
10388
11074
|
program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
|
|
@@ -10422,9 +11108,100 @@ program.command("server").description("Start the SparkECoder server (API + Web U
|
|
|
10422
11108
|
process.exit(1);
|
|
10423
11109
|
}
|
|
10424
11110
|
});
|
|
10425
|
-
program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
|
|
11111
|
+
program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
|
|
10426
11112
|
await runChat(options);
|
|
10427
11113
|
});
|
|
11114
|
+
program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
|
|
11115
|
+
await ensureDependencies({ quiet: true });
|
|
11116
|
+
loadApiKeysIntoEnv();
|
|
11117
|
+
const baseUrl = `http://${options.host}:${options.port}`;
|
|
11118
|
+
const running = await isServerRunning(baseUrl);
|
|
11119
|
+
if (!running) {
|
|
11120
|
+
if (options.autoStart === false) {
|
|
11121
|
+
console.error(chalk.red(`Server not running at ${baseUrl}`));
|
|
11122
|
+
process.exit(1);
|
|
11123
|
+
}
|
|
11124
|
+
const spinner2 = ora("Starting server...").start();
|
|
11125
|
+
try {
|
|
11126
|
+
await startServer({
|
|
11127
|
+
port: parseInt(options.port),
|
|
11128
|
+
host: options.host,
|
|
11129
|
+
configPath: options.config,
|
|
11130
|
+
workingDirectory: options.workingDir,
|
|
11131
|
+
quiet: true,
|
|
11132
|
+
webUI: false
|
|
11133
|
+
});
|
|
11134
|
+
spinner2.succeed("Server started");
|
|
11135
|
+
} catch (err) {
|
|
11136
|
+
spinner2.fail(chalk.red(`Failed to start server: ${err.message}`));
|
|
11137
|
+
process.exit(1);
|
|
11138
|
+
}
|
|
11139
|
+
}
|
|
11140
|
+
let outputSchema;
|
|
11141
|
+
try {
|
|
11142
|
+
const schemaStr = options.schema;
|
|
11143
|
+
if (existsSync16(schemaStr)) {
|
|
11144
|
+
outputSchema = JSON.parse(readFileSync6(schemaStr, "utf-8"));
|
|
11145
|
+
} else {
|
|
11146
|
+
outputSchema = JSON.parse(schemaStr);
|
|
11147
|
+
}
|
|
11148
|
+
} catch (err) {
|
|
11149
|
+
console.error(chalk.red(`Invalid schema: ${err.message}`));
|
|
11150
|
+
console.error(chalk.dim("Provide a path to a JSON Schema file or an inline JSON string"));
|
|
11151
|
+
process.exit(1);
|
|
11152
|
+
}
|
|
11153
|
+
const body = {
|
|
11154
|
+
prompt: options.prompt,
|
|
11155
|
+
outputSchema,
|
|
11156
|
+
maxIterations: parseInt(options.maxIterations)
|
|
11157
|
+
};
|
|
11158
|
+
if (options.webhook) body.webhookUrl = options.webhook;
|
|
11159
|
+
if (options.model) body.model = options.model;
|
|
11160
|
+
if (options.workingDir) body.workingDirectory = options.workingDir;
|
|
11161
|
+
if (options.name) body.name = options.name;
|
|
11162
|
+
const spinner = ora("Creating task...").start();
|
|
11163
|
+
const response = await apiRequest(baseUrl, "/tasks", {
|
|
11164
|
+
method: "POST",
|
|
11165
|
+
body
|
|
11166
|
+
});
|
|
11167
|
+
if (!response.ok) {
|
|
11168
|
+
const err = await response.json();
|
|
11169
|
+
spinner.fail(chalk.red(`Failed to create task: ${err.error || "Unknown error"}`));
|
|
11170
|
+
process.exit(1);
|
|
11171
|
+
}
|
|
11172
|
+
const { taskId } = await response.json();
|
|
11173
|
+
spinner.succeed(`Task created: ${chalk.cyan(taskId)}`);
|
|
11174
|
+
if (!options.wait) {
|
|
11175
|
+
console.log("");
|
|
11176
|
+
console.log(chalk.dim("Task is running in the background."));
|
|
11177
|
+
console.log(chalk.dim(`Check status: sparkecoder task-status ${taskId} --port ${options.port}`));
|
|
11178
|
+
console.log(chalk.dim(`Cancel: curl -X POST ${baseUrl}/tasks/${taskId}/cancel`));
|
|
11179
|
+
process.exit(0);
|
|
11180
|
+
}
|
|
11181
|
+
console.log(chalk.dim("\nWaiting for task to complete..."));
|
|
11182
|
+
const pollInterval = 3e3;
|
|
11183
|
+
while (true) {
|
|
11184
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
11185
|
+
const statusRes = await apiRequest(baseUrl, `/tasks/${taskId}`);
|
|
11186
|
+
if (!statusRes.ok) {
|
|
11187
|
+
console.error(chalk.red("Failed to check task status"));
|
|
11188
|
+
process.exit(1);
|
|
11189
|
+
}
|
|
11190
|
+
const status = await statusRes.json();
|
|
11191
|
+
if (status.status === "completed") {
|
|
11192
|
+
console.log(chalk.green(`
|
|
11193
|
+
Task completed in ${status.iterations} iteration(s).`));
|
|
11194
|
+
console.log(chalk.bold("Result:"));
|
|
11195
|
+
console.log(JSON.stringify(status.result, null, 2));
|
|
11196
|
+
process.exit(0);
|
|
11197
|
+
} else if (status.status === "failed") {
|
|
11198
|
+
console.error(chalk.red(`
|
|
11199
|
+
Task failed: ${status.error}`));
|
|
11200
|
+
process.exit(1);
|
|
11201
|
+
}
|
|
11202
|
+
process.stdout.write(chalk.dim("."));
|
|
11203
|
+
}
|
|
11204
|
+
});
|
|
10428
11205
|
program.command("init").description("Create a sparkecoder.config.json file").option("-f, --force", "Overwrite existing config").option("-g, --global", "Create global config in app data directory").action((options) => {
|
|
10429
11206
|
let configPath;
|
|
10430
11207
|
let configLocation;
|