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.
Files changed (51) hide show
  1. package/dist/index.js +712 -67
  2. package/dist/public/assets/account-view-Bj-Sn682.js +1 -0
  3. package/dist/public/assets/api-BuJ1dPr6.js +1 -0
  4. package/dist/public/assets/browser-tab-C9hYTMS0.js +1 -0
  5. package/dist/public/assets/context-menu-DeHLk-VY.js +1 -0
  6. package/dist/public/assets/conversation-history-view-cEjKqUjG.js +1 -0
  7. package/dist/public/assets/dialogs-config-CteuxXdD.js +46 -0
  8. package/dist/public/assets/diff-view-B2VYMGHR.js +3 -0
  9. package/dist/public/assets/explorer-tab-view-CROJs14G.js +2 -0
  10. package/dist/public/assets/explorer-tree-BMhkGVQO.js +1 -0
  11. package/dist/public/assets/explorer-view-BsDRu4DY.js +1 -0
  12. package/dist/public/assets/history-view-zlzHeGwz.js +1 -0
  13. package/dist/public/assets/index-BsVrm-8c.js +80 -0
  14. package/dist/public/assets/index-DyoTajdY.css +1 -0
  15. package/dist/public/assets/onboarding-C0LojpU9.js +1 -0
  16. package/dist/public/assets/onboarding-dialog-DdeXtx1_.js +1 -0
  17. package/dist/public/assets/project-settings-view-a6woZhAb.js +1 -0
  18. package/dist/public/assets/provider-details-view-CMYKkW6H.js +1 -0
  19. package/dist/public/assets/{providers-sidebar-DSrnVDNy.js → providers-sidebar-8XbQfUhe.js} +1 -1
  20. package/dist/public/assets/react-vendor-Bt_xeXcZ.js +17 -0
  21. package/dist/public/assets/settings-view-CrZ7av2I.js +2 -0
  22. package/dist/public/assets/{store-BuY90jLM.js → store-DXPO1PQf.js} +1 -1
  23. package/dist/public/assets/tab-context-B7Q9fKIl.js +1 -0
  24. package/dist/public/assets/{terminal-panel-Zyr6xth3.js → terminal-panel-BxS2vp4u.js} +1 -1
  25. package/dist/public/assets/{textarea-CxhrnOOz.js → textarea-BBmfPFv5.js} +1 -1
  26. package/dist/public/assets/todos-view-E35eshII.js +1 -0
  27. package/dist/public/assets/use-toast-BhhMvy1W.js +1 -0
  28. package/dist/public/assets/{utils-7CkwsQJ0.js → utils-DcGbvYrk.js} +1 -1
  29. package/dist/public/index.html +12 -10
  30. package/package.json +2 -2
  31. package/dist/public/assets/account-view-CXpwWuJZ.js +0 -1
  32. package/dist/public/assets/api-BfUK32d1.js +0 -1
  33. package/dist/public/assets/browser-tab-BdzIB5R4.js +0 -1
  34. package/dist/public/assets/context-menu-CC9JLx5B.js +0 -1
  35. package/dist/public/assets/conversation-history-view-C6iUsZjA.js +0 -1
  36. package/dist/public/assets/dialogs-config-Ofxg1Bn0.js +0 -46
  37. package/dist/public/assets/diff-view-C-Kz7R_T.js +0 -3
  38. package/dist/public/assets/explorer-tab-view-DC8arYcw.js +0 -2
  39. package/dist/public/assets/explorer-tree-BwLCrdfR.js +0 -1
  40. package/dist/public/assets/explorer-view-CPCrOqXp.js +0 -1
  41. package/dist/public/assets/history-view-CAVl9GFy.js +0 -1
  42. package/dist/public/assets/index-BVLzVjoR.css +0 -1
  43. package/dist/public/assets/index-DIWoaAFy.js +0 -30
  44. package/dist/public/assets/onboarding-DTzcWokE.js +0 -1
  45. package/dist/public/assets/onboarding-dialog-BQvu_Nac.js +0 -1
  46. package/dist/public/assets/project-settings-view-CZPKYKCu.js +0 -1
  47. package/dist/public/assets/provider-details-view-BBvKe6Am.js +0 -1
  48. package/dist/public/assets/react-vendor-9yNfIRPs.js +0 -17
  49. package/dist/public/assets/settings-view-D6FAPBQP.js +0 -2
  50. package/dist/public/assets/todos-view-yiH-gaUK.js +0 -1
  51. /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 Hono20 } from "hono";
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
- return {
3643
- content: [
3644
- {
3645
- type: "text",
3646
- text: "No image models are enabled. Please enable an image model in settings before generating images."
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
- details: void 0
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 baseTools = [
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
- baseTools.push(createGenerateImageTool({ metadataManager: options.metadataManager }));
4207
+ optionalTools.push(createGenerateImageTool({ metadataManager: options.metadataManager, cwd }));
3803
4208
  }
3804
- const toolsWithSkills = [...baseTools];
4209
+ optionalTools.push(createFindImagesTool(cwd));
3805
4210
  if (options?.skills && options.skills.length > 0) {
3806
- const skillTools = [
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
- const mcpTools = await createMCPTools(cwd);
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
- return toolsWithSkills;
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 Type14 } from "@sinclair/typebox";
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 = Type14.Object({
4170
- prompt: Type14.String({ description: "The task or question for the subagent to work on" }),
4171
- agentName: Type14.Optional(
4172
- Type14.String({
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: Type14.Optional(
4177
- Type14.String({
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 contextBlock = formatConversationContext(priorMessages);
6509
- const promptWithContext = contextBlock.length > 0 ? `${contextBlock}
6510
-
6511
- Current message:
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
- promptWithContext,
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 { program, setupScript, name, runCommand, commitMethod, runNow } = body;
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 dirname2 } from "path";
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(dirname2(localPath), { recursive: true });
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 dirname3 } from "path";
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 = dirname3(targetPath);
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 dirname4 } from "path";
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(dirname4(targetPath), { recursive: true });
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 Hono20();
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);