tarsk 0.4.12 → 0.4.14

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 (44) hide show
  1. package/dist/index.js +644 -61
  2. package/dist/public/assets/{account-view-BvHnxfR-.js → account-view-bgfCh9fD.js} +1 -1
  3. package/dist/public/assets/api-tXifOYo2.js +1 -0
  4. package/dist/public/assets/browser-tab-BqBW_AK-.js +1 -0
  5. package/dist/public/assets/context-menu-CmaVJQOJ.js +1 -0
  6. package/dist/public/assets/conversation-history-view-BU-YAZS_.js +1 -0
  7. package/dist/public/assets/dialogs-config-bLEt-2lj.js +46 -0
  8. package/dist/public/assets/diff-view-DjfyQW_j.js +3 -0
  9. package/dist/public/assets/{explorer-tab-view-Dxet8lje.js → explorer-tab-view-DRpNcRXM.js} +2 -2
  10. package/dist/public/assets/explorer-tree-z3yBekT5.js +1 -0
  11. package/dist/public/assets/explorer-view-CjevBGrc.js +1 -0
  12. package/dist/public/assets/history-view-ETKpXvpZ.js +1 -0
  13. package/dist/public/assets/index-CQrq9F6C.js +65 -0
  14. package/dist/public/assets/index-Dwos0qd5.css +1 -0
  15. package/dist/public/assets/onboarding-Dvaoo4rL.js +1 -0
  16. package/dist/public/assets/onboarding-dialog-D7LOFzso.js +1 -0
  17. package/dist/public/assets/{project-settings-view-Bi5zT3_9.js → project-settings-view-CBW-nuLi.js} +1 -1
  18. package/dist/public/assets/provider-details-view-DJXhGdNI.js +1 -0
  19. package/dist/public/assets/{providers-sidebar-CFwpsg_Y.js → providers-sidebar-Cn7XvmQ-.js} +1 -1
  20. package/dist/public/assets/react-vendor-CUw-HIQD.js +17 -0
  21. package/dist/public/assets/{settings-view-DWnpQjWE.js → settings-view-TH1bX0FR.js} +2 -2
  22. package/dist/public/assets/{store-BuY90jLM.js → store-CDLJd-q_.js} +1 -1
  23. package/dist/public/assets/{terminal-panel-CLpupIW9.js → terminal-panel-BRdTXLkg.js} +1 -1
  24. package/dist/public/assets/{textarea-CxhrnOOz.js → textarea-DtvDY2H_.js} +1 -1
  25. package/dist/public/assets/todos-view-BHuGeR5Y.js +1 -0
  26. package/dist/public/assets/{utils-7CkwsQJ0.js → utils-BxHvPw5k.js} +1 -1
  27. package/dist/public/index.html +9 -9
  28. package/package.json +2 -2
  29. package/dist/public/assets/api-BfUK32d1.js +0 -1
  30. package/dist/public/assets/browser-tab-DYDpQIZc.js +0 -1
  31. package/dist/public/assets/context-menu-CoedP3Pg.js +0 -1
  32. package/dist/public/assets/conversation-history-view-CH3HrZK9.js +0 -1
  33. package/dist/public/assets/dialogs-config-Ofxg1Bn0.js +0 -46
  34. package/dist/public/assets/diff-view-BAPFEZbh.js +0 -3
  35. package/dist/public/assets/explorer-tree-DYE9rGNc.js +0 -1
  36. package/dist/public/assets/explorer-view-DgNDIV-D.js +0 -1
  37. package/dist/public/assets/history-view-BIN8vsvx.js +0 -1
  38. package/dist/public/assets/index-BOioHxIi.js +0 -30
  39. package/dist/public/assets/index-BVLzVjoR.css +0 -1
  40. package/dist/public/assets/onboarding-QnvOtCAY.js +0 -1
  41. package/dist/public/assets/onboarding-dialog-80pqyrMv.js +0 -1
  42. package/dist/public/assets/provider-details-view-3tvO3r6m.js +0 -1
  43. package/dist/public/assets/react-vendor-Bi5X_3XM.js +0 -17
  44. package/dist/public/assets/todos-view-D2xLDhsC.js +0 -1
package/dist/index.js CHANGED
@@ -663,7 +663,7 @@ function isValidPackageManager(value) {
663
663
 
664
664
  // src/server.ts
665
665
  import fs3 from "fs";
666
- import { Hono as Hono20 } from "hono";
666
+ import { Hono as Hono21 } from "hono";
667
667
  import { cors } from "hono/cors";
668
668
  import open3 from "open";
669
669
  import path5 from "path";
@@ -671,6 +671,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
671
671
 
672
672
  // src/agent/agent.executor.ts
673
673
  import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
674
+ import { streamSimple } from "@mariozechner/pi-ai";
674
675
  import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
675
676
 
676
677
  // src/tools/bash.ts
@@ -3285,6 +3286,8 @@ async function createMCPTools(projectPath) {
3285
3286
 
3286
3287
  // src/tools/generate-image.ts
3287
3288
  import { Type as Type13 } from "@sinclair/typebox";
3289
+ import { mkdir as fsMkdir2, writeFile as fsWriteFile3 } from "fs/promises";
3290
+ import { dirname as dirname2 } from "path";
3288
3291
 
3289
3292
  // src/features/models/models-catalog.ts
3290
3293
  init_database();
@@ -3610,6 +3613,29 @@ async function generateWithOpenRouter(apiKey, model, prompt, options = {}) {
3610
3613
  };
3611
3614
  }
3612
3615
 
3616
+ // src/features/image/image-search.client.ts
3617
+ var IMAGE_SEARCH_API = "https://api.webnative.dev/images";
3618
+ async function searchImage(query, size, signal) {
3619
+ const url = new URL(IMAGE_SEARCH_API);
3620
+ url.searchParams.set("query", query);
3621
+ if (size) {
3622
+ url.searchParams.set("size", size);
3623
+ }
3624
+ const response = await fetch(url.toString(), { signal });
3625
+ if (!response.ok) {
3626
+ throw new Error(`Image search failed (${response.status}): ${await response.text()}`);
3627
+ }
3628
+ const contentType = response.headers.get("content-type") ?? "image/jpeg";
3629
+ const buffer = await response.arrayBuffer();
3630
+ const bytes = new Uint8Array(buffer);
3631
+ let binary = "";
3632
+ for (let i = 0; i < bytes.length; i++) {
3633
+ binary += String.fromCharCode(bytes[i]);
3634
+ }
3635
+ const imageData = btoa(binary);
3636
+ return { imageData, contentType };
3637
+ }
3638
+
3613
3639
  // src/tools/generate-image.ts
3614
3640
  var generateImageSchema = Type13.Object({
3615
3641
  prompt: Type13.String({ description: "Text description of the image to generate" }),
@@ -3617,16 +3643,19 @@ var generateImageSchema = Type13.Object({
3617
3643
  Type13.String({
3618
3644
  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
3645
  })
3620
- )
3646
+ ),
3647
+ save_to_file: Type13.String({
3648
+ 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."
3649
+ })
3621
3650
  });
3622
3651
  function createGenerateImageTool(options) {
3623
- const { metadataManager } = options;
3652
+ const { metadataManager, cwd } = options;
3624
3653
  return {
3625
3654
  name: "generate_image",
3626
3655
  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.",
3656
+ 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
3657
  parameters: generateImageSchema,
3629
- execute: async (_toolCallId, { prompt, model: requestedModel }, signal) => {
3658
+ execute: async (_toolCallId, { prompt, model: requestedModel, save_to_file }, signal) => {
3630
3659
  return withAbortSignal(signal, async (isAborted) => {
3631
3660
  const modelManager = new ModelManager(metadataManager);
3632
3661
  const enabledByProvider = await modelManager.getAllEnabledImageModels();
@@ -3639,14 +3668,59 @@ function createGenerateImageTool(options) {
3639
3668
  }
3640
3669
  }
3641
3670
  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."
3671
+ console.log(
3672
+ `[generate_image] No image models enabled \u2014 falling back to image search for: "${prompt.substring(0, 100)}"`
3673
+ );
3674
+ let imageData2;
3675
+ let contentType;
3676
+ try {
3677
+ const result = await searchImage(prompt, void 0, signal);
3678
+ imageData2 = result.imageData;
3679
+ contentType = result.contentType;
3680
+ } catch (err) {
3681
+ const message = err instanceof Error ? err.message : String(err);
3682
+ return {
3683
+ content: [
3684
+ {
3685
+ type: "text",
3686
+ text: `No image models are enabled and the image search fallback also failed: ${message}`
3687
+ }
3688
+ ],
3689
+ details: void 0
3690
+ };
3691
+ }
3692
+ if (isAborted()) return { content: [], details: void 0 };
3693
+ let savedPath2;
3694
+ if (cwd) {
3695
+ try {
3696
+ const absolutePath = resolveToCwd(save_to_file, cwd);
3697
+ validatePathWithinCwd(absolutePath, cwd);
3698
+ const binary = atob(imageData2);
3699
+ const bytes = new Uint8Array(binary.length);
3700
+ for (let i = 0; i < binary.length; i++) {
3701
+ bytes[i] = binary.charCodeAt(i);
3647
3702
  }
3648
- ],
3649
- details: void 0
3703
+ await fsMkdir2(dirname2(absolutePath), { recursive: true });
3704
+ await fsWriteFile3(absolutePath, bytes);
3705
+ savedPath2 = save_to_file;
3706
+ } catch (saveError) {
3707
+ const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
3708
+ return {
3709
+ content: [
3710
+ {
3711
+ type: "text",
3712
+ text: `Stock image found but failed to save to "${save_to_file}": ${errMsg}`
3713
+ }
3714
+ ],
3715
+ details: { imageData: imageData2, contentType }
3716
+ };
3717
+ }
3718
+ }
3719
+ const fallbackPayload = { imageData: imageData2, contentType };
3720
+ if (savedPath2) fallbackPayload.savedPath = savedPath2;
3721
+ return {
3722
+ content: [{ type: "text", text: JSON.stringify(fallbackPayload) }],
3723
+ details: { imageData: imageData2, contentType }
3650
3724
  };
3651
3725
  }
3652
3726
  let selectedModel = imageModels[0];
@@ -3766,12 +3840,52 @@ function createGenerateImageTool(options) {
3766
3840
  imageData = imageResult.b64_json;
3767
3841
  }
3768
3842
  if (isAborted()) return { content: [], details: void 0 };
3843
+ let savedPath;
3844
+ if (cwd) {
3845
+ try {
3846
+ const absolutePath = resolveToCwd(save_to_file, cwd);
3847
+ validatePathWithinCwd(absolutePath, cwd);
3848
+ let imageBytes;
3849
+ if (imageData) {
3850
+ const binary = atob(imageData);
3851
+ imageBytes = new Uint8Array(binary.length);
3852
+ for (let i = 0; i < binary.length; i++) {
3853
+ imageBytes[i] = binary.charCodeAt(i);
3854
+ }
3855
+ } else if (imageUrl) {
3856
+ const res = await fetch(imageUrl);
3857
+ if (!res.ok) {
3858
+ throw new Error(`Failed to fetch image from URL: ${res.status}`);
3859
+ }
3860
+ imageBytes = new Uint8Array(await res.arrayBuffer());
3861
+ } else {
3862
+ throw new Error("No image data or URL available to save");
3863
+ }
3864
+ await fsMkdir2(dirname2(absolutePath), { recursive: true });
3865
+ await fsWriteFile3(absolutePath, imageBytes);
3866
+ savedPath = save_to_file;
3867
+ console.log(`[generate_image] Saved image to ${absolutePath}`);
3868
+ } catch (saveError) {
3869
+ console.error("[generate_image] Failed to save image to file:", saveError);
3870
+ const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
3871
+ return {
3872
+ content: [
3873
+ {
3874
+ type: "text",
3875
+ text: `Image was generated but failed to save to "${save_to_file}": ${errMsg}`
3876
+ }
3877
+ ],
3878
+ details: { imageUrl, imageData, model: selectedModel.id }
3879
+ };
3880
+ }
3881
+ }
3769
3882
  const resultPayload = {
3770
3883
  model: `${selectedModel.provider}/${selectedModel.id}`,
3771
3884
  imageUrl,
3772
3885
  imageData
3773
3886
  };
3774
3887
  if (text) resultPayload.text = text;
3888
+ if (savedPath) resultPayload.savedPath = savedPath;
3775
3889
  return {
3776
3890
  content: [
3777
3891
  {
@@ -3786,9 +3900,288 @@ function createGenerateImageTool(options) {
3786
3900
  };
3787
3901
  }
3788
3902
 
3903
+ // src/tools/find-images.ts
3904
+ import { Type as Type14 } from "@sinclair/typebox";
3905
+ import { mkdir as fsMkdir3, writeFile as fsWriteFile4 } from "fs/promises";
3906
+ import { dirname as dirname3 } from "path";
3907
+ var findImagesSchema = Type14.Object({
3908
+ query: Type14.String({ description: "Search term for the image (e.g. 'sunset over mountains')" }),
3909
+ size: Type14.Optional(
3910
+ Type14.Union(
3911
+ [
3912
+ Type14.Literal("original"),
3913
+ Type14.Literal("large2x"),
3914
+ Type14.Literal("large"),
3915
+ Type14.Literal("medium"),
3916
+ Type14.Literal("small"),
3917
+ Type14.Literal("portrait"),
3918
+ Type14.Literal("landscape"),
3919
+ Type14.Literal("tiny")
3920
+ ],
3921
+ {
3922
+ description: "Image size variant: original, large2x, large, medium (default), small, portrait, landscape, tiny"
3923
+ }
3924
+ )
3925
+ ),
3926
+ save_to_file: Type14.Optional(
3927
+ Type14.String({
3928
+ 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."
3929
+ })
3930
+ )
3931
+ });
3932
+ function createFindImagesTool(cwd) {
3933
+ return {
3934
+ name: "find_images",
3935
+ label: "find_images",
3936
+ 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.",
3937
+ parameters: findImagesSchema,
3938
+ execute: async (_toolCallId, { query, size, save_to_file }, signal) => {
3939
+ return withAbortSignal(signal, async (isAborted) => {
3940
+ console.log(
3941
+ `[find_images] Searching for image \u2014 query: "${query}"${size ? `, size: ${size}` : ""}`
3942
+ );
3943
+ let imageData;
3944
+ let contentType;
3945
+ try {
3946
+ const result = await searchImage(query, size, signal);
3947
+ imageData = result.imageData;
3948
+ contentType = result.contentType;
3949
+ } catch (err) {
3950
+ const message = err instanceof Error ? err.message : String(err);
3951
+ return {
3952
+ content: [{ type: "text", text: `Image search failed: ${message}` }],
3953
+ details: void 0
3954
+ };
3955
+ }
3956
+ if (isAborted()) return { content: [], details: void 0 };
3957
+ let savedPath;
3958
+ if (cwd && save_to_file) {
3959
+ try {
3960
+ const absolutePath = resolveToCwd(save_to_file, cwd);
3961
+ validatePathWithinCwd(absolutePath, cwd);
3962
+ const binary = atob(imageData);
3963
+ const bytes = new Uint8Array(binary.length);
3964
+ for (let i = 0; i < binary.length; i++) {
3965
+ bytes[i] = binary.charCodeAt(i);
3966
+ }
3967
+ await fsMkdir3(dirname3(absolutePath), { recursive: true });
3968
+ await fsWriteFile4(absolutePath, bytes);
3969
+ savedPath = save_to_file;
3970
+ console.log(`[find_images] Saved image to ${absolutePath}`);
3971
+ } catch (saveError) {
3972
+ const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
3973
+ return {
3974
+ content: [
3975
+ {
3976
+ type: "text",
3977
+ text: `Image was found but failed to save to "${save_to_file}": ${errMsg}`
3978
+ }
3979
+ ],
3980
+ details: { imageData, contentType }
3981
+ };
3982
+ }
3983
+ }
3984
+ const resultPayload = { imageData, contentType };
3985
+ if (savedPath) resultPayload.savedPath = savedPath;
3986
+ return {
3987
+ content: [{ type: "text", text: JSON.stringify(resultPayload) }],
3988
+ details: { imageData, contentType }
3989
+ };
3990
+ });
3991
+ }
3992
+ };
3993
+ }
3994
+
3995
+ // src/tools/execute-browser-js.ts
3996
+ import { Type as Type15 } from "@sinclair/typebox";
3997
+ var runWebWorkerSchema = Type15.Object({
3998
+ intention: Type15.String({
3999
+ description: "A short description of what this code execution is trying to accomplish."
4000
+ }),
4001
+ code: Type15.String({
4002
+ 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;`."
4003
+ })
4004
+ });
4005
+ var pendingTasks = /* @__PURE__ */ new Map();
4006
+ function submitWebWorkerResult(toolCallId, result) {
4007
+ const pending = pendingTasks.get(toolCallId);
4008
+ if (!pending) return false;
4009
+ pending.resolve(result);
4010
+ pendingTasks.delete(toolCallId);
4011
+ return true;
4012
+ }
4013
+ function cancelPendingWebWorker(toolCallId) {
4014
+ const pending = pendingTasks.get(toolCallId);
4015
+ if (!pending) return false;
4016
+ pending.reject(new Error("Web worker execution cancelled"));
4017
+ pendingTasks.delete(toolCallId);
4018
+ return true;
4019
+ }
4020
+ function buildDescription(tools) {
4021
+ 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.";
4022
+ if (tools && tools.length > 0) {
4023
+ const signatures = tools.map((t) => `${t.name}: ${JSON.stringify(t.parameters?.properties ?? {})}`).join("\n");
4024
+ desc += `
4025
+
4026
+ Available tools:
4027
+ ${signatures}`;
4028
+ }
4029
+ return desc;
4030
+ }
4031
+ function createRunWebWorkerTool(options) {
4032
+ return {
4033
+ name: "run_js",
4034
+ label: "run_js",
4035
+ description: buildDescription(options?.tools),
4036
+ parameters: runWebWorkerSchema,
4037
+ execute: async (toolCallId, { intention: _intention, code }, signal) => {
4038
+ console.log(`[ai] run_js: executing script of length ${code.length}`);
4039
+ if (signal?.aborted) {
4040
+ throw new Error("Operation aborted");
4041
+ }
4042
+ const result = await new Promise((resolve6, reject) => {
4043
+ pendingTasks.set(toolCallId, {
4044
+ resolve: resolve6,
4045
+ reject,
4046
+ code
4047
+ });
4048
+ const onAbort = () => {
4049
+ cancelPendingWebWorker(toolCallId);
4050
+ };
4051
+ signal?.addEventListener("abort", onAbort, { once: true });
4052
+ });
4053
+ return {
4054
+ content: [
4055
+ {
4056
+ type: "text",
4057
+ text: typeof result === "string" ? result : JSON.stringify(result, null, 2) || "undefined"
4058
+ }
4059
+ ],
4060
+ details: void 0
4061
+ };
4062
+ }
4063
+ };
4064
+ }
4065
+ var runWebWorkerTool = createRunWebWorkerTool();
4066
+
4067
+ // src/tools/tool-search.ts
4068
+ import { Type as Type16 } from "@sinclair/typebox";
4069
+ var toolSearchSchema = Type16.Object({
4070
+ query: Type16.String({
4071
+ description: 'Search query to find tools. Use "select:name1,name2" to fetch exact tools by name, or keywords to search by description.'
4072
+ }),
4073
+ max_results: Type16.Optional(
4074
+ Type16.Number({
4075
+ description: "Maximum number of results to return (default: 5)"
4076
+ })
4077
+ )
4078
+ });
4079
+ function formatToolSchema(tool) {
4080
+ const schema = JSON.stringify(tool.parameters, null, 2);
4081
+ return `<function>{"name": "${tool.name}", "description": ${JSON.stringify(tool.description)}, "parameters": ${schema}}</function>`;
4082
+ }
4083
+ function parseQuery(query) {
4084
+ const trimmed = query.trim();
4085
+ if (trimmed.startsWith("select:")) {
4086
+ const names = trimmed.slice(7).split(",").map((n) => n.trim().toLowerCase()).filter(Boolean);
4087
+ return { mode: "select", names };
4088
+ }
4089
+ if (trimmed.startsWith("+")) {
4090
+ const parts = trimmed.slice(1).trim().split(/\s+/);
4091
+ const requiredName = parts[0]?.toLowerCase();
4092
+ const terms2 = parts.slice(1).map((t) => t.toLowerCase());
4093
+ return { mode: "search", terms: terms2, requiredName };
4094
+ }
4095
+ const terms = trimmed.toLowerCase().split(/\s+/).filter(Boolean);
4096
+ return { mode: "search", terms };
4097
+ }
4098
+ function scoreTool(tool, terms) {
4099
+ if (terms.length === 0) return 1;
4100
+ const name = tool.name.toLowerCase();
4101
+ const label = ("label" in tool ? tool.label : "").toLowerCase();
4102
+ const desc = tool.description.toLowerCase();
4103
+ let score = 0;
4104
+ for (const term of terms) {
4105
+ if (name === term) score += 10;
4106
+ else if (name.includes(term)) score += 5;
4107
+ if (label.includes(term)) score += 3;
4108
+ if (desc.includes(term)) score += 1;
4109
+ }
4110
+ return score;
4111
+ }
4112
+ function createToolSearchTool(options) {
4113
+ const { liveTools, deferredTools } = options;
4114
+ const activated = /* @__PURE__ */ new Set();
4115
+ return {
4116
+ name: "tool_search",
4117
+ label: "tool_search",
4118
+ 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.',
4119
+ parameters: toolSearchSchema,
4120
+ execute: async (_toolCallId, input) => {
4121
+ const maxResults = input.max_results ?? 5;
4122
+ const parsed = parseQuery(input.query);
4123
+ let matched = [];
4124
+ if (parsed.mode === "select") {
4125
+ for (const name of parsed.names) {
4126
+ const tool = deferredTools.get(name);
4127
+ if (tool) matched.push(tool);
4128
+ }
4129
+ } else {
4130
+ const candidates = [];
4131
+ for (const tool of deferredTools.values()) {
4132
+ if (parsed.requiredName) {
4133
+ const name = tool.name.toLowerCase();
4134
+ if (!name.includes(parsed.requiredName)) continue;
4135
+ }
4136
+ const score = scoreTool(tool, parsed.terms);
4137
+ if (score > 0) {
4138
+ candidates.push({ tool, score });
4139
+ }
4140
+ }
4141
+ candidates.sort((a, b) => b.score - a.score);
4142
+ matched = candidates.slice(0, maxResults).map((c) => c.tool);
4143
+ }
4144
+ if (matched.length === 0) {
4145
+ const available = Array.from(deferredTools.keys()).join(", ");
4146
+ return {
4147
+ content: [
4148
+ {
4149
+ type: "text",
4150
+ text: `No tools matched query "${input.query}". Available deferred tools: ${available || "none"}`
4151
+ }
4152
+ ],
4153
+ details: void 0
4154
+ };
4155
+ }
4156
+ for (const tool of matched) {
4157
+ if (!activated.has(tool.name)) {
4158
+ activated.add(tool.name);
4159
+ liveTools.push(tool);
4160
+ }
4161
+ }
4162
+ const schemaBlocks = matched.map(formatToolSchema).join("\n");
4163
+ const text = `Found ${matched.length} tool(s). They are now available for use.
4164
+
4165
+ <functions>
4166
+ ${schemaBlocks}
4167
+ </functions>`;
4168
+ return {
4169
+ content: [{ type: "text", text }],
4170
+ details: void 0
4171
+ };
4172
+ }
4173
+ };
4174
+ }
4175
+ function formatDeferredToolsList(deferredTools) {
4176
+ if (deferredTools.size === 0) return "";
4177
+ const names = Array.from(deferredTools.keys()).join(", ");
4178
+ 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;
4179
+ }
4180
+
3789
4181
  // src/tools/index.ts
3790
4182
  async function createCodingTools(cwd, options) {
3791
- const baseTools = [
4183
+ const deferOption = options?.deferTools ?? true;
4184
+ const coreTools = [
3792
4185
  createReadTool(cwd, options?.read),
3793
4186
  createBashTool(cwd, options?.bash),
3794
4187
  createEditTool(cwd),
@@ -3796,30 +4189,81 @@ async function createCodingTools(cwd, options) {
3796
4189
  createAskUserTool(),
3797
4190
  createTodoTool(options?.threadId ?? ""),
3798
4191
  createFindTool(cwd),
3799
- createAstGrepTool(cwd)
4192
+ createAstGrepTool(cwd),
4193
+ createRunWebWorkerTool()
3800
4194
  ];
4195
+ const optionalTools = [];
3801
4196
  if (options?.metadataManager) {
3802
- baseTools.push(createGenerateImageTool({ metadataManager: options.metadataManager }));
4197
+ optionalTools.push(createGenerateImageTool({ metadataManager: options.metadataManager, cwd }));
3803
4198
  }
3804
- const toolsWithSkills = [...baseTools];
4199
+ optionalTools.push(createFindImagesTool(cwd));
3805
4200
  if (options?.skills && options.skills.length > 0) {
3806
- const skillTools = [
4201
+ optionalTools.push(
3807
4202
  createSkillScriptTool(options.skills, cwd),
3808
4203
  createSkillReferenceTool(options.skills)
3809
- ];
3810
- toolsWithSkills.push(...skillTools);
4204
+ );
3811
4205
  }
4206
+ let mcpTools = [];
3812
4207
  try {
3813
- const mcpTools = await createMCPTools(cwd);
3814
- toolsWithSkills.push(...mcpTools);
4208
+ mcpTools = await createMCPTools(cwd);
3815
4209
  } catch (error) {
3816
4210
  console.warn(`[Tools] Failed to load MCP tools for ${cwd}:`, error);
3817
4211
  }
3818
- return toolsWithSkills;
4212
+ const deferredTools = /* @__PURE__ */ new Map();
4213
+ if (deferOption === false) {
4214
+ coreTools.push(...optionalTools, ...mcpTools);
4215
+ } else if (deferOption === true) {
4216
+ for (const tool of [...mcpTools, ...optionalTools]) {
4217
+ deferredTools.set(tool.name, tool);
4218
+ }
4219
+ } else {
4220
+ const deferSet = new Set(deferOption.map((n) => n.toLowerCase()));
4221
+ for (const tool of [...optionalTools, ...mcpTools]) {
4222
+ if (deferSet.has(tool.name.toLowerCase())) {
4223
+ deferredTools.set(tool.name, tool);
4224
+ } else {
4225
+ coreTools.push(tool);
4226
+ }
4227
+ }
4228
+ }
4229
+ const tools = [...coreTools];
4230
+ if (deferredTools.size > 0) {
4231
+ const toolSearch = createToolSearchTool({
4232
+ liveTools: tools,
4233
+ deferredTools
4234
+ });
4235
+ tools.push(toolSearch);
4236
+ console.log(
4237
+ `[Tools] ${deferredTools.size} tool(s) deferred behind tool_search: ${Array.from(deferredTools.keys()).join(", ")}`
4238
+ );
4239
+ }
4240
+ return { tools, deferredToolNames: deferredTools };
4241
+ }
4242
+ function createAllTools(cwd, options) {
4243
+ const tools = {
4244
+ read: createReadTool(cwd, options?.read),
4245
+ bash: createBashTool(cwd, options?.bash),
4246
+ edit: createEditTool(cwd),
4247
+ write: createWriteTool(cwd),
4248
+ grep: createGrepTool(cwd),
4249
+ ast_grep: createAstGrepTool(cwd),
4250
+ find: createFindTool(cwd),
4251
+ ask_user: createAskUserTool(),
4252
+ ls: createLsTool(cwd),
4253
+ run_js: createRunWebWorkerTool()
4254
+ };
4255
+ if (options?.metadataManager) {
4256
+ tools.generate_image = createGenerateImageTool({
4257
+ metadataManager: options.metadataManager,
4258
+ cwd
4259
+ });
4260
+ }
4261
+ tools.find_images = createFindImagesTool(cwd);
4262
+ return tools;
3819
4263
  }
3820
4264
 
3821
4265
  // src/tools/agent-tool.ts
3822
- import { Type as Type14 } from "@sinclair/typebox";
4266
+ import { Type as Type17 } from "@sinclair/typebox";
3823
4267
 
3824
4268
  // src/agent/agent.subagent-executor.ts
3825
4269
  import { Agent } from "@mariozechner/pi-agent-core";
@@ -4090,6 +4534,7 @@ async function executeSubagent(options) {
4090
4534
  model: resolvedModel,
4091
4535
  tools
4092
4536
  },
4537
+ streamFn: cachedStreamFn,
4093
4538
  getApiKey: async () => apiKey
4094
4539
  });
4095
4540
  const eventQueue = new EventQueue();
@@ -4166,15 +4611,15 @@ async function executeSubagent(options) {
4166
4611
  }
4167
4612
 
4168
4613
  // 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({
4614
+ var agentToolSchema = Type17.Object({
4615
+ prompt: Type17.String({ description: "The task or question for the subagent to work on" }),
4616
+ agentName: Type17.Optional(
4617
+ Type17.String({
4173
4618
  description: "Name of a defined agent (from AGENT.md) to use. If omitted, a general-purpose subagent is created."
4174
4619
  })
4175
4620
  ),
4176
- description: Type14.Optional(
4177
- Type14.String({
4621
+ description: Type17.Optional(
4622
+ Type17.String({
4178
4623
  description: "A short (3-5 word) label describing what the subagent will do"
4179
4624
  })
4180
4625
  )
@@ -4710,7 +5155,7 @@ var devServerCache = new DevServerCache();
4710
5155
  // src/agent/agent.prompt-loader.ts
4711
5156
  function buildDefaultPrompt(tools) {
4712
5157
  const toolList = tools.map((t) => t.name).join(", ");
4713
- return `You are a helpful coding assistant. You have access to ${toolList} tools. Use them to explore and modify the codebase as needed. Break tasks down into smaller, manageable steps using the todo tool. Skills are created and stored in .agents/skills/ . `;
5158
+ return `You are a helpful coding assistant. You have access to ${toolList} tools. Use them to explore and modify the codebase as needed. Skills are created and stored in .agents/skills/ . `;
4714
5159
  }
4715
5160
  var PLAN_MODE_INSTRUCTIONS = `
4716
5161
 
@@ -4880,7 +5325,7 @@ function formatAgentsSection(agents) {
4880
5325
  );
4881
5326
  return section;
4882
5327
  }
4883
- async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents) {
5328
+ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools) {
4884
5329
  let prompt = buildDefaultPrompt(tools);
4885
5330
  const agentsContent = loadDeveloperContext(threadPath);
4886
5331
  if (agentsContent) {
@@ -4909,15 +5354,50 @@ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents
4909
5354
  if (agentsSection) {
4910
5355
  prompt += agentsSection;
4911
5356
  }
5357
+ if (deferredTools && deferredTools.size > 0) {
5358
+ prompt += formatDeferredToolsList(deferredTools);
5359
+ }
4912
5360
  return prompt;
4913
5361
  }
4914
5362
 
4915
5363
  // src/agent/agent.executor.ts
5364
+ var cachedStreamFn = (model, context, options) => {
5365
+ return streamSimple(model, context, { ...options, cacheRetention: "long" });
5366
+ };
5367
+ function buildHistoryMessages(history) {
5368
+ const messages = [];
5369
+ const now = Date.now();
5370
+ for (const exchange of history) {
5371
+ messages.push({
5372
+ role: "user",
5373
+ content: exchange.userMessage,
5374
+ timestamp: now
5375
+ });
5376
+ messages.push({
5377
+ role: "assistant",
5378
+ content: [{ type: "text", text: exchange.assistantResponse }],
5379
+ api: "",
5380
+ provider: "",
5381
+ model: "",
5382
+ usage: {
5383
+ input: 0,
5384
+ output: 0,
5385
+ cacheRead: 0,
5386
+ cacheWrite: 0,
5387
+ totalTokens: 0,
5388
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
5389
+ },
5390
+ stopReason: "stop",
5391
+ timestamp: now
5392
+ });
5393
+ }
5394
+ return messages;
5395
+ }
4916
5396
  var PiExecutorImpl = class {
4917
5397
  constructor(metadataManager) {
4918
5398
  this.metadataManager = metadataManager;
4919
5399
  }
4920
- async *execute(userPrompt, context, signal) {
5400
+ async *execute(userPrompt, context, signal, conversationHistory) {
4921
5401
  try {
4922
5402
  const providerName = context.provider;
4923
5403
  if (!providerName) {
@@ -4970,7 +5450,7 @@ var PiExecutorImpl = class {
4970
5450
  api: resolvedModel.api,
4971
5451
  provider: resolvedModel.provider
4972
5452
  });
4973
- const tools = await createCodingTools(cwd, {
5453
+ const { tools, deferredToolNames } = await createCodingTools(cwd, {
4974
5454
  skills: context.skills,
4975
5455
  threadId: context.threadId,
4976
5456
  metadataManager: this.metadataManager
@@ -4980,7 +5460,8 @@ var PiExecutorImpl = class {
4980
5460
  tools,
4981
5461
  context.skills,
4982
5462
  context.planMode,
4983
- context.agents
5463
+ context.agents,
5464
+ deferredToolNames
4984
5465
  );
4985
5466
  console.log(
4986
5467
  "[ai] System prompt loaded from thread path",
@@ -5002,12 +5483,15 @@ var PiExecutorImpl = class {
5002
5483
  });
5003
5484
  tools.push(agentToolInstance);
5004
5485
  }
5486
+ const historyMessages = buildHistoryMessages(conversationHistory ?? []);
5005
5487
  const agent = new Agent2({
5006
5488
  initialState: {
5007
5489
  systemPrompt,
5008
5490
  model: resolvedModel,
5009
- tools
5491
+ tools,
5492
+ messages: historyMessages
5010
5493
  },
5494
+ streamFn: cachedStreamFn,
5011
5495
  getApiKey: async () => apiKey
5012
5496
  });
5013
5497
  let done = false;
@@ -5894,18 +6378,6 @@ function streamAsyncGenerator(c, generator, options) {
5894
6378
  }
5895
6379
 
5896
6380
  // 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
6381
  var extractTextBlocksFromContent = (content) => {
5910
6382
  const trimmed = content.trim();
5911
6383
  if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) {
@@ -6505,11 +6977,10 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
6505
6977
  const priorMessages = (history?.messages ?? []).filter(
6506
6978
  (m) => m.id !== messageId && m.response != null
6507
6979
  );
6508
- const contextBlock = formatConversationContext(priorMessages);
6509
- const promptWithContext = contextBlock.length > 0 ? `${contextBlock}
6510
-
6511
- Current message:
6512
- User: ${content}` : content;
6980
+ const conversationHistory = priorMessages.slice(-10).map((m) => ({
6981
+ userMessage: m.request.message,
6982
+ assistantResponse: m.response.content
6983
+ }));
6513
6984
  const abortController = new AbortController();
6514
6985
  const requestSignal = c.req.raw.signal;
6515
6986
  if (requestSignal.aborted) {
@@ -6530,9 +7001,10 @@ User: ${content}` : content;
6530
7001
  let fullContent = "";
6531
7002
  try {
6532
7003
  for await (const event of agentExecutor.execute(
6533
- promptWithContext,
7004
+ content,
6534
7005
  context,
6535
- abortController.signal
7006
+ abortController.signal,
7007
+ conversationHistory
6536
7008
  )) {
6537
7009
  capturedEvents.push(event);
6538
7010
  if (event.type === "message" && event.content) {
@@ -14455,6 +14927,8 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
14455
14927
  if (!message.response) {
14456
14928
  return [userMessage];
14457
14929
  }
14930
+ const tokenCount = message.inputTokens || message.outputTokens ? (message.inputTokens ?? 0) + (message.outputTokens ?? 0) : void 0;
14931
+ const processingTimeMs = message.timestamp && message.response.completedAt ? new Date(message.response.completedAt).getTime() - new Date(message.timestamp).getTime() : void 0;
14458
14932
  const agentMessage = {
14459
14933
  messageId: `agent-${message.id}`,
14460
14934
  sender: "agent",
@@ -14465,6 +14939,12 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
14465
14939
  contentType: "markdown",
14466
14940
  timestamp: message.response.completedAt,
14467
14941
  model: message.request.model,
14942
+ ...(tokenCount || processingTimeMs) && {
14943
+ metadata: {
14944
+ ...tokenCount && { tokenCount },
14945
+ ...processingTimeMs && processingTimeMs > 0 && { processingTimeMs }
14946
+ }
14947
+ },
14468
14948
  ...message.response.imageUrl && { generatedImageUrl: message.response.imageUrl }
14469
14949
  };
14470
14950
  return [userMessage, agentMessage];
@@ -14973,7 +15453,7 @@ import { existsSync as existsSync16 } from "fs";
14973
15453
 
14974
15454
  // src/features/git/git-download-folder.ts
14975
15455
  import { mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
14976
- import { join as join24, dirname as dirname2 } from "path";
15456
+ import { join as join24, dirname as dirname4 } from "path";
14977
15457
  async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
14978
15458
  const { token, ref } = options;
14979
15459
  const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
@@ -15010,7 +15490,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
15010
15490
  files.map(async (file) => {
15011
15491
  const relativePath = file.path.slice(prefix.length);
15012
15492
  const localPath = join24(destPath, relativePath);
15013
- await mkdir7(dirname2(localPath), { recursive: true });
15493
+ await mkdir7(dirname4(localPath), { recursive: true });
15014
15494
  const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${file.path}`;
15015
15495
  const fileRes = await fetch(rawUrl, { headers });
15016
15496
  if (!fileRes.ok) {
@@ -15646,7 +16126,7 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
15646
16126
  // src/features/git/git.manager.ts
15647
16127
  init_utils();
15648
16128
  import { mkdir as mkdir9 } from "fs/promises";
15649
- import { dirname as dirname3 } from "path";
16129
+ import { dirname as dirname5 } from "path";
15650
16130
  var GitManagerImpl = class {
15651
16131
  /**
15652
16132
  * Regular expressions for validating different git URL formats
@@ -15716,7 +16196,7 @@ var GitManagerImpl = class {
15716
16196
  return;
15717
16197
  }
15718
16198
  try {
15719
- const parentDir = dirname3(targetPath);
16199
+ const parentDir = dirname5(targetPath);
15720
16200
  await mkdir9(parentDir, { recursive: true });
15721
16201
  const gitProcess = spawnProcess("git", ["clone", "--progress", gitUrl, targetPath]);
15722
16202
  const eventQueue = [];
@@ -18511,7 +18991,7 @@ function createUpdateRoutes(updater) {
18511
18991
  // src/features/mcp/mcp.routes.ts
18512
18992
  import { Hono as Hono13 } from "hono";
18513
18993
  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";
18994
+ import { join as join28, dirname as dirname6 } from "path";
18515
18995
 
18516
18996
  // src/features/mcp/mcp.popular.json
18517
18997
  var mcp_popular_default = [
@@ -18637,7 +19117,7 @@ async function readMCPConfig(projectPath) {
18637
19117
  async function writeMCPConfig(projectPath, config) {
18638
19118
  const existing = await readMCPConfig(projectPath);
18639
19119
  const targetPath = existing?.filePath ?? join28(projectPath, ".agents/mcp.json");
18640
- await mkdir10(dirname4(targetPath), { recursive: true });
19120
+ await mkdir10(dirname6(targetPath), { recursive: true });
18641
19121
  await writeFile7(targetPath, JSON.stringify(config, null, 2), "utf-8");
18642
19122
  }
18643
19123
  function createMCPRoutes() {
@@ -19025,9 +19505,35 @@ function createProjectTodosRoutes(metadataManager) {
19025
19505
 
19026
19506
  // src/features/account/account.routes.ts
19027
19507
  import { Hono as Hono17 } from "hono";
19508
+
19509
+ // src/features/account/account-settings.route.ts
19510
+ init_database();
19511
+ var KEY_USE_TOOLS_THROUGH_CODE = "UseToolsThroughCode";
19512
+ async function getAccountSettings(c) {
19513
+ const db = await getDatabase();
19514
+ const raw = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
19515
+ return c.json({
19516
+ useToolsThroughCode: raw !== false
19517
+ });
19518
+ }
19519
+ async function updateAccountSettings(c) {
19520
+ const body = await c.req.json();
19521
+ const db = await getDatabase();
19522
+ if (typeof body.useToolsThroughCode === "boolean") {
19523
+ await setState(db, KEY_USE_TOOLS_THROUGH_CODE, body.useToolsThroughCode);
19524
+ }
19525
+ const raw = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
19526
+ return c.json({
19527
+ useToolsThroughCode: raw !== false
19528
+ });
19529
+ }
19530
+
19531
+ // src/features/account/account.routes.ts
19028
19532
  function createAccountRoutes() {
19029
19533
  const router = new Hono17();
19030
19534
  router.get("/info", (c) => getAccountInfo(c));
19535
+ router.get("/settings", (c) => getAccountSettings(c));
19536
+ router.put("/settings", (c) => updateAccountSettings(c));
19031
19537
  return router;
19032
19538
  }
19033
19539
 
@@ -19311,13 +19817,89 @@ function createImageRoutes(metadataManager, conversationManager, threadManager)
19311
19817
  return router;
19312
19818
  }
19313
19819
 
19820
+ // src/features/browser-js/browser-js.routes.ts
19821
+ import { Hono as Hono20 } from "hono";
19822
+ function createBrowserJsRoutes(threadManager) {
19823
+ const router = new Hono20();
19824
+ router.post("/respond", async (c) => {
19825
+ try {
19826
+ const body = await c.req.json();
19827
+ const { toolCallId, result } = body;
19828
+ if (!toolCallId || typeof toolCallId !== "string") {
19829
+ return errorResponse(
19830
+ c,
19831
+ ErrorCodes.INVALID_REQUEST,
19832
+ "toolCallId is required and must be a string",
19833
+ 400
19834
+ );
19835
+ }
19836
+ const resolved = submitWebWorkerResult(toolCallId, result);
19837
+ if (!resolved) {
19838
+ return errorResponse(
19839
+ c,
19840
+ ErrorCodes.INVALID_REQUEST,
19841
+ "No pending web worker execution found for this toolCallId",
19842
+ 404
19843
+ );
19844
+ }
19845
+ return c.json({ success: true });
19846
+ } catch (error) {
19847
+ return errorResponse(
19848
+ c,
19849
+ ErrorCodes.REQUEST_PARSE_ERROR,
19850
+ "Failed to parse request body",
19851
+ 400,
19852
+ error instanceof Error ? error.message : String(error)
19853
+ );
19854
+ }
19855
+ });
19856
+ router.post("/execute-tool", async (c) => {
19857
+ try {
19858
+ const body = await c.req.json();
19859
+ const { threadId, toolName, params } = body;
19860
+ if (!threadId || typeof threadId !== "string") {
19861
+ return errorResponse(
19862
+ c,
19863
+ ErrorCodes.INVALID_REQUEST,
19864
+ "threadId is required and must be a string",
19865
+ 400
19866
+ );
19867
+ }
19868
+ if (!toolName || typeof toolName !== "string") {
19869
+ return errorResponse(
19870
+ c,
19871
+ ErrorCodes.INVALID_REQUEST,
19872
+ "toolName is required and must be a string",
19873
+ 400
19874
+ );
19875
+ }
19876
+ const thread = await threadManager.getThread(threadId);
19877
+ if (!thread) {
19878
+ return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, "Thread not found", 404);
19879
+ }
19880
+ const tools = createAllTools(thread.path);
19881
+ const tool = tools[toolName];
19882
+ if (!tool) {
19883
+ return errorResponse(c, ErrorCodes.INVALID_REQUEST, `Tool ${toolName} not found`, 404);
19884
+ }
19885
+ const executionId = "web-worker-" + Date.now().toString() + "-" + Math.random().toString(36).substr(2, 9);
19886
+ const result = await tool.execute(executionId, params);
19887
+ return c.json(result);
19888
+ } catch (error) {
19889
+ const detail = error instanceof Error ? error.message : String(error);
19890
+ return errorResponse(c, ErrorCodes.REQUEST_PARSE_ERROR, detail, 500, detail);
19891
+ }
19892
+ });
19893
+ return router;
19894
+ }
19895
+
19314
19896
  // src/server.ts
19315
19897
  var __filename = fileURLToPath2(import.meta.url);
19316
19898
  var __dirname = path5.dirname(__filename);
19317
19899
  async function startTarskServer(options) {
19318
19900
  const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
19319
19901
  const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
19320
- const app = new Hono20();
19902
+ const app = new Hono21();
19321
19903
  app.use("/*", cors());
19322
19904
  app.use("/*", async (c, next) => {
19323
19905
  if (c.req.path.startsWith("/api/")) {
@@ -19388,6 +19970,7 @@ async function startTarskServer(options) {
19388
19970
  app.route("/api/image", createImageRoutes(metadataManager, conversationManager, threadManager));
19389
19971
  app.route("/api/scaffold", createScaffoldRoutes(projectManager));
19390
19972
  app.route("/api/ask-user", createAskUserRoutes());
19973
+ app.route("/api/browser-js", createBrowserJsRoutes(threadManager));
19391
19974
  app.route("/api/update", createUpdateRoutes(options.updater));
19392
19975
  createSlashCommandRoutes(app, threadManager);
19393
19976
  createRuleRoutes(app, projectManager);