tarsk 0.4.13 → 0.4.15
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/index.js +712 -67
- package/dist/public/assets/account-view-Bj-Sn682.js +1 -0
- package/dist/public/assets/api-BuJ1dPr6.js +1 -0
- package/dist/public/assets/browser-tab-C9hYTMS0.js +1 -0
- package/dist/public/assets/context-menu-DeHLk-VY.js +1 -0
- package/dist/public/assets/conversation-history-view-cEjKqUjG.js +1 -0
- package/dist/public/assets/dialogs-config-CteuxXdD.js +46 -0
- package/dist/public/assets/diff-view-B2VYMGHR.js +3 -0
- package/dist/public/assets/explorer-tab-view-CROJs14G.js +2 -0
- package/dist/public/assets/explorer-tree-BMhkGVQO.js +1 -0
- package/dist/public/assets/explorer-view-BsDRu4DY.js +1 -0
- package/dist/public/assets/history-view-zlzHeGwz.js +1 -0
- package/dist/public/assets/index-BsVrm-8c.js +80 -0
- package/dist/public/assets/index-DyoTajdY.css +1 -0
- package/dist/public/assets/onboarding-C0LojpU9.js +1 -0
- package/dist/public/assets/onboarding-dialog-DdeXtx1_.js +1 -0
- package/dist/public/assets/project-settings-view-a6woZhAb.js +1 -0
- package/dist/public/assets/provider-details-view-CMYKkW6H.js +1 -0
- package/dist/public/assets/{providers-sidebar-DSrnVDNy.js → providers-sidebar-8XbQfUhe.js} +1 -1
- package/dist/public/assets/react-vendor-Bt_xeXcZ.js +17 -0
- package/dist/public/assets/settings-view-CrZ7av2I.js +2 -0
- package/dist/public/assets/{store-BuY90jLM.js → store-DXPO1PQf.js} +1 -1
- package/dist/public/assets/tab-context-B7Q9fKIl.js +1 -0
- package/dist/public/assets/{terminal-panel-Zyr6xth3.js → terminal-panel-BxS2vp4u.js} +1 -1
- package/dist/public/assets/{textarea-CxhrnOOz.js → textarea-BBmfPFv5.js} +1 -1
- package/dist/public/assets/todos-view-E35eshII.js +1 -0
- package/dist/public/assets/use-toast-BhhMvy1W.js +1 -0
- package/dist/public/assets/{utils-7CkwsQJ0.js → utils-DcGbvYrk.js} +1 -1
- package/dist/public/index.html +12 -10
- package/package.json +2 -2
- package/dist/public/assets/account-view-CXpwWuJZ.js +0 -1
- package/dist/public/assets/api-BfUK32d1.js +0 -1
- package/dist/public/assets/browser-tab-BdzIB5R4.js +0 -1
- package/dist/public/assets/context-menu-CC9JLx5B.js +0 -1
- package/dist/public/assets/conversation-history-view-C6iUsZjA.js +0 -1
- package/dist/public/assets/dialogs-config-Ofxg1Bn0.js +0 -46
- package/dist/public/assets/diff-view-C-Kz7R_T.js +0 -3
- package/dist/public/assets/explorer-tab-view-DC8arYcw.js +0 -2
- package/dist/public/assets/explorer-tree-BwLCrdfR.js +0 -1
- package/dist/public/assets/explorer-view-CPCrOqXp.js +0 -1
- package/dist/public/assets/history-view-CAVl9GFy.js +0 -1
- package/dist/public/assets/index-BVLzVjoR.css +0 -1
- package/dist/public/assets/index-DIWoaAFy.js +0 -30
- package/dist/public/assets/onboarding-DTzcWokE.js +0 -1
- package/dist/public/assets/onboarding-dialog-BQvu_Nac.js +0 -1
- package/dist/public/assets/project-settings-view-CZPKYKCu.js +0 -1
- package/dist/public/assets/provider-details-view-BBvKe6Am.js +0 -1
- package/dist/public/assets/react-vendor-9yNfIRPs.js +0 -17
- package/dist/public/assets/settings-view-D6FAPBQP.js +0 -2
- package/dist/public/assets/todos-view-yiH-gaUK.js +0 -1
- /package/dist/public/assets/{monaco-CgzMkQ7t.js → monaco-DvsnxTfD.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -558,6 +558,16 @@ async function runMigrations(db) {
|
|
|
558
558
|
)
|
|
559
559
|
`);
|
|
560
560
|
}
|
|
561
|
+
const projectsInfo = await db.execute(`PRAGMA table_info(projects)`);
|
|
562
|
+
const hasPlanPrompt = projectsInfo.rows.some(
|
|
563
|
+
(col) => col.name === "planPrompt"
|
|
564
|
+
);
|
|
565
|
+
if (!hasPlanPrompt) {
|
|
566
|
+
console.log("[db] Running migration: Adding AI prompt columns to projects");
|
|
567
|
+
await db.execute(`ALTER TABLE projects ADD COLUMN planPrompt TEXT`);
|
|
568
|
+
await db.execute(`ALTER TABLE projects ADD COLUMN testPrompt TEXT`);
|
|
569
|
+
await db.execute(`ALTER TABLE projects ADD COLUMN reviewPrompt TEXT`);
|
|
570
|
+
}
|
|
561
571
|
} catch (error) {
|
|
562
572
|
console.error("Failed to run migrations:", error);
|
|
563
573
|
throw error;
|
|
@@ -663,7 +673,7 @@ function isValidPackageManager(value) {
|
|
|
663
673
|
|
|
664
674
|
// src/server.ts
|
|
665
675
|
import fs3 from "fs";
|
|
666
|
-
import { Hono as
|
|
676
|
+
import { Hono as Hono21 } from "hono";
|
|
667
677
|
import { cors } from "hono/cors";
|
|
668
678
|
import open3 from "open";
|
|
669
679
|
import path5 from "path";
|
|
@@ -671,6 +681,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
671
681
|
|
|
672
682
|
// src/agent/agent.executor.ts
|
|
673
683
|
import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
|
|
684
|
+
import { streamSimple } from "@mariozechner/pi-ai";
|
|
674
685
|
import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
675
686
|
|
|
676
687
|
// src/tools/bash.ts
|
|
@@ -3285,6 +3296,8 @@ async function createMCPTools(projectPath) {
|
|
|
3285
3296
|
|
|
3286
3297
|
// src/tools/generate-image.ts
|
|
3287
3298
|
import { Type as Type13 } from "@sinclair/typebox";
|
|
3299
|
+
import { mkdir as fsMkdir2, writeFile as fsWriteFile3 } from "fs/promises";
|
|
3300
|
+
import { dirname as dirname2 } from "path";
|
|
3288
3301
|
|
|
3289
3302
|
// src/features/models/models-catalog.ts
|
|
3290
3303
|
init_database();
|
|
@@ -3610,6 +3623,29 @@ async function generateWithOpenRouter(apiKey, model, prompt, options = {}) {
|
|
|
3610
3623
|
};
|
|
3611
3624
|
}
|
|
3612
3625
|
|
|
3626
|
+
// src/features/image/image-search.client.ts
|
|
3627
|
+
var IMAGE_SEARCH_API = "https://api.webnative.dev/images";
|
|
3628
|
+
async function searchImage(query, size, signal) {
|
|
3629
|
+
const url = new URL(IMAGE_SEARCH_API);
|
|
3630
|
+
url.searchParams.set("query", query);
|
|
3631
|
+
if (size) {
|
|
3632
|
+
url.searchParams.set("size", size);
|
|
3633
|
+
}
|
|
3634
|
+
const response = await fetch(url.toString(), { signal });
|
|
3635
|
+
if (!response.ok) {
|
|
3636
|
+
throw new Error(`Image search failed (${response.status}): ${await response.text()}`);
|
|
3637
|
+
}
|
|
3638
|
+
const contentType = response.headers.get("content-type") ?? "image/jpeg";
|
|
3639
|
+
const buffer = await response.arrayBuffer();
|
|
3640
|
+
const bytes = new Uint8Array(buffer);
|
|
3641
|
+
let binary = "";
|
|
3642
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3643
|
+
binary += String.fromCharCode(bytes[i]);
|
|
3644
|
+
}
|
|
3645
|
+
const imageData = btoa(binary);
|
|
3646
|
+
return { imageData, contentType };
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3613
3649
|
// src/tools/generate-image.ts
|
|
3614
3650
|
var generateImageSchema = Type13.Object({
|
|
3615
3651
|
prompt: Type13.String({ description: "Text description of the image to generate" }),
|
|
@@ -3617,16 +3653,19 @@ var generateImageSchema = Type13.Object({
|
|
|
3617
3653
|
Type13.String({
|
|
3618
3654
|
description: "Image model name or ID to use (e.g. 'dall-e-3', 'gpt-image-1', 'google/gemini-2.5-flash-image'). If omitted the first enabled image model is used."
|
|
3619
3655
|
})
|
|
3620
|
-
)
|
|
3656
|
+
),
|
|
3657
|
+
save_to_file: Type13.String({
|
|
3658
|
+
description: "File path (relative to the project root) to save the generated image to, e.g. 'assets/logo.png' or 'docs/diagram.png'. The file and any parent directories will be created automatically. The image is also returned as data so it can be displayed."
|
|
3659
|
+
})
|
|
3621
3660
|
});
|
|
3622
3661
|
function createGenerateImageTool(options) {
|
|
3623
|
-
const { metadataManager } = options;
|
|
3662
|
+
const { metadataManager, cwd } = options;
|
|
3624
3663
|
return {
|
|
3625
3664
|
name: "generate_image",
|
|
3626
3665
|
label: "generate_image",
|
|
3627
|
-
description: "Generate an image from a text prompt. Returns a URL or base64 data for the generated image. Use this when the user asks you to create, draw, design, or generate an image.",
|
|
3666
|
+
description: "Generate an image from a text prompt. Returns a URL or base64 data for the generated image. Use this when the user asks you to create, draw, design, or generate an image. Use the save_to_file parameter to save the image directly to a file in the project (e.g. 'assets/hero.png').",
|
|
3628
3667
|
parameters: generateImageSchema,
|
|
3629
|
-
execute: async (_toolCallId, { prompt, model: requestedModel }, signal) => {
|
|
3668
|
+
execute: async (_toolCallId, { prompt, model: requestedModel, save_to_file }, signal) => {
|
|
3630
3669
|
return withAbortSignal(signal, async (isAborted) => {
|
|
3631
3670
|
const modelManager = new ModelManager(metadataManager);
|
|
3632
3671
|
const enabledByProvider = await modelManager.getAllEnabledImageModels();
|
|
@@ -3639,14 +3678,59 @@ function createGenerateImageTool(options) {
|
|
|
3639
3678
|
}
|
|
3640
3679
|
}
|
|
3641
3680
|
if (imageModels.length === 0) {
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3681
|
+
console.log(
|
|
3682
|
+
`[generate_image] No image models enabled \u2014 falling back to image search for: "${prompt.substring(0, 100)}"`
|
|
3683
|
+
);
|
|
3684
|
+
let imageData2;
|
|
3685
|
+
let contentType;
|
|
3686
|
+
try {
|
|
3687
|
+
const result = await searchImage(prompt, void 0, signal);
|
|
3688
|
+
imageData2 = result.imageData;
|
|
3689
|
+
contentType = result.contentType;
|
|
3690
|
+
} catch (err) {
|
|
3691
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3692
|
+
return {
|
|
3693
|
+
content: [
|
|
3694
|
+
{
|
|
3695
|
+
type: "text",
|
|
3696
|
+
text: `No image models are enabled and the image search fallback also failed: ${message}`
|
|
3697
|
+
}
|
|
3698
|
+
],
|
|
3699
|
+
details: void 0
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
if (isAborted()) return { content: [], details: void 0 };
|
|
3703
|
+
let savedPath2;
|
|
3704
|
+
if (cwd) {
|
|
3705
|
+
try {
|
|
3706
|
+
const absolutePath = resolveToCwd(save_to_file, cwd);
|
|
3707
|
+
validatePathWithinCwd(absolutePath, cwd);
|
|
3708
|
+
const binary = atob(imageData2);
|
|
3709
|
+
const bytes = new Uint8Array(binary.length);
|
|
3710
|
+
for (let i = 0; i < binary.length; i++) {
|
|
3711
|
+
bytes[i] = binary.charCodeAt(i);
|
|
3647
3712
|
}
|
|
3648
|
-
|
|
3649
|
-
|
|
3713
|
+
await fsMkdir2(dirname2(absolutePath), { recursive: true });
|
|
3714
|
+
await fsWriteFile3(absolutePath, bytes);
|
|
3715
|
+
savedPath2 = save_to_file;
|
|
3716
|
+
} catch (saveError) {
|
|
3717
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
3718
|
+
return {
|
|
3719
|
+
content: [
|
|
3720
|
+
{
|
|
3721
|
+
type: "text",
|
|
3722
|
+
text: `Stock image found but failed to save to "${save_to_file}": ${errMsg}`
|
|
3723
|
+
}
|
|
3724
|
+
],
|
|
3725
|
+
details: { imageData: imageData2, contentType }
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
const fallbackPayload = { imageData: imageData2, contentType };
|
|
3730
|
+
if (savedPath2) fallbackPayload.savedPath = savedPath2;
|
|
3731
|
+
return {
|
|
3732
|
+
content: [{ type: "text", text: JSON.stringify(fallbackPayload) }],
|
|
3733
|
+
details: { imageData: imageData2, contentType }
|
|
3650
3734
|
};
|
|
3651
3735
|
}
|
|
3652
3736
|
let selectedModel = imageModels[0];
|
|
@@ -3766,12 +3850,52 @@ function createGenerateImageTool(options) {
|
|
|
3766
3850
|
imageData = imageResult.b64_json;
|
|
3767
3851
|
}
|
|
3768
3852
|
if (isAborted()) return { content: [], details: void 0 };
|
|
3853
|
+
let savedPath;
|
|
3854
|
+
if (cwd) {
|
|
3855
|
+
try {
|
|
3856
|
+
const absolutePath = resolveToCwd(save_to_file, cwd);
|
|
3857
|
+
validatePathWithinCwd(absolutePath, cwd);
|
|
3858
|
+
let imageBytes;
|
|
3859
|
+
if (imageData) {
|
|
3860
|
+
const binary = atob(imageData);
|
|
3861
|
+
imageBytes = new Uint8Array(binary.length);
|
|
3862
|
+
for (let i = 0; i < binary.length; i++) {
|
|
3863
|
+
imageBytes[i] = binary.charCodeAt(i);
|
|
3864
|
+
}
|
|
3865
|
+
} else if (imageUrl) {
|
|
3866
|
+
const res = await fetch(imageUrl);
|
|
3867
|
+
if (!res.ok) {
|
|
3868
|
+
throw new Error(`Failed to fetch image from URL: ${res.status}`);
|
|
3869
|
+
}
|
|
3870
|
+
imageBytes = new Uint8Array(await res.arrayBuffer());
|
|
3871
|
+
} else {
|
|
3872
|
+
throw new Error("No image data or URL available to save");
|
|
3873
|
+
}
|
|
3874
|
+
await fsMkdir2(dirname2(absolutePath), { recursive: true });
|
|
3875
|
+
await fsWriteFile3(absolutePath, imageBytes);
|
|
3876
|
+
savedPath = save_to_file;
|
|
3877
|
+
console.log(`[generate_image] Saved image to ${absolutePath}`);
|
|
3878
|
+
} catch (saveError) {
|
|
3879
|
+
console.error("[generate_image] Failed to save image to file:", saveError);
|
|
3880
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
3881
|
+
return {
|
|
3882
|
+
content: [
|
|
3883
|
+
{
|
|
3884
|
+
type: "text",
|
|
3885
|
+
text: `Image was generated but failed to save to "${save_to_file}": ${errMsg}`
|
|
3886
|
+
}
|
|
3887
|
+
],
|
|
3888
|
+
details: { imageUrl, imageData, model: selectedModel.id }
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3769
3892
|
const resultPayload = {
|
|
3770
3893
|
model: `${selectedModel.provider}/${selectedModel.id}`,
|
|
3771
3894
|
imageUrl,
|
|
3772
3895
|
imageData
|
|
3773
3896
|
};
|
|
3774
3897
|
if (text) resultPayload.text = text;
|
|
3898
|
+
if (savedPath) resultPayload.savedPath = savedPath;
|
|
3775
3899
|
return {
|
|
3776
3900
|
content: [
|
|
3777
3901
|
{
|
|
@@ -3786,9 +3910,288 @@ function createGenerateImageTool(options) {
|
|
|
3786
3910
|
};
|
|
3787
3911
|
}
|
|
3788
3912
|
|
|
3913
|
+
// src/tools/find-images.ts
|
|
3914
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
3915
|
+
import { mkdir as fsMkdir3, writeFile as fsWriteFile4 } from "fs/promises";
|
|
3916
|
+
import { dirname as dirname3 } from "path";
|
|
3917
|
+
var findImagesSchema = Type14.Object({
|
|
3918
|
+
query: Type14.String({ description: "Search term for the image (e.g. 'sunset over mountains')" }),
|
|
3919
|
+
size: Type14.Optional(
|
|
3920
|
+
Type14.Union(
|
|
3921
|
+
[
|
|
3922
|
+
Type14.Literal("original"),
|
|
3923
|
+
Type14.Literal("large2x"),
|
|
3924
|
+
Type14.Literal("large"),
|
|
3925
|
+
Type14.Literal("medium"),
|
|
3926
|
+
Type14.Literal("small"),
|
|
3927
|
+
Type14.Literal("portrait"),
|
|
3928
|
+
Type14.Literal("landscape"),
|
|
3929
|
+
Type14.Literal("tiny")
|
|
3930
|
+
],
|
|
3931
|
+
{
|
|
3932
|
+
description: "Image size variant: original, large2x, large, medium (default), small, portrait, landscape, tiny"
|
|
3933
|
+
}
|
|
3934
|
+
)
|
|
3935
|
+
),
|
|
3936
|
+
save_to_file: Type14.Optional(
|
|
3937
|
+
Type14.String({
|
|
3938
|
+
description: "File path relative to the project root to save the image to (e.g. 'assets/hero.jpg'). Parent directories are created automatically. If omitted the image is returned as base64 data only."
|
|
3939
|
+
})
|
|
3940
|
+
)
|
|
3941
|
+
});
|
|
3942
|
+
function createFindImagesTool(cwd) {
|
|
3943
|
+
return {
|
|
3944
|
+
name: "find_images",
|
|
3945
|
+
label: "find_images",
|
|
3946
|
+
description: "Find a stock image matching a search query. Returns the image as base64 data and optionally saves it to a file. Use this when the user needs an image and the user has not requested it be generated.",
|
|
3947
|
+
parameters: findImagesSchema,
|
|
3948
|
+
execute: async (_toolCallId, { query, size, save_to_file }, signal) => {
|
|
3949
|
+
return withAbortSignal(signal, async (isAborted) => {
|
|
3950
|
+
console.log(
|
|
3951
|
+
`[find_images] Searching for image \u2014 query: "${query}"${size ? `, size: ${size}` : ""}`
|
|
3952
|
+
);
|
|
3953
|
+
let imageData;
|
|
3954
|
+
let contentType;
|
|
3955
|
+
try {
|
|
3956
|
+
const result = await searchImage(query, size, signal);
|
|
3957
|
+
imageData = result.imageData;
|
|
3958
|
+
contentType = result.contentType;
|
|
3959
|
+
} catch (err) {
|
|
3960
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3961
|
+
return {
|
|
3962
|
+
content: [{ type: "text", text: `Image search failed: ${message}` }],
|
|
3963
|
+
details: void 0
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
if (isAborted()) return { content: [], details: void 0 };
|
|
3967
|
+
let savedPath;
|
|
3968
|
+
if (cwd && save_to_file) {
|
|
3969
|
+
try {
|
|
3970
|
+
const absolutePath = resolveToCwd(save_to_file, cwd);
|
|
3971
|
+
validatePathWithinCwd(absolutePath, cwd);
|
|
3972
|
+
const binary = atob(imageData);
|
|
3973
|
+
const bytes = new Uint8Array(binary.length);
|
|
3974
|
+
for (let i = 0; i < binary.length; i++) {
|
|
3975
|
+
bytes[i] = binary.charCodeAt(i);
|
|
3976
|
+
}
|
|
3977
|
+
await fsMkdir3(dirname3(absolutePath), { recursive: true });
|
|
3978
|
+
await fsWriteFile4(absolutePath, bytes);
|
|
3979
|
+
savedPath = save_to_file;
|
|
3980
|
+
console.log(`[find_images] Saved image to ${absolutePath}`);
|
|
3981
|
+
} catch (saveError) {
|
|
3982
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
3983
|
+
return {
|
|
3984
|
+
content: [
|
|
3985
|
+
{
|
|
3986
|
+
type: "text",
|
|
3987
|
+
text: `Image was found but failed to save to "${save_to_file}": ${errMsg}`
|
|
3988
|
+
}
|
|
3989
|
+
],
|
|
3990
|
+
details: { imageData, contentType }
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
const resultPayload = { imageData, contentType };
|
|
3995
|
+
if (savedPath) resultPayload.savedPath = savedPath;
|
|
3996
|
+
return {
|
|
3997
|
+
content: [{ type: "text", text: JSON.stringify(resultPayload) }],
|
|
3998
|
+
details: { imageData, contentType }
|
|
3999
|
+
};
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
// src/tools/execute-browser-js.ts
|
|
4006
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
4007
|
+
var runWebWorkerSchema = Type15.Object({
|
|
4008
|
+
intention: Type15.String({
|
|
4009
|
+
description: "A short description of what this code execution is trying to accomplish."
|
|
4010
|
+
}),
|
|
4011
|
+
code: Type15.String({
|
|
4012
|
+
description: "The javascript code to execute in browser context(not nodejs). The environment provides an async function `callTool(toolName, params)` which allows calling any of the other available tools by name. callTool always returns `{ text: string | null, error: string | null }`. On success `text` contains the result and `error` is null. On failure `error` contains the error message and `text` is null. Example: `const r = await callTool('read', { path: 'foo.ts' }); if (r.error) return r.error; return r.text;`."
|
|
4013
|
+
})
|
|
4014
|
+
});
|
|
4015
|
+
var pendingTasks = /* @__PURE__ */ new Map();
|
|
4016
|
+
function submitWebWorkerResult(toolCallId, result) {
|
|
4017
|
+
const pending = pendingTasks.get(toolCallId);
|
|
4018
|
+
if (!pending) return false;
|
|
4019
|
+
pending.resolve(result);
|
|
4020
|
+
pendingTasks.delete(toolCallId);
|
|
4021
|
+
return true;
|
|
4022
|
+
}
|
|
4023
|
+
function cancelPendingWebWorker(toolCallId) {
|
|
4024
|
+
const pending = pendingTasks.get(toolCallId);
|
|
4025
|
+
if (!pending) return false;
|
|
4026
|
+
pending.reject(new Error("Web worker execution cancelled"));
|
|
4027
|
+
pendingTasks.delete(toolCallId);
|
|
4028
|
+
return true;
|
|
4029
|
+
}
|
|
4030
|
+
function buildDescription(tools) {
|
|
4031
|
+
let desc = "Execute javascript code in a web worker (not nodejs). Use this when running JS code is the easiest way to accomplish the goal, such as data transformations, calculations, or orchestrating multiple tool calls programmatically. Returns the result of the execution. You can call other tools by using `await callTool('toolName', { arg: 'value' })`. callTool always returns `{ text: string | null, error: string | null }`. On success `text` contains the result and `error` is null. On failure `error` contains the error message and `text` is null. Example: `const r = await callTool('read', { path: 'foo.ts' }); if (r.error) return r.error; return r.text;`. Write the most compact code possible: no comments, short variable names, minimal whitespace.";
|
|
4032
|
+
if (tools && tools.length > 0) {
|
|
4033
|
+
const signatures = tools.map((t) => `${t.name}: ${JSON.stringify(t.parameters?.properties ?? {})}`).join("\n");
|
|
4034
|
+
desc += `
|
|
4035
|
+
|
|
4036
|
+
Available tools:
|
|
4037
|
+
${signatures}`;
|
|
4038
|
+
}
|
|
4039
|
+
return desc;
|
|
4040
|
+
}
|
|
4041
|
+
function createRunWebWorkerTool(options) {
|
|
4042
|
+
return {
|
|
4043
|
+
name: "run_js",
|
|
4044
|
+
label: "run_js",
|
|
4045
|
+
description: buildDescription(options?.tools),
|
|
4046
|
+
parameters: runWebWorkerSchema,
|
|
4047
|
+
execute: async (toolCallId, { intention: _intention, code }, signal) => {
|
|
4048
|
+
console.log(`[ai] run_js: executing script of length ${code.length}`);
|
|
4049
|
+
if (signal?.aborted) {
|
|
4050
|
+
throw new Error("Operation aborted");
|
|
4051
|
+
}
|
|
4052
|
+
const result = await new Promise((resolve6, reject) => {
|
|
4053
|
+
pendingTasks.set(toolCallId, {
|
|
4054
|
+
resolve: resolve6,
|
|
4055
|
+
reject,
|
|
4056
|
+
code
|
|
4057
|
+
});
|
|
4058
|
+
const onAbort = () => {
|
|
4059
|
+
cancelPendingWebWorker(toolCallId);
|
|
4060
|
+
};
|
|
4061
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4062
|
+
});
|
|
4063
|
+
return {
|
|
4064
|
+
content: [
|
|
4065
|
+
{
|
|
4066
|
+
type: "text",
|
|
4067
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2) || "undefined"
|
|
4068
|
+
}
|
|
4069
|
+
],
|
|
4070
|
+
details: void 0
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
var runWebWorkerTool = createRunWebWorkerTool();
|
|
4076
|
+
|
|
4077
|
+
// src/tools/tool-search.ts
|
|
4078
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
4079
|
+
var toolSearchSchema = Type16.Object({
|
|
4080
|
+
query: Type16.String({
|
|
4081
|
+
description: 'Search query to find tools. Use "select:name1,name2" to fetch exact tools by name, or keywords to search by description.'
|
|
4082
|
+
}),
|
|
4083
|
+
max_results: Type16.Optional(
|
|
4084
|
+
Type16.Number({
|
|
4085
|
+
description: "Maximum number of results to return (default: 5)"
|
|
4086
|
+
})
|
|
4087
|
+
)
|
|
4088
|
+
});
|
|
4089
|
+
function formatToolSchema(tool) {
|
|
4090
|
+
const schema = JSON.stringify(tool.parameters, null, 2);
|
|
4091
|
+
return `<function>{"name": "${tool.name}", "description": ${JSON.stringify(tool.description)}, "parameters": ${schema}}</function>`;
|
|
4092
|
+
}
|
|
4093
|
+
function parseQuery(query) {
|
|
4094
|
+
const trimmed = query.trim();
|
|
4095
|
+
if (trimmed.startsWith("select:")) {
|
|
4096
|
+
const names = trimmed.slice(7).split(",").map((n) => n.trim().toLowerCase()).filter(Boolean);
|
|
4097
|
+
return { mode: "select", names };
|
|
4098
|
+
}
|
|
4099
|
+
if (trimmed.startsWith("+")) {
|
|
4100
|
+
const parts = trimmed.slice(1).trim().split(/\s+/);
|
|
4101
|
+
const requiredName = parts[0]?.toLowerCase();
|
|
4102
|
+
const terms2 = parts.slice(1).map((t) => t.toLowerCase());
|
|
4103
|
+
return { mode: "search", terms: terms2, requiredName };
|
|
4104
|
+
}
|
|
4105
|
+
const terms = trimmed.toLowerCase().split(/\s+/).filter(Boolean);
|
|
4106
|
+
return { mode: "search", terms };
|
|
4107
|
+
}
|
|
4108
|
+
function scoreTool(tool, terms) {
|
|
4109
|
+
if (terms.length === 0) return 1;
|
|
4110
|
+
const name = tool.name.toLowerCase();
|
|
4111
|
+
const label = ("label" in tool ? tool.label : "").toLowerCase();
|
|
4112
|
+
const desc = tool.description.toLowerCase();
|
|
4113
|
+
let score = 0;
|
|
4114
|
+
for (const term of terms) {
|
|
4115
|
+
if (name === term) score += 10;
|
|
4116
|
+
else if (name.includes(term)) score += 5;
|
|
4117
|
+
if (label.includes(term)) score += 3;
|
|
4118
|
+
if (desc.includes(term)) score += 1;
|
|
4119
|
+
}
|
|
4120
|
+
return score;
|
|
4121
|
+
}
|
|
4122
|
+
function createToolSearchTool(options) {
|
|
4123
|
+
const { liveTools, deferredTools } = options;
|
|
4124
|
+
const activated = /* @__PURE__ */ new Set();
|
|
4125
|
+
return {
|
|
4126
|
+
name: "tool_search",
|
|
4127
|
+
label: "tool_search",
|
|
4128
|
+
description: 'Search for and activate deferred tools. Returns full schema definitions so you can call them. Use "select:name1,name2" to fetch exact tools, or keywords to search by description.',
|
|
4129
|
+
parameters: toolSearchSchema,
|
|
4130
|
+
execute: async (_toolCallId, input) => {
|
|
4131
|
+
const maxResults = input.max_results ?? 5;
|
|
4132
|
+
const parsed = parseQuery(input.query);
|
|
4133
|
+
let matched = [];
|
|
4134
|
+
if (parsed.mode === "select") {
|
|
4135
|
+
for (const name of parsed.names) {
|
|
4136
|
+
const tool = deferredTools.get(name);
|
|
4137
|
+
if (tool) matched.push(tool);
|
|
4138
|
+
}
|
|
4139
|
+
} else {
|
|
4140
|
+
const candidates = [];
|
|
4141
|
+
for (const tool of deferredTools.values()) {
|
|
4142
|
+
if (parsed.requiredName) {
|
|
4143
|
+
const name = tool.name.toLowerCase();
|
|
4144
|
+
if (!name.includes(parsed.requiredName)) continue;
|
|
4145
|
+
}
|
|
4146
|
+
const score = scoreTool(tool, parsed.terms);
|
|
4147
|
+
if (score > 0) {
|
|
4148
|
+
candidates.push({ tool, score });
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
4152
|
+
matched = candidates.slice(0, maxResults).map((c) => c.tool);
|
|
4153
|
+
}
|
|
4154
|
+
if (matched.length === 0) {
|
|
4155
|
+
const available = Array.from(deferredTools.keys()).join(", ");
|
|
4156
|
+
return {
|
|
4157
|
+
content: [
|
|
4158
|
+
{
|
|
4159
|
+
type: "text",
|
|
4160
|
+
text: `No tools matched query "${input.query}". Available deferred tools: ${available || "none"}`
|
|
4161
|
+
}
|
|
4162
|
+
],
|
|
4163
|
+
details: void 0
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
for (const tool of matched) {
|
|
4167
|
+
if (!activated.has(tool.name)) {
|
|
4168
|
+
activated.add(tool.name);
|
|
4169
|
+
liveTools.push(tool);
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
const schemaBlocks = matched.map(formatToolSchema).join("\n");
|
|
4173
|
+
const text = `Found ${matched.length} tool(s). They are now available for use.
|
|
4174
|
+
|
|
4175
|
+
<functions>
|
|
4176
|
+
${schemaBlocks}
|
|
4177
|
+
</functions>`;
|
|
4178
|
+
return {
|
|
4179
|
+
content: [{ type: "text", text }],
|
|
4180
|
+
details: void 0
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
function formatDeferredToolsList(deferredTools) {
|
|
4186
|
+
if (deferredTools.size === 0) return "";
|
|
4187
|
+
const names = Array.from(deferredTools.keys()).join(", ");
|
|
4188
|
+
return "\n\n# Deferred Tools\n\nThe following tools are available but deferred. Use the `tool_search` tool to fetch their full definitions before calling them:\n" + names;
|
|
4189
|
+
}
|
|
4190
|
+
|
|
3789
4191
|
// src/tools/index.ts
|
|
3790
4192
|
async function createCodingTools(cwd, options) {
|
|
3791
|
-
const
|
|
4193
|
+
const deferOption = options?.deferTools ?? true;
|
|
4194
|
+
const coreTools = [
|
|
3792
4195
|
createReadTool(cwd, options?.read),
|
|
3793
4196
|
createBashTool(cwd, options?.bash),
|
|
3794
4197
|
createEditTool(cwd),
|
|
@@ -3796,30 +4199,81 @@ async function createCodingTools(cwd, options) {
|
|
|
3796
4199
|
createAskUserTool(),
|
|
3797
4200
|
createTodoTool(options?.threadId ?? ""),
|
|
3798
4201
|
createFindTool(cwd),
|
|
3799
|
-
createAstGrepTool(cwd)
|
|
4202
|
+
createAstGrepTool(cwd),
|
|
4203
|
+
createRunWebWorkerTool()
|
|
3800
4204
|
];
|
|
4205
|
+
const optionalTools = [];
|
|
3801
4206
|
if (options?.metadataManager) {
|
|
3802
|
-
|
|
4207
|
+
optionalTools.push(createGenerateImageTool({ metadataManager: options.metadataManager, cwd }));
|
|
3803
4208
|
}
|
|
3804
|
-
|
|
4209
|
+
optionalTools.push(createFindImagesTool(cwd));
|
|
3805
4210
|
if (options?.skills && options.skills.length > 0) {
|
|
3806
|
-
|
|
4211
|
+
optionalTools.push(
|
|
3807
4212
|
createSkillScriptTool(options.skills, cwd),
|
|
3808
4213
|
createSkillReferenceTool(options.skills)
|
|
3809
|
-
|
|
3810
|
-
toolsWithSkills.push(...skillTools);
|
|
4214
|
+
);
|
|
3811
4215
|
}
|
|
4216
|
+
let mcpTools = [];
|
|
3812
4217
|
try {
|
|
3813
|
-
|
|
3814
|
-
toolsWithSkills.push(...mcpTools);
|
|
4218
|
+
mcpTools = await createMCPTools(cwd);
|
|
3815
4219
|
} catch (error) {
|
|
3816
4220
|
console.warn(`[Tools] Failed to load MCP tools for ${cwd}:`, error);
|
|
3817
4221
|
}
|
|
3818
|
-
|
|
4222
|
+
const deferredTools = /* @__PURE__ */ new Map();
|
|
4223
|
+
if (deferOption === false) {
|
|
4224
|
+
coreTools.push(...optionalTools, ...mcpTools);
|
|
4225
|
+
} else if (deferOption === true) {
|
|
4226
|
+
for (const tool of [...mcpTools, ...optionalTools]) {
|
|
4227
|
+
deferredTools.set(tool.name, tool);
|
|
4228
|
+
}
|
|
4229
|
+
} else {
|
|
4230
|
+
const deferSet = new Set(deferOption.map((n) => n.toLowerCase()));
|
|
4231
|
+
for (const tool of [...optionalTools, ...mcpTools]) {
|
|
4232
|
+
if (deferSet.has(tool.name.toLowerCase())) {
|
|
4233
|
+
deferredTools.set(tool.name, tool);
|
|
4234
|
+
} else {
|
|
4235
|
+
coreTools.push(tool);
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
const tools = [...coreTools];
|
|
4240
|
+
if (deferredTools.size > 0) {
|
|
4241
|
+
const toolSearch = createToolSearchTool({
|
|
4242
|
+
liveTools: tools,
|
|
4243
|
+
deferredTools
|
|
4244
|
+
});
|
|
4245
|
+
tools.push(toolSearch);
|
|
4246
|
+
console.log(
|
|
4247
|
+
`[Tools] ${deferredTools.size} tool(s) deferred behind tool_search: ${Array.from(deferredTools.keys()).join(", ")}`
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
return { tools, deferredToolNames: deferredTools };
|
|
4251
|
+
}
|
|
4252
|
+
function createAllTools(cwd, options) {
|
|
4253
|
+
const tools = {
|
|
4254
|
+
read: createReadTool(cwd, options?.read),
|
|
4255
|
+
bash: createBashTool(cwd, options?.bash),
|
|
4256
|
+
edit: createEditTool(cwd),
|
|
4257
|
+
write: createWriteTool(cwd),
|
|
4258
|
+
grep: createGrepTool(cwd),
|
|
4259
|
+
ast_grep: createAstGrepTool(cwd),
|
|
4260
|
+
find: createFindTool(cwd),
|
|
4261
|
+
ask_user: createAskUserTool(),
|
|
4262
|
+
ls: createLsTool(cwd),
|
|
4263
|
+
run_js: createRunWebWorkerTool()
|
|
4264
|
+
};
|
|
4265
|
+
if (options?.metadataManager) {
|
|
4266
|
+
tools.generate_image = createGenerateImageTool({
|
|
4267
|
+
metadataManager: options.metadataManager,
|
|
4268
|
+
cwd
|
|
4269
|
+
});
|
|
4270
|
+
}
|
|
4271
|
+
tools.find_images = createFindImagesTool(cwd);
|
|
4272
|
+
return tools;
|
|
3819
4273
|
}
|
|
3820
4274
|
|
|
3821
4275
|
// src/tools/agent-tool.ts
|
|
3822
|
-
import { Type as
|
|
4276
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
3823
4277
|
|
|
3824
4278
|
// src/agent/agent.subagent-executor.ts
|
|
3825
4279
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
@@ -4090,6 +4544,7 @@ async function executeSubagent(options) {
|
|
|
4090
4544
|
model: resolvedModel,
|
|
4091
4545
|
tools
|
|
4092
4546
|
},
|
|
4547
|
+
streamFn: cachedStreamFn,
|
|
4093
4548
|
getApiKey: async () => apiKey
|
|
4094
4549
|
});
|
|
4095
4550
|
const eventQueue = new EventQueue();
|
|
@@ -4166,15 +4621,15 @@ async function executeSubagent(options) {
|
|
|
4166
4621
|
}
|
|
4167
4622
|
|
|
4168
4623
|
// src/tools/agent-tool.ts
|
|
4169
|
-
var agentToolSchema =
|
|
4170
|
-
prompt:
|
|
4171
|
-
agentName:
|
|
4172
|
-
|
|
4624
|
+
var agentToolSchema = Type17.Object({
|
|
4625
|
+
prompt: Type17.String({ description: "The task or question for the subagent to work on" }),
|
|
4626
|
+
agentName: Type17.Optional(
|
|
4627
|
+
Type17.String({
|
|
4173
4628
|
description: "Name of a defined agent (from AGENT.md) to use. If omitted, a general-purpose subagent is created."
|
|
4174
4629
|
})
|
|
4175
4630
|
),
|
|
4176
|
-
description:
|
|
4177
|
-
|
|
4631
|
+
description: Type17.Optional(
|
|
4632
|
+
Type17.String({
|
|
4178
4633
|
description: "A short (3-5 word) label describing what the subagent will do"
|
|
4179
4634
|
})
|
|
4180
4635
|
)
|
|
@@ -4880,7 +5335,7 @@ function formatAgentsSection(agents) {
|
|
|
4880
5335
|
);
|
|
4881
5336
|
return section;
|
|
4882
5337
|
}
|
|
4883
|
-
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents) {
|
|
5338
|
+
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools) {
|
|
4884
5339
|
let prompt = buildDefaultPrompt(tools);
|
|
4885
5340
|
const agentsContent = loadDeveloperContext(threadPath);
|
|
4886
5341
|
if (agentsContent) {
|
|
@@ -4909,15 +5364,50 @@ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents
|
|
|
4909
5364
|
if (agentsSection) {
|
|
4910
5365
|
prompt += agentsSection;
|
|
4911
5366
|
}
|
|
5367
|
+
if (deferredTools && deferredTools.size > 0) {
|
|
5368
|
+
prompt += formatDeferredToolsList(deferredTools);
|
|
5369
|
+
}
|
|
4912
5370
|
return prompt;
|
|
4913
5371
|
}
|
|
4914
5372
|
|
|
4915
5373
|
// src/agent/agent.executor.ts
|
|
5374
|
+
var cachedStreamFn = (model, context, options) => {
|
|
5375
|
+
return streamSimple(model, context, { ...options, cacheRetention: "long" });
|
|
5376
|
+
};
|
|
5377
|
+
function buildHistoryMessages(history) {
|
|
5378
|
+
const messages = [];
|
|
5379
|
+
const now = Date.now();
|
|
5380
|
+
for (const exchange of history) {
|
|
5381
|
+
messages.push({
|
|
5382
|
+
role: "user",
|
|
5383
|
+
content: exchange.userMessage,
|
|
5384
|
+
timestamp: now
|
|
5385
|
+
});
|
|
5386
|
+
messages.push({
|
|
5387
|
+
role: "assistant",
|
|
5388
|
+
content: [{ type: "text", text: exchange.assistantResponse }],
|
|
5389
|
+
api: "",
|
|
5390
|
+
provider: "",
|
|
5391
|
+
model: "",
|
|
5392
|
+
usage: {
|
|
5393
|
+
input: 0,
|
|
5394
|
+
output: 0,
|
|
5395
|
+
cacheRead: 0,
|
|
5396
|
+
cacheWrite: 0,
|
|
5397
|
+
totalTokens: 0,
|
|
5398
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
5399
|
+
},
|
|
5400
|
+
stopReason: "stop",
|
|
5401
|
+
timestamp: now
|
|
5402
|
+
});
|
|
5403
|
+
}
|
|
5404
|
+
return messages;
|
|
5405
|
+
}
|
|
4916
5406
|
var PiExecutorImpl = class {
|
|
4917
5407
|
constructor(metadataManager) {
|
|
4918
5408
|
this.metadataManager = metadataManager;
|
|
4919
5409
|
}
|
|
4920
|
-
async *execute(userPrompt, context, signal) {
|
|
5410
|
+
async *execute(userPrompt, context, signal, conversationHistory) {
|
|
4921
5411
|
try {
|
|
4922
5412
|
const providerName = context.provider;
|
|
4923
5413
|
if (!providerName) {
|
|
@@ -4970,7 +5460,7 @@ var PiExecutorImpl = class {
|
|
|
4970
5460
|
api: resolvedModel.api,
|
|
4971
5461
|
provider: resolvedModel.provider
|
|
4972
5462
|
});
|
|
4973
|
-
const tools = await createCodingTools(cwd, {
|
|
5463
|
+
const { tools, deferredToolNames } = await createCodingTools(cwd, {
|
|
4974
5464
|
skills: context.skills,
|
|
4975
5465
|
threadId: context.threadId,
|
|
4976
5466
|
metadataManager: this.metadataManager
|
|
@@ -4980,7 +5470,8 @@ var PiExecutorImpl = class {
|
|
|
4980
5470
|
tools,
|
|
4981
5471
|
context.skills,
|
|
4982
5472
|
context.planMode,
|
|
4983
|
-
context.agents
|
|
5473
|
+
context.agents,
|
|
5474
|
+
deferredToolNames
|
|
4984
5475
|
);
|
|
4985
5476
|
console.log(
|
|
4986
5477
|
"[ai] System prompt loaded from thread path",
|
|
@@ -5002,12 +5493,15 @@ var PiExecutorImpl = class {
|
|
|
5002
5493
|
});
|
|
5003
5494
|
tools.push(agentToolInstance);
|
|
5004
5495
|
}
|
|
5496
|
+
const historyMessages = buildHistoryMessages(conversationHistory ?? []);
|
|
5005
5497
|
const agent = new Agent2({
|
|
5006
5498
|
initialState: {
|
|
5007
5499
|
systemPrompt,
|
|
5008
5500
|
model: resolvedModel,
|
|
5009
|
-
tools
|
|
5501
|
+
tools,
|
|
5502
|
+
messages: historyMessages
|
|
5010
5503
|
},
|
|
5504
|
+
streamFn: cachedStreamFn,
|
|
5011
5505
|
getApiKey: async () => apiKey
|
|
5012
5506
|
});
|
|
5013
5507
|
let done = false;
|
|
@@ -5894,18 +6388,6 @@ function streamAsyncGenerator(c, generator, options) {
|
|
|
5894
6388
|
}
|
|
5895
6389
|
|
|
5896
6390
|
// src/features/conversations/conversations.content.ts
|
|
5897
|
-
var DEFAULT_MAX_CONTEXT_MESSAGES = 10;
|
|
5898
|
-
function formatConversationContext(messages, maxMessages = DEFAULT_MAX_CONTEXT_MESSAGES) {
|
|
5899
|
-
const completed = messages.filter((m) => m.response != null);
|
|
5900
|
-
const recent = completed.slice(-maxMessages);
|
|
5901
|
-
if (recent.length === 0) return "";
|
|
5902
|
-
const lines = ["Previous conversation:"];
|
|
5903
|
-
for (const m of recent) {
|
|
5904
|
-
lines.push(`User: ${m.request.message}`);
|
|
5905
|
-
lines.push(`Assistant: ${m.response.content}`);
|
|
5906
|
-
}
|
|
5907
|
-
return lines.join("\n");
|
|
5908
|
-
}
|
|
5909
6391
|
var extractTextBlocksFromContent = (content) => {
|
|
5910
6392
|
const trimmed = content.trim();
|
|
5911
6393
|
if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) {
|
|
@@ -6505,11 +6987,10 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
6505
6987
|
const priorMessages = (history?.messages ?? []).filter(
|
|
6506
6988
|
(m) => m.id !== messageId && m.response != null
|
|
6507
6989
|
);
|
|
6508
|
-
const
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
User: ${content}` : content;
|
|
6990
|
+
const conversationHistory = priorMessages.slice(-10).map((m) => ({
|
|
6991
|
+
userMessage: m.request.message,
|
|
6992
|
+
assistantResponse: m.response.content
|
|
6993
|
+
}));
|
|
6513
6994
|
const abortController = new AbortController();
|
|
6514
6995
|
const requestSignal = c.req.raw.signal;
|
|
6515
6996
|
if (requestSignal.aborted) {
|
|
@@ -6530,9 +7011,10 @@ User: ${content}` : content;
|
|
|
6530
7011
|
let fullContent = "";
|
|
6531
7012
|
try {
|
|
6532
7013
|
for await (const event of agentExecutor.execute(
|
|
6533
|
-
|
|
7014
|
+
content,
|
|
6534
7015
|
context,
|
|
6535
|
-
abortController.signal
|
|
7016
|
+
abortController.signal,
|
|
7017
|
+
conversationHistory
|
|
6536
7018
|
)) {
|
|
6537
7019
|
capturedEvents.push(event);
|
|
6538
7020
|
if (event.type === "message" && event.content) {
|
|
@@ -7849,8 +8331,8 @@ async function insertProject(db, project) {
|
|
|
7849
8331
|
try {
|
|
7850
8332
|
await db.execute(
|
|
7851
8333
|
`
|
|
7852
|
-
INSERT INTO projects (id, name, gitUrl, path, createdAt, openWith, commands, setupScript, runCommand, commitMethod)
|
|
7853
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8334
|
+
INSERT INTO projects (id, name, gitUrl, path, createdAt, openWith, commands, setupScript, runCommand, commitMethod, planPrompt, testPrompt, reviewPrompt)
|
|
8335
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7854
8336
|
`,
|
|
7855
8337
|
[
|
|
7856
8338
|
project.id,
|
|
@@ -7862,7 +8344,10 @@ async function insertProject(db, project) {
|
|
|
7862
8344
|
project.commands ? JSON.stringify(project.commands) : null,
|
|
7863
8345
|
project.setupScript ?? null,
|
|
7864
8346
|
project.runCommand ?? null,
|
|
7865
|
-
project.commitMethod ?? null
|
|
8347
|
+
project.commitMethod ?? null,
|
|
8348
|
+
project.planPrompt ?? null,
|
|
8349
|
+
project.testPrompt ?? null,
|
|
8350
|
+
project.reviewPrompt ?? null
|
|
7866
8351
|
]
|
|
7867
8352
|
);
|
|
7868
8353
|
} catch (error) {
|
|
@@ -7906,6 +8391,18 @@ async function updateProject(db, id, updates) {
|
|
|
7906
8391
|
fields.push("commitMethod = ?");
|
|
7907
8392
|
values.push(updates.commitMethod ?? null);
|
|
7908
8393
|
}
|
|
8394
|
+
if (updates.planPrompt !== void 0) {
|
|
8395
|
+
fields.push("planPrompt = ?");
|
|
8396
|
+
values.push(updates.planPrompt ?? null);
|
|
8397
|
+
}
|
|
8398
|
+
if (updates.testPrompt !== void 0) {
|
|
8399
|
+
fields.push("testPrompt = ?");
|
|
8400
|
+
values.push(updates.testPrompt ?? null);
|
|
8401
|
+
}
|
|
8402
|
+
if (updates.reviewPrompt !== void 0) {
|
|
8403
|
+
fields.push("reviewPrompt = ?");
|
|
8404
|
+
values.push(updates.reviewPrompt ?? null);
|
|
8405
|
+
}
|
|
7909
8406
|
if (fields.length === 0) {
|
|
7910
8407
|
return;
|
|
7911
8408
|
}
|
|
@@ -7980,7 +8477,10 @@ async function deserializeProject(db, row) {
|
|
|
7980
8477
|
commands: row.commands ? JSON.parse(row.commands) : void 0,
|
|
7981
8478
|
setupScript: row.setupScript ?? void 0,
|
|
7982
8479
|
runCommand: row.runCommand ?? void 0,
|
|
7983
|
-
commitMethod: row.commitMethod && isValidCommitMethod(row.commitMethod) ? row.commitMethod : void 0
|
|
8480
|
+
commitMethod: row.commitMethod && isValidCommitMethod(row.commitMethod) ? row.commitMethod : void 0,
|
|
8481
|
+
planPrompt: row.planPrompt ?? void 0,
|
|
8482
|
+
testPrompt: row.testPrompt ?? void 0,
|
|
8483
|
+
reviewPrompt: row.reviewPrompt ?? void 0
|
|
7984
8484
|
};
|
|
7985
8485
|
}
|
|
7986
8486
|
|
|
@@ -9205,7 +9705,17 @@ async function handleUpdateProject(c, projectManager) {
|
|
|
9205
9705
|
try {
|
|
9206
9706
|
const projectId = c.req.param("id");
|
|
9207
9707
|
const body = await c.req.json();
|
|
9208
|
-
const {
|
|
9708
|
+
const {
|
|
9709
|
+
program,
|
|
9710
|
+
setupScript,
|
|
9711
|
+
name,
|
|
9712
|
+
runCommand,
|
|
9713
|
+
commitMethod,
|
|
9714
|
+
runNow,
|
|
9715
|
+
planPrompt,
|
|
9716
|
+
testPrompt,
|
|
9717
|
+
reviewPrompt
|
|
9718
|
+
} = body;
|
|
9209
9719
|
if (!projectId) {
|
|
9210
9720
|
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Project ID is required", 400);
|
|
9211
9721
|
}
|
|
@@ -9243,6 +9753,15 @@ async function handleUpdateProject(c, projectManager) {
|
|
|
9243
9753
|
}
|
|
9244
9754
|
await projectManager.updateCommitMethod(projectId, commitMethod);
|
|
9245
9755
|
}
|
|
9756
|
+
if (planPrompt !== void 0) {
|
|
9757
|
+
await projectManager.updateProjectPrompt(projectId, "planPrompt", planPrompt);
|
|
9758
|
+
}
|
|
9759
|
+
if (testPrompt !== void 0) {
|
|
9760
|
+
await projectManager.updateProjectPrompt(projectId, "testPrompt", testPrompt);
|
|
9761
|
+
}
|
|
9762
|
+
if (reviewPrompt !== void 0) {
|
|
9763
|
+
await projectManager.updateProjectPrompt(projectId, "reviewPrompt", reviewPrompt);
|
|
9764
|
+
}
|
|
9246
9765
|
if (name !== void 0) {
|
|
9247
9766
|
if (!name?.trim()) {
|
|
9248
9767
|
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Project name is required", 400);
|
|
@@ -10131,10 +10650,10 @@ var ProcessManager = class extends EventEmitter {
|
|
|
10131
10650
|
addToQueue({ type: "stderr", content: text });
|
|
10132
10651
|
});
|
|
10133
10652
|
let processEnded = false;
|
|
10134
|
-
childProcess.on("close", () => {
|
|
10653
|
+
childProcess.on("close", (code) => {
|
|
10135
10654
|
processEnded = true;
|
|
10136
10655
|
this.processes.delete(projectId);
|
|
10137
|
-
addToQueue({ type: "complete" });
|
|
10656
|
+
addToQueue({ type: "complete", exitCode: code ?? 0 });
|
|
10138
10657
|
const res = this.resolvers.get(projectId);
|
|
10139
10658
|
if (res) {
|
|
10140
10659
|
res.forEach((r) => r());
|
|
@@ -12114,6 +12633,21 @@ ___CWD___%s
|
|
|
12114
12633
|
project.commitMethod = commitMethod;
|
|
12115
12634
|
await this.metadataManager.saveProjects(projects);
|
|
12116
12635
|
}
|
|
12636
|
+
/**
|
|
12637
|
+
* Updates a prompt field on a project
|
|
12638
|
+
* @param projectId - The project ID
|
|
12639
|
+
* @param field - Which prompt field to update
|
|
12640
|
+
* @param value - The prompt value (null or empty string clears it)
|
|
12641
|
+
*/
|
|
12642
|
+
async updateProjectPrompt(projectId, field, value) {
|
|
12643
|
+
const projects = await this.metadataManager.loadProjects();
|
|
12644
|
+
const project = projects.find((p) => p.id === projectId);
|
|
12645
|
+
if (!project) {
|
|
12646
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
12647
|
+
}
|
|
12648
|
+
project[field] = value && value.trim() ? value : void 0;
|
|
12649
|
+
await this.metadataManager.saveProjects(projects);
|
|
12650
|
+
}
|
|
12117
12651
|
/**
|
|
12118
12652
|
* Starts running a dev server process for a project
|
|
12119
12653
|
* @param projectId - The project ID
|
|
@@ -14455,6 +14989,8 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
|
|
|
14455
14989
|
if (!message.response) {
|
|
14456
14990
|
return [userMessage];
|
|
14457
14991
|
}
|
|
14992
|
+
const tokenCount = message.inputTokens || message.outputTokens ? (message.inputTokens ?? 0) + (message.outputTokens ?? 0) : void 0;
|
|
14993
|
+
const processingTimeMs = message.timestamp && message.response.completedAt ? new Date(message.response.completedAt).getTime() - new Date(message.timestamp).getTime() : void 0;
|
|
14458
14994
|
const agentMessage = {
|
|
14459
14995
|
messageId: `agent-${message.id}`,
|
|
14460
14996
|
sender: "agent",
|
|
@@ -14465,6 +15001,12 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
|
|
|
14465
15001
|
contentType: "markdown",
|
|
14466
15002
|
timestamp: message.response.completedAt,
|
|
14467
15003
|
model: message.request.model,
|
|
15004
|
+
...(tokenCount || processingTimeMs) && {
|
|
15005
|
+
metadata: {
|
|
15006
|
+
...tokenCount && { tokenCount },
|
|
15007
|
+
...processingTimeMs && processingTimeMs > 0 && { processingTimeMs }
|
|
15008
|
+
}
|
|
15009
|
+
},
|
|
14468
15010
|
...message.response.imageUrl && { generatedImageUrl: message.response.imageUrl }
|
|
14469
15011
|
};
|
|
14470
15012
|
return [userMessage, agentMessage];
|
|
@@ -14973,7 +15515,7 @@ import { existsSync as existsSync16 } from "fs";
|
|
|
14973
15515
|
|
|
14974
15516
|
// src/features/git/git-download-folder.ts
|
|
14975
15517
|
import { mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
|
|
14976
|
-
import { join as join24, dirname as
|
|
15518
|
+
import { join as join24, dirname as dirname4 } from "path";
|
|
14977
15519
|
async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
14978
15520
|
const { token, ref } = options;
|
|
14979
15521
|
const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
@@ -15010,7 +15552,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
|
15010
15552
|
files.map(async (file) => {
|
|
15011
15553
|
const relativePath = file.path.slice(prefix.length);
|
|
15012
15554
|
const localPath = join24(destPath, relativePath);
|
|
15013
|
-
await mkdir7(
|
|
15555
|
+
await mkdir7(dirname4(localPath), { recursive: true });
|
|
15014
15556
|
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${file.path}`;
|
|
15015
15557
|
const fileRes = await fetch(rawUrl, { headers });
|
|
15016
15558
|
if (!fileRes.ok) {
|
|
@@ -15646,7 +16188,7 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
15646
16188
|
// src/features/git/git.manager.ts
|
|
15647
16189
|
init_utils();
|
|
15648
16190
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
15649
|
-
import { dirname as
|
|
16191
|
+
import { dirname as dirname5 } from "path";
|
|
15650
16192
|
var GitManagerImpl = class {
|
|
15651
16193
|
/**
|
|
15652
16194
|
* Regular expressions for validating different git URL formats
|
|
@@ -15716,7 +16258,7 @@ var GitManagerImpl = class {
|
|
|
15716
16258
|
return;
|
|
15717
16259
|
}
|
|
15718
16260
|
try {
|
|
15719
|
-
const parentDir =
|
|
16261
|
+
const parentDir = dirname5(targetPath);
|
|
15720
16262
|
await mkdir9(parentDir, { recursive: true });
|
|
15721
16263
|
const gitProcess = spawnProcess("git", ["clone", "--progress", gitUrl, targetPath]);
|
|
15722
16264
|
const eventQueue = [];
|
|
@@ -18511,7 +19053,7 @@ function createUpdateRoutes(updater) {
|
|
|
18511
19053
|
// src/features/mcp/mcp.routes.ts
|
|
18512
19054
|
import { Hono as Hono13 } from "hono";
|
|
18513
19055
|
import { readFile as readFile15, writeFile as writeFile7, mkdir as mkdir10, access as access6 } from "fs/promises";
|
|
18514
|
-
import { join as join28, dirname as
|
|
19056
|
+
import { join as join28, dirname as dirname6 } from "path";
|
|
18515
19057
|
|
|
18516
19058
|
// src/features/mcp/mcp.popular.json
|
|
18517
19059
|
var mcp_popular_default = [
|
|
@@ -18637,7 +19179,7 @@ async function readMCPConfig(projectPath) {
|
|
|
18637
19179
|
async function writeMCPConfig(projectPath, config) {
|
|
18638
19180
|
const existing = await readMCPConfig(projectPath);
|
|
18639
19181
|
const targetPath = existing?.filePath ?? join28(projectPath, ".agents/mcp.json");
|
|
18640
|
-
await mkdir10(
|
|
19182
|
+
await mkdir10(dirname6(targetPath), { recursive: true });
|
|
18641
19183
|
await writeFile7(targetPath, JSON.stringify(config, null, 2), "utf-8");
|
|
18642
19184
|
}
|
|
18643
19185
|
function createMCPRoutes() {
|
|
@@ -19025,9 +19567,35 @@ function createProjectTodosRoutes(metadataManager) {
|
|
|
19025
19567
|
|
|
19026
19568
|
// src/features/account/account.routes.ts
|
|
19027
19569
|
import { Hono as Hono17 } from "hono";
|
|
19570
|
+
|
|
19571
|
+
// src/features/account/account-settings.route.ts
|
|
19572
|
+
init_database();
|
|
19573
|
+
var KEY_USE_TOOLS_THROUGH_CODE = "UseToolsThroughCode";
|
|
19574
|
+
async function getAccountSettings(c) {
|
|
19575
|
+
const db = await getDatabase();
|
|
19576
|
+
const raw = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
|
|
19577
|
+
return c.json({
|
|
19578
|
+
useToolsThroughCode: raw !== false
|
|
19579
|
+
});
|
|
19580
|
+
}
|
|
19581
|
+
async function updateAccountSettings(c) {
|
|
19582
|
+
const body = await c.req.json();
|
|
19583
|
+
const db = await getDatabase();
|
|
19584
|
+
if (typeof body.useToolsThroughCode === "boolean") {
|
|
19585
|
+
await setState(db, KEY_USE_TOOLS_THROUGH_CODE, body.useToolsThroughCode);
|
|
19586
|
+
}
|
|
19587
|
+
const raw = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
|
|
19588
|
+
return c.json({
|
|
19589
|
+
useToolsThroughCode: raw !== false
|
|
19590
|
+
});
|
|
19591
|
+
}
|
|
19592
|
+
|
|
19593
|
+
// src/features/account/account.routes.ts
|
|
19028
19594
|
function createAccountRoutes() {
|
|
19029
19595
|
const router = new Hono17();
|
|
19030
19596
|
router.get("/info", (c) => getAccountInfo(c));
|
|
19597
|
+
router.get("/settings", (c) => getAccountSettings(c));
|
|
19598
|
+
router.put("/settings", (c) => updateAccountSettings(c));
|
|
19031
19599
|
return router;
|
|
19032
19600
|
}
|
|
19033
19601
|
|
|
@@ -19311,13 +19879,89 @@ function createImageRoutes(metadataManager, conversationManager, threadManager)
|
|
|
19311
19879
|
return router;
|
|
19312
19880
|
}
|
|
19313
19881
|
|
|
19882
|
+
// src/features/browser-js/browser-js.routes.ts
|
|
19883
|
+
import { Hono as Hono20 } from "hono";
|
|
19884
|
+
function createBrowserJsRoutes(threadManager) {
|
|
19885
|
+
const router = new Hono20();
|
|
19886
|
+
router.post("/respond", async (c) => {
|
|
19887
|
+
try {
|
|
19888
|
+
const body = await c.req.json();
|
|
19889
|
+
const { toolCallId, result } = body;
|
|
19890
|
+
if (!toolCallId || typeof toolCallId !== "string") {
|
|
19891
|
+
return errorResponse(
|
|
19892
|
+
c,
|
|
19893
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19894
|
+
"toolCallId is required and must be a string",
|
|
19895
|
+
400
|
|
19896
|
+
);
|
|
19897
|
+
}
|
|
19898
|
+
const resolved = submitWebWorkerResult(toolCallId, result);
|
|
19899
|
+
if (!resolved) {
|
|
19900
|
+
return errorResponse(
|
|
19901
|
+
c,
|
|
19902
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19903
|
+
"No pending web worker execution found for this toolCallId",
|
|
19904
|
+
404
|
|
19905
|
+
);
|
|
19906
|
+
}
|
|
19907
|
+
return c.json({ success: true });
|
|
19908
|
+
} catch (error) {
|
|
19909
|
+
return errorResponse(
|
|
19910
|
+
c,
|
|
19911
|
+
ErrorCodes.REQUEST_PARSE_ERROR,
|
|
19912
|
+
"Failed to parse request body",
|
|
19913
|
+
400,
|
|
19914
|
+
error instanceof Error ? error.message : String(error)
|
|
19915
|
+
);
|
|
19916
|
+
}
|
|
19917
|
+
});
|
|
19918
|
+
router.post("/execute-tool", async (c) => {
|
|
19919
|
+
try {
|
|
19920
|
+
const body = await c.req.json();
|
|
19921
|
+
const { threadId, toolName, params } = body;
|
|
19922
|
+
if (!threadId || typeof threadId !== "string") {
|
|
19923
|
+
return errorResponse(
|
|
19924
|
+
c,
|
|
19925
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19926
|
+
"threadId is required and must be a string",
|
|
19927
|
+
400
|
|
19928
|
+
);
|
|
19929
|
+
}
|
|
19930
|
+
if (!toolName || typeof toolName !== "string") {
|
|
19931
|
+
return errorResponse(
|
|
19932
|
+
c,
|
|
19933
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19934
|
+
"toolName is required and must be a string",
|
|
19935
|
+
400
|
|
19936
|
+
);
|
|
19937
|
+
}
|
|
19938
|
+
const thread = await threadManager.getThread(threadId);
|
|
19939
|
+
if (!thread) {
|
|
19940
|
+
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, "Thread not found", 404);
|
|
19941
|
+
}
|
|
19942
|
+
const tools = createAllTools(thread.path);
|
|
19943
|
+
const tool = tools[toolName];
|
|
19944
|
+
if (!tool) {
|
|
19945
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, `Tool ${toolName} not found`, 404);
|
|
19946
|
+
}
|
|
19947
|
+
const executionId = "web-worker-" + Date.now().toString() + "-" + Math.random().toString(36).substr(2, 9);
|
|
19948
|
+
const result = await tool.execute(executionId, params);
|
|
19949
|
+
return c.json(result);
|
|
19950
|
+
} catch (error) {
|
|
19951
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19952
|
+
return errorResponse(c, ErrorCodes.REQUEST_PARSE_ERROR, detail, 500, detail);
|
|
19953
|
+
}
|
|
19954
|
+
});
|
|
19955
|
+
return router;
|
|
19956
|
+
}
|
|
19957
|
+
|
|
19314
19958
|
// src/server.ts
|
|
19315
19959
|
var __filename = fileURLToPath2(import.meta.url);
|
|
19316
19960
|
var __dirname = path5.dirname(__filename);
|
|
19317
19961
|
async function startTarskServer(options) {
|
|
19318
19962
|
const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
|
|
19319
19963
|
const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
|
|
19320
|
-
const app = new
|
|
19964
|
+
const app = new Hono21();
|
|
19321
19965
|
app.use("/*", cors());
|
|
19322
19966
|
app.use("/*", async (c, next) => {
|
|
19323
19967
|
if (c.req.path.startsWith("/api/")) {
|
|
@@ -19388,6 +20032,7 @@ async function startTarskServer(options) {
|
|
|
19388
20032
|
app.route("/api/image", createImageRoutes(metadataManager, conversationManager, threadManager));
|
|
19389
20033
|
app.route("/api/scaffold", createScaffoldRoutes(projectManager));
|
|
19390
20034
|
app.route("/api/ask-user", createAskUserRoutes());
|
|
20035
|
+
app.route("/api/browser-js", createBrowserJsRoutes(threadManager));
|
|
19391
20036
|
app.route("/api/update", createUpdateRoutes(options.updater));
|
|
19392
20037
|
createSlashCommandRoutes(app, threadManager);
|
|
19393
20038
|
createRuleRoutes(app, projectManager);
|