tarsk 0.5.40 → 0.5.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1898 -485
- package/dist/public/assets/{account-view-BT90zNSx.js → account-view-BAm4Az95.js} +1 -1
- package/dist/public/assets/api-C44iPrsZ.js +1 -0
- package/dist/public/assets/browser-tab-DV6mFCbu.js +1 -0
- package/dist/public/assets/commit-dialog-DYdmiA4p.js +1 -0
- package/dist/public/assets/context-menu-BVP2PUoS.js +1 -0
- package/dist/public/assets/create-repo-dialog-DrGJUrGO.js +1 -0
- package/dist/public/assets/{dialogs-config-DZKtVbNK.js → dialogs-config-BPzX42HH.js} +14 -14
- package/dist/public/assets/diff-view-BnNtDcmG.js +3 -0
- package/dist/public/assets/explorer-tab-view-OGWG1dH9.js +2 -0
- package/dist/public/assets/explorer-tree--Ho2K8-i.js +1 -0
- package/dist/public/assets/explorer-view-Dw0Kyxb8.js +1 -0
- package/dist/public/assets/git-history-dialog-BpOpNbze.js +1 -0
- package/dist/public/assets/git-ops-button-DKOfNm4s.js +2 -0
- package/dist/public/assets/history-view-DwSqsTGI.js +9 -0
- package/dist/public/assets/index-BdLH7zyV.css +1 -0
- package/dist/public/assets/index-BqaiWs7x.js +88 -0
- package/dist/public/assets/mcp-server-card-BbAUiI-X.js +1 -0
- package/dist/public/assets/merged-pr-dialog-Cvo93bRH.js +1 -0
- package/dist/public/assets/onboarding-BmBbv0At.js +1 -0
- package/dist/public/assets/project-settings-view-D-8Ua1Tu.js +1 -0
- package/dist/public/assets/providers-list-view-Gm13Fagd.js +1 -0
- package/dist/public/assets/pull-request-dialog-BkzdP5F2.js +1 -0
- package/dist/public/assets/pull-with-changes-dialog-DQU7jHYL.js +1 -0
- package/dist/public/assets/push-before-pr-dialog-DII3gUVw.js +1 -0
- package/dist/public/assets/radio-group-DkDRaQdF.js +1 -0
- package/dist/public/assets/react-vendor-BmFalJ0W.js +16 -0
- package/dist/public/assets/settings-general-view-Br89Vrbv.js +1 -0
- package/dist/public/assets/{settings-instructions-view-CV7lRPuw.js → settings-instructions-view-ByxlAb9U.js} +1 -1
- package/dist/public/assets/settings-list-DSrSoTv7.js +1 -0
- package/dist/public/assets/settings-mcp-servers-view-U_la7C8z.js +5 -0
- package/dist/public/assets/{settings-models-skeleton-l966LEN8.js → settings-models-skeleton-C5XrzM39.js} +1 -1
- package/dist/public/assets/settings-models-view-obQa4doG.js +1 -0
- package/dist/public/assets/settings-rules-view-Cd9WHEAE.js +8 -0
- package/dist/public/assets/settings-skills-view-B81njz98.js +2 -0
- package/dist/public/assets/settings-slash-commands-view-Ct8WC1Z0.js +1 -0
- package/dist/public/assets/settings-subagents-view-BwNTnGkz.js +2 -0
- package/dist/public/assets/{settings-system-prompt-view-DQmSdSnp.js → settings-system-prompt-view-C4uLn68y.js} +1 -1
- package/dist/public/assets/settings-view-DQW_9pnm.js +2 -0
- package/dist/public/assets/skeleton-HsHVnOKO.js +1 -0
- package/dist/public/assets/{terminal-panel-BzCO142Q.js → terminal-panel-wOltBu70.js} +2 -2
- package/dist/public/assets/{ui-components-f8jJJzEo.js → ui-components-CaeQlGTd.js} +1 -1
- package/dist/public/assets/{utils-BlXPFbY9.js → utils-5mrXWXCs.js} +1 -1
- package/dist/public/assets/web-Gjw6klhQ.js +1 -0
- package/dist/public/assets/web-NsuCXAqU.js +1 -0
- package/dist/public/browser-preview-rpc.js +484 -0
- package/dist/public/index.html +7 -7
- package/dist/recommended-models.txt +1 -1
- package/package.json +1 -1
- package/dist/public/assets/api-DWU0D34A.js +0 -1
- package/dist/public/assets/browser-tab-Bp486a9f.js +0 -1
- package/dist/public/assets/commit-dialog-90PGbqs8.js +0 -1
- package/dist/public/assets/context-menu-C7FTMa2c.js +0 -1
- package/dist/public/assets/create-repo-dialog-BkUdww3q.js +0 -1
- package/dist/public/assets/diff-view-B4G8vqZa.js +0 -3
- package/dist/public/assets/explorer-tab-view-DEQFid8r.js +0 -2
- package/dist/public/assets/explorer-tree-CPHGgahA.js +0 -1
- package/dist/public/assets/explorer-view-BTvhSMDQ.js +0 -1
- package/dist/public/assets/git-history-dialog-Bqv0bD41.js +0 -1
- package/dist/public/assets/git-ops-button-BnVMlOO3.js +0 -2
- package/dist/public/assets/history-view-jX3PPID6.js +0 -9
- package/dist/public/assets/index-DjD2SPNZ.js +0 -65
- package/dist/public/assets/index-jIBJk8xl.css +0 -1
- package/dist/public/assets/mcp-server-card-CTCrhFIS.js +0 -1
- package/dist/public/assets/merged-pr-dialog-B2qqrQ1g.js +0 -1
- package/dist/public/assets/onboarding-CxtzQldG.js +0 -1
- package/dist/public/assets/project-settings-view-ZJjQbFbQ.js +0 -1
- package/dist/public/assets/providers-list-view-BH13AB_H.js +0 -1
- package/dist/public/assets/pull-request-dialog-ZzVXh6M9.js +0 -1
- package/dist/public/assets/pull-with-changes-dialog-LUgd2a4S.js +0 -1
- package/dist/public/assets/push-before-pr-dialog-CFmgu9H_.js +0 -1
- package/dist/public/assets/radio-group-Bd8YbtFD.js +0 -1
- package/dist/public/assets/react-vendor-Ba8DSNiO.js +0 -16
- package/dist/public/assets/settings-general-view-C8muiP_K.js +0 -1
- package/dist/public/assets/settings-list-CKPEGohm.js +0 -1
- package/dist/public/assets/settings-mcp-servers-view-3gN9ly3z.js +0 -5
- package/dist/public/assets/settings-models-view-CAMouMhE.js +0 -1
- package/dist/public/assets/settings-rules-view-BnazfbJb.js +0 -8
- package/dist/public/assets/settings-skills-view-DpzKFcpf.js +0 -2
- package/dist/public/assets/settings-slash-commands-view-Drje2Dl4.js +0 -1
- package/dist/public/assets/settings-subagents-view-D05kHO1W.js +0 -2
- package/dist/public/assets/settings-view-B6e0GfSv.js +0 -2
- package/dist/public/assets/skeleton-qzmFgKhS.js +0 -1
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ import { homedir } from "node:os";
|
|
|
50
50
|
import { join } from "node:path";
|
|
51
51
|
import { spawn, spawnSync, execSync } from "child_process";
|
|
52
52
|
function delay(ms) {
|
|
53
|
-
return new Promise((
|
|
53
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
54
54
|
}
|
|
55
55
|
function mergePath(parentPath, shellPath) {
|
|
56
56
|
const parent = (parentPath ?? "").split(":").filter(Boolean);
|
|
@@ -1224,6 +1224,15 @@ function buildDefaultSystemPrompt(toolNames) {
|
|
|
1224
1224
|
const toolList = toolNames.length > 0 ? toolNames.join(", ") : "read, bash, edit, and write";
|
|
1225
1225
|
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/ . Use Mermaid for flowcharts, sequence diagrams, state diagrams, or graphs when appropriate. Do not use emojis.`;
|
|
1226
1226
|
}
|
|
1227
|
+
function formatCurrentDateLine(referenceDate = /* @__PURE__ */ new Date()) {
|
|
1228
|
+
const date = referenceDate.toLocaleDateString("en-US", {
|
|
1229
|
+
weekday: "long",
|
|
1230
|
+
year: "numeric",
|
|
1231
|
+
month: "long",
|
|
1232
|
+
day: "numeric"
|
|
1233
|
+
});
|
|
1234
|
+
return `The current date is ${date}.`;
|
|
1235
|
+
}
|
|
1227
1236
|
|
|
1228
1237
|
// ../shared/dist/image-payload.js
|
|
1229
1238
|
function isDataImageUrl(value) {
|
|
@@ -1287,6 +1296,7 @@ function parseGeneratedImagePayload(value) {
|
|
|
1287
1296
|
function stripGeneratedImagePayload(payload) {
|
|
1288
1297
|
const next = { ...payload };
|
|
1289
1298
|
delete next.imageData;
|
|
1299
|
+
delete next.image_data;
|
|
1290
1300
|
if (typeof next.imageUrl === "string" && isDataImageUrl(next.imageUrl)) {
|
|
1291
1301
|
delete next.imageUrl;
|
|
1292
1302
|
}
|
|
@@ -1308,12 +1318,13 @@ function stripGeneratedImageText(text) {
|
|
|
1308
1318
|
return text;
|
|
1309
1319
|
}
|
|
1310
1320
|
}
|
|
1321
|
+
var IMAGE_RESULT_TOOL_NAMES = /* @__PURE__ */ new Set(["generate_image", "find_images", "browser"]);
|
|
1311
1322
|
function sanitizeToolResultBlock(block, collected) {
|
|
1312
1323
|
if (block.type !== "tool-result") {
|
|
1313
1324
|
return block;
|
|
1314
1325
|
}
|
|
1315
1326
|
const toolName = typeof block.toolName === "string" ? block.toolName : "";
|
|
1316
|
-
if (toolName
|
|
1327
|
+
if (!IMAGE_RESULT_TOOL_NAMES.has(toolName)) {
|
|
1317
1328
|
return block;
|
|
1318
1329
|
}
|
|
1319
1330
|
const blockRefs = parseGeneratedImagePayload(block);
|
|
@@ -1458,7 +1469,7 @@ function extractGeneratedImageFromEvents(events) {
|
|
|
1458
1469
|
const parsed = JSON.parse(event.content.trim());
|
|
1459
1470
|
const blocks = Array.isArray(parsed) ? parsed : [parsed];
|
|
1460
1471
|
for (const block of blocks) {
|
|
1461
|
-
if (block && typeof block === "object" && "type" in block && block.type === "tool-result" && block.toolName
|
|
1472
|
+
if (block && typeof block === "object" && "type" in block && block.type === "tool-result" && IMAGE_RESULT_TOOL_NAMES.has(block.toolName)) {
|
|
1462
1473
|
const refs = parseGeneratedImagePayload(block);
|
|
1463
1474
|
if (refs?.imageUrl) {
|
|
1464
1475
|
collected.imageUrl = refs.imageUrl;
|
|
@@ -1481,11 +1492,29 @@ function extractGeneratedImageFromEvents(events) {
|
|
|
1481
1492
|
return collected;
|
|
1482
1493
|
}
|
|
1483
1494
|
|
|
1495
|
+
// ../shared/dist/image-url.js
|
|
1496
|
+
function isAbsoluteHttpOrDataUrl(url) {
|
|
1497
|
+
return !url || url.startsWith("data:") || /^https?:\/\//i.test(url);
|
|
1498
|
+
}
|
|
1499
|
+
function buildThreadExplorerMediaUrl(threadId, projectPath, origin = "http://localhost:641") {
|
|
1500
|
+
const base = origin.replace(/\/$/, "");
|
|
1501
|
+
return `${base}/api/threads/${encodeURIComponent(threadId)}/explorer/media?path=${encodeURIComponent(projectPath)}`;
|
|
1502
|
+
}
|
|
1503
|
+
function resolveAbsoluteImageUrl(imageUrl, baseUrl) {
|
|
1504
|
+
if (isAbsoluteHttpOrDataUrl(imageUrl)) {
|
|
1505
|
+
return imageUrl;
|
|
1506
|
+
}
|
|
1507
|
+
const origin = baseUrl.replace(/\/$/, "");
|
|
1508
|
+
const path7 = imageUrl.startsWith("/") ? imageUrl : `/${imageUrl}`;
|
|
1509
|
+
return `${origin}${path7}`;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1484
1512
|
// ../shared/dist/tool-display.js
|
|
1485
1513
|
var toolDisplayRegistry = [
|
|
1486
1514
|
{ name: "agent", displayName: "Subagent", activeDisplayName: "Subagent" },
|
|
1487
1515
|
{ name: "ask_user", displayName: "Ask", activeDisplayName: "Asking" },
|
|
1488
1516
|
{ name: "bash", displayName: "Ran", activeDisplayName: "Running" },
|
|
1517
|
+
{ name: "browser", displayName: "Browser", activeDisplayName: "Browsing" },
|
|
1489
1518
|
{ name: "code_search", displayName: "Code Search", activeDisplayName: "Searching" },
|
|
1490
1519
|
{ name: "edit", displayName: "Edit", activeDisplayName: "Editing" },
|
|
1491
1520
|
{ name: "execute_skill_script", displayName: "Run Script", activeDisplayName: "Running Script" },
|
|
@@ -1506,6 +1535,11 @@ var toolDisplayRegistry = [
|
|
|
1506
1535
|
{ name: "simple_grep", displayName: "Search", activeDisplayName: "Searching" },
|
|
1507
1536
|
{ name: "todo", displayName: "Todo", activeDisplayName: "Updating" },
|
|
1508
1537
|
{ name: "tool_search", displayName: "Find Tools", activeDisplayName: "Finding Tools" },
|
|
1538
|
+
{
|
|
1539
|
+
name: "web_search",
|
|
1540
|
+
displayName: "Web Search",
|
|
1541
|
+
activeDisplayName: "Searching Web"
|
|
1542
|
+
},
|
|
1509
1543
|
{ name: "write", displayName: "Write", activeDisplayName: "Writing" }
|
|
1510
1544
|
];
|
|
1511
1545
|
var displayByName = new Map(toolDisplayRegistry.map((entry) => [entry.name, entry]));
|
|
@@ -1628,9 +1662,27 @@ function getExplorerFileMediaType(path7, name) {
|
|
|
1628
1662
|
return null;
|
|
1629
1663
|
}
|
|
1630
1664
|
|
|
1665
|
+
// ../shared/dist/browser-tool.js
|
|
1666
|
+
var BROWSER_HOST_ACTIONS = [
|
|
1667
|
+
"reload",
|
|
1668
|
+
"go_back",
|
|
1669
|
+
"go_forward",
|
|
1670
|
+
"get_state",
|
|
1671
|
+
"set_viewport"
|
|
1672
|
+
];
|
|
1673
|
+
function isBrowserHostAction(action) {
|
|
1674
|
+
return BROWSER_HOST_ACTIONS.includes(action);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// ../shared/dist/tmp-images.js
|
|
1678
|
+
var TARSK_TMP_IMAGES_DIR = ".tarsk/tmp-images";
|
|
1679
|
+
function buildTmpImagePath(filename) {
|
|
1680
|
+
return `${TARSK_TMP_IMAGES_DIR}/${filename}`;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1631
1683
|
// src/server.ts
|
|
1632
1684
|
import fs3 from "fs";
|
|
1633
|
-
import { Hono as
|
|
1685
|
+
import { Hono as Hono27 } from "hono";
|
|
1634
1686
|
import { cors } from "hono/cors";
|
|
1635
1687
|
import open3 from "open";
|
|
1636
1688
|
import path5 from "path";
|
|
@@ -2439,7 +2491,7 @@ var bashSchema = Type.Object({
|
|
|
2439
2491
|
});
|
|
2440
2492
|
var defaultBashOperations = {
|
|
2441
2493
|
exec: (command, cwd, { onData, signal, timeout, env }) => {
|
|
2442
|
-
return new Promise((
|
|
2494
|
+
return new Promise((resolve7, reject) => {
|
|
2443
2495
|
const { shell, args: args2 } = getShellConfig();
|
|
2444
2496
|
if (!existsSync3(cwd)) {
|
|
2445
2497
|
reject(new Error(`Working directory does not exist: ${cwd}`));
|
|
@@ -2488,7 +2540,7 @@ var defaultBashOperations = {
|
|
|
2488
2540
|
reject(new Error(`timeout:${timeout}`));
|
|
2489
2541
|
return;
|
|
2490
2542
|
}
|
|
2491
|
-
|
|
2543
|
+
resolve7({ exitCode: code });
|
|
2492
2544
|
});
|
|
2493
2545
|
});
|
|
2494
2546
|
}
|
|
@@ -2511,7 +2563,7 @@ ${command}` : command;
|
|
|
2511
2563
|
if (shellPermissionGate) {
|
|
2512
2564
|
await shellPermissionGate.ensureAllowed(toolCallId, resolvedCommand, "bash", signal);
|
|
2513
2565
|
}
|
|
2514
|
-
return new Promise((
|
|
2566
|
+
return new Promise((resolve7, reject) => {
|
|
2515
2567
|
let tempFilePath;
|
|
2516
2568
|
let tempFileStream;
|
|
2517
2569
|
let totalBytes = 0;
|
|
@@ -2580,7 +2632,7 @@ ${command}` : command;
|
|
|
2580
2632
|
Command exited with code ${exitCode}`;
|
|
2581
2633
|
reject(new Error(outputText));
|
|
2582
2634
|
} else {
|
|
2583
|
-
|
|
2635
|
+
resolve7({ content: [{ type: "text", text: outputText }], details });
|
|
2584
2636
|
}
|
|
2585
2637
|
}).catch((err) => {
|
|
2586
2638
|
tarskDebugLog(`[ai] bash-end: ${resolvedCommand}`);
|
|
@@ -2736,7 +2788,7 @@ function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
|
2736
2788
|
|
|
2737
2789
|
// src/tools/tool-helpers.ts
|
|
2738
2790
|
async function withAbortSignal(signal, operation) {
|
|
2739
|
-
return new Promise((
|
|
2791
|
+
return new Promise((resolve7, reject) => {
|
|
2740
2792
|
if (signal?.aborted) {
|
|
2741
2793
|
reject(new Error("Operation aborted"));
|
|
2742
2794
|
return;
|
|
@@ -2755,7 +2807,7 @@ async function withAbortSignal(signal, operation) {
|
|
|
2755
2807
|
const abortCheck = () => aborted;
|
|
2756
2808
|
operation(abortCheck).then((result) => {
|
|
2757
2809
|
cleanup();
|
|
2758
|
-
if (!aborted)
|
|
2810
|
+
if (!aborted) resolve7(result);
|
|
2759
2811
|
}).catch((error) => {
|
|
2760
2812
|
cleanup();
|
|
2761
2813
|
if (!aborted) reject(error);
|
|
@@ -2878,7 +2930,7 @@ function createFindTool(cwd, options) {
|
|
|
2878
2930
|
description: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Output truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB.`,
|
|
2879
2931
|
parameters: findSchema,
|
|
2880
2932
|
execute: async (_toolCallId, { pattern, path: searchDir, limit }, signal) => {
|
|
2881
|
-
return new Promise((
|
|
2933
|
+
return new Promise((resolve7, reject) => {
|
|
2882
2934
|
if (signal?.aborted) {
|
|
2883
2935
|
reject(new Error("Operation aborted"));
|
|
2884
2936
|
return;
|
|
@@ -2911,7 +2963,7 @@ function createFindTool(cwd, options) {
|
|
|
2911
2963
|
});
|
|
2912
2964
|
signal?.removeEventListener("abort", onAbort);
|
|
2913
2965
|
if (results.length === 0) {
|
|
2914
|
-
|
|
2966
|
+
resolve7({
|
|
2915
2967
|
content: [{ type: "text", text: "No files found matching pattern" }],
|
|
2916
2968
|
details: void 0
|
|
2917
2969
|
});
|
|
@@ -2942,7 +2994,7 @@ function createFindTool(cwd, options) {
|
|
|
2942
2994
|
if (notices.length > 0) resultOutput += `
|
|
2943
2995
|
|
|
2944
2996
|
[${notices.join(". ")}]`;
|
|
2945
|
-
|
|
2997
|
+
resolve7({
|
|
2946
2998
|
content: [{ type: "text", text: resultOutput }],
|
|
2947
2999
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
2948
3000
|
});
|
|
@@ -2967,7 +3019,7 @@ import path2 from "path";
|
|
|
2967
3019
|
// src/tools/resolve-bin.ts
|
|
2968
3020
|
init_utils();
|
|
2969
3021
|
function resolveBin(name) {
|
|
2970
|
-
return new Promise((
|
|
3022
|
+
return new Promise((resolve7) => {
|
|
2971
3023
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
2972
3024
|
const args2 = process.platform === "win32" ? [name + ".exe", name] : [name];
|
|
2973
3025
|
try {
|
|
@@ -2975,13 +3027,13 @@ function resolveBin(name) {
|
|
|
2975
3027
|
if (result.status === 0 && result.stdout) {
|
|
2976
3028
|
const first = (typeof result.stdout === "string" ? result.stdout : result.stdout?.toString() || "").trim().split(/\r?\n/)[0]?.trim();
|
|
2977
3029
|
if (first) {
|
|
2978
|
-
|
|
3030
|
+
resolve7(first);
|
|
2979
3031
|
return;
|
|
2980
3032
|
}
|
|
2981
3033
|
}
|
|
2982
3034
|
} catch {
|
|
2983
3035
|
}
|
|
2984
|
-
|
|
3036
|
+
resolve7(null);
|
|
2985
3037
|
});
|
|
2986
3038
|
}
|
|
2987
3039
|
|
|
@@ -3020,7 +3072,7 @@ function createSimpleGrepTool(cwd, options) {
|
|
|
3020
3072
|
description: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output truncated to ${DEFAULT_LIMIT2} matches or ${DEFAULT_MAX_BYTES / 1024}KB. Long lines truncated to ${GREP_MAX_LINE_LENGTH} chars.`,
|
|
3021
3073
|
parameters: grepSchema,
|
|
3022
3074
|
execute: async (_toolCallId, { pattern, path: searchDir, glob: glob3, ignoreCase, literal, context, limit }, signal) => {
|
|
3023
|
-
return new Promise((
|
|
3075
|
+
return new Promise((resolve7, reject) => {
|
|
3024
3076
|
if (signal?.aborted) {
|
|
3025
3077
|
reject(new Error("Operation aborted"));
|
|
3026
3078
|
return;
|
|
@@ -3169,7 +3221,7 @@ function createSimpleGrepTool(cwd, options) {
|
|
|
3169
3221
|
}
|
|
3170
3222
|
if (matchCount === 0) {
|
|
3171
3223
|
settle(
|
|
3172
|
-
() =>
|
|
3224
|
+
() => resolve7({
|
|
3173
3225
|
content: [{ type: "text", text: "No matches found" }],
|
|
3174
3226
|
details: void 0
|
|
3175
3227
|
})
|
|
@@ -3205,7 +3257,7 @@ function createSimpleGrepTool(cwd, options) {
|
|
|
3205
3257
|
|
|
3206
3258
|
[${notices.join(". ")}]`;
|
|
3207
3259
|
settle(
|
|
3208
|
-
() =>
|
|
3260
|
+
() => resolve7({
|
|
3209
3261
|
content: [{ type: "text", text: output }],
|
|
3210
3262
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
3211
3263
|
})
|
|
@@ -4232,7 +4284,7 @@ function createLsTool(cwd, options) {
|
|
|
4232
4284
|
description: `List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Output truncated to ${DEFAULT_LIMIT4} entries or ${DEFAULT_MAX_BYTES / 1024}KB.`,
|
|
4233
4285
|
parameters: lsSchema,
|
|
4234
4286
|
execute: async (_toolCallId, { path: dirPathArg, limit }, signal) => {
|
|
4235
|
-
return new Promise((
|
|
4287
|
+
return new Promise((resolve7, reject) => {
|
|
4236
4288
|
if (signal?.aborted) {
|
|
4237
4289
|
reject(new Error("Operation aborted"));
|
|
4238
4290
|
return;
|
|
@@ -4280,7 +4332,7 @@ function createLsTool(cwd, options) {
|
|
|
4280
4332
|
}
|
|
4281
4333
|
signal?.removeEventListener("abort", onAbort);
|
|
4282
4334
|
if (results.length === 0) {
|
|
4283
|
-
|
|
4335
|
+
resolve7({
|
|
4284
4336
|
content: [{ type: "text", text: "(empty directory)" }],
|
|
4285
4337
|
details: void 0
|
|
4286
4338
|
});
|
|
@@ -4304,7 +4356,7 @@ function createLsTool(cwd, options) {
|
|
|
4304
4356
|
if (notices.length > 0) output += `
|
|
4305
4357
|
|
|
4306
4358
|
[${notices.join(". ")}]`;
|
|
4307
|
-
|
|
4359
|
+
resolve7({
|
|
4308
4360
|
content: [{ type: "text", text: output }],
|
|
4309
4361
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
4310
4362
|
});
|
|
@@ -4430,7 +4482,7 @@ function createWriteTool(cwd, options) {
|
|
|
4430
4482
|
validatePathWithinCwd(absolutePath, cwd);
|
|
4431
4483
|
const dir = dirname(absolutePath);
|
|
4432
4484
|
return new Promise(
|
|
4433
|
-
(
|
|
4485
|
+
(resolve7, reject) => {
|
|
4434
4486
|
if (signal?.aborted) {
|
|
4435
4487
|
reject(new Error("Operation aborted"));
|
|
4436
4488
|
return;
|
|
@@ -4448,7 +4500,7 @@ function createWriteTool(cwd, options) {
|
|
|
4448
4500
|
await ops.writeFile(absolutePath, content);
|
|
4449
4501
|
if (aborted) return;
|
|
4450
4502
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
4451
|
-
|
|
4503
|
+
resolve7({
|
|
4452
4504
|
content: [
|
|
4453
4505
|
{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path7}` }
|
|
4454
4506
|
],
|
|
@@ -4500,7 +4552,7 @@ async function executeScript(scriptPath, args2, cwd, timeout = 30) {
|
|
|
4500
4552
|
if (!interpreter) {
|
|
4501
4553
|
throw new Error(`Unsupported script type: ${scriptPath}`);
|
|
4502
4554
|
}
|
|
4503
|
-
return new Promise((
|
|
4555
|
+
return new Promise((resolve7, reject) => {
|
|
4504
4556
|
const child = spawnProcess(interpreter.command, [...interpreter.args, ...args2], {
|
|
4505
4557
|
cwd,
|
|
4506
4558
|
timeout: timeout * 1e3
|
|
@@ -4532,7 +4584,7 @@ async function executeScript(scriptPath, args2, cwd, timeout = 30) {
|
|
|
4532
4584
|
reject(new Error(`Script execution timed out after ${timeout}s`));
|
|
4533
4585
|
return;
|
|
4534
4586
|
}
|
|
4535
|
-
|
|
4587
|
+
resolve7({ stdout, stderr, exitCode: code });
|
|
4536
4588
|
});
|
|
4537
4589
|
});
|
|
4538
4590
|
}
|
|
@@ -4811,9 +4863,9 @@ function createAskUserTool() {
|
|
|
4811
4863
|
if (signal?.aborted) {
|
|
4812
4864
|
throw new Error("Operation aborted");
|
|
4813
4865
|
}
|
|
4814
|
-
const answer = await new Promise((
|
|
4866
|
+
const answer = await new Promise((resolve7, reject) => {
|
|
4815
4867
|
pendingQuestions.set(toolCallId, {
|
|
4816
|
-
resolve:
|
|
4868
|
+
resolve: resolve7,
|
|
4817
4869
|
reject,
|
|
4818
4870
|
question,
|
|
4819
4871
|
options,
|
|
@@ -6541,8 +6593,46 @@ async function executeGenerateImage(options) {
|
|
|
6541
6593
|
|
|
6542
6594
|
// src/tools/find-images.ts
|
|
6543
6595
|
import { Type as Type15 } from "@sinclair/typebox";
|
|
6596
|
+
import { join as join9 } from "path";
|
|
6597
|
+
|
|
6598
|
+
// src/core/server-origin.ts
|
|
6599
|
+
function getServerOrigin() {
|
|
6600
|
+
const port = process.env.PORT ?? "641";
|
|
6601
|
+
return `http://localhost:${port}`;
|
|
6602
|
+
}
|
|
6603
|
+
|
|
6604
|
+
// src/tools/tool-image-storage.ts
|
|
6544
6605
|
import { mkdir as fsMkdir3, writeFile as fsWriteFile4 } from "fs/promises";
|
|
6545
6606
|
import { dirname as dirname3 } from "path";
|
|
6607
|
+
function decodeBase64ImageData(imageData) {
|
|
6608
|
+
const binary = atob(imageData);
|
|
6609
|
+
const bytes = new Uint8Array(binary.length);
|
|
6610
|
+
for (let i = 0; i < binary.length; i++) {
|
|
6611
|
+
bytes[i] = binary.charCodeAt(i);
|
|
6612
|
+
}
|
|
6613
|
+
return bytes;
|
|
6614
|
+
}
|
|
6615
|
+
async function saveToolImage(options) {
|
|
6616
|
+
const bytes = decodeBase64ImageData(options.imageData);
|
|
6617
|
+
const absolutePath = resolveToCwd(options.relativePath, options.cwd);
|
|
6618
|
+
validatePathWithinCwd(absolutePath, options.cwd);
|
|
6619
|
+
await fsMkdir3(dirname3(absolutePath), { recursive: true });
|
|
6620
|
+
await fsWriteFile4(absolutePath, bytes);
|
|
6621
|
+
const saved = {
|
|
6622
|
+
savedPath: options.relativePath,
|
|
6623
|
+
sizeInBytes: bytes.length
|
|
6624
|
+
};
|
|
6625
|
+
if (options.threadId && options.serverOrigin) {
|
|
6626
|
+
saved.imageUrl = buildThreadExplorerMediaUrl(
|
|
6627
|
+
options.threadId,
|
|
6628
|
+
options.relativePath,
|
|
6629
|
+
options.serverOrigin
|
|
6630
|
+
);
|
|
6631
|
+
}
|
|
6632
|
+
return saved;
|
|
6633
|
+
}
|
|
6634
|
+
|
|
6635
|
+
// src/tools/find-images.ts
|
|
6546
6636
|
var findImagesSchema = Type15.Object({
|
|
6547
6637
|
query: Type15.String({ description: "Search term for the image (e.g. 'sunset over mountains')" }),
|
|
6548
6638
|
size: Type15.Optional(
|
|
@@ -6564,15 +6654,36 @@ var findImagesSchema = Type15.Object({
|
|
|
6564
6654
|
),
|
|
6565
6655
|
save_to_file: Type15.Optional(
|
|
6566
6656
|
Type15.String({
|
|
6567
|
-
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
|
|
6657
|
+
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 saved under .tarsk/tmp-images/."
|
|
6568
6658
|
})
|
|
6569
6659
|
)
|
|
6570
6660
|
});
|
|
6571
|
-
function
|
|
6661
|
+
function extensionForContentType(contentType) {
|
|
6662
|
+
if (contentType.includes("png")) {
|
|
6663
|
+
return ".png";
|
|
6664
|
+
}
|
|
6665
|
+
if (contentType.includes("webp")) {
|
|
6666
|
+
return ".webp";
|
|
6667
|
+
}
|
|
6668
|
+
if (contentType.includes("gif")) {
|
|
6669
|
+
return ".gif";
|
|
6670
|
+
}
|
|
6671
|
+
if (contentType.includes("jpeg") || contentType.includes("jpg")) {
|
|
6672
|
+
return ".jpg";
|
|
6673
|
+
}
|
|
6674
|
+
return ".jpg";
|
|
6675
|
+
}
|
|
6676
|
+
function defaultSavePath(query, contentType) {
|
|
6677
|
+
const slug = query.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "image";
|
|
6678
|
+
const id = Math.random().toString(36).slice(2, 8);
|
|
6679
|
+
return buildTmpImagePath(`${slug}-${id}${extensionForContentType(contentType)}`);
|
|
6680
|
+
}
|
|
6681
|
+
function createFindImagesTool(options = {}) {
|
|
6682
|
+
const { cwd, threadId, serverOrigin = getServerOrigin() } = options;
|
|
6572
6683
|
return {
|
|
6573
6684
|
name: "find_images",
|
|
6574
6685
|
label: "find_images",
|
|
6575
|
-
description: "Find a stock image matching a search query. Returns
|
|
6686
|
+
description: "Find a stock image matching a search query. Returns a URL served by the Tarsk API and optionally saves the image to a file. Use this when the user needs an image and the user has not requested it be generated.",
|
|
6576
6687
|
parameters: findImagesSchema,
|
|
6577
6688
|
execute: async (_toolCallId, { query, size, save_to_file }, signal) => {
|
|
6578
6689
|
return withAbortSignal(signal, async (isAborted) => {
|
|
@@ -6593,52 +6704,889 @@ function createFindImagesTool(cwd) {
|
|
|
6593
6704
|
};
|
|
6594
6705
|
}
|
|
6595
6706
|
if (isAborted()) return { content: [], details: void 0 };
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6707
|
+
if (!cwd) {
|
|
6708
|
+
return {
|
|
6709
|
+
content: [
|
|
6710
|
+
{
|
|
6711
|
+
type: "text",
|
|
6712
|
+
text: "Image search succeeded but no project directory is available to save the image."
|
|
6713
|
+
}
|
|
6714
|
+
],
|
|
6715
|
+
details: void 0
|
|
6716
|
+
};
|
|
6717
|
+
}
|
|
6718
|
+
const relativePath = save_to_file ?? defaultSavePath(query, contentType);
|
|
6719
|
+
let saved;
|
|
6720
|
+
try {
|
|
6721
|
+
if (save_to_file) {
|
|
6722
|
+
const absolutePath = resolveToCwd(relativePath, cwd);
|
|
6600
6723
|
validatePathWithinCwd(absolutePath, cwd);
|
|
6601
|
-
const binary = atob(imageData);
|
|
6602
|
-
const bytes = new Uint8Array(binary.length);
|
|
6603
|
-
for (let i = 0; i < binary.length; i++) {
|
|
6604
|
-
bytes[i] = binary.charCodeAt(i);
|
|
6605
|
-
}
|
|
6606
|
-
await fsMkdir3(dirname3(absolutePath), { recursive: true });
|
|
6607
|
-
await fsWriteFile4(absolutePath, bytes);
|
|
6608
|
-
savedPath = save_to_file;
|
|
6609
|
-
console.log(`[find_images] Saved image to ${absolutePath}`);
|
|
6610
|
-
} catch (saveError) {
|
|
6611
|
-
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
6612
|
-
return {
|
|
6613
|
-
content: [
|
|
6614
|
-
{
|
|
6615
|
-
type: "text",
|
|
6616
|
-
text: `Image was found but failed to save to "${save_to_file}": ${errMsg}`
|
|
6617
|
-
}
|
|
6618
|
-
],
|
|
6619
|
-
details: { imageData, contentType }
|
|
6620
|
-
};
|
|
6621
6724
|
}
|
|
6725
|
+
saved = await saveToolImage({
|
|
6726
|
+
imageData,
|
|
6727
|
+
cwd,
|
|
6728
|
+
relativePath,
|
|
6729
|
+
threadId,
|
|
6730
|
+
serverOrigin
|
|
6731
|
+
});
|
|
6732
|
+
console.log(`[find_images] Saved image to ${join9(cwd, saved.savedPath)}`);
|
|
6733
|
+
} catch (saveError) {
|
|
6734
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
6735
|
+
return {
|
|
6736
|
+
content: [
|
|
6737
|
+
{
|
|
6738
|
+
type: "text",
|
|
6739
|
+
text: `Image was found but failed to save to "${relativePath}": ${errMsg}`
|
|
6740
|
+
}
|
|
6741
|
+
],
|
|
6742
|
+
details: void 0
|
|
6743
|
+
};
|
|
6744
|
+
}
|
|
6745
|
+
const resultPayload = {
|
|
6746
|
+
contentType,
|
|
6747
|
+
savedPath: saved.savedPath,
|
|
6748
|
+
sizeInBytes: saved.sizeInBytes
|
|
6749
|
+
};
|
|
6750
|
+
if (saved.imageUrl) {
|
|
6751
|
+
resultPayload.imageUrl = saved.imageUrl;
|
|
6622
6752
|
}
|
|
6623
|
-
const resultPayload = { imageData, contentType };
|
|
6624
|
-
if (savedPath) resultPayload.savedPath = savedPath;
|
|
6625
6753
|
return {
|
|
6626
|
-
content: [{ type: "text", text:
|
|
6627
|
-
details: {
|
|
6754
|
+
content: [{ type: "text", text: buildGenerateImageToolText(resultPayload) }],
|
|
6755
|
+
details: saved.imageUrl ? { imageUrl: saved.imageUrl } : void 0
|
|
6628
6756
|
};
|
|
6629
6757
|
});
|
|
6630
6758
|
}
|
|
6631
6759
|
};
|
|
6632
6760
|
}
|
|
6633
6761
|
|
|
6762
|
+
// src/tools/browser.ts
|
|
6763
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6764
|
+
|
|
6765
|
+
// src/core/browser-preview-bridge.ts
|
|
6766
|
+
async function invokePreviewApi(webview, method, params) {
|
|
6767
|
+
if (!webview.rpc?.request?.evaluateJavascriptWithResponse) {
|
|
6768
|
+
throw new Error("Browser preview webview RPC is not available");
|
|
6769
|
+
}
|
|
6770
|
+
const paramsLiteral = JSON.stringify(params ?? null);
|
|
6771
|
+
const script = `return (async () => {
|
|
6772
|
+
const api = window.__tarskBrowserPreview;
|
|
6773
|
+
if (!api) {
|
|
6774
|
+
throw new Error("Browser preview API is not available");
|
|
6775
|
+
}
|
|
6776
|
+
const fn = api[${JSON.stringify(method)}];
|
|
6777
|
+
if (typeof fn !== "function") {
|
|
6778
|
+
throw new Error("Unknown browser preview API method: ${method}");
|
|
6779
|
+
}
|
|
6780
|
+
return await fn.call(api, ${paramsLiteral});
|
|
6781
|
+
})()`;
|
|
6782
|
+
return webview.rpc.request.evaluateJavascriptWithResponse({ script });
|
|
6783
|
+
}
|
|
6784
|
+
|
|
6785
|
+
// src/core/browser-native-screenshot.ts
|
|
6786
|
+
import { execFile } from "node:child_process";
|
|
6787
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6788
|
+
import { readFile as readFile5, unlink } from "node:fs/promises";
|
|
6789
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
6790
|
+
import { join as join10 } from "node:path";
|
|
6791
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
6792
|
+
import { promisify } from "node:util";
|
|
6793
|
+
|
|
6794
|
+
// src/core/browser-view-access.ts
|
|
6795
|
+
var electrobunModule = null;
|
|
6796
|
+
var electrobunLoadAttempted = false;
|
|
6797
|
+
async function loadElectrobunBrowserView() {
|
|
6798
|
+
if (electrobunLoadAttempted) {
|
|
6799
|
+
return electrobunModule;
|
|
6800
|
+
}
|
|
6801
|
+
electrobunLoadAttempted = true;
|
|
6802
|
+
try {
|
|
6803
|
+
electrobunModule = await import("electrobun/bun");
|
|
6804
|
+
} catch {
|
|
6805
|
+
electrobunModule = null;
|
|
6806
|
+
}
|
|
6807
|
+
return electrobunModule;
|
|
6808
|
+
}
|
|
6809
|
+
function getBrowserViewById(webviewId) {
|
|
6810
|
+
if (!electrobunModule) {
|
|
6811
|
+
return null;
|
|
6812
|
+
}
|
|
6813
|
+
return electrobunModule.BrowserView.getById(webviewId) ?? null;
|
|
6814
|
+
}
|
|
6815
|
+
function getBrowserWindowById(windowId) {
|
|
6816
|
+
if (!electrobunModule) {
|
|
6817
|
+
return null;
|
|
6818
|
+
}
|
|
6819
|
+
return electrobunModule.BrowserWindow.getById(windowId) ?? null;
|
|
6820
|
+
}
|
|
6821
|
+
function waitForBrowserEvent(webview, eventName, timeoutMs) {
|
|
6822
|
+
return new Promise((resolve7, reject) => {
|
|
6823
|
+
const timeout = setTimeout(() => {
|
|
6824
|
+
reject(new Error(`Timed out waiting for ${eventName} after ${timeoutMs}ms`));
|
|
6825
|
+
}, timeoutMs);
|
|
6826
|
+
webview.on(eventName, () => {
|
|
6827
|
+
clearTimeout(timeout);
|
|
6828
|
+
resolve7();
|
|
6829
|
+
});
|
|
6830
|
+
});
|
|
6831
|
+
}
|
|
6832
|
+
|
|
6833
|
+
// src/core/browser-native-screenshot.ts
|
|
6834
|
+
var execFileAsync = promisify(execFile);
|
|
6835
|
+
var NATIVE_FOCUS_DELAY_MS = 75;
|
|
6836
|
+
var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
6837
|
+
var SCREEN_RECORDING_HINT = "Grant Screen Recording permission to Tarsk in System Settings \u2192 Privacy & Security \u2192 Screen Recording, then restart Tarsk.";
|
|
6838
|
+
function parsePngDimensions(bytes) {
|
|
6839
|
+
if (bytes.length < 24 || !bytes.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
6840
|
+
return null;
|
|
6841
|
+
}
|
|
6842
|
+
return {
|
|
6843
|
+
width: bytes.readUInt32BE(16),
|
|
6844
|
+
height: bytes.readUInt32BE(20)
|
|
6845
|
+
};
|
|
6846
|
+
}
|
|
6847
|
+
function pngHasImageData(bytes) {
|
|
6848
|
+
return bytes.includes(Buffer.from("IDAT")) && bytes.includes(Buffer.from("IEND"));
|
|
6849
|
+
}
|
|
6850
|
+
function parseJpegDimensions(bytes) {
|
|
6851
|
+
if (bytes.length < 4 || bytes[0] !== 255 || bytes[1] !== 216) {
|
|
6852
|
+
return null;
|
|
6853
|
+
}
|
|
6854
|
+
let offset = 2;
|
|
6855
|
+
while (offset + 9 < bytes.length) {
|
|
6856
|
+
if (bytes[offset] !== 255) {
|
|
6857
|
+
return null;
|
|
6858
|
+
}
|
|
6859
|
+
const marker = bytes[offset + 1];
|
|
6860
|
+
if (marker === 217) {
|
|
6861
|
+
break;
|
|
6862
|
+
}
|
|
6863
|
+
const segmentLength = bytes.readUInt16BE(offset + 2);
|
|
6864
|
+
if (segmentLength < 2 || offset + 2 + segmentLength > bytes.length) {
|
|
6865
|
+
return null;
|
|
6866
|
+
}
|
|
6867
|
+
const isStartOfFrame = marker === 192 || marker === 193 || marker === 194 || marker === 195;
|
|
6868
|
+
if (isStartOfFrame) {
|
|
6869
|
+
return {
|
|
6870
|
+
height: bytes.readUInt16BE(offset + 5),
|
|
6871
|
+
width: bytes.readUInt16BE(offset + 7)
|
|
6872
|
+
};
|
|
6873
|
+
}
|
|
6874
|
+
offset += 2 + segmentLength;
|
|
6875
|
+
}
|
|
6876
|
+
return null;
|
|
6877
|
+
}
|
|
6878
|
+
function isTruncatedCapture(bytes, width, height) {
|
|
6879
|
+
if (width >= 64 || height >= 64) {
|
|
6880
|
+
return bytes.length < 1024;
|
|
6881
|
+
}
|
|
6882
|
+
return bytes.length < 68;
|
|
6883
|
+
}
|
|
6884
|
+
function validateCapturedImage(bytes, format) {
|
|
6885
|
+
if (bytes.length === 0) {
|
|
6886
|
+
return { error: "macOS screencapture produced an empty image file." };
|
|
6887
|
+
}
|
|
6888
|
+
if (format === "png") {
|
|
6889
|
+
const dimensions2 = parsePngDimensions(bytes);
|
|
6890
|
+
if (!dimensions2) {
|
|
6891
|
+
return { error: "macOS screencapture produced an invalid PNG file." };
|
|
6892
|
+
}
|
|
6893
|
+
if (!pngHasImageData(bytes) || isTruncatedCapture(bytes, dimensions2.width, dimensions2.height)) {
|
|
6894
|
+
return {
|
|
6895
|
+
error: `macOS screencapture wrote a truncated PNG (${bytes.length} bytes for ${dimensions2.width}x${dimensions2.height}). ` + SCREEN_RECORDING_HINT
|
|
6896
|
+
};
|
|
6897
|
+
}
|
|
6898
|
+
return { ...dimensions2, format: "png" };
|
|
6899
|
+
}
|
|
6900
|
+
const dimensions = parseJpegDimensions(bytes);
|
|
6901
|
+
if (!dimensions) {
|
|
6902
|
+
return { error: "macOS screencapture produced an invalid JPEG file." };
|
|
6903
|
+
}
|
|
6904
|
+
if (isTruncatedCapture(bytes, dimensions.width, dimensions.height)) {
|
|
6905
|
+
return {
|
|
6906
|
+
error: `macOS screencapture wrote a truncated JPEG (${bytes.length} bytes for ${dimensions.width}x${dimensions.height}). ` + SCREEN_RECORDING_HINT
|
|
6907
|
+
};
|
|
6908
|
+
}
|
|
6909
|
+
return { ...dimensions, format: "jpeg" };
|
|
6910
|
+
}
|
|
6911
|
+
function isValidRect(rect) {
|
|
6912
|
+
return Number.isFinite(rect.x) && Number.isFinite(rect.y) && rect.width > 0 && rect.height > 0;
|
|
6913
|
+
}
|
|
6914
|
+
async function resolveViewFrame(view) {
|
|
6915
|
+
if (view.getFrame) {
|
|
6916
|
+
return await view.getFrame();
|
|
6917
|
+
}
|
|
6918
|
+
if (view.frame) {
|
|
6919
|
+
return view.frame;
|
|
6920
|
+
}
|
|
6921
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
6922
|
+
}
|
|
6923
|
+
async function captureMacOSRect(options) {
|
|
6924
|
+
const formatFlag = options.format === "jpeg" ? "jpg" : "png";
|
|
6925
|
+
const region = `${Math.round(options.rect.x)},${Math.round(options.rect.y)},${Math.round(options.rect.width)},${Math.round(options.rect.height)}`;
|
|
6926
|
+
await execFileAsync("screencapture", ["-x", "-t", formatFlag, "-R", region, options.path]);
|
|
6927
|
+
}
|
|
6928
|
+
async function resolveCaptureRect(webviewId) {
|
|
6929
|
+
const view = getBrowserViewById(webviewId);
|
|
6930
|
+
if (!view) {
|
|
6931
|
+
throw new Error(`Browser preview webview ${webviewId} is not available`);
|
|
6932
|
+
}
|
|
6933
|
+
const window = getBrowserWindowById(view.windowId);
|
|
6934
|
+
if (!window) {
|
|
6935
|
+
throw new Error(`Electrobun window ${view.windowId} is not available for screenshot capture`);
|
|
6936
|
+
}
|
|
6937
|
+
const windowFrame = window.getFrame();
|
|
6938
|
+
const viewFrame = await resolveViewFrame(view);
|
|
6939
|
+
if (!isValidRect(windowFrame)) {
|
|
6940
|
+
throw new Error(`Window ${window.id} does not expose a usable frame for screenshots`);
|
|
6941
|
+
}
|
|
6942
|
+
if (!isValidRect(viewFrame)) {
|
|
6943
|
+
throw new Error(`View ${view.id} does not expose a usable frame for screenshots`);
|
|
6944
|
+
}
|
|
6945
|
+
return {
|
|
6946
|
+
rect: {
|
|
6947
|
+
x: windowFrame.x + viewFrame.x,
|
|
6948
|
+
y: windowFrame.y + viewFrame.y,
|
|
6949
|
+
width: viewFrame.width,
|
|
6950
|
+
height: viewFrame.height
|
|
6951
|
+
},
|
|
6952
|
+
viewId: view.id,
|
|
6953
|
+
windowId: window.id
|
|
6954
|
+
};
|
|
6955
|
+
}
|
|
6956
|
+
async function captureNativeBrowserScreenshot(webviewId, format) {
|
|
6957
|
+
if (process.platform !== "darwin") {
|
|
6958
|
+
return {
|
|
6959
|
+
ok: false,
|
|
6960
|
+
reason: "Native Electrobun screenshots are only supported on macOS."
|
|
6961
|
+
};
|
|
6962
|
+
}
|
|
6963
|
+
const outputPath = join10(
|
|
6964
|
+
tmpdir2(),
|
|
6965
|
+
`tarsk-browser-screenshot-${randomUUID3()}.${format === "jpeg" ? "jpg" : "png"}`
|
|
6966
|
+
);
|
|
6967
|
+
try {
|
|
6968
|
+
const target = await resolveCaptureRect(webviewId);
|
|
6969
|
+
const window = getBrowserWindowById(target.windowId);
|
|
6970
|
+
window?.focus();
|
|
6971
|
+
await sleep(NATIVE_FOCUS_DELAY_MS);
|
|
6972
|
+
await captureMacOSRect({
|
|
6973
|
+
format,
|
|
6974
|
+
path: outputPath,
|
|
6975
|
+
rect: target.rect
|
|
6976
|
+
});
|
|
6977
|
+
const imageBytes = await readFile5(outputPath);
|
|
6978
|
+
const validated = validateCapturedImage(imageBytes, format);
|
|
6979
|
+
if ("error" in validated) {
|
|
6980
|
+
return {
|
|
6981
|
+
ok: false,
|
|
6982
|
+
reason: validated.error
|
|
6983
|
+
};
|
|
6984
|
+
}
|
|
6985
|
+
return {
|
|
6986
|
+
ok: true,
|
|
6987
|
+
result: {
|
|
6988
|
+
format: validated.format,
|
|
6989
|
+
width: validated.width,
|
|
6990
|
+
height: validated.height,
|
|
6991
|
+
image_data: imageBytes.toString("base64"),
|
|
6992
|
+
capture_method: "native-screencapture",
|
|
6993
|
+
window_id: target.windowId,
|
|
6994
|
+
view_id: target.viewId
|
|
6995
|
+
}
|
|
6996
|
+
};
|
|
6997
|
+
} catch (error) {
|
|
6998
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6999
|
+
return {
|
|
7000
|
+
ok: false,
|
|
7001
|
+
reason: `${message} Native screenshots require a visible on-screen preview. ${SCREEN_RECORDING_HINT}`
|
|
7002
|
+
};
|
|
7003
|
+
} finally {
|
|
7004
|
+
await unlink(outputPath).catch(() => void 0);
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
7007
|
+
|
|
7008
|
+
// src/tools/browser.ts
|
|
7009
|
+
var browserSchema = Type16.Object({
|
|
7010
|
+
action: Type16.Union(
|
|
7011
|
+
[
|
|
7012
|
+
Type16.Literal("open_url"),
|
|
7013
|
+
Type16.Literal("evaluate_javascript"),
|
|
7014
|
+
Type16.Literal("find_in_page"),
|
|
7015
|
+
Type16.Literal("stop_find_in_page"),
|
|
7016
|
+
Type16.Literal("reload"),
|
|
7017
|
+
Type16.Literal("go_back"),
|
|
7018
|
+
Type16.Literal("go_forward"),
|
|
7019
|
+
Type16.Literal("get_state"),
|
|
7020
|
+
Type16.Literal("screenshot"),
|
|
7021
|
+
Type16.Literal("set_viewport"),
|
|
7022
|
+
Type16.Literal("snapshot"),
|
|
7023
|
+
Type16.Literal("click"),
|
|
7024
|
+
Type16.Literal("fill"),
|
|
7025
|
+
Type16.Literal("press"),
|
|
7026
|
+
Type16.Literal("hover"),
|
|
7027
|
+
Type16.Literal("wait_for"),
|
|
7028
|
+
Type16.Literal("get_console_logs")
|
|
7029
|
+
],
|
|
7030
|
+
{
|
|
7031
|
+
description: "Browser action. Navigation: open_url, reload, go_back, go_forward, get_state. Inspection: snapshot, screenshot, get_console_logs, find_in_page, stop_find_in_page. Interaction: click, fill, press, hover, wait_for, evaluate_javascript. Layout: set_viewport."
|
|
7032
|
+
}
|
|
7033
|
+
),
|
|
7034
|
+
url: Type16.Optional(
|
|
7035
|
+
Type16.String({
|
|
7036
|
+
description: "URL to open in the Browser tab preview (required for open_url)."
|
|
7037
|
+
})
|
|
7038
|
+
),
|
|
7039
|
+
script: Type16.Optional(
|
|
7040
|
+
Type16.String({
|
|
7041
|
+
description: "JavaScript to execute in the preview page context (required for evaluate_javascript)."
|
|
7042
|
+
})
|
|
7043
|
+
),
|
|
7044
|
+
text: Type16.Optional(
|
|
7045
|
+
Type16.String({
|
|
7046
|
+
description: "Text to find in the page (required for find_in_page)."
|
|
7047
|
+
})
|
|
7048
|
+
),
|
|
7049
|
+
forward: Type16.Optional(
|
|
7050
|
+
Type16.Boolean({
|
|
7051
|
+
description: "Search forward through matches for find_in_page (default true)."
|
|
7052
|
+
})
|
|
7053
|
+
),
|
|
7054
|
+
match_case: Type16.Optional(
|
|
7055
|
+
Type16.Boolean({
|
|
7056
|
+
description: "Case-sensitive search for find_in_page (default false)."
|
|
7057
|
+
})
|
|
7058
|
+
),
|
|
7059
|
+
wait_for_load_ms: Type16.Optional(
|
|
7060
|
+
Type16.Number({
|
|
7061
|
+
description: "Milliseconds to wait for the dom-ready event after navigation (default 5000, max 30000)."
|
|
7062
|
+
})
|
|
7063
|
+
),
|
|
7064
|
+
preset: Type16.Optional(
|
|
7065
|
+
Type16.Union([Type16.Literal("desktop"), Type16.Literal("tablet"), Type16.Literal("mobile")], {
|
|
7066
|
+
description: "Viewport preset for set_viewport."
|
|
7067
|
+
})
|
|
7068
|
+
),
|
|
7069
|
+
format: Type16.Optional(
|
|
7070
|
+
Type16.Union([Type16.Literal("png"), Type16.Literal("jpeg")], {
|
|
7071
|
+
description: "Image format for screenshot (default png)."
|
|
7072
|
+
})
|
|
7073
|
+
),
|
|
7074
|
+
ref: Type16.Optional(
|
|
7075
|
+
Type16.String({
|
|
7076
|
+
description: "Element ref from snapshot for click, fill, or hover (e.g. @e1)."
|
|
7077
|
+
})
|
|
7078
|
+
),
|
|
7079
|
+
value: Type16.Optional(
|
|
7080
|
+
Type16.String({
|
|
7081
|
+
description: "Value to type for fill."
|
|
7082
|
+
})
|
|
7083
|
+
),
|
|
7084
|
+
key: Type16.Optional(
|
|
7085
|
+
Type16.String({
|
|
7086
|
+
description: "Key to press for press (e.g. Enter, Tab, Escape)."
|
|
7087
|
+
})
|
|
7088
|
+
),
|
|
7089
|
+
selector: Type16.Optional(
|
|
7090
|
+
Type16.String({
|
|
7091
|
+
description: "CSS selector to wait for in wait_for."
|
|
7092
|
+
})
|
|
7093
|
+
),
|
|
7094
|
+
timeout_ms: Type16.Optional(
|
|
7095
|
+
Type16.Number({
|
|
7096
|
+
description: "Timeout in milliseconds for wait_for (default 10000, max 60000)."
|
|
7097
|
+
})
|
|
7098
|
+
),
|
|
7099
|
+
interactive_only: Type16.Optional(
|
|
7100
|
+
Type16.Boolean({
|
|
7101
|
+
description: "Only include interactive elements in snapshot (default false)."
|
|
7102
|
+
})
|
|
7103
|
+
),
|
|
7104
|
+
max_depth: Type16.Optional(
|
|
7105
|
+
Type16.Number({
|
|
7106
|
+
description: "Maximum DOM depth for snapshot (default 15)."
|
|
7107
|
+
})
|
|
7108
|
+
),
|
|
7109
|
+
since_ms: Type16.Optional(
|
|
7110
|
+
Type16.Number({
|
|
7111
|
+
description: "Only return console logs from the last N milliseconds for get_console_logs."
|
|
7112
|
+
})
|
|
7113
|
+
),
|
|
7114
|
+
levels: Type16.Optional(
|
|
7115
|
+
Type16.Array(Type16.String(), {
|
|
7116
|
+
description: 'Console levels to include for get_console_logs (e.g. ["error", "warn"]).'
|
|
7117
|
+
})
|
|
7118
|
+
),
|
|
7119
|
+
clear: Type16.Optional(
|
|
7120
|
+
Type16.Boolean({
|
|
7121
|
+
description: "Clear buffered console logs after reading for get_console_logs (default false)."
|
|
7122
|
+
})
|
|
7123
|
+
)
|
|
7124
|
+
});
|
|
7125
|
+
var pendingBrowserTasks = /* @__PURE__ */ new Map();
|
|
7126
|
+
var bufferedWebviewIds = /* @__PURE__ */ new Map();
|
|
7127
|
+
var pendingBrowserActionResults = /* @__PURE__ */ new Map();
|
|
7128
|
+
var bufferedBrowserActionResults = /* @__PURE__ */ new Map();
|
|
7129
|
+
var DEFAULT_WAIT_FOR_LOAD_MS = 5e3;
|
|
7130
|
+
var MAX_WAIT_FOR_LOAD_MS = 3e4;
|
|
7131
|
+
var DEFAULT_WEBVIEW_READY_TIMEOUT_MS = 2e4;
|
|
7132
|
+
var DEFAULT_WAIT_FOR_TIMEOUT_MS = 1e4;
|
|
7133
|
+
var MAX_WAIT_FOR_TIMEOUT_MS = 6e4;
|
|
7134
|
+
var DEFAULT_BROWSER_ACTION_TIMEOUT_MS = 2e4;
|
|
7135
|
+
function submitBrowserWebviewReady(toolCallId, webviewId) {
|
|
7136
|
+
const pending = pendingBrowserTasks.get(toolCallId);
|
|
7137
|
+
if (pending) {
|
|
7138
|
+
pending.resolve(webviewId);
|
|
7139
|
+
pendingBrowserTasks.delete(toolCallId);
|
|
7140
|
+
return true;
|
|
7141
|
+
}
|
|
7142
|
+
bufferedWebviewIds.set(toolCallId, webviewId);
|
|
7143
|
+
return true;
|
|
7144
|
+
}
|
|
7145
|
+
function submitBrowserActionResult(toolCallId, result) {
|
|
7146
|
+
const pending = pendingBrowserActionResults.get(toolCallId);
|
|
7147
|
+
if (pending) {
|
|
7148
|
+
pending.resolve(result);
|
|
7149
|
+
pendingBrowserActionResults.delete(toolCallId);
|
|
7150
|
+
return true;
|
|
7151
|
+
}
|
|
7152
|
+
bufferedBrowserActionResults.set(toolCallId, result);
|
|
7153
|
+
return true;
|
|
7154
|
+
}
|
|
7155
|
+
function cancelPendingBrowserTask(toolCallId) {
|
|
7156
|
+
const pending = pendingBrowserTasks.get(toolCallId);
|
|
7157
|
+
if (!pending) return false;
|
|
7158
|
+
pending.reject(new Error("Browser tool execution cancelled"));
|
|
7159
|
+
pendingBrowserTasks.delete(toolCallId);
|
|
7160
|
+
return true;
|
|
7161
|
+
}
|
|
7162
|
+
function cancelPendingBrowserActionResult(toolCallId) {
|
|
7163
|
+
const pending = pendingBrowserActionResults.get(toolCallId);
|
|
7164
|
+
if (!pending) return false;
|
|
7165
|
+
pending.reject(new Error("Browser tool execution cancelled"));
|
|
7166
|
+
pendingBrowserActionResults.delete(toolCallId);
|
|
7167
|
+
return true;
|
|
7168
|
+
}
|
|
7169
|
+
function normalizeUrl(url) {
|
|
7170
|
+
const trimmed = url.trim();
|
|
7171
|
+
if (!trimmed) {
|
|
7172
|
+
throw new Error("url must not be empty");
|
|
7173
|
+
}
|
|
7174
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
7175
|
+
return trimmed;
|
|
7176
|
+
}
|
|
7177
|
+
return `http://${trimmed}`;
|
|
7178
|
+
}
|
|
7179
|
+
function waitForPreviewWebview(toolCallId, signal, timeoutMs = DEFAULT_WEBVIEW_READY_TIMEOUT_MS) {
|
|
7180
|
+
const buffered = bufferedWebviewIds.get(toolCallId);
|
|
7181
|
+
if (buffered !== void 0) {
|
|
7182
|
+
bufferedWebviewIds.delete(toolCallId);
|
|
7183
|
+
return Promise.resolve(buffered);
|
|
7184
|
+
}
|
|
7185
|
+
return new Promise((resolve7, reject) => {
|
|
7186
|
+
if (signal?.aborted) {
|
|
7187
|
+
reject(new Error("Operation aborted"));
|
|
7188
|
+
return;
|
|
7189
|
+
}
|
|
7190
|
+
let timeoutHandle = null;
|
|
7191
|
+
const pending = pendingBrowserTasks.get(toolCallId);
|
|
7192
|
+
const onAbort = () => {
|
|
7193
|
+
cancelPendingBrowserTask(toolCallId);
|
|
7194
|
+
};
|
|
7195
|
+
function cleanup() {
|
|
7196
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7197
|
+
if (timeoutHandle) {
|
|
7198
|
+
clearTimeout(timeoutHandle);
|
|
7199
|
+
}
|
|
7200
|
+
}
|
|
7201
|
+
const wrappedResolve = (webviewId) => {
|
|
7202
|
+
cleanup();
|
|
7203
|
+
resolve7(webviewId);
|
|
7204
|
+
};
|
|
7205
|
+
const wrappedReject = (error) => {
|
|
7206
|
+
cleanup();
|
|
7207
|
+
reject(error);
|
|
7208
|
+
};
|
|
7209
|
+
if (pending) {
|
|
7210
|
+
pending.resolve = wrappedResolve;
|
|
7211
|
+
pending.reject = wrappedReject;
|
|
7212
|
+
} else {
|
|
7213
|
+
pendingBrowserTasks.set(toolCallId, {
|
|
7214
|
+
resolve: wrappedResolve,
|
|
7215
|
+
reject: wrappedReject
|
|
7216
|
+
});
|
|
7217
|
+
}
|
|
7218
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7219
|
+
timeoutHandle = setTimeout(() => {
|
|
7220
|
+
if (!pendingBrowserTasks.has(toolCallId)) return;
|
|
7221
|
+
pendingBrowserTasks.delete(toolCallId);
|
|
7222
|
+
wrappedReject(
|
|
7223
|
+
new Error(
|
|
7224
|
+
`browser tool timed out after ${timeoutMs}ms waiting for the Browser tab preview webview`
|
|
7225
|
+
)
|
|
7226
|
+
);
|
|
7227
|
+
}, timeoutMs);
|
|
7228
|
+
});
|
|
7229
|
+
}
|
|
7230
|
+
function waitForBrowserActionResult(toolCallId, signal, timeoutMs = DEFAULT_BROWSER_ACTION_TIMEOUT_MS) {
|
|
7231
|
+
const buffered = bufferedBrowserActionResults.get(toolCallId);
|
|
7232
|
+
if (buffered) {
|
|
7233
|
+
bufferedBrowserActionResults.delete(toolCallId);
|
|
7234
|
+
return Promise.resolve(buffered);
|
|
7235
|
+
}
|
|
7236
|
+
return new Promise((resolve7, reject) => {
|
|
7237
|
+
if (signal?.aborted) {
|
|
7238
|
+
reject(new Error("Operation aborted"));
|
|
7239
|
+
return;
|
|
7240
|
+
}
|
|
7241
|
+
let timeoutHandle = null;
|
|
7242
|
+
const onAbort = () => {
|
|
7243
|
+
cancelPendingBrowserActionResult(toolCallId);
|
|
7244
|
+
};
|
|
7245
|
+
function cleanup() {
|
|
7246
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7247
|
+
if (timeoutHandle) {
|
|
7248
|
+
clearTimeout(timeoutHandle);
|
|
7249
|
+
}
|
|
7250
|
+
}
|
|
7251
|
+
const wrappedResolve = (result) => {
|
|
7252
|
+
cleanup();
|
|
7253
|
+
resolve7(result);
|
|
7254
|
+
};
|
|
7255
|
+
const wrappedReject = (error) => {
|
|
7256
|
+
cleanup();
|
|
7257
|
+
reject(error);
|
|
7258
|
+
};
|
|
7259
|
+
pendingBrowserActionResults.set(toolCallId, {
|
|
7260
|
+
resolve: wrappedResolve,
|
|
7261
|
+
reject: wrappedReject
|
|
7262
|
+
});
|
|
7263
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7264
|
+
timeoutHandle = setTimeout(() => {
|
|
7265
|
+
if (!pendingBrowserActionResults.has(toolCallId)) return;
|
|
7266
|
+
pendingBrowserActionResults.delete(toolCallId);
|
|
7267
|
+
wrappedReject(
|
|
7268
|
+
new Error(
|
|
7269
|
+
`browser tool timed out after ${timeoutMs}ms waiting for the Browser tab host action`
|
|
7270
|
+
)
|
|
7271
|
+
);
|
|
7272
|
+
}, timeoutMs);
|
|
7273
|
+
});
|
|
7274
|
+
}
|
|
7275
|
+
function logBrowserPreviewFailure(action, webviewId, webview, error, context) {
|
|
7276
|
+
console.error(`[browser] ${action} failed`, {
|
|
7277
|
+
webview_id: webviewId,
|
|
7278
|
+
current_url: webview.url,
|
|
7279
|
+
...context,
|
|
7280
|
+
error: error instanceof Error ? error.message : String(error),
|
|
7281
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
7282
|
+
});
|
|
7283
|
+
}
|
|
7284
|
+
async function executeBrowserAction(webviewId, input) {
|
|
7285
|
+
const webview = getBrowserViewById(webviewId);
|
|
7286
|
+
if (!webview) {
|
|
7287
|
+
throw new Error(`Browser preview webview ${webviewId} is not available`);
|
|
7288
|
+
}
|
|
7289
|
+
switch (input.action) {
|
|
7290
|
+
case "open_url": {
|
|
7291
|
+
if (!input.url?.trim()) {
|
|
7292
|
+
throw new Error("url is required for open_url");
|
|
7293
|
+
}
|
|
7294
|
+
const url = normalizeUrl(input.url);
|
|
7295
|
+
const waitMs = Math.min(
|
|
7296
|
+
Math.max(input.wait_for_load_ms ?? DEFAULT_WAIT_FOR_LOAD_MS, 0),
|
|
7297
|
+
MAX_WAIT_FOR_LOAD_MS
|
|
7298
|
+
);
|
|
7299
|
+
const domReadyPromise = waitForBrowserEvent(webview, "dom-ready", waitMs);
|
|
7300
|
+
webview.loadURL(url);
|
|
7301
|
+
let domReady = false;
|
|
7302
|
+
try {
|
|
7303
|
+
await domReadyPromise;
|
|
7304
|
+
domReady = true;
|
|
7305
|
+
} catch {
|
|
7306
|
+
}
|
|
7307
|
+
return {
|
|
7308
|
+
action: "open_url",
|
|
7309
|
+
url,
|
|
7310
|
+
webview_id: webviewId,
|
|
7311
|
+
current_url: webview.url,
|
|
7312
|
+
dom_ready: domReady
|
|
7313
|
+
};
|
|
7314
|
+
}
|
|
7315
|
+
case "evaluate_javascript": {
|
|
7316
|
+
if (!input.script?.trim()) {
|
|
7317
|
+
throw new Error("script is required for evaluate_javascript");
|
|
7318
|
+
}
|
|
7319
|
+
if (!webview.rpc?.request?.evaluateJavascriptWithResponse) {
|
|
7320
|
+
throw new Error("Browser preview webview RPC is not available for JavaScript execution");
|
|
7321
|
+
}
|
|
7322
|
+
const result = await webview.rpc.request.evaluateJavascriptWithResponse({
|
|
7323
|
+
script: input.script
|
|
7324
|
+
});
|
|
7325
|
+
return {
|
|
7326
|
+
action: "evaluate_javascript",
|
|
7327
|
+
webview_id: webviewId,
|
|
7328
|
+
result
|
|
7329
|
+
};
|
|
7330
|
+
}
|
|
7331
|
+
case "find_in_page": {
|
|
7332
|
+
if (!input.text?.trim()) {
|
|
7333
|
+
throw new Error("text is required for find_in_page");
|
|
7334
|
+
}
|
|
7335
|
+
webview.findInPage(input.text, {
|
|
7336
|
+
forward: input.forward ?? true,
|
|
7337
|
+
matchCase: input.match_case ?? false
|
|
7338
|
+
});
|
|
7339
|
+
return {
|
|
7340
|
+
action: "find_in_page",
|
|
7341
|
+
webview_id: webviewId,
|
|
7342
|
+
text: input.text,
|
|
7343
|
+
forward: input.forward ?? true,
|
|
7344
|
+
match_case: input.match_case ?? false
|
|
7345
|
+
};
|
|
7346
|
+
}
|
|
7347
|
+
case "stop_find_in_page": {
|
|
7348
|
+
webview.stopFindInPage();
|
|
7349
|
+
return {
|
|
7350
|
+
action: "stop_find_in_page",
|
|
7351
|
+
webview_id: webviewId
|
|
7352
|
+
};
|
|
7353
|
+
}
|
|
7354
|
+
case "snapshot": {
|
|
7355
|
+
const result = await invokePreviewApi(webview, "snapshot", {
|
|
7356
|
+
interactive_only: input.interactive_only ?? false,
|
|
7357
|
+
max_depth: input.max_depth ?? 15
|
|
7358
|
+
});
|
|
7359
|
+
return {
|
|
7360
|
+
action: "snapshot",
|
|
7361
|
+
webview_id: webviewId,
|
|
7362
|
+
...typeof result === "object" && result !== null ? result : { result }
|
|
7363
|
+
};
|
|
7364
|
+
}
|
|
7365
|
+
case "click": {
|
|
7366
|
+
if (!input.ref?.trim()) {
|
|
7367
|
+
throw new Error("ref is required for click");
|
|
7368
|
+
}
|
|
7369
|
+
const result = await invokePreviewApi(webview, "click", { ref: input.ref });
|
|
7370
|
+
return {
|
|
7371
|
+
action: "click",
|
|
7372
|
+
webview_id: webviewId,
|
|
7373
|
+
ref: input.ref,
|
|
7374
|
+
result
|
|
7375
|
+
};
|
|
7376
|
+
}
|
|
7377
|
+
case "fill": {
|
|
7378
|
+
if (!input.ref?.trim()) {
|
|
7379
|
+
throw new Error("ref is required for fill");
|
|
7380
|
+
}
|
|
7381
|
+
if (input.value === void 0) {
|
|
7382
|
+
throw new Error("value is required for fill");
|
|
7383
|
+
}
|
|
7384
|
+
const result = await invokePreviewApi(webview, "fill", {
|
|
7385
|
+
ref: input.ref,
|
|
7386
|
+
value: input.value
|
|
7387
|
+
});
|
|
7388
|
+
return {
|
|
7389
|
+
action: "fill",
|
|
7390
|
+
webview_id: webviewId,
|
|
7391
|
+
ref: input.ref,
|
|
7392
|
+
value: input.value,
|
|
7393
|
+
result
|
|
7394
|
+
};
|
|
7395
|
+
}
|
|
7396
|
+
case "press": {
|
|
7397
|
+
if (!input.key?.trim()) {
|
|
7398
|
+
throw new Error("key is required for press");
|
|
7399
|
+
}
|
|
7400
|
+
const result = await invokePreviewApi(webview, "press", { key: input.key });
|
|
7401
|
+
return {
|
|
7402
|
+
action: "press",
|
|
7403
|
+
webview_id: webviewId,
|
|
7404
|
+
key: input.key,
|
|
7405
|
+
result
|
|
7406
|
+
};
|
|
7407
|
+
}
|
|
7408
|
+
case "hover": {
|
|
7409
|
+
if (!input.ref?.trim()) {
|
|
7410
|
+
throw new Error("ref is required for hover");
|
|
7411
|
+
}
|
|
7412
|
+
const result = await invokePreviewApi(webview, "hover", { ref: input.ref });
|
|
7413
|
+
return {
|
|
7414
|
+
action: "hover",
|
|
7415
|
+
webview_id: webviewId,
|
|
7416
|
+
ref: input.ref,
|
|
7417
|
+
result
|
|
7418
|
+
};
|
|
7419
|
+
}
|
|
7420
|
+
case "wait_for": {
|
|
7421
|
+
if (!input.selector?.trim() && !input.text?.trim()) {
|
|
7422
|
+
throw new Error("selector or text is required for wait_for");
|
|
7423
|
+
}
|
|
7424
|
+
const timeoutMs = Math.min(
|
|
7425
|
+
Math.max(input.timeout_ms ?? DEFAULT_WAIT_FOR_TIMEOUT_MS, 0),
|
|
7426
|
+
MAX_WAIT_FOR_TIMEOUT_MS
|
|
7427
|
+
);
|
|
7428
|
+
const result = await invokePreviewApi(webview, "waitFor", {
|
|
7429
|
+
selector: input.selector,
|
|
7430
|
+
text: input.text,
|
|
7431
|
+
timeout_ms: timeoutMs
|
|
7432
|
+
});
|
|
7433
|
+
return {
|
|
7434
|
+
action: "wait_for",
|
|
7435
|
+
webview_id: webviewId,
|
|
7436
|
+
selector: input.selector,
|
|
7437
|
+
text: input.text,
|
|
7438
|
+
timeout_ms: timeoutMs,
|
|
7439
|
+
result
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
case "get_console_logs": {
|
|
7443
|
+
const result = await invokePreviewApi(webview, "getConsoleLogs", {
|
|
7444
|
+
since_ms: input.since_ms ?? 0,
|
|
7445
|
+
levels: input.levels,
|
|
7446
|
+
clear: input.clear ?? false
|
|
7447
|
+
});
|
|
7448
|
+
return {
|
|
7449
|
+
action: "get_console_logs",
|
|
7450
|
+
webview_id: webviewId,
|
|
7451
|
+
...typeof result === "object" && result !== null ? result : { result }
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
case "screenshot": {
|
|
7455
|
+
const format = input.format ?? "png";
|
|
7456
|
+
const nativeCapture = await captureNativeBrowserScreenshot(webviewId, format);
|
|
7457
|
+
if (nativeCapture.ok) {
|
|
7458
|
+
return {
|
|
7459
|
+
action: "screenshot",
|
|
7460
|
+
webview_id: webviewId,
|
|
7461
|
+
...nativeCapture.result
|
|
7462
|
+
};
|
|
7463
|
+
}
|
|
7464
|
+
try {
|
|
7465
|
+
const result = await invokePreviewApi(webview, "takeScreenshot", { format });
|
|
7466
|
+
return {
|
|
7467
|
+
action: "screenshot",
|
|
7468
|
+
webview_id: webviewId,
|
|
7469
|
+
capture_method: "svg-foreign-object",
|
|
7470
|
+
native_capture_unavailable: nativeCapture.reason,
|
|
7471
|
+
...typeof result === "object" && result !== null ? result : { result }
|
|
7472
|
+
};
|
|
7473
|
+
} catch (error) {
|
|
7474
|
+
logBrowserPreviewFailure("screenshot", webviewId, webview, error, {
|
|
7475
|
+
format,
|
|
7476
|
+
capture_method: "svg-foreign-object",
|
|
7477
|
+
native_capture_unavailable: nativeCapture.reason,
|
|
7478
|
+
troubleshooting: "Native macOS capture uses screencapture on the visible preview rect. If native capture fails, grant Screen Recording permission to Tarsk and restart the app. SVG fallback can fail on large third-party pages. Check tarsk-logs.txt for [browser-preview] takeScreenshot failed entries."
|
|
7479
|
+
});
|
|
7480
|
+
throw error;
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
default:
|
|
7484
|
+
throw new Error(`Unsupported browser action: ${String(input.action)}`);
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
function formatBrowserResult(payload) {
|
|
7488
|
+
if (payload.action === "screenshot") {
|
|
7489
|
+
const display = stripScreenshotPayload(payload);
|
|
7490
|
+
return {
|
|
7491
|
+
text: buildGenerateImageToolText(display),
|
|
7492
|
+
details: payload
|
|
7493
|
+
};
|
|
7494
|
+
}
|
|
7495
|
+
return {
|
|
7496
|
+
text: JSON.stringify(payload, null, 2),
|
|
7497
|
+
details: payload
|
|
7498
|
+
};
|
|
7499
|
+
}
|
|
7500
|
+
function stripScreenshotPayload(payload) {
|
|
7501
|
+
const display = {};
|
|
7502
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
7503
|
+
if (key === "image_data") continue;
|
|
7504
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
7505
|
+
display[key] = value;
|
|
7506
|
+
}
|
|
7507
|
+
}
|
|
7508
|
+
return display;
|
|
7509
|
+
}
|
|
7510
|
+
async function persistScreenshotResult(payload, options) {
|
|
7511
|
+
const imageData = payload.image_data;
|
|
7512
|
+
if (typeof imageData !== "string" || !imageData) {
|
|
7513
|
+
return payload;
|
|
7514
|
+
}
|
|
7515
|
+
const sizeInBytes = decodeBase64ImageData(imageData).length;
|
|
7516
|
+
const { image_data: _removed, ...rest } = payload;
|
|
7517
|
+
if (!options.cwd) {
|
|
7518
|
+
return { ...rest, sizeInBytes };
|
|
7519
|
+
}
|
|
7520
|
+
const format = payload.format === "jpeg" ? "jpeg" : "png";
|
|
7521
|
+
const extension = format === "jpeg" ? "jpg" : "png";
|
|
7522
|
+
const id = Math.random().toString(36).slice(2, 8);
|
|
7523
|
+
const relativePath = buildTmpImagePath(`screenshot-${id}.${extension}`);
|
|
7524
|
+
const saved = await saveToolImage({
|
|
7525
|
+
imageData,
|
|
7526
|
+
cwd: options.cwd,
|
|
7527
|
+
relativePath,
|
|
7528
|
+
threadId: options.threadId,
|
|
7529
|
+
serverOrigin: options.serverOrigin
|
|
7530
|
+
});
|
|
7531
|
+
return {
|
|
7532
|
+
...rest,
|
|
7533
|
+
savedPath: saved.savedPath,
|
|
7534
|
+
sizeInBytes: saved.sizeInBytes,
|
|
7535
|
+
...saved.imageUrl ? { imageUrl: saved.imageUrl } : {}
|
|
7536
|
+
};
|
|
7537
|
+
}
|
|
7538
|
+
var BROWSER_TOOL_DESCRIPTION = "Control the in-app Browser. Use to read information from websites \u2014 open_url then snapshot for page content and structure, get_state for URL/title, find_in_page to locate text, evaluate_javascript to extract data, or screenshot for visual reference. Also use for interacting with pages: click, fill, press, hover, wait_for. Actions: open_url, reload, go_back, go_forward, get_state, set_viewport, screenshot, snapshot, click, fill, press, hover, wait_for, get_console_logs, evaluate_javascript, find_in_page, stop_find_in_page. Use snapshot to get @eN element refs, then click/fill/hover by ref. The Browser tab opens automatically. Start the dev server first when testing localhost apps.";
|
|
7539
|
+
function createBrowserTool(options = {}) {
|
|
7540
|
+
const serverOrigin = options.serverOrigin ?? getServerOrigin();
|
|
7541
|
+
return {
|
|
7542
|
+
name: "browser",
|
|
7543
|
+
label: "browser",
|
|
7544
|
+
description: BROWSER_TOOL_DESCRIPTION,
|
|
7545
|
+
parameters: browserSchema,
|
|
7546
|
+
execute: async (toolCallId, input, signal) => {
|
|
7547
|
+
return withAbortSignal(signal, async () => {
|
|
7548
|
+
const electrobun = await loadElectrobunBrowserView();
|
|
7549
|
+
if (!electrobun) {
|
|
7550
|
+
throw new Error(
|
|
7551
|
+
"browser tool is only available in the Tarsk desktop app (Electrobun), not in server-only mode"
|
|
7552
|
+
);
|
|
7553
|
+
}
|
|
7554
|
+
if (isBrowserHostAction(input.action)) {
|
|
7555
|
+
const result2 = await waitForBrowserActionResult(toolCallId, signal);
|
|
7556
|
+
const formatted2 = formatBrowserResult(result2);
|
|
7557
|
+
return {
|
|
7558
|
+
content: [{ type: "text", text: formatted2.text }],
|
|
7559
|
+
details: formatted2.details
|
|
7560
|
+
};
|
|
7561
|
+
}
|
|
7562
|
+
const webviewId = await waitForPreviewWebview(toolCallId, signal);
|
|
7563
|
+
let result = await executeBrowserAction(webviewId, input);
|
|
7564
|
+
if (input.action === "screenshot") {
|
|
7565
|
+
result = await persistScreenshotResult(result, {
|
|
7566
|
+
cwd: options.cwd,
|
|
7567
|
+
threadId: options.threadId,
|
|
7568
|
+
serverOrigin
|
|
7569
|
+
});
|
|
7570
|
+
}
|
|
7571
|
+
const formatted = formatBrowserResult(result);
|
|
7572
|
+
return {
|
|
7573
|
+
content: [{ type: "text", text: formatted.text }],
|
|
7574
|
+
details: typeof formatted.details.imageUrl === "string" ? { imageUrl: formatted.details.imageUrl } : formatted.details
|
|
7575
|
+
};
|
|
7576
|
+
});
|
|
7577
|
+
}
|
|
7578
|
+
};
|
|
7579
|
+
}
|
|
7580
|
+
var browserTool = createBrowserTool();
|
|
7581
|
+
|
|
6634
7582
|
// src/tools/execute-browser-js.ts
|
|
6635
7583
|
init_tarsk_debug();
|
|
6636
|
-
import { Type as
|
|
6637
|
-
var runWebWorkerSchema =
|
|
6638
|
-
intention:
|
|
7584
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
7585
|
+
var runWebWorkerSchema = Type17.Object({
|
|
7586
|
+
intention: Type17.String({
|
|
6639
7587
|
description: "A short description of what this code execution is trying to accomplish."
|
|
6640
7588
|
}),
|
|
6641
|
-
code:
|
|
7589
|
+
code: Type17.String({
|
|
6642
7590
|
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;`."
|
|
6643
7591
|
})
|
|
6644
7592
|
});
|
|
@@ -6685,11 +7633,11 @@ function createRunWebWorkerTool(options) {
|
|
|
6685
7633
|
if (signal?.aborted) {
|
|
6686
7634
|
throw new Error("Operation aborted");
|
|
6687
7635
|
}
|
|
6688
|
-
const result = await new Promise((
|
|
7636
|
+
const result = await new Promise((resolve7, reject) => {
|
|
6689
7637
|
const earlyResult = bufferedResults.get(toolCallId);
|
|
6690
7638
|
if (earlyResult !== void 0) {
|
|
6691
7639
|
bufferedResults.delete(toolCallId);
|
|
6692
|
-
|
|
7640
|
+
resolve7(earlyResult);
|
|
6693
7641
|
return;
|
|
6694
7642
|
}
|
|
6695
7643
|
let timeoutHandle = null;
|
|
@@ -6704,7 +7652,7 @@ function createRunWebWorkerTool(options) {
|
|
|
6704
7652
|
}
|
|
6705
7653
|
const wrappedResolve = (value) => {
|
|
6706
7654
|
cleanup();
|
|
6707
|
-
|
|
7655
|
+
resolve7(value);
|
|
6708
7656
|
};
|
|
6709
7657
|
const wrappedReject = (error) => {
|
|
6710
7658
|
cleanup();
|
|
@@ -6772,7 +7720,7 @@ function looksLikeMissingReturn(code) {
|
|
|
6772
7720
|
var runWebWorkerTool = createRunWebWorkerTool();
|
|
6773
7721
|
|
|
6774
7722
|
// src/tools/fetch.ts
|
|
6775
|
-
import { Type as
|
|
7723
|
+
import { Type as Type18 } from "@sinclair/typebox";
|
|
6776
7724
|
|
|
6777
7725
|
// src/features/web-fetch/content-processor.ts
|
|
6778
7726
|
import { completeSimple as completeSimple2 } from "@earendil-works/pi-ai";
|
|
@@ -6803,7 +7751,7 @@ function runCommandTimed(stepName, command, args2, cwd) {
|
|
|
6803
7751
|
let tFirstIo = null;
|
|
6804
7752
|
let tExit = null;
|
|
6805
7753
|
logSubprocessMark(scope, stepName, "spawn-call", 0);
|
|
6806
|
-
return new Promise((
|
|
7754
|
+
return new Promise((resolve7, reject) => {
|
|
6807
7755
|
const proc = spawnProcess(command, args2, { cwd });
|
|
6808
7756
|
proc.on("spawn", () => {
|
|
6809
7757
|
tSpawnEvent = Date.now();
|
|
@@ -6842,7 +7790,7 @@ function runCommandTimed(stepName, command, args2, cwd) {
|
|
|
6842
7790
|
console.log(
|
|
6843
7791
|
`${GITOPS_TIMING_PREFIX} subprocess ${scope} step=${stepName} summary total=${tClose - t0}ms` + (preSpawnMs !== null ? ` pre-spawn=${preSpawnMs}ms` : "") + (processActiveMs !== null ? ` process-active=${processActiveMs}ms` : "") + (exitToCloseMs !== null ? ` exit-to-close=${exitToCloseMs}ms` : "") + ` cmd=${command} ${args2.join(" ")}`
|
|
6844
7792
|
);
|
|
6845
|
-
|
|
7793
|
+
resolve7({ code, stdout, stderr });
|
|
6846
7794
|
});
|
|
6847
7795
|
proc.on("error", (error) => {
|
|
6848
7796
|
logSubprocessMark(scope, stepName, "spawn-error", Date.now() - t0);
|
|
@@ -6875,13 +7823,13 @@ function startEventLoopLagMonitor(threadId) {
|
|
|
6875
7823
|
|
|
6876
7824
|
// src/features/git/git.utils.ts
|
|
6877
7825
|
import { existsSync as existsSync8, readFileSync as readFileSync3, statSync as statSync5 } from "fs";
|
|
6878
|
-
import { isAbsolute as isAbsolute2, normalize as normalize2, resolve, join as
|
|
7826
|
+
import { isAbsolute as isAbsolute2, normalize as normalize2, resolve, join as join12 } from "path";
|
|
6879
7827
|
|
|
6880
7828
|
// src/core/paths.ts
|
|
6881
|
-
import { join as
|
|
7829
|
+
import { join as join11 } from "path";
|
|
6882
7830
|
import { homedir as homedir4 } from "os";
|
|
6883
|
-
var APP_SUPPORT_DIR =
|
|
6884
|
-
var DATA_DIR =
|
|
7831
|
+
var APP_SUPPORT_DIR = join11(homedir4(), "Library", "Application Support", "Tarsk");
|
|
7832
|
+
var DATA_DIR = join11(APP_SUPPORT_DIR, "data");
|
|
6885
7833
|
function getDataDir() {
|
|
6886
7834
|
return DATA_DIR;
|
|
6887
7835
|
}
|
|
@@ -7149,7 +8097,7 @@ async function getCurrentBranch(gitRoot) {
|
|
|
7149
8097
|
]);
|
|
7150
8098
|
return result.stdout.trim();
|
|
7151
8099
|
}
|
|
7152
|
-
return new Promise((
|
|
8100
|
+
return new Promise((resolve7) => {
|
|
7153
8101
|
const proc = spawnProcess("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
|
|
7154
8102
|
let out = "";
|
|
7155
8103
|
if (proc.stdout) {
|
|
@@ -7158,9 +8106,9 @@ async function getCurrentBranch(gitRoot) {
|
|
|
7158
8106
|
});
|
|
7159
8107
|
}
|
|
7160
8108
|
proc.on("close", () => {
|
|
7161
|
-
|
|
8109
|
+
resolve7(out.trim());
|
|
7162
8110
|
});
|
|
7163
|
-
proc.on("error", () =>
|
|
8111
|
+
proc.on("error", () => resolve7(""));
|
|
7164
8112
|
});
|
|
7165
8113
|
}
|
|
7166
8114
|
async function getGitDiff(gitRoot) {
|
|
@@ -7237,7 +8185,7 @@ async function getUntrackedFilesDiff(gitRoot) {
|
|
|
7237
8185
|
const parts = [];
|
|
7238
8186
|
const maxFileSize = 1e5;
|
|
7239
8187
|
for (const relPath of untrackedPaths) {
|
|
7240
|
-
const fullPath =
|
|
8188
|
+
const fullPath = join12(gitRoot, relPath);
|
|
7241
8189
|
if (!existsSync8(fullPath)) continue;
|
|
7242
8190
|
try {
|
|
7243
8191
|
if (statSync5(fullPath).isDirectory()) continue;
|
|
@@ -7270,7 +8218,7 @@ async function getGitStatus(gitRoot) {
|
|
|
7270
8218
|
]);
|
|
7271
8219
|
return result.stdout;
|
|
7272
8220
|
}
|
|
7273
|
-
return new Promise((
|
|
8221
|
+
return new Promise((resolve7) => {
|
|
7274
8222
|
const proc = spawnProcess("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
7275
8223
|
cwd: gitRoot
|
|
7276
8224
|
});
|
|
@@ -7280,15 +8228,15 @@ async function getGitStatus(gitRoot) {
|
|
|
7280
8228
|
out += d.toString();
|
|
7281
8229
|
});
|
|
7282
8230
|
}
|
|
7283
|
-
proc.on("close", () =>
|
|
7284
|
-
proc.on("error", () =>
|
|
8231
|
+
proc.on("close", () => resolve7(out));
|
|
8232
|
+
proc.on("error", () => resolve7(""));
|
|
7285
8233
|
});
|
|
7286
8234
|
}
|
|
7287
8235
|
function runGit(stepName, gitRoot, args2) {
|
|
7288
8236
|
if (isGitSubprocessTimingEnabled()) {
|
|
7289
8237
|
return runGitCommandTimed(stepName, gitRoot, args2);
|
|
7290
8238
|
}
|
|
7291
|
-
return new Promise((
|
|
8239
|
+
return new Promise((resolve7) => {
|
|
7292
8240
|
const proc = spawnProcess("git", args2, { cwd: gitRoot });
|
|
7293
8241
|
let stdout = "";
|
|
7294
8242
|
let stderr = "";
|
|
@@ -7302,8 +8250,8 @@ function runGit(stepName, gitRoot, args2) {
|
|
|
7302
8250
|
stderr += d.toString();
|
|
7303
8251
|
});
|
|
7304
8252
|
}
|
|
7305
|
-
proc.on("close", (code) =>
|
|
7306
|
-
proc.on("error", () =>
|
|
8253
|
+
proc.on("close", (code) => resolve7({ code, stdout, stderr }));
|
|
8254
|
+
proc.on("error", () => resolve7({ code: -1, stdout, stderr }));
|
|
7307
8255
|
});
|
|
7308
8256
|
}
|
|
7309
8257
|
async function getStatusPorcelainV2(gitRoot) {
|
|
@@ -7401,7 +8349,7 @@ async function fetchRemoteRefs(gitRoot, refs) {
|
|
|
7401
8349
|
await runGit("fetch-remote-refs", gitRoot, ["fetch", "--no-tags", "origin", ...refs]);
|
|
7402
8350
|
}
|
|
7403
8351
|
function hasUnpushedCommits(gitRoot, currentBranch) {
|
|
7404
|
-
return new Promise((
|
|
8352
|
+
return new Promise((resolve7) => {
|
|
7405
8353
|
const proc = spawnProcess("git", ["log", `origin/${currentBranch}..HEAD`, "--oneline"], {
|
|
7406
8354
|
cwd: gitRoot
|
|
7407
8355
|
});
|
|
@@ -7412,13 +8360,13 @@ function hasUnpushedCommits(gitRoot, currentBranch) {
|
|
|
7412
8360
|
});
|
|
7413
8361
|
}
|
|
7414
8362
|
proc.on("close", () => {
|
|
7415
|
-
|
|
8363
|
+
resolve7(out.trim().length > 0);
|
|
7416
8364
|
});
|
|
7417
|
-
proc.on("error", () =>
|
|
8365
|
+
proc.on("error", () => resolve7(false));
|
|
7418
8366
|
});
|
|
7419
8367
|
}
|
|
7420
8368
|
function hasCommitsAheadOfDefault(gitRoot, defaultBranch) {
|
|
7421
|
-
return new Promise((
|
|
8369
|
+
return new Promise((resolve7) => {
|
|
7422
8370
|
const proc = spawnProcess("git", ["rev-list", "--count", `origin/${defaultBranch}..HEAD`], {
|
|
7423
8371
|
cwd: gitRoot
|
|
7424
8372
|
});
|
|
@@ -7430,12 +8378,12 @@ function hasCommitsAheadOfDefault(gitRoot, defaultBranch) {
|
|
|
7430
8378
|
}
|
|
7431
8379
|
proc.on("close", (code) => {
|
|
7432
8380
|
if (code === 0) {
|
|
7433
|
-
|
|
8381
|
+
resolve7(parseInt(out.trim(), 10) > 0);
|
|
7434
8382
|
} else {
|
|
7435
|
-
|
|
8383
|
+
resolve7(false);
|
|
7436
8384
|
}
|
|
7437
8385
|
});
|
|
7438
|
-
proc.on("error", () =>
|
|
8386
|
+
proc.on("error", () => resolve7(false));
|
|
7439
8387
|
});
|
|
7440
8388
|
}
|
|
7441
8389
|
async function getRemoteOrigin(gitRoot) {
|
|
@@ -7450,7 +8398,7 @@ async function getRemoteOrigin(gitRoot) {
|
|
|
7450
8398
|
}
|
|
7451
8399
|
return "";
|
|
7452
8400
|
}
|
|
7453
|
-
return new Promise((
|
|
8401
|
+
return new Promise((resolve7) => {
|
|
7454
8402
|
const proc = spawnProcess("git", ["remote", "get-url", "origin"], { cwd: gitRoot });
|
|
7455
8403
|
let out = "";
|
|
7456
8404
|
if (proc.stdout) {
|
|
@@ -7460,12 +8408,12 @@ async function getRemoteOrigin(gitRoot) {
|
|
|
7460
8408
|
}
|
|
7461
8409
|
proc.on("close", (code) => {
|
|
7462
8410
|
if (code === 0) {
|
|
7463
|
-
|
|
8411
|
+
resolve7(out.trim());
|
|
7464
8412
|
} else {
|
|
7465
|
-
|
|
8413
|
+
resolve7("");
|
|
7466
8414
|
}
|
|
7467
8415
|
});
|
|
7468
|
-
proc.on("error", () =>
|
|
8416
|
+
proc.on("error", () => resolve7(""));
|
|
7469
8417
|
});
|
|
7470
8418
|
}
|
|
7471
8419
|
async function getGitSyncStatus(gitRoot, branch, options) {
|
|
@@ -7473,20 +8421,20 @@ async function getGitSyncStatus(gitRoot, branch, options) {
|
|
|
7473
8421
|
console.log(`[git-status] Checking sync status for branch: ${branch}`);
|
|
7474
8422
|
if (fetchRemote) {
|
|
7475
8423
|
console.log(`[git-status] Fetching from origin...`);
|
|
7476
|
-
await new Promise((
|
|
8424
|
+
await new Promise((resolve7) => {
|
|
7477
8425
|
const fetchProc = spawnProcess("git", ["fetch", "origin"], { cwd: gitRoot });
|
|
7478
8426
|
fetchProc.on("close", () => {
|
|
7479
8427
|
console.log(`[git-status] \u2713 Fetch completed`);
|
|
7480
|
-
|
|
8428
|
+
resolve7();
|
|
7481
8429
|
});
|
|
7482
8430
|
fetchProc.on("error", () => {
|
|
7483
8431
|
console.log(`[git-status] Fetch error (continuing anyway)`);
|
|
7484
|
-
|
|
8432
|
+
resolve7();
|
|
7485
8433
|
});
|
|
7486
8434
|
});
|
|
7487
8435
|
}
|
|
7488
8436
|
console.log(`[git-status] Checking status against remote tracking branch...`);
|
|
7489
|
-
return new Promise((
|
|
8437
|
+
return new Promise((resolve7) => {
|
|
7490
8438
|
const statusProc = spawnProcess("git", ["status", "-sb"], { cwd: gitRoot });
|
|
7491
8439
|
let statusOut = "";
|
|
7492
8440
|
let statusErr = "";
|
|
@@ -7506,49 +8454,49 @@ async function getGitSyncStatus(gitRoot, branch, options) {
|
|
|
7506
8454
|
const firstLine = output.split("\n")[0] ?? "";
|
|
7507
8455
|
if (!firstLine.includes(branch)) {
|
|
7508
8456
|
console.log(`[git-status] Branch name not found in status output, returning 'Up to date'`);
|
|
7509
|
-
|
|
8457
|
+
resolve7("Up to date");
|
|
7510
8458
|
return;
|
|
7511
8459
|
}
|
|
7512
8460
|
if (!firstLine.includes("...")) {
|
|
7513
8461
|
console.log(`[git-status] No upstream tracking branch detected, returning 'Up to date'`);
|
|
7514
|
-
|
|
8462
|
+
resolve7("Up to date");
|
|
7515
8463
|
return;
|
|
7516
8464
|
}
|
|
7517
8465
|
if (firstLine.includes("ahead") && firstLine.includes("behind")) {
|
|
7518
8466
|
console.log(`[git-status] \u2713 Status: Diverged`);
|
|
7519
|
-
|
|
8467
|
+
resolve7("Diverged");
|
|
7520
8468
|
} else if (firstLine.includes("behind")) {
|
|
7521
8469
|
console.log(`[git-status] \u2713 Status: Behind`);
|
|
7522
|
-
|
|
8470
|
+
resolve7("Behind");
|
|
7523
8471
|
} else if (firstLine.includes("ahead")) {
|
|
7524
8472
|
console.log(`[git-status] \u2713 Status: Ahead`);
|
|
7525
|
-
|
|
8473
|
+
resolve7("Ahead");
|
|
7526
8474
|
} else {
|
|
7527
8475
|
console.log(`[git-status] \u2713 Status: Up to date`);
|
|
7528
|
-
|
|
8476
|
+
resolve7("Up to date");
|
|
7529
8477
|
}
|
|
7530
8478
|
});
|
|
7531
8479
|
statusProc.on("error", () => {
|
|
7532
8480
|
console.log(`[git-status] Error getting status, defaulting to 'Up to date'`);
|
|
7533
|
-
|
|
8481
|
+
resolve7("Up to date");
|
|
7534
8482
|
});
|
|
7535
8483
|
});
|
|
7536
8484
|
}
|
|
7537
8485
|
async function hasRemoteBranch(gitRoot, branch) {
|
|
7538
|
-
return new Promise((
|
|
8486
|
+
return new Promise((resolve7) => {
|
|
7539
8487
|
const proc = spawnProcess("git", ["rev-parse", "--verify", `origin/${branch}`], {
|
|
7540
8488
|
cwd: gitRoot
|
|
7541
8489
|
});
|
|
7542
8490
|
proc.on("close", (code) => {
|
|
7543
|
-
|
|
8491
|
+
resolve7(code === 0);
|
|
7544
8492
|
});
|
|
7545
8493
|
proc.on("error", () => {
|
|
7546
|
-
|
|
8494
|
+
resolve7(false);
|
|
7547
8495
|
});
|
|
7548
8496
|
});
|
|
7549
8497
|
}
|
|
7550
8498
|
function getDefaultBranch(gitRoot) {
|
|
7551
|
-
return new Promise((
|
|
8499
|
+
return new Promise((resolve7) => {
|
|
7552
8500
|
const proc = spawnProcess("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
7553
8501
|
cwd: gitRoot
|
|
7554
8502
|
});
|
|
@@ -7561,12 +8509,12 @@ function getDefaultBranch(gitRoot) {
|
|
|
7561
8509
|
proc.on("close", (code) => {
|
|
7562
8510
|
if (code === 0 && out.trim()) {
|
|
7563
8511
|
const match = out.trim().match(/refs\/remotes\/origin\/(.+)/);
|
|
7564
|
-
|
|
8512
|
+
resolve7(match ? match[1] : null);
|
|
7565
8513
|
} else {
|
|
7566
|
-
|
|
8514
|
+
resolve7(null);
|
|
7567
8515
|
}
|
|
7568
8516
|
});
|
|
7569
|
-
proc.on("error", () =>
|
|
8517
|
+
proc.on("error", () => resolve7(null));
|
|
7570
8518
|
});
|
|
7571
8519
|
}
|
|
7572
8520
|
async function findDefaultBranch(gitRoot) {
|
|
@@ -7586,7 +8534,7 @@ async function resolvePrCommitLogRange(gitRoot, baseBranch, currentBranch) {
|
|
|
7586
8534
|
}
|
|
7587
8535
|
async function getPrBranchDiff(gitRoot, baseBranch) {
|
|
7588
8536
|
const diffRange = await hasRemoteBranch(gitRoot, baseBranch) ? `origin/${baseBranch}...HEAD` : `${baseBranch}...HEAD`;
|
|
7589
|
-
return new Promise((
|
|
8537
|
+
return new Promise((resolve7) => {
|
|
7590
8538
|
const proc = spawnProcess("git", ["diff", diffRange], { cwd: gitRoot });
|
|
7591
8539
|
let out = "";
|
|
7592
8540
|
if (proc.stdout) {
|
|
@@ -7594,13 +8542,13 @@ async function getPrBranchDiff(gitRoot, baseBranch) {
|
|
|
7594
8542
|
out += d.toString();
|
|
7595
8543
|
});
|
|
7596
8544
|
}
|
|
7597
|
-
proc.on("close", () =>
|
|
7598
|
-
proc.on("error", () =>
|
|
8545
|
+
proc.on("close", () => resolve7(out.trim()));
|
|
8546
|
+
proc.on("error", () => resolve7(""));
|
|
7599
8547
|
});
|
|
7600
8548
|
}
|
|
7601
8549
|
async function getCommitLog(gitRoot, baseBranch, currentBranch) {
|
|
7602
8550
|
const range = await resolvePrCommitLogRange(gitRoot, baseBranch, currentBranch);
|
|
7603
|
-
return new Promise((
|
|
8551
|
+
return new Promise((resolve7) => {
|
|
7604
8552
|
const proc = spawnProcess("git", ["log", "--oneline", range], {
|
|
7605
8553
|
cwd: gitRoot
|
|
7606
8554
|
});
|
|
@@ -7610,13 +8558,13 @@ async function getCommitLog(gitRoot, baseBranch, currentBranch) {
|
|
|
7610
8558
|
out += d.toString();
|
|
7611
8559
|
});
|
|
7612
8560
|
}
|
|
7613
|
-
proc.on("close", () =>
|
|
7614
|
-
proc.on("error", () =>
|
|
8561
|
+
proc.on("close", () => resolve7(out.trim()));
|
|
8562
|
+
proc.on("error", () => resolve7(""));
|
|
7615
8563
|
});
|
|
7616
8564
|
}
|
|
7617
8565
|
async function getDetailedCommits(gitRoot, baseBranch, currentBranch) {
|
|
7618
8566
|
const range = await resolvePrCommitLogRange(gitRoot, baseBranch, currentBranch);
|
|
7619
|
-
return new Promise((
|
|
8567
|
+
return new Promise((resolve7) => {
|
|
7620
8568
|
const proc = spawnProcess("git", ["log", range, "--format=%h %s%n%b%n---"], { cwd: gitRoot });
|
|
7621
8569
|
let out = "";
|
|
7622
8570
|
if (proc.stdout) {
|
|
@@ -7625,14 +8573,14 @@ async function getDetailedCommits(gitRoot, baseBranch, currentBranch) {
|
|
|
7625
8573
|
});
|
|
7626
8574
|
}
|
|
7627
8575
|
proc.on("close", () => {
|
|
7628
|
-
|
|
8576
|
+
resolve7(out.length > 2e3 ? out.substring(0, 2e3) + "\n...(more commits)" : out);
|
|
7629
8577
|
});
|
|
7630
|
-
proc.on("error", () =>
|
|
8578
|
+
proc.on("error", () => resolve7(""));
|
|
7631
8579
|
});
|
|
7632
8580
|
}
|
|
7633
8581
|
function getGitLog(gitRoot, limit) {
|
|
7634
8582
|
return new Promise(
|
|
7635
|
-
(
|
|
8583
|
+
(resolve7, reject) => {
|
|
7636
8584
|
const proc = spawnProcess(
|
|
7637
8585
|
"git",
|
|
7638
8586
|
["log", "--oneline", "-n", limit.toString(), "--format=%H|%s|%an|%ai"],
|
|
@@ -7662,7 +8610,7 @@ function getGitLog(gitRoot, limit) {
|
|
|
7662
8610
|
date: parts[3] || ""
|
|
7663
8611
|
};
|
|
7664
8612
|
});
|
|
7665
|
-
|
|
8613
|
+
resolve7(parsed);
|
|
7666
8614
|
} else {
|
|
7667
8615
|
reject(new Error(err || "Failed to get git log"));
|
|
7668
8616
|
}
|
|
@@ -7672,27 +8620,27 @@ function getGitLog(gitRoot, limit) {
|
|
|
7672
8620
|
);
|
|
7673
8621
|
}
|
|
7674
8622
|
function stageAllChanges(gitRoot) {
|
|
7675
|
-
return new Promise((
|
|
8623
|
+
return new Promise((resolve7, reject) => {
|
|
7676
8624
|
const proc = spawnProcess("git", ["add", "-A"], { cwd: gitRoot });
|
|
7677
8625
|
proc.on("close", (code) => {
|
|
7678
|
-
if (code === 0)
|
|
8626
|
+
if (code === 0) resolve7();
|
|
7679
8627
|
else reject(new Error("Failed to stage changes"));
|
|
7680
8628
|
});
|
|
7681
8629
|
proc.on("error", reject);
|
|
7682
8630
|
});
|
|
7683
8631
|
}
|
|
7684
8632
|
function commitChanges(gitRoot, message) {
|
|
7685
|
-
return new Promise((
|
|
8633
|
+
return new Promise((resolve7, reject) => {
|
|
7686
8634
|
const proc = spawnProcess("git", ["commit", "-m", message], { cwd: gitRoot });
|
|
7687
8635
|
proc.on("close", (code) => {
|
|
7688
|
-
if (code === 0)
|
|
8636
|
+
if (code === 0) resolve7();
|
|
7689
8637
|
else reject(new Error("Failed to commit changes"));
|
|
7690
8638
|
});
|
|
7691
8639
|
proc.on("error", reject);
|
|
7692
8640
|
});
|
|
7693
8641
|
}
|
|
7694
8642
|
function pushToOrigin(gitRoot, currentBranch) {
|
|
7695
|
-
return new Promise((
|
|
8643
|
+
return new Promise((resolve7, reject) => {
|
|
7696
8644
|
console.log(`[git-push] Executing: git push -u origin ${currentBranch}`);
|
|
7697
8645
|
const proc = spawnProcess("git", ["push", "-u", "origin", currentBranch], { cwd: gitRoot });
|
|
7698
8646
|
let err = "";
|
|
@@ -7713,7 +8661,7 @@ function pushToOrigin(gitRoot, currentBranch) {
|
|
|
7713
8661
|
console.log(`[git-push] git push exited with code: ${code}`);
|
|
7714
8662
|
if (code === 0) {
|
|
7715
8663
|
console.log(`[git-push] \u2713 Push completed successfully`);
|
|
7716
|
-
|
|
8664
|
+
resolve7();
|
|
7717
8665
|
} else {
|
|
7718
8666
|
console.log(`[git-push] \u2717 Push failed with error: ${err || "Unknown error"}`);
|
|
7719
8667
|
reject(new Error(err || "Failed to push changes"));
|
|
@@ -7728,7 +8676,7 @@ function pushToOrigin(gitRoot, currentBranch) {
|
|
|
7728
8676
|
});
|
|
7729
8677
|
}
|
|
7730
8678
|
function fetchFromOrigin(gitRoot) {
|
|
7731
|
-
return new Promise((
|
|
8679
|
+
return new Promise((resolve7, reject) => {
|
|
7732
8680
|
const proc = spawnProcess("git", ["fetch", "origin"], { cwd: gitRoot });
|
|
7733
8681
|
let err = "";
|
|
7734
8682
|
if (proc.stderr) {
|
|
@@ -7737,14 +8685,14 @@ function fetchFromOrigin(gitRoot) {
|
|
|
7737
8685
|
});
|
|
7738
8686
|
}
|
|
7739
8687
|
proc.on("close", (code) => {
|
|
7740
|
-
if (code === 0)
|
|
8688
|
+
if (code === 0) resolve7();
|
|
7741
8689
|
else reject(new Error(err || "Failed to fetch from origin"));
|
|
7742
8690
|
});
|
|
7743
8691
|
proc.on("error", reject);
|
|
7744
8692
|
});
|
|
7745
8693
|
}
|
|
7746
8694
|
function pullFromOrigin(gitRoot) {
|
|
7747
|
-
return new Promise((
|
|
8695
|
+
return new Promise((resolve7, reject) => {
|
|
7748
8696
|
const proc = spawnProcess("git", ["pull"], { cwd: gitRoot });
|
|
7749
8697
|
let err = "";
|
|
7750
8698
|
if (proc.stderr) {
|
|
@@ -7753,14 +8701,14 @@ function pullFromOrigin(gitRoot) {
|
|
|
7753
8701
|
});
|
|
7754
8702
|
}
|
|
7755
8703
|
proc.on("close", (code) => {
|
|
7756
|
-
if (code === 0)
|
|
8704
|
+
if (code === 0) resolve7();
|
|
7757
8705
|
else reject(new Error(err || "Failed to pull from origin"));
|
|
7758
8706
|
});
|
|
7759
8707
|
proc.on("error", reject);
|
|
7760
8708
|
});
|
|
7761
8709
|
}
|
|
7762
8710
|
function createGitHubRepo(gitRoot, repoName, description, isPrivate) {
|
|
7763
|
-
return new Promise((
|
|
8711
|
+
return new Promise((resolve7, reject) => {
|
|
7764
8712
|
const args2 = ["repo", "create"];
|
|
7765
8713
|
if (repoName) {
|
|
7766
8714
|
args2.push(repoName);
|
|
@@ -7791,7 +8739,7 @@ function createGitHubRepo(gitRoot, repoName, description, isPrivate) {
|
|
|
7791
8739
|
if (code === 0) {
|
|
7792
8740
|
invalidateRepoInfoCache(gitRoot);
|
|
7793
8741
|
const urlMatch = out.match(/https:\/\/[^\s]+/);
|
|
7794
|
-
|
|
8742
|
+
resolve7(urlMatch ? urlMatch[0] : out.trim());
|
|
7795
8743
|
} else {
|
|
7796
8744
|
reject(new Error(err || "Failed to create repository"));
|
|
7797
8745
|
}
|
|
@@ -7803,7 +8751,7 @@ function createGitHubRepo(gitRoot, repoName, description, isPrivate) {
|
|
|
7803
8751
|
});
|
|
7804
8752
|
}
|
|
7805
8753
|
function createPullRequest(gitRoot, title, description) {
|
|
7806
|
-
return new Promise((
|
|
8754
|
+
return new Promise((resolve7, reject) => {
|
|
7807
8755
|
const args2 = ["pr", "create", "--title", title ?? "", "--body", description ?? ""];
|
|
7808
8756
|
console.log(`[git-create-pr] Executing: gh ${args2.join(" ")}`);
|
|
7809
8757
|
const proc = spawnProcess("gh", args2, { cwd: gitRoot });
|
|
@@ -7828,7 +8776,7 @@ function createPullRequest(gitRoot, title, description) {
|
|
|
7828
8776
|
const urlMatch = out.match(/https:\/\/[^\s]+/);
|
|
7829
8777
|
const prUrl = urlMatch ? urlMatch[0] : out.trim();
|
|
7830
8778
|
console.log(`[git-create-pr] \u2713 PR URL extracted: ${prUrl}`);
|
|
7831
|
-
|
|
8779
|
+
resolve7(prUrl);
|
|
7832
8780
|
} else {
|
|
7833
8781
|
console.log(`[git-create-pr] \u2717 gh pr create failed with error: ${err || "Unknown error"}`);
|
|
7834
8782
|
reject(new Error(err || "Failed to create PR"));
|
|
@@ -7867,7 +8815,7 @@ async function getPullRequestStatus(gitRoot) {
|
|
|
7867
8815
|
}
|
|
7868
8816
|
throw new Error(result.stderr || "Failed to check PR status");
|
|
7869
8817
|
}
|
|
7870
|
-
return new Promise((
|
|
8818
|
+
return new Promise((resolve7, reject) => {
|
|
7871
8819
|
const args2 = ["pr", "view", "--json", "url,state"];
|
|
7872
8820
|
const proc = spawnProcess("gh", args2, { cwd: gitRoot });
|
|
7873
8821
|
let out = "";
|
|
@@ -7886,7 +8834,7 @@ async function getPullRequestStatus(gitRoot) {
|
|
|
7886
8834
|
if (code === 0) {
|
|
7887
8835
|
try {
|
|
7888
8836
|
const prData = JSON.parse(out.trim());
|
|
7889
|
-
|
|
8837
|
+
resolve7({
|
|
7890
8838
|
exists: true,
|
|
7891
8839
|
url: prData.url,
|
|
7892
8840
|
state: prData.state
|
|
@@ -7896,7 +8844,7 @@ async function getPullRequestStatus(gitRoot) {
|
|
|
7896
8844
|
}
|
|
7897
8845
|
} else {
|
|
7898
8846
|
if (err.includes("no pull requests") || err.includes("not found")) {
|
|
7899
|
-
|
|
8847
|
+
resolve7({ exists: false });
|
|
7900
8848
|
} else {
|
|
7901
8849
|
reject(new Error(err || "Failed to check PR status"));
|
|
7902
8850
|
}
|
|
@@ -8083,7 +9031,7 @@ function buildCrossHostRedirectResult(redirectUrl) {
|
|
|
8083
9031
|
|
|
8084
9032
|
// src/features/web-fetch/web-fetch.client.ts
|
|
8085
9033
|
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
8086
|
-
import { join as
|
|
9034
|
+
import { join as join13, extname as extname3 } from "path";
|
|
8087
9035
|
import { createHash as createHash2 } from "crypto";
|
|
8088
9036
|
|
|
8089
9037
|
// src/features/web-fetch/web-fetch-cache.ts
|
|
@@ -8188,7 +9136,7 @@ async function readResponseBody(response, signal) {
|
|
|
8188
9136
|
return { body, bytes: body.length };
|
|
8189
9137
|
}
|
|
8190
9138
|
async function saveBinaryContent(body, contentType, url) {
|
|
8191
|
-
const downloadsDir =
|
|
9139
|
+
const downloadsDir = join13(getDataDir(), "fetch-downloads");
|
|
8192
9140
|
await mkdir(downloadsDir, { recursive: true });
|
|
8193
9141
|
const hash = createHash2("sha256").update(url).digest("hex").slice(0, 12);
|
|
8194
9142
|
let extension = extname3(new URL(url).pathname);
|
|
@@ -8203,7 +9151,7 @@ async function saveBinaryContent(body, contentType, url) {
|
|
|
8203
9151
|
if (!extension) {
|
|
8204
9152
|
extension = ".bin";
|
|
8205
9153
|
}
|
|
8206
|
-
const filePath =
|
|
9154
|
+
const filePath = join13(downloadsDir, `${hash}${extension}`);
|
|
8207
9155
|
await writeFile2(filePath, body);
|
|
8208
9156
|
return filePath;
|
|
8209
9157
|
}
|
|
@@ -8364,9 +9312,9 @@ function truncateFetchUrl(url, maxLength = 80) {
|
|
|
8364
9312
|
}
|
|
8365
9313
|
|
|
8366
9314
|
// src/tools/fetch.ts
|
|
8367
|
-
var fetchSchema =
|
|
8368
|
-
url:
|
|
8369
|
-
prompt:
|
|
9315
|
+
var fetchSchema = Type18.Object({
|
|
9316
|
+
url: Type18.String({ description: "Fully qualified URL to fetch (http or https)" }),
|
|
9317
|
+
prompt: Type18.String({
|
|
8370
9318
|
description: "What to extract or summarize from the page content"
|
|
8371
9319
|
})
|
|
8372
9320
|
});
|
|
@@ -8446,13 +9394,187 @@ function createFetchTool(options) {
|
|
|
8446
9394
|
});
|
|
8447
9395
|
}
|
|
8448
9396
|
const details = {
|
|
8449
|
-
url: fetchResult.finalUrl,
|
|
8450
|
-
bytes: fetchResult.bytes,
|
|
8451
|
-
code: fetchResult.code,
|
|
8452
|
-
codeText: fetchResult.codeText,
|
|
8453
|
-
durationMs: fetchResult.durationMs
|
|
9397
|
+
url: fetchResult.finalUrl,
|
|
9398
|
+
bytes: fetchResult.bytes,
|
|
9399
|
+
code: fetchResult.code,
|
|
9400
|
+
codeText: fetchResult.codeText,
|
|
9401
|
+
durationMs: fetchResult.durationMs
|
|
9402
|
+
};
|
|
9403
|
+
const output = JSON.stringify({ ...details, result });
|
|
9404
|
+
return {
|
|
9405
|
+
content: [{ type: "text", text: output }],
|
|
9406
|
+
details
|
|
9407
|
+
};
|
|
9408
|
+
});
|
|
9409
|
+
}
|
|
9410
|
+
};
|
|
9411
|
+
}
|
|
9412
|
+
|
|
9413
|
+
// src/tools/web-search.ts
|
|
9414
|
+
import { Type as Type19 } from "@sinclair/typebox";
|
|
9415
|
+
|
|
9416
|
+
// src/features/web-search/web-search.client.ts
|
|
9417
|
+
var WEB_SEARCH_API = "https://api.webnative.dev/websearch";
|
|
9418
|
+
function clampNumResults(numResults) {
|
|
9419
|
+
if (numResults === void 0) return 10;
|
|
9420
|
+
return Math.min(100, Math.max(1, Math.round(numResults)));
|
|
9421
|
+
}
|
|
9422
|
+
function normalizeResultItem(raw) {
|
|
9423
|
+
const highlights = Array.isArray(raw.highlights) ? raw.highlights.filter((item) => typeof item === "string") : void 0;
|
|
9424
|
+
return {
|
|
9425
|
+
title: typeof raw.title === "string" ? raw.title : "Untitled",
|
|
9426
|
+
url: typeof raw.url === "string" ? raw.url : typeof raw.id === "string" ? raw.id : "",
|
|
9427
|
+
publishedDate: typeof raw.publishedDate === "string" || raw.publishedDate === null ? raw.publishedDate : void 0,
|
|
9428
|
+
author: typeof raw.author === "string" || raw.author === null ? raw.author : void 0,
|
|
9429
|
+
highlights
|
|
9430
|
+
};
|
|
9431
|
+
}
|
|
9432
|
+
function parseSearchErrorMessage(status, body) {
|
|
9433
|
+
try {
|
|
9434
|
+
const parsed = JSON.parse(body);
|
|
9435
|
+
const message = typeof parsed.error === "string" && parsed.error || typeof parsed.message === "string" && parsed.message;
|
|
9436
|
+
if (message) return `Web search failed (${status}): ${message}`;
|
|
9437
|
+
} catch {
|
|
9438
|
+
}
|
|
9439
|
+
return `Web search failed (${status}): ${body || "Unknown error"}`;
|
|
9440
|
+
}
|
|
9441
|
+
async function searchWeb(options) {
|
|
9442
|
+
const payload = {
|
|
9443
|
+
query: options.query,
|
|
9444
|
+
type: options.type ?? "auto",
|
|
9445
|
+
numResults: clampNumResults(options.numResults),
|
|
9446
|
+
contents: {
|
|
9447
|
+
highlights: true
|
|
9448
|
+
}
|
|
9449
|
+
};
|
|
9450
|
+
if (options.includeDomains?.length) {
|
|
9451
|
+
payload.includeDomains = options.includeDomains;
|
|
9452
|
+
}
|
|
9453
|
+
if (options.excludeDomains?.length) {
|
|
9454
|
+
payload.excludeDomains = options.excludeDomains;
|
|
9455
|
+
}
|
|
9456
|
+
const response = await fetch(WEB_SEARCH_API, {
|
|
9457
|
+
method: "POST",
|
|
9458
|
+
headers: {
|
|
9459
|
+
"Content-Type": "application/json"
|
|
9460
|
+
},
|
|
9461
|
+
body: JSON.stringify(payload),
|
|
9462
|
+
signal: options.signal
|
|
9463
|
+
});
|
|
9464
|
+
const body = await response.text();
|
|
9465
|
+
if (!response.ok) {
|
|
9466
|
+
throw new Error(parseSearchErrorMessage(response.status, body));
|
|
9467
|
+
}
|
|
9468
|
+
let parsed;
|
|
9469
|
+
try {
|
|
9470
|
+
parsed = JSON.parse(body);
|
|
9471
|
+
} catch {
|
|
9472
|
+
throw new Error("Web search failed: invalid JSON response");
|
|
9473
|
+
}
|
|
9474
|
+
const rawResults = Array.isArray(parsed.results) ? parsed.results : [];
|
|
9475
|
+
const results = rawResults.filter((item) => item !== null && typeof item === "object").map(normalizeResultItem);
|
|
9476
|
+
return {
|
|
9477
|
+
requestId: typeof parsed.requestId === "string" ? parsed.requestId : void 0,
|
|
9478
|
+
searchType: typeof parsed.searchType === "string" ? parsed.searchType : void 0,
|
|
9479
|
+
results
|
|
9480
|
+
};
|
|
9481
|
+
}
|
|
9482
|
+
|
|
9483
|
+
// src/tools/web-search.ts
|
|
9484
|
+
var searchTypeSchema = Type19.Union([
|
|
9485
|
+
Type19.Literal("auto"),
|
|
9486
|
+
Type19.Literal("fast"),
|
|
9487
|
+
Type19.Literal("instant"),
|
|
9488
|
+
Type19.Literal("deep-lite"),
|
|
9489
|
+
Type19.Literal("deep"),
|
|
9490
|
+
Type19.Literal("deep-reasoning")
|
|
9491
|
+
]);
|
|
9492
|
+
var webSearchSchema = Type19.Object({
|
|
9493
|
+
query: Type19.String({
|
|
9494
|
+
description: "Natural language search query (e.g. 'Next.js route handler authentication example')"
|
|
9495
|
+
}),
|
|
9496
|
+
numResults: Type19.Optional(
|
|
9497
|
+
Type19.Number({
|
|
9498
|
+
description: "Number of results to return (1-100, default 10)",
|
|
9499
|
+
minimum: 1,
|
|
9500
|
+
maximum: 100
|
|
9501
|
+
})
|
|
9502
|
+
),
|
|
9503
|
+
type: Type19.Optional(
|
|
9504
|
+
Type19.Union([searchTypeSchema], {
|
|
9505
|
+
description: "Search depth/latency tradeoff. Use auto (default) for most queries; fast/instant for low latency; deep variants for research."
|
|
9506
|
+
})
|
|
9507
|
+
),
|
|
9508
|
+
includeDomains: Type19.Optional(
|
|
9509
|
+
Type19.Array(Type19.String(), {
|
|
9510
|
+
description: "Only return results from these domains (e.g. ['docs.github.com', 'nodejs.org'])"
|
|
9511
|
+
})
|
|
9512
|
+
),
|
|
9513
|
+
excludeDomains: Type19.Optional(
|
|
9514
|
+
Type19.Array(Type19.String(), {
|
|
9515
|
+
description: "Exclude results from these domains"
|
|
9516
|
+
})
|
|
9517
|
+
)
|
|
9518
|
+
});
|
|
9519
|
+
function createWebSearchTool() {
|
|
9520
|
+
return {
|
|
9521
|
+
name: "web_search",
|
|
9522
|
+
label: "web_search",
|
|
9523
|
+
description: "Search the web for current information, documentation, API references, and code examples. Returns titles, URLs, and query-relevant highlights for each result. Use fetch to read a specific URL in full after finding it.",
|
|
9524
|
+
parameters: webSearchSchema,
|
|
9525
|
+
execute: async (_toolCallId, { query, numResults, type, includeDomains, excludeDomains }, signal, onUpdate) => {
|
|
9526
|
+
return withAbortSignal(signal, async (isAborted) => {
|
|
9527
|
+
onUpdate?.({
|
|
9528
|
+
content: [{ type: "text", text: `Searching the web for "${query}"\u2026` }],
|
|
9529
|
+
details: void 0
|
|
9530
|
+
});
|
|
9531
|
+
let response;
|
|
9532
|
+
try {
|
|
9533
|
+
response = await searchWeb({
|
|
9534
|
+
query,
|
|
9535
|
+
numResults,
|
|
9536
|
+
type,
|
|
9537
|
+
includeDomains,
|
|
9538
|
+
excludeDomains,
|
|
9539
|
+
signal
|
|
9540
|
+
});
|
|
9541
|
+
} catch (err) {
|
|
9542
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9543
|
+
return {
|
|
9544
|
+
content: [{ type: "text", text: message }],
|
|
9545
|
+
details: void 0
|
|
9546
|
+
};
|
|
9547
|
+
}
|
|
9548
|
+
if (isAborted()) {
|
|
9549
|
+
return { content: [], details: void 0 };
|
|
9550
|
+
}
|
|
9551
|
+
const payload = {
|
|
9552
|
+
query,
|
|
9553
|
+
searchType: response.searchType ?? type ?? "auto",
|
|
9554
|
+
requestId: response.requestId,
|
|
9555
|
+
resultCount: response.results.length,
|
|
9556
|
+
results: response.results.map((result) => ({
|
|
9557
|
+
title: result.title,
|
|
9558
|
+
url: result.url,
|
|
9559
|
+
publishedDate: result.publishedDate ?? void 0,
|
|
9560
|
+
author: result.author ?? void 0,
|
|
9561
|
+
highlights: result.highlights ?? []
|
|
9562
|
+
}))
|
|
9563
|
+
};
|
|
9564
|
+
const serialized = JSON.stringify(payload, null, 2);
|
|
9565
|
+
const truncated = truncateHead(serialized, { maxLines: 500, maxBytes: 50 * 1024 });
|
|
9566
|
+
let output = truncated.content;
|
|
9567
|
+
if (truncated.truncated) {
|
|
9568
|
+
output += `
|
|
9569
|
+
|
|
9570
|
+
[Output truncated: showing ${truncated.outputLines}/${truncated.totalLines} lines]`;
|
|
9571
|
+
}
|
|
9572
|
+
const details = {
|
|
9573
|
+
query,
|
|
9574
|
+
searchType: payload.searchType,
|
|
9575
|
+
resultCount: payload.resultCount,
|
|
9576
|
+
requestId: response.requestId
|
|
8454
9577
|
};
|
|
8455
|
-
const output = JSON.stringify({ ...details, result });
|
|
8456
9578
|
return {
|
|
8457
9579
|
content: [{ type: "text", text: output }],
|
|
8458
9580
|
details
|
|
@@ -8463,13 +9585,13 @@ function createFetchTool(options) {
|
|
|
8463
9585
|
}
|
|
8464
9586
|
|
|
8465
9587
|
// src/tools/tool-search.ts
|
|
8466
|
-
import { Type as
|
|
8467
|
-
var toolSearchSchema =
|
|
8468
|
-
query:
|
|
9588
|
+
import { Type as Type20 } from "@sinclair/typebox";
|
|
9589
|
+
var toolSearchSchema = Type20.Object({
|
|
9590
|
+
query: Type20.String({
|
|
8469
9591
|
description: 'Search query to find tools. Use "select:name1,name2" to fetch exact tools by name, or keywords to search by description.'
|
|
8470
9592
|
}),
|
|
8471
|
-
max_results:
|
|
8472
|
-
|
|
9593
|
+
max_results: Type20.Optional(
|
|
9594
|
+
Type20.Number({
|
|
8473
9595
|
description: "Maximum number of results to return (default: 5)"
|
|
8474
9596
|
})
|
|
8475
9597
|
)
|
|
@@ -8597,6 +9719,7 @@ var toolRegistry = [
|
|
|
8597
9719
|
{ name: "agent", enabled: true, displayName: "Subagent", activeDisplayName: "Subagent" },
|
|
8598
9720
|
{ name: "ask_user", enabled: true, displayName: "Ask", activeDisplayName: "Asking" },
|
|
8599
9721
|
{ name: "bash", enabled: true, displayName: "Ran", activeDisplayName: "Running" },
|
|
9722
|
+
{ name: "browser", enabled: true, displayName: "Browser", activeDisplayName: "Browsing" },
|
|
8600
9723
|
{
|
|
8601
9724
|
name: "code_search",
|
|
8602
9725
|
enabled: false,
|
|
@@ -8648,6 +9771,12 @@ var toolRegistry = [
|
|
|
8648
9771
|
displayName: "Find Tools",
|
|
8649
9772
|
activeDisplayName: "Finding Tools"
|
|
8650
9773
|
},
|
|
9774
|
+
{
|
|
9775
|
+
name: "web_search",
|
|
9776
|
+
enabled: true,
|
|
9777
|
+
displayName: "Web Search",
|
|
9778
|
+
activeDisplayName: "Searching Web"
|
|
9779
|
+
},
|
|
8651
9780
|
{ name: "write", enabled: true, displayName: "Write", activeDisplayName: "Writing" }
|
|
8652
9781
|
];
|
|
8653
9782
|
var registryByName = new Map(toolRegistry.map((entry) => [entry.name, entry]));
|
|
@@ -8671,6 +9800,9 @@ function resolveRegistryEntry(toolName) {
|
|
|
8671
9800
|
if (lowerName === "fetch" || lowerName.includes("web_fetch")) {
|
|
8672
9801
|
return registryByName.get("fetch");
|
|
8673
9802
|
}
|
|
9803
|
+
if (lowerName === "web_search") {
|
|
9804
|
+
return registryByName.get("web_search");
|
|
9805
|
+
}
|
|
8674
9806
|
if (lowerName.includes("read")) {
|
|
8675
9807
|
return registryByName.get("read");
|
|
8676
9808
|
}
|
|
@@ -8774,7 +9906,7 @@ var WebFetchPermissionGate = class {
|
|
|
8774
9906
|
wildcardOptions: buildWildcardOptions2(normalized)
|
|
8775
9907
|
};
|
|
8776
9908
|
this.config.onPermissionRequired?.(request);
|
|
8777
|
-
return new Promise((
|
|
9909
|
+
return new Promise((resolve7, reject) => {
|
|
8778
9910
|
const cleanup = () => {
|
|
8779
9911
|
pendingPermissions.delete(toolCallId);
|
|
8780
9912
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -8792,7 +9924,7 @@ var WebFetchPermissionGate = class {
|
|
|
8792
9924
|
pendingPermissions.set(toolCallId, {
|
|
8793
9925
|
resolve: (decision) => {
|
|
8794
9926
|
cleanup();
|
|
8795
|
-
void this.applyDecision(decision, normalized).then(() =>
|
|
9927
|
+
void this.applyDecision(decision, normalized).then(() => resolve7(decision));
|
|
8796
9928
|
},
|
|
8797
9929
|
reject: (error) => {
|
|
8798
9930
|
cleanup();
|
|
@@ -8932,12 +10064,16 @@ async function createCodingTools(cwd, options) {
|
|
|
8932
10064
|
createFetchTool({
|
|
8933
10065
|
metadataManager: options?.metadataManager,
|
|
8934
10066
|
webFetchPermissionGate: options?.webFetchPermissionGate
|
|
8935
|
-
})
|
|
10067
|
+
}),
|
|
10068
|
+
createWebSearchTool()
|
|
8936
10069
|
]);
|
|
8937
10070
|
if (options?.metadataManager && isToolEnabled("generate_image")) {
|
|
8938
10071
|
coreTools.push(createGenerateImageTool({ metadataManager: options.metadataManager, cwd }));
|
|
8939
10072
|
}
|
|
8940
|
-
const optionalTools = filterEnabledTools([
|
|
10073
|
+
const optionalTools = filterEnabledTools([
|
|
10074
|
+
createFindImagesTool({ cwd, threadId: options?.threadId }),
|
|
10075
|
+
createBrowserTool({ cwd, threadId: options?.threadId })
|
|
10076
|
+
]);
|
|
8941
10077
|
if (options?.skills && options.skills.length > 0) {
|
|
8942
10078
|
optionalTools.push(
|
|
8943
10079
|
...filterEnabledTools([
|
|
@@ -9016,7 +10152,7 @@ function createAllTools(cwd, options) {
|
|
|
9016
10152
|
});
|
|
9017
10153
|
}
|
|
9018
10154
|
if (isToolEnabled("find_images")) {
|
|
9019
|
-
tools.find_images = createFindImagesTool(cwd);
|
|
10155
|
+
tools.find_images = createFindImagesTool({ cwd, threadId: options?.threadId });
|
|
9020
10156
|
}
|
|
9021
10157
|
if (options?.threadId) {
|
|
9022
10158
|
for (const [name, tool] of Object.entries(tools)) {
|
|
@@ -9050,10 +10186,10 @@ async function* loadCodingToolsWithMcpStatus(cwd, options) {
|
|
|
9050
10186
|
if (completed) {
|
|
9051
10187
|
break;
|
|
9052
10188
|
}
|
|
9053
|
-
await new Promise((
|
|
9054
|
-
notify =
|
|
10189
|
+
await new Promise((resolve7) => {
|
|
10190
|
+
notify = resolve7;
|
|
9055
10191
|
if (completed) {
|
|
9056
|
-
|
|
10192
|
+
resolve7();
|
|
9057
10193
|
}
|
|
9058
10194
|
});
|
|
9059
10195
|
notify = null;
|
|
@@ -9065,7 +10201,7 @@ async function* loadCodingToolsWithMcpStatus(cwd, options) {
|
|
|
9065
10201
|
}
|
|
9066
10202
|
|
|
9067
10203
|
// src/tools/agent-tool.ts
|
|
9068
|
-
import { Type as
|
|
10204
|
+
import { Type as Type21 } from "@sinclair/typebox";
|
|
9069
10205
|
|
|
9070
10206
|
// src/agent/agent.subagent-executor.ts
|
|
9071
10207
|
import { Agent } from "@earendil-works/pi-agent-core";
|
|
@@ -9183,7 +10319,7 @@ var EventQueue = class {
|
|
|
9183
10319
|
content: resultContent,
|
|
9184
10320
|
isError: event.isError
|
|
9185
10321
|
};
|
|
9186
|
-
if (event.toolName === "generate_image") {
|
|
10322
|
+
if (event.toolName === "generate_image" || event.toolName === "find_images" || event.toolName === "browser") {
|
|
9187
10323
|
toolResultBlock.content = stripGeneratedImageText(resultContent);
|
|
9188
10324
|
const details = event.result?.details;
|
|
9189
10325
|
if (details?.imageUrl) {
|
|
@@ -9224,6 +10360,7 @@ var EventQueue = class {
|
|
|
9224
10360
|
*/
|
|
9225
10361
|
push(event) {
|
|
9226
10362
|
this.queue.push(event);
|
|
10363
|
+
this.notify();
|
|
9227
10364
|
}
|
|
9228
10365
|
/**
|
|
9229
10366
|
* Removes and returns the next event from the queue
|
|
@@ -9389,8 +10526,8 @@ async function executeSubagent(options) {
|
|
|
9389
10526
|
const e = eventQueue.shift();
|
|
9390
10527
|
if (e) onEvent(e);
|
|
9391
10528
|
} else {
|
|
9392
|
-
await new Promise((
|
|
9393
|
-
eventQueue.setResolver(
|
|
10529
|
+
await new Promise((resolve7) => {
|
|
10530
|
+
eventQueue.setResolver(resolve7);
|
|
9394
10531
|
});
|
|
9395
10532
|
}
|
|
9396
10533
|
}
|
|
@@ -9414,15 +10551,15 @@ async function executeSubagent(options) {
|
|
|
9414
10551
|
}
|
|
9415
10552
|
|
|
9416
10553
|
// src/tools/agent-tool.ts
|
|
9417
|
-
var agentToolSchema =
|
|
9418
|
-
prompt:
|
|
9419
|
-
agentName:
|
|
9420
|
-
|
|
10554
|
+
var agentToolSchema = Type21.Object({
|
|
10555
|
+
prompt: Type21.String({ description: "The task or question for the subagent to work on" }),
|
|
10556
|
+
agentName: Type21.Optional(
|
|
10557
|
+
Type21.String({
|
|
9421
10558
|
description: "Name of a defined agent (from AGENT.md) to use. If omitted, a general-purpose subagent is created."
|
|
9422
10559
|
})
|
|
9423
10560
|
),
|
|
9424
|
-
description:
|
|
9425
|
-
|
|
10561
|
+
description: Type21.Optional(
|
|
10562
|
+
Type21.String({
|
|
9426
10563
|
description: "A short (3-5 word) label describing what the subagent will do"
|
|
9427
10564
|
})
|
|
9428
10565
|
)
|
|
@@ -9450,7 +10587,7 @@ function createAgentTool(options) {
|
|
|
9450
10587
|
label: "agent",
|
|
9451
10588
|
description: "Launch a specialized subagent to handle a focused subtask in an isolated context. The subagent runs autonomously and returns its result. Use this to delegate research, analysis, or parallel work without polluting the current context. If agentName is provided, that agent's system prompt and tool restrictions are applied.",
|
|
9452
10589
|
parameters: agentToolSchema,
|
|
9453
|
-
execute: async (
|
|
10590
|
+
execute: async (toolCallId, { prompt, agentName, description }) => {
|
|
9454
10591
|
const agentDef = agentName ? agents.find((a) => a.name === agentName && !a.disableModelInvocation) : void 0;
|
|
9455
10592
|
if (agentName && !agentDef) {
|
|
9456
10593
|
const available = agents.filter((a) => !a.disableModelInvocation).map((a) => a.name).join(", ");
|
|
@@ -9474,6 +10611,7 @@ function createAgentTool(options) {
|
|
|
9474
10611
|
);
|
|
9475
10612
|
onEvent({
|
|
9476
10613
|
type: "subagent_start",
|
|
10614
|
+
toolCallId,
|
|
9477
10615
|
subagentName: label,
|
|
9478
10616
|
subagentDescription: agentDef?.description,
|
|
9479
10617
|
subagentPrompt: prompt
|
|
@@ -9490,6 +10628,7 @@ function createAgentTool(options) {
|
|
|
9490
10628
|
onEvent: (event) => {
|
|
9491
10629
|
onEvent({
|
|
9492
10630
|
type: "subagent_event",
|
|
10631
|
+
toolCallId,
|
|
9493
10632
|
subagentName: label,
|
|
9494
10633
|
subagentEvent: event
|
|
9495
10634
|
});
|
|
@@ -9501,6 +10640,7 @@ function createAgentTool(options) {
|
|
|
9501
10640
|
console.error(`[agent-tool] Subagent '${label}' failed:`, message);
|
|
9502
10641
|
onEvent({
|
|
9503
10642
|
type: "subagent_complete",
|
|
10643
|
+
toolCallId,
|
|
9504
10644
|
subagentName: label,
|
|
9505
10645
|
subagentResult: `Error: ${message}`
|
|
9506
10646
|
});
|
|
@@ -9511,6 +10651,7 @@ function createAgentTool(options) {
|
|
|
9511
10651
|
}
|
|
9512
10652
|
onEvent({
|
|
9513
10653
|
type: "subagent_complete",
|
|
10654
|
+
toolCallId,
|
|
9514
10655
|
subagentName: label,
|
|
9515
10656
|
subagentResult: result
|
|
9516
10657
|
});
|
|
@@ -9529,7 +10670,7 @@ import { readFileSync as readFileSync5 } from "fs";
|
|
|
9529
10670
|
|
|
9530
10671
|
// src/project-analyzer.ts
|
|
9531
10672
|
import { readFileSync as readFileSync4, existsSync as existsSync9 } from "fs";
|
|
9532
|
-
import { join as
|
|
10673
|
+
import { join as join14 } from "path";
|
|
9533
10674
|
var ProjectAnalyzer = class {
|
|
9534
10675
|
projectPath;
|
|
9535
10676
|
constructor(projectPath = process.cwd()) {
|
|
@@ -9540,7 +10681,7 @@ var ProjectAnalyzer = class {
|
|
|
9540
10681
|
return this.generateDescription(info);
|
|
9541
10682
|
}
|
|
9542
10683
|
getProjectInfo() {
|
|
9543
|
-
const packageJsonPath =
|
|
10684
|
+
const packageJsonPath = join14(this.projectPath, "package.json");
|
|
9544
10685
|
const info = { description: "" };
|
|
9545
10686
|
if (existsSync9(packageJsonPath)) {
|
|
9546
10687
|
const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
|
|
@@ -9582,15 +10723,15 @@ var ProjectAnalyzer = class {
|
|
|
9582
10723
|
}
|
|
9583
10724
|
}
|
|
9584
10725
|
detectBuildTool(_scripts = {}, deps, info) {
|
|
9585
|
-
if (deps.vite || existsSync9(
|
|
10726
|
+
if (deps.vite || existsSync9(join14(this.projectPath, "vite.config.js")) || existsSync9(join14(this.projectPath, "vite.config.ts"))) {
|
|
9586
10727
|
info.buildTool = "Vite";
|
|
9587
10728
|
return;
|
|
9588
10729
|
}
|
|
9589
|
-
if (deps.webpack || existsSync9(
|
|
10730
|
+
if (deps.webpack || existsSync9(join14(this.projectPath, "webpack.config.js"))) {
|
|
9590
10731
|
info.buildTool = "Webpack";
|
|
9591
10732
|
return;
|
|
9592
10733
|
}
|
|
9593
|
-
if (deps.rollup || existsSync9(
|
|
10734
|
+
if (deps.rollup || existsSync9(join14(this.projectPath, "rollup.config.js"))) {
|
|
9594
10735
|
info.buildTool = "Rollup";
|
|
9595
10736
|
return;
|
|
9596
10737
|
}
|
|
@@ -9640,40 +10781,40 @@ var ProjectAnalyzer = class {
|
|
|
9640
10781
|
info.uiLibraries = [...new Set(uiLibs)].filter(Boolean);
|
|
9641
10782
|
}
|
|
9642
10783
|
detectProjectType(info) {
|
|
9643
|
-
if (existsSync9(
|
|
10784
|
+
if (existsSync9(join14(this.projectPath, ".xcodeproj")) || existsSync9(join14(this.projectPath, ".xcworkspace")) || existsSync9(join14(this.projectPath, "project.pbxproj"))) {
|
|
9644
10785
|
info.projectType = "Xcode";
|
|
9645
|
-
} else if (existsSync9(
|
|
10786
|
+
} else if (existsSync9(join14(this.projectPath, "build.gradle")) || existsSync9(join14(this.projectPath, "build.gradle.kts")) || existsSync9(join14(this.projectPath, "app/build.gradle")) || existsSync9(join14(this.projectPath, "settings.gradle"))) {
|
|
9646
10787
|
info.projectType = "Android Studio";
|
|
9647
|
-
} else if (existsSync9(
|
|
10788
|
+
} else if (existsSync9(join14(this.projectPath, "pubspec.yaml"))) {
|
|
9648
10789
|
info.projectType = "Flutter";
|
|
9649
|
-
} else if (existsSync9(
|
|
10790
|
+
} else if (existsSync9(join14(this.projectPath, "go.mod"))) {
|
|
9650
10791
|
info.projectType = "Go";
|
|
9651
|
-
} else if (existsSync9(
|
|
10792
|
+
} else if (existsSync9(join14(this.projectPath, "Cargo.toml"))) {
|
|
9652
10793
|
info.projectType = "Rust";
|
|
9653
|
-
} else if (existsSync9(
|
|
10794
|
+
} else if (existsSync9(join14(this.projectPath, "requirements.txt")) || existsSync9(join14(this.projectPath, "pyproject.toml")) || existsSync9(join14(this.projectPath, "setup.py"))) {
|
|
9654
10795
|
info.projectType = "Python";
|
|
9655
|
-
} else if (existsSync9(
|
|
10796
|
+
} else if (existsSync9(join14(this.projectPath, "Gemfile"))) {
|
|
9656
10797
|
info.projectType = "Ruby";
|
|
9657
|
-
} else if (existsSync9(
|
|
10798
|
+
} else if (existsSync9(join14(this.projectPath, "composer.json"))) {
|
|
9658
10799
|
info.projectType = "PHP";
|
|
9659
|
-
} else if (existsSync9(
|
|
10800
|
+
} else if (existsSync9(join14(this.projectPath, "pom.xml")) || existsSync9(join14(this.projectPath, "build.xml"))) {
|
|
9660
10801
|
info.projectType = "Java";
|
|
9661
|
-
} else if (existsSync9(
|
|
10802
|
+
} else if (existsSync9(join14(this.projectPath, ".csproj")) || existsSync9(join14(this.projectPath, "project.json"))) {
|
|
9662
10803
|
info.projectType = ".NET";
|
|
9663
10804
|
}
|
|
9664
10805
|
}
|
|
9665
10806
|
detectPackageManager(info) {
|
|
9666
|
-
if (existsSync9(
|
|
10807
|
+
if (existsSync9(join14(this.projectPath, "bun.lockb"))) {
|
|
9667
10808
|
info.packageManager = "Bun";
|
|
9668
|
-
} else if (existsSync9(
|
|
10809
|
+
} else if (existsSync9(join14(this.projectPath, "bun.lock"))) {
|
|
9669
10810
|
info.packageManager = "Bun";
|
|
9670
|
-
} else if (existsSync9(
|
|
10811
|
+
} else if (existsSync9(join14(this.projectPath, "pnpm-lock.yaml"))) {
|
|
9671
10812
|
info.packageManager = "pnpm";
|
|
9672
|
-
} else if (existsSync9(
|
|
10813
|
+
} else if (existsSync9(join14(this.projectPath, "yarn.lock"))) {
|
|
9673
10814
|
info.packageManager = "Yarn";
|
|
9674
|
-
} else if (existsSync9(
|
|
10815
|
+
} else if (existsSync9(join14(this.projectPath, "package-lock.json"))) {
|
|
9675
10816
|
info.packageManager = "npm";
|
|
9676
|
-
} else if (existsSync9(
|
|
10817
|
+
} else if (existsSync9(join14(this.projectPath, "npm-shrinkwrap.json"))) {
|
|
9677
10818
|
info.packageManager = "npm";
|
|
9678
10819
|
}
|
|
9679
10820
|
}
|
|
@@ -9735,8 +10876,8 @@ function analyzeProject(projectPath) {
|
|
|
9735
10876
|
}
|
|
9736
10877
|
|
|
9737
10878
|
// src/features/rules/rules.manager.ts
|
|
9738
|
-
import { readdir as readdir3, readFile as
|
|
9739
|
-
import { join as
|
|
10879
|
+
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
10880
|
+
import { join as join15, relative as relative4 } from "path";
|
|
9740
10881
|
import { existsSync as existsSync10 } from "fs";
|
|
9741
10882
|
import { homedir as homedir5 } from "os";
|
|
9742
10883
|
function parseRuleFrontmatter(markdown) {
|
|
@@ -9780,10 +10921,10 @@ function parseRuleFrontmatter(markdown) {
|
|
|
9780
10921
|
return { metadata, content };
|
|
9781
10922
|
}
|
|
9782
10923
|
function getGlobalRulesDir() {
|
|
9783
|
-
return
|
|
10924
|
+
return join15(homedir5(), ".agents", "rules");
|
|
9784
10925
|
}
|
|
9785
10926
|
function getProjectRulesDir(threadPath) {
|
|
9786
|
-
return
|
|
10927
|
+
return join15(threadPath, ".agents", "rules");
|
|
9787
10928
|
}
|
|
9788
10929
|
var RuleManager = class {
|
|
9789
10930
|
/**
|
|
@@ -9816,13 +10957,13 @@ var RuleManager = class {
|
|
|
9816
10957
|
try {
|
|
9817
10958
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
9818
10959
|
for (const entry of entries) {
|
|
9819
|
-
const fullPath =
|
|
10960
|
+
const fullPath = join15(dir, entry.name);
|
|
9820
10961
|
if (entry.isDirectory()) {
|
|
9821
10962
|
const subRules = await this.loadRulesFromDir(fullPath, threadPath, scope);
|
|
9822
10963
|
rules.push(...subRules);
|
|
9823
10964
|
} else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdc"))) {
|
|
9824
10965
|
try {
|
|
9825
|
-
const fileContent = await
|
|
10966
|
+
const fileContent = await readFile6(fullPath, "utf-8");
|
|
9826
10967
|
const { metadata, content } = parseRuleFrontmatter(fileContent);
|
|
9827
10968
|
const ruleName = entry.name.replace(/\.(md|mdc)$/, "");
|
|
9828
10969
|
const relativePath = relative4(threadPath, fullPath);
|
|
@@ -9951,6 +11092,12 @@ init_tarsk_debug();
|
|
|
9951
11092
|
function buildDefaultPrompt(tools) {
|
|
9952
11093
|
return buildDefaultSystemPrompt(tools.map((tool) => tool.name));
|
|
9953
11094
|
}
|
|
11095
|
+
function resolveBaseSystemPrompt(tools, overrideOptions) {
|
|
11096
|
+
const base = resolveEffectiveSystemPrompt(buildDefaultPrompt(tools), overrideOptions);
|
|
11097
|
+
return `${base}
|
|
11098
|
+
|
|
11099
|
+
${formatCurrentDateLine()}`;
|
|
11100
|
+
}
|
|
9954
11101
|
var PLAN_MODE_INSTRUCTIONS = `
|
|
9955
11102
|
|
|
9956
11103
|
# Plan Mode (ACTIVE)
|
|
@@ -10119,7 +11266,7 @@ function resolveEffectiveSystemPrompt(generatedPrompt, options) {
|
|
|
10119
11266
|
return generatedPrompt;
|
|
10120
11267
|
}
|
|
10121
11268
|
async function loadPromptSections(threadPath, tools, skills, planMode, agents, deferredTools, ralphMode, overrideOptions) {
|
|
10122
|
-
let systemPrompt =
|
|
11269
|
+
let systemPrompt = resolveBaseSystemPrompt(tools, overrideOptions);
|
|
10123
11270
|
const agentsContent = loadDeveloperContext(threadPath);
|
|
10124
11271
|
if (agentsContent) {
|
|
10125
11272
|
systemPrompt += "\n\n# Developer Context\n\n" + agentsContent;
|
|
@@ -10201,7 +11348,7 @@ function formatAgentsSection(agents) {
|
|
|
10201
11348
|
return section;
|
|
10202
11349
|
}
|
|
10203
11350
|
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools, ralphMode, overrideOptions) {
|
|
10204
|
-
let prompt =
|
|
11351
|
+
let prompt = resolveBaseSystemPrompt(tools, overrideOptions);
|
|
10205
11352
|
const agentsContent = loadDeveloperContext(threadPath);
|
|
10206
11353
|
if (agentsContent) {
|
|
10207
11354
|
prompt += "\n\n# Developer Context\n\n" + agentsContent;
|
|
@@ -10318,7 +11465,7 @@ var ShellPermissionGate = class {
|
|
|
10318
11465
|
wildcardOptions: buildWildcardOptions(command)
|
|
10319
11466
|
};
|
|
10320
11467
|
this.config.onPermissionRequired?.(request);
|
|
10321
|
-
return new Promise((
|
|
11468
|
+
return new Promise((resolve7, reject) => {
|
|
10322
11469
|
const cleanup = () => {
|
|
10323
11470
|
pendingPermissions2.delete(toolCallId);
|
|
10324
11471
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -10336,7 +11483,7 @@ var ShellPermissionGate = class {
|
|
|
10336
11483
|
pendingPermissions2.set(toolCallId, {
|
|
10337
11484
|
resolve: (decision, pattern) => {
|
|
10338
11485
|
cleanup();
|
|
10339
|
-
void this.applyDecision(decision, command, pattern).then(() =>
|
|
11486
|
+
void this.applyDecision(decision, command, pattern).then(() => resolve7(decision));
|
|
10340
11487
|
},
|
|
10341
11488
|
reject: (error) => {
|
|
10342
11489
|
cleanup();
|
|
@@ -10765,8 +11912,8 @@ ${userPrompt}`;
|
|
|
10765
11912
|
if (eventQueue.length > 0) {
|
|
10766
11913
|
yield eventQueue.shift();
|
|
10767
11914
|
} else {
|
|
10768
|
-
await new Promise((
|
|
10769
|
-
eventQueue.setResolver(
|
|
11915
|
+
await new Promise((resolve7) => {
|
|
11916
|
+
eventQueue.setResolver(resolve7);
|
|
10770
11917
|
});
|
|
10771
11918
|
}
|
|
10772
11919
|
}
|
|
@@ -10922,14 +12069,14 @@ var ProcessingStateManagerImpl = class {
|
|
|
10922
12069
|
|
|
10923
12070
|
// src/core/env-manager.ts
|
|
10924
12071
|
import { promises as fs } from "fs";
|
|
10925
|
-
import { join as
|
|
12072
|
+
import { join as join16 } from "path";
|
|
10926
12073
|
function updateRuntimeEnv(values) {
|
|
10927
12074
|
for (const [key, value] of Object.entries(values)) {
|
|
10928
12075
|
process.env[key] = value;
|
|
10929
12076
|
}
|
|
10930
12077
|
}
|
|
10931
12078
|
async function updateEnvFile(keyNames) {
|
|
10932
|
-
const envPath =
|
|
12079
|
+
const envPath = join16(process.cwd(), ".env");
|
|
10933
12080
|
let content = "";
|
|
10934
12081
|
try {
|
|
10935
12082
|
content = await fs.readFile(envPath, "utf-8");
|
|
@@ -10958,7 +12105,7 @@ async function updateEnvFile(keyNames) {
|
|
|
10958
12105
|
await fs.writeFile(envPath, newLines.join("\n") + "\n", "utf-8");
|
|
10959
12106
|
}
|
|
10960
12107
|
async function readEnvFile() {
|
|
10961
|
-
const envPath =
|
|
12108
|
+
const envPath = join16(process.cwd(), ".env");
|
|
10962
12109
|
const envMap = {};
|
|
10963
12110
|
try {
|
|
10964
12111
|
const content = await fs.readFile(envPath, "utf-8");
|
|
@@ -10981,7 +12128,7 @@ async function readEnvFile() {
|
|
|
10981
12128
|
import { Hono } from "hono";
|
|
10982
12129
|
|
|
10983
12130
|
// src/agent/agent-run-guard.ts
|
|
10984
|
-
import { randomUUID as
|
|
12131
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
10985
12132
|
var DEFAULT_LONG_RUNNING_TURN_LIMIT = 50;
|
|
10986
12133
|
var LONG_RUNNING_CONFIRMATION_QUESTION = "The agent has been working for a long time. Do you want to continue?";
|
|
10987
12134
|
var LONG_RUNNING_CONFIRMATION_OPTIONS = ["Yes", "No"];
|
|
@@ -11023,7 +12170,7 @@ function shouldPromptForLongRunningConfirmation(state) {
|
|
|
11023
12170
|
return state.turnCount >= state.maximumTurnCount;
|
|
11024
12171
|
}
|
|
11025
12172
|
function createLongRunningConfirmationRequest(maximumTurnCount) {
|
|
11026
|
-
const toolCallId = `long-running-confirmation-${
|
|
12173
|
+
const toolCallId = `long-running-confirmation-${randomUUID4()}`;
|
|
11027
12174
|
const event = {
|
|
11028
12175
|
type: "long_running_confirmation",
|
|
11029
12176
|
prompt: {
|
|
@@ -11049,7 +12196,7 @@ function createLongRunningConfirmationRequest(maximumTurnCount) {
|
|
|
11049
12196
|
toolCallId,
|
|
11050
12197
|
event,
|
|
11051
12198
|
waitForAnswer: (signal) => {
|
|
11052
|
-
return new Promise((
|
|
12199
|
+
return new Promise((resolve7, reject) => {
|
|
11053
12200
|
const resolvers = getLongRunningResolvers();
|
|
11054
12201
|
const cleanup = () => {
|
|
11055
12202
|
resolvers.delete(toolCallId);
|
|
@@ -11072,7 +12219,7 @@ function createLongRunningConfirmationRequest(maximumTurnCount) {
|
|
|
11072
12219
|
resolvers.set(toolCallId, {
|
|
11073
12220
|
resolve: (answer) => {
|
|
11074
12221
|
cleanup();
|
|
11075
|
-
|
|
12222
|
+
resolve7(answer);
|
|
11076
12223
|
},
|
|
11077
12224
|
reject: (error) => {
|
|
11078
12225
|
cleanup();
|
|
@@ -11399,11 +12546,11 @@ function createWebFetchPermissionRoutes() {
|
|
|
11399
12546
|
import { Hono as Hono4 } from "hono";
|
|
11400
12547
|
|
|
11401
12548
|
// src/features/chat/chat-post.route.ts
|
|
11402
|
-
import { randomUUID as
|
|
12549
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
11403
12550
|
|
|
11404
12551
|
// src/features/skills/skills.manager.ts
|
|
11405
|
-
import { readdir as readdir4, readFile as
|
|
11406
|
-
import { join as
|
|
12552
|
+
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
12553
|
+
import { join as join17 } from "path";
|
|
11407
12554
|
import { existsSync as existsSync11 } from "fs";
|
|
11408
12555
|
import { homedir as homedir6 } from "os";
|
|
11409
12556
|
function parseFrontmatter(markdown) {
|
|
@@ -11451,10 +12598,10 @@ function parseFrontmatter(markdown) {
|
|
|
11451
12598
|
return { metadata, content };
|
|
11452
12599
|
}
|
|
11453
12600
|
function getGlobalSkillsDir() {
|
|
11454
|
-
return
|
|
12601
|
+
return join17(homedir6(), ".agents", "skills");
|
|
11455
12602
|
}
|
|
11456
12603
|
function getProjectSkillsDir(threadPath) {
|
|
11457
|
-
return
|
|
12604
|
+
return join17(threadPath, ".agents", "skills");
|
|
11458
12605
|
}
|
|
11459
12606
|
function validateSkillName(name) {
|
|
11460
12607
|
if (!name || name.length === 0 || name.length > 64) {
|
|
@@ -11507,14 +12654,14 @@ var SkillManager = class {
|
|
|
11507
12654
|
for (const entry of entries) {
|
|
11508
12655
|
if (!entry.isDirectory()) continue;
|
|
11509
12656
|
const skillDirName = entry.name;
|
|
11510
|
-
const skillPath =
|
|
11511
|
-
const skillFilePath =
|
|
12657
|
+
const skillPath = join17(dir, skillDirName);
|
|
12658
|
+
const skillFilePath = join17(skillPath, "SKILL.md");
|
|
11512
12659
|
if (!existsSync11(skillFilePath)) {
|
|
11513
12660
|
console.warn(`Skipping skill directory ${skillDirName}: SKILL.md not found`);
|
|
11514
12661
|
continue;
|
|
11515
12662
|
}
|
|
11516
12663
|
try {
|
|
11517
|
-
const fileContent = await
|
|
12664
|
+
const fileContent = await readFile7(skillFilePath, "utf-8");
|
|
11518
12665
|
const { metadata, content } = parseFrontmatter(fileContent);
|
|
11519
12666
|
if (!metadata.name) {
|
|
11520
12667
|
console.warn(`Skipping skill in ${skillDirName}: missing 'name' in frontmatter`);
|
|
@@ -11687,8 +12834,8 @@ async function activateSkills(allSkills, taskDescription, thread, options) {
|
|
|
11687
12834
|
}
|
|
11688
12835
|
|
|
11689
12836
|
// src/features/agents/agents.manager.ts
|
|
11690
|
-
import { readdir as readdir5, readFile as
|
|
11691
|
-
import { join as
|
|
12837
|
+
import { readdir as readdir5, readFile as readFile8 } from "fs/promises";
|
|
12838
|
+
import { join as join18 } from "path";
|
|
11692
12839
|
import { existsSync as existsSync12 } from "fs";
|
|
11693
12840
|
import { homedir as homedir7 } from "os";
|
|
11694
12841
|
function parseFrontmatter2(markdown) {
|
|
@@ -11746,10 +12893,10 @@ function validateDescription2(description) {
|
|
|
11746
12893
|
return !!description && description.length > 0 && description.length <= 1024;
|
|
11747
12894
|
}
|
|
11748
12895
|
function getGlobalAgentsDir() {
|
|
11749
|
-
return
|
|
12896
|
+
return join18(homedir7(), ".agents", "agents");
|
|
11750
12897
|
}
|
|
11751
12898
|
function getProjectAgentsDir(threadPath) {
|
|
11752
|
-
return
|
|
12899
|
+
return join18(threadPath, ".agents", "agents");
|
|
11753
12900
|
}
|
|
11754
12901
|
var AgentsManager = class {
|
|
11755
12902
|
/**
|
|
@@ -11781,14 +12928,14 @@ var AgentsManager = class {
|
|
|
11781
12928
|
for (const entry of entries) {
|
|
11782
12929
|
if (!entry.isDirectory()) continue;
|
|
11783
12930
|
const agentDirName = entry.name;
|
|
11784
|
-
const agentPath =
|
|
11785
|
-
const agentFilePath =
|
|
12931
|
+
const agentPath = join18(dir, agentDirName);
|
|
12932
|
+
const agentFilePath = join18(agentPath, "AGENT.md");
|
|
11786
12933
|
if (!existsSync12(agentFilePath)) {
|
|
11787
12934
|
console.warn(`[agents] Skipping agent directory ${agentDirName}: AGENT.md not found`);
|
|
11788
12935
|
continue;
|
|
11789
12936
|
}
|
|
11790
12937
|
try {
|
|
11791
|
-
const fileContent = await
|
|
12938
|
+
const fileContent = await readFile8(agentFilePath, "utf-8");
|
|
11792
12939
|
const { metadata, content } = parseFrontmatter2(fileContent);
|
|
11793
12940
|
if (!metadata.name) {
|
|
11794
12941
|
console.warn(
|
|
@@ -11971,12 +13118,12 @@ function extractAssistantContent(events, fallback) {
|
|
|
11971
13118
|
|
|
11972
13119
|
// src/features/chat/chat-post.route.ts
|
|
11973
13120
|
init_database();
|
|
11974
|
-
import { readFile as
|
|
11975
|
-
import { join as
|
|
13121
|
+
import { readFile as readFile9, unlink as unlink2 } from "fs/promises";
|
|
13122
|
+
import { join as join19 } from "path";
|
|
11976
13123
|
|
|
11977
13124
|
// src/features/project-todos/project-todos.database.ts
|
|
11978
13125
|
init_database();
|
|
11979
|
-
import { randomUUID as
|
|
13126
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
11980
13127
|
function serializeTodo(row) {
|
|
11981
13128
|
return {
|
|
11982
13129
|
...row,
|
|
@@ -12001,7 +13148,7 @@ async function getTodoById(db, todoId) {
|
|
|
12001
13148
|
}
|
|
12002
13149
|
async function insertTodo(projectId, title, description) {
|
|
12003
13150
|
const db = await getDatabase();
|
|
12004
|
-
const id =
|
|
13151
|
+
const id = randomUUID5();
|
|
12005
13152
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12006
13153
|
await db.execute({
|
|
12007
13154
|
sql: `INSERT INTO project_todos (id, projectId, title, description, status, working, createdAt, updatedAt)
|
|
@@ -12123,7 +13270,7 @@ async function invalidateGitStatusCache(db, threadId) {
|
|
|
12123
13270
|
|
|
12124
13271
|
// src/features/account/account-get-info.route.ts
|
|
12125
13272
|
init_database();
|
|
12126
|
-
import { randomUUID as
|
|
13273
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
12127
13274
|
var KEY_BALANCE = "PromptBalance";
|
|
12128
13275
|
var KEY_VERIFICATION = "PromptBalanceVerification";
|
|
12129
13276
|
var KEY_PLAN = "Plan";
|
|
@@ -12169,7 +13316,7 @@ async function getOrCreateClientReferenceId() {
|
|
|
12169
13316
|
if (existing !== null && existing !== void 0) {
|
|
12170
13317
|
return existing;
|
|
12171
13318
|
}
|
|
12172
|
-
const id =
|
|
13319
|
+
const id = randomUUID6();
|
|
12173
13320
|
await setState(db, KEY_CLIENT_REFERENCE_ID, id);
|
|
12174
13321
|
return id;
|
|
12175
13322
|
}
|
|
@@ -12450,7 +13597,7 @@ async function deserializeProject(db, row) {
|
|
|
12450
13597
|
// src/features/chat/chat-post.route.ts
|
|
12451
13598
|
init_utils();
|
|
12452
13599
|
function runValidationScript(script, cwd) {
|
|
12453
|
-
return new Promise((
|
|
13600
|
+
return new Promise((resolve7) => {
|
|
12454
13601
|
const child = spawnShell(script, { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
12455
13602
|
let output = "";
|
|
12456
13603
|
const stdoutDecoder = new TextDecoder();
|
|
@@ -12464,12 +13611,12 @@ function runValidationScript(script, cwd) {
|
|
|
12464
13611
|
child.on("close", (code) => {
|
|
12465
13612
|
output += stdoutDecoder.decode();
|
|
12466
13613
|
output += stderrDecoder.decode();
|
|
12467
|
-
|
|
13614
|
+
resolve7({ exitCode: code ?? 1, output: output.trim() });
|
|
12468
13615
|
});
|
|
12469
13616
|
child.on("error", (err) => {
|
|
12470
13617
|
output += stdoutDecoder.decode();
|
|
12471
13618
|
output += stderrDecoder.decode();
|
|
12472
|
-
|
|
13619
|
+
resolve7({
|
|
12473
13620
|
exitCode: 1,
|
|
12474
13621
|
output: `${output}${output ? "\n" : ""}${err.message}`.trim()
|
|
12475
13622
|
});
|
|
@@ -12586,7 +13733,7 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
12586
13733
|
}
|
|
12587
13734
|
let conversationId = thread.currentConversationId;
|
|
12588
13735
|
if (!conversationId) {
|
|
12589
|
-
conversationId =
|
|
13736
|
+
conversationId = randomUUID7();
|
|
12590
13737
|
const db2 = await getDatabase();
|
|
12591
13738
|
await db2.execute({
|
|
12592
13739
|
sql: "DELETE FROM todos WHERE threadId = ?",
|
|
@@ -12896,7 +14043,7 @@ ${result.output}`;
|
|
|
12896
14043
|
};
|
|
12897
14044
|
processingStateManager.pushEvent(threadId, iterationEvent);
|
|
12898
14045
|
yield iterationEvent;
|
|
12899
|
-
const newConversationId =
|
|
14046
|
+
const newConversationId = randomUUID7();
|
|
12900
14047
|
await threadManager.updateThread(threadId, {
|
|
12901
14048
|
currentConversationId: newConversationId
|
|
12902
14049
|
});
|
|
@@ -12988,11 +14135,11 @@ ${result.output}`;
|
|
|
12988
14135
|
try {
|
|
12989
14136
|
const todo = await getTodoByThreadId(db2, threadId);
|
|
12990
14137
|
if (todo && todo.status === "Plan") {
|
|
12991
|
-
const planFilePath =
|
|
14138
|
+
const planFilePath = join19(threadPath, `${todo.id}-plan.md`);
|
|
12992
14139
|
try {
|
|
12993
|
-
const planContent = await
|
|
14140
|
+
const planContent = await readFile9(planFilePath, "utf-8");
|
|
12994
14141
|
await updateTodo(db2, todo.id, { description: planContent.trim() });
|
|
12995
|
-
await
|
|
14142
|
+
await unlink2(planFilePath);
|
|
12996
14143
|
} catch {
|
|
12997
14144
|
}
|
|
12998
14145
|
}
|
|
@@ -13329,7 +14476,7 @@ ${summary}`;
|
|
|
13329
14476
|
}
|
|
13330
14477
|
|
|
13331
14478
|
// src/features/chat/chat-delete.route.ts
|
|
13332
|
-
import { randomUUID as
|
|
14479
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
13333
14480
|
init_database();
|
|
13334
14481
|
async function deleteChat(c, threadManager) {
|
|
13335
14482
|
try {
|
|
@@ -13351,7 +14498,7 @@ async function deleteChat(c, threadManager) {
|
|
|
13351
14498
|
sql: "DELETE FROM todos WHERE threadId = ?",
|
|
13352
14499
|
args: [threadId]
|
|
13353
14500
|
});
|
|
13354
|
-
const newConversationId =
|
|
14501
|
+
const newConversationId = randomUUID8();
|
|
13355
14502
|
await threadManager.updateThread(threadId, { currentConversationId: newConversationId });
|
|
13356
14503
|
return successResponse(
|
|
13357
14504
|
c,
|
|
@@ -13380,7 +14527,7 @@ function subscribeToChatStream(c, processingStateManager) {
|
|
|
13380
14527
|
return c.json({ error: "Thread is not currently processing" }, 404);
|
|
13381
14528
|
}
|
|
13382
14529
|
return stream2(c, async (writer) => {
|
|
13383
|
-
let
|
|
14530
|
+
let resolve7 = null;
|
|
13384
14531
|
const queue = [];
|
|
13385
14532
|
let done = false;
|
|
13386
14533
|
const replay = processingStateManager.getBufferedEvents(threadId).slice();
|
|
@@ -13391,18 +14538,18 @@ function subscribeToChatStream(c, processingStateManager) {
|
|
|
13391
14538
|
} else {
|
|
13392
14539
|
queue.push(event);
|
|
13393
14540
|
}
|
|
13394
|
-
if (
|
|
13395
|
-
|
|
13396
|
-
|
|
14541
|
+
if (resolve7) {
|
|
14542
|
+
resolve7();
|
|
14543
|
+
resolve7 = null;
|
|
13397
14544
|
}
|
|
13398
14545
|
});
|
|
13399
14546
|
const finishedBeforeSubscribe = !processingStateManager.isProcessing(threadId);
|
|
13400
14547
|
writer.onAbort(() => {
|
|
13401
14548
|
done = true;
|
|
13402
14549
|
unsubscribe();
|
|
13403
|
-
if (
|
|
13404
|
-
|
|
13405
|
-
|
|
14550
|
+
if (resolve7) {
|
|
14551
|
+
resolve7();
|
|
14552
|
+
resolve7 = null;
|
|
13406
14553
|
}
|
|
13407
14554
|
});
|
|
13408
14555
|
try {
|
|
@@ -13427,7 +14574,7 @@ function subscribeToChatStream(c, processingStateManager) {
|
|
|
13427
14574
|
}
|
|
13428
14575
|
if (!done) {
|
|
13429
14576
|
await new Promise((r) => {
|
|
13430
|
-
|
|
14577
|
+
resolve7 = r;
|
|
13431
14578
|
});
|
|
13432
14579
|
}
|
|
13433
14580
|
}
|
|
@@ -13475,10 +14622,10 @@ function createChatRoutes(threadManager, agentExecutor, conversationManager, pro
|
|
|
13475
14622
|
init_database();
|
|
13476
14623
|
|
|
13477
14624
|
// src/features/conversations/conversations.database.ts
|
|
13478
|
-
import { randomUUID as
|
|
14625
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
13479
14626
|
async function insertMessage(db, threadId, conversationId, message, model, attachments, planMode, imageMode, ralphMode, checkpointRef) {
|
|
13480
14627
|
try {
|
|
13481
|
-
const messageId =
|
|
14628
|
+
const messageId = randomUUID9();
|
|
13482
14629
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13483
14630
|
await db.execute(
|
|
13484
14631
|
`
|
|
@@ -15777,17 +16924,17 @@ async function getOnboardingStatus(c, metadataManager) {
|
|
|
15777
16924
|
init_utils();
|
|
15778
16925
|
async function getGitCheck(c) {
|
|
15779
16926
|
try {
|
|
15780
|
-
const gitInstalled = await new Promise((
|
|
16927
|
+
const gitInstalled = await new Promise((resolve7) => {
|
|
15781
16928
|
const proc = spawnProcess("git", ["--version"]);
|
|
15782
16929
|
let _err = "";
|
|
15783
16930
|
proc.stderr?.on("data", (d) => {
|
|
15784
16931
|
_err += d.toString();
|
|
15785
16932
|
});
|
|
15786
16933
|
proc.on("close", (code) => {
|
|
15787
|
-
|
|
16934
|
+
resolve7(code === 0);
|
|
15788
16935
|
});
|
|
15789
16936
|
proc.on("error", () => {
|
|
15790
|
-
|
|
16937
|
+
resolve7(false);
|
|
15791
16938
|
});
|
|
15792
16939
|
});
|
|
15793
16940
|
return c.json({ gitInstalled });
|
|
@@ -15821,13 +16968,13 @@ async function postOnboardingReset(c, metadataManager) {
|
|
|
15821
16968
|
|
|
15822
16969
|
// src/features/onboarding/onboarding-get-recommended-models.route.ts
|
|
15823
16970
|
import { readFileSync as readFileSync6, existsSync as existsSync13 } from "fs";
|
|
15824
|
-
import { join as
|
|
16971
|
+
import { join as join20, dirname as dirname4 } from "path";
|
|
15825
16972
|
import { fileURLToPath } from "url";
|
|
15826
16973
|
function getRecommendedModelsFilePath() {
|
|
15827
16974
|
const moduleDir = dirname4(fileURLToPath(import.meta.url));
|
|
15828
16975
|
const candidates = [
|
|
15829
|
-
|
|
15830
|
-
|
|
16976
|
+
join20(moduleDir, "../../../recommended-models.txt"),
|
|
16977
|
+
join20(moduleDir, "recommended-models.txt")
|
|
15831
16978
|
];
|
|
15832
16979
|
for (const candidate of candidates) {
|
|
15833
16980
|
if (existsSync13(candidate)) {
|
|
@@ -16482,12 +17629,12 @@ async function handleCheckRunning(c, projectManager) {
|
|
|
16482
17629
|
}
|
|
16483
17630
|
|
|
16484
17631
|
// src/core/run-command-detector.ts
|
|
16485
|
-
import { readFile as
|
|
16486
|
-
import { join as
|
|
17632
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
17633
|
+
import { join as join21 } from "path";
|
|
16487
17634
|
async function detectPackageManager(projectPath) {
|
|
16488
17635
|
try {
|
|
16489
|
-
const packageJsonPath =
|
|
16490
|
-
const content = await
|
|
17636
|
+
const packageJsonPath = join21(projectPath, "package.json");
|
|
17637
|
+
const content = await readFile10(packageJsonPath, "utf-8");
|
|
16491
17638
|
const packageJson = JSON.parse(content);
|
|
16492
17639
|
if (packageJson.packageManager) {
|
|
16493
17640
|
const pm = packageJson.packageManager.split("@")[0];
|
|
@@ -16502,8 +17649,8 @@ async function detectPackageManager(projectPath) {
|
|
|
16502
17649
|
}
|
|
16503
17650
|
async function detectRunScripts(projectPath) {
|
|
16504
17651
|
try {
|
|
16505
|
-
const packageJsonPath =
|
|
16506
|
-
const content = await
|
|
17652
|
+
const packageJsonPath = join21(projectPath, "package.json");
|
|
17653
|
+
const content = await readFile10(packageJsonPath, "utf-8");
|
|
16507
17654
|
const packageJson = JSON.parse(content);
|
|
16508
17655
|
const scripts = packageJson.scripts ?? {};
|
|
16509
17656
|
return {
|
|
@@ -16548,8 +17695,8 @@ async function suggestRunCommand(projectPath) {
|
|
|
16548
17695
|
}
|
|
16549
17696
|
|
|
16550
17697
|
// src/features/projects/projects-package-scripts.route.ts
|
|
16551
|
-
import { readFile as
|
|
16552
|
-
import { join as
|
|
17698
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
17699
|
+
import { join as join22 } from "path";
|
|
16553
17700
|
import { glob } from "glob";
|
|
16554
17701
|
function formatScriptName(scriptName) {
|
|
16555
17702
|
return scriptName.replace(/[-:_]/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
@@ -16577,8 +17724,8 @@ async function findPackageScripts(projectPath) {
|
|
|
16577
17724
|
const packageManager = await detectPackageManager(projectPath);
|
|
16578
17725
|
for (const filePath of packageJsonFiles) {
|
|
16579
17726
|
try {
|
|
16580
|
-
const fullPath =
|
|
16581
|
-
const content = await
|
|
17727
|
+
const fullPath = join22(projectPath, filePath);
|
|
17728
|
+
const content = await readFile11(fullPath, "utf-8");
|
|
16582
17729
|
const packageJson = JSON.parse(content);
|
|
16583
17730
|
if (packageJson.scripts) {
|
|
16584
17731
|
for (const [scriptName] of Object.entries(packageJson.scripts)) {
|
|
@@ -16625,8 +17772,8 @@ async function handleGetPackageScripts(c, projectManager) {
|
|
|
16625
17772
|
}
|
|
16626
17773
|
|
|
16627
17774
|
// src/features/projects/projects-ai-files.route.ts
|
|
16628
|
-
import { readdir as readdir6, readFile as
|
|
16629
|
-
import { join as
|
|
17775
|
+
import { readdir as readdir6, readFile as readFile12, writeFile as writeFile3, mkdir as mkdir2, access as access2, rm } from "fs/promises";
|
|
17776
|
+
import { join as join23, extname as extname4, basename } from "path";
|
|
16630
17777
|
import { existsSync as existsSync14 } from "fs";
|
|
16631
17778
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
16632
17779
|
".git",
|
|
@@ -16645,8 +17792,8 @@ async function buildFullTree(dirPath, relativeDirPath) {
|
|
|
16645
17792
|
try {
|
|
16646
17793
|
const entries = await readdir6(dirPath, { withFileTypes: true });
|
|
16647
17794
|
for (const entry of entries) {
|
|
16648
|
-
const entryRelPath =
|
|
16649
|
-
const entryAbsPath =
|
|
17795
|
+
const entryRelPath = join23(relativeDirPath, entry.name);
|
|
17796
|
+
const entryAbsPath = join23(dirPath, entry.name);
|
|
16650
17797
|
if (entry.isDirectory()) {
|
|
16651
17798
|
const children = await buildFullTree(entryAbsPath, entryRelPath);
|
|
16652
17799
|
nodes.push({
|
|
@@ -16674,8 +17821,8 @@ async function buildMarkdownFilteredTree(dirPath, relativeDirPath) {
|
|
|
16674
17821
|
try {
|
|
16675
17822
|
const entries = await readdir6(dirPath, { withFileTypes: true });
|
|
16676
17823
|
for (const entry of entries) {
|
|
16677
|
-
const entryRelPath =
|
|
16678
|
-
const entryAbsPath =
|
|
17824
|
+
const entryRelPath = join23(relativeDirPath, entry.name);
|
|
17825
|
+
const entryAbsPath = join23(dirPath, entry.name);
|
|
16679
17826
|
if (entry.isDirectory()) {
|
|
16680
17827
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
16681
17828
|
const children = await buildMarkdownFilteredTree(entryAbsPath, entryRelPath);
|
|
@@ -16710,8 +17857,8 @@ async function buildAIFileTree(projectPath) {
|
|
|
16710
17857
|
{ key: "agents", label: "Agents" }
|
|
16711
17858
|
];
|
|
16712
17859
|
for (const { key, label } of agentsFolders) {
|
|
16713
|
-
const relPath =
|
|
16714
|
-
const absPath =
|
|
17860
|
+
const relPath = join23(".agents", key);
|
|
17861
|
+
const absPath = join23(projectPath, relPath);
|
|
16715
17862
|
const exists = existsSync14(absPath);
|
|
16716
17863
|
if (exists) {
|
|
16717
17864
|
const children = await buildFullTree(absPath, relPath);
|
|
@@ -16746,7 +17893,7 @@ async function buildAIFileTree(projectPath) {
|
|
|
16746
17893
|
if (entry.name === ".agents" || entry.name === "AGENTS.md" || IGNORED_DIRS.has(entry.name))
|
|
16747
17894
|
continue;
|
|
16748
17895
|
const entryRelPath = entry.name;
|
|
16749
|
-
const entryAbsPath =
|
|
17896
|
+
const entryAbsPath = join23(projectPath, entry.name);
|
|
16750
17897
|
if (entry.isFile() && MARKDOWN_EXTS.has(extname4(entry.name))) {
|
|
16751
17898
|
nodes.push({
|
|
16752
17899
|
id: entryRelPath,
|
|
@@ -16773,7 +17920,7 @@ async function buildAIFileTree(projectPath) {
|
|
|
16773
17920
|
}
|
|
16774
17921
|
function validateFilePath(projectPath, filePath) {
|
|
16775
17922
|
if (!filePath) return null;
|
|
16776
|
-
const abs =
|
|
17923
|
+
const abs = join23(projectPath, filePath);
|
|
16777
17924
|
if (!abs.startsWith(projectPath + "/") && abs !== projectPath) return null;
|
|
16778
17925
|
return abs;
|
|
16779
17926
|
}
|
|
@@ -16860,7 +18007,7 @@ Links to important documentation, tools, or references.
|
|
|
16860
18007
|
} catch {
|
|
16861
18008
|
return c.json({ error: { code: "NOT_FOUND", message: `File not found: ${filePath}` } }, 404);
|
|
16862
18009
|
}
|
|
16863
|
-
const content = await
|
|
18010
|
+
const content = await readFile12(absPath, "utf-8");
|
|
16864
18011
|
return c.json({ content, path: filePath });
|
|
16865
18012
|
} catch (error) {
|
|
16866
18013
|
return errorResponse(
|
|
@@ -16899,7 +18046,7 @@ async function handleSaveAIFile(c, projectManager) {
|
|
|
16899
18046
|
if (!absPath) {
|
|
16900
18047
|
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
|
|
16901
18048
|
}
|
|
16902
|
-
const parentDir =
|
|
18049
|
+
const parentDir = join23(absPath, "..");
|
|
16903
18050
|
await mkdir2(parentDir, { recursive: true });
|
|
16904
18051
|
await writeFile3(absPath, content, "utf-8");
|
|
16905
18052
|
return c.json({ success: true, path: filePath });
|
|
@@ -16999,10 +18146,10 @@ async function handleCreateSkill(c, projectManager) {
|
|
|
16999
18146
|
if (!project) {
|
|
17000
18147
|
return errorResponse(c, ErrorCodes.PROJECT_NOT_FOUND, `Project not found: ${projectId}`, 404);
|
|
17001
18148
|
}
|
|
17002
|
-
const skillRelPath =
|
|
17003
|
-
const skillAbsPath =
|
|
17004
|
-
const skillFileRelPath =
|
|
17005
|
-
const skillFileAbsPath =
|
|
18149
|
+
const skillRelPath = join23(".agents", "skills", name);
|
|
18150
|
+
const skillAbsPath = join23(project.path, skillRelPath);
|
|
18151
|
+
const skillFileRelPath = join23(skillRelPath, "SKILL.md");
|
|
18152
|
+
const skillFileAbsPath = join23(skillAbsPath, "SKILL.md");
|
|
17006
18153
|
if (existsSync14(skillAbsPath)) {
|
|
17007
18154
|
return c.json(
|
|
17008
18155
|
{ error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
|
|
@@ -17074,11 +18221,14 @@ async function handleGetProjectEnvVars(c, projectManager) {
|
|
|
17074
18221
|
return errorResponse(c, ErrorCodes.PROJECT_NOT_FOUND, `Project not found: ${projectId}`, 404);
|
|
17075
18222
|
}
|
|
17076
18223
|
const db = await getDatabase();
|
|
17077
|
-
const rows = await
|
|
17078
|
-
const vars =
|
|
17079
|
-
|
|
17080
|
-
|
|
17081
|
-
|
|
18224
|
+
const rows = await listProjectEnvVars(db, projectId);
|
|
18225
|
+
const vars = await Promise.all(
|
|
18226
|
+
rows.map(async (row) => ({
|
|
18227
|
+
name: row.name,
|
|
18228
|
+
hasValue: true,
|
|
18229
|
+
value: await decryptProviderKey(row.encryptedValue)
|
|
18230
|
+
}))
|
|
18231
|
+
);
|
|
17082
18232
|
return c.json({ vars });
|
|
17083
18233
|
} catch (error) {
|
|
17084
18234
|
return errorResponse(
|
|
@@ -17248,7 +18398,7 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
17248
18398
|
|
|
17249
18399
|
// src/features/projects/projects.manager.ts
|
|
17250
18400
|
init_utils();
|
|
17251
|
-
import { join as
|
|
18401
|
+
import { join as join26 } from "path";
|
|
17252
18402
|
import { realpathSync as realpathSync2 } from "fs";
|
|
17253
18403
|
import { rm as rm4 } from "fs/promises";
|
|
17254
18404
|
|
|
@@ -17315,13 +18465,13 @@ var ProcessManager = class extends EventEmitter {
|
|
|
17315
18465
|
}
|
|
17316
18466
|
};
|
|
17317
18467
|
const waitForQueue = () => {
|
|
17318
|
-
return new Promise((
|
|
18468
|
+
return new Promise((resolve7) => {
|
|
17319
18469
|
const queue = this.queues.get(projectId);
|
|
17320
18470
|
if (queue && queue.length > 0) {
|
|
17321
|
-
|
|
18471
|
+
resolve7();
|
|
17322
18472
|
} else {
|
|
17323
18473
|
const res = this.resolvers.get(projectId);
|
|
17324
|
-
if (res) res.push(
|
|
18474
|
+
if (res) res.push(resolve7);
|
|
17325
18475
|
}
|
|
17326
18476
|
});
|
|
17327
18477
|
};
|
|
@@ -17469,14 +18619,14 @@ var ProcessManager = class extends EventEmitter {
|
|
|
17469
18619
|
|
|
17470
18620
|
// src/features/projects/projects.creator.ts
|
|
17471
18621
|
init_utils();
|
|
17472
|
-
import { randomUUID as
|
|
17473
|
-
import { basename as basename2, join as
|
|
18622
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
18623
|
+
import { basename as basename2, join as join25, relative as relative5 } from "path";
|
|
17474
18624
|
import { access as access3, mkdir as mkdir4, rm as rm3, stat as stat3, readdir as readdir8 } from "fs/promises";
|
|
17475
18625
|
|
|
17476
18626
|
// src/features/scaffold/scaffold.runner.ts
|
|
17477
18627
|
init_utils();
|
|
17478
18628
|
import { existsSync as existsSync15 } from "fs";
|
|
17479
|
-
import { join as
|
|
18629
|
+
import { join as join24 } from "path";
|
|
17480
18630
|
import { mkdir as mkdir3, readdir as readdir7, stat as stat2, rename, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
17481
18631
|
import { createInterface as createInterface2 } from "readline";
|
|
17482
18632
|
|
|
@@ -18198,7 +19348,7 @@ function loadCatalog() {
|
|
|
18198
19348
|
}
|
|
18199
19349
|
async function writeAgentsMd(projectPath, agentsMd) {
|
|
18200
19350
|
if (!agentsMd) return;
|
|
18201
|
-
const agentsPath =
|
|
19351
|
+
const agentsPath = join24(projectPath, "AGENTS.md");
|
|
18202
19352
|
if (existsSync15(agentsPath)) return;
|
|
18203
19353
|
await writeFile4(agentsPath, agentsMd, "utf-8");
|
|
18204
19354
|
}
|
|
@@ -18356,7 +19506,7 @@ async function* runCommandStreaming(cwd, command) {
|
|
|
18356
19506
|
}
|
|
18357
19507
|
}
|
|
18358
19508
|
async function* createScaffoldedProjectStreaming(options) {
|
|
18359
|
-
const projectPath =
|
|
19509
|
+
const projectPath = join24(options.parentDir, options.threadId);
|
|
18360
19510
|
if (!existsSync15(options.parentDir)) {
|
|
18361
19511
|
yield {
|
|
18362
19512
|
type: "result",
|
|
@@ -18446,7 +19596,7 @@ async function* createScaffoldedProjectStreaming(options) {
|
|
|
18446
19596
|
}
|
|
18447
19597
|
try {
|
|
18448
19598
|
const projectName2 = getProjectName(options.projectName);
|
|
18449
|
-
const projectSubDir =
|
|
19599
|
+
const projectSubDir = join24(projectPath, projectName2);
|
|
18450
19600
|
try {
|
|
18451
19601
|
const subDirStat = await stat2(projectSubDir);
|
|
18452
19602
|
if (subDirStat.isDirectory()) {
|
|
@@ -18456,8 +19606,8 @@ async function* createScaffoldedProjectStreaming(options) {
|
|
|
18456
19606
|
};
|
|
18457
19607
|
const items = await readdir7(projectSubDir);
|
|
18458
19608
|
for (const item of items) {
|
|
18459
|
-
const oldPath =
|
|
18460
|
-
const newPath =
|
|
19609
|
+
const oldPath = join24(projectSubDir, item);
|
|
19610
|
+
const newPath = join24(projectPath, item);
|
|
18461
19611
|
await rename(oldPath, newPath);
|
|
18462
19612
|
}
|
|
18463
19613
|
await rm2(projectSubDir, { recursive: true, force: true });
|
|
@@ -18655,14 +19805,14 @@ var ProjectCreator = class {
|
|
|
18655
19805
|
}
|
|
18656
19806
|
let initialThreadId;
|
|
18657
19807
|
try {
|
|
18658
|
-
const projectId =
|
|
19808
|
+
const projectId = randomUUID10();
|
|
18659
19809
|
const existingProjects = await this.metadataManager.loadProjects();
|
|
18660
19810
|
const existingNames = existingProjects.map((p) => p.name);
|
|
18661
19811
|
const projectName = this.ensureUniqueProjectName(
|
|
18662
19812
|
this.deriveProjectName(gitUrl),
|
|
18663
19813
|
existingNames
|
|
18664
19814
|
);
|
|
18665
|
-
initialThreadId =
|
|
19815
|
+
initialThreadId = randomUUID10();
|
|
18666
19816
|
const initialThreadTitle = generateRandomThreadName();
|
|
18667
19817
|
const baseFolderName = this.toFolderName(this.deriveProjectName(gitUrl));
|
|
18668
19818
|
if (!baseFolderName) {
|
|
@@ -18673,7 +19823,7 @@ var ProjectCreator = class {
|
|
|
18673
19823
|
return;
|
|
18674
19824
|
}
|
|
18675
19825
|
const uniqueFolderName = await this.ensureUniqueFolderName(baseFolderName);
|
|
18676
|
-
const firstThreadPath =
|
|
19826
|
+
const firstThreadPath = join25(this.rootFolder, uniqueFolderName);
|
|
18677
19827
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18678
19828
|
yield {
|
|
18679
19829
|
type: "progress",
|
|
@@ -18811,8 +19961,8 @@ var ProjectCreator = class {
|
|
|
18811
19961
|
};
|
|
18812
19962
|
return;
|
|
18813
19963
|
}
|
|
18814
|
-
const projectId =
|
|
18815
|
-
const initialThreadId =
|
|
19964
|
+
const projectId = randomUUID10();
|
|
19965
|
+
const initialThreadId = randomUUID10();
|
|
18816
19966
|
const initialThreadTitle = generateRandomThreadName();
|
|
18817
19967
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18818
19968
|
try {
|
|
@@ -18923,15 +20073,15 @@ var ProjectCreator = class {
|
|
|
18923
20073
|
};
|
|
18924
20074
|
return;
|
|
18925
20075
|
}
|
|
18926
|
-
const projectId =
|
|
18927
|
-
const initialThreadId =
|
|
20076
|
+
const projectId = randomUUID10();
|
|
20077
|
+
const initialThreadId = randomUUID10();
|
|
18928
20078
|
const initialThreadTitle = generateRandomThreadName();
|
|
18929
20079
|
let folderPath;
|
|
18930
20080
|
let projectMetadataSaved = false;
|
|
18931
20081
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18932
20082
|
try {
|
|
18933
20083
|
const uniqueFolderName = await this.ensureUniqueFolderName(folderName);
|
|
18934
|
-
folderPath =
|
|
20084
|
+
folderPath = join25(this.rootFolder, uniqueFolderName);
|
|
18935
20085
|
yield {
|
|
18936
20086
|
type: "progress",
|
|
18937
20087
|
message: `Creating folder "${uniqueFolderName}"...`,
|
|
@@ -19018,7 +20168,7 @@ var ProjectCreator = class {
|
|
|
19018
20168
|
return name;
|
|
19019
20169
|
}
|
|
19020
20170
|
generateThreadPath(_projectId, threadId) {
|
|
19021
|
-
return
|
|
20171
|
+
return join25(this.rootFolder, threadId);
|
|
19022
20172
|
}
|
|
19023
20173
|
toFolderName(name) {
|
|
19024
20174
|
return name.trim().toLowerCase().replace(/[\\/]+/g, " ").replace(/[^a-z0-9._ -]+/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -19034,7 +20184,7 @@ var ProjectCreator = class {
|
|
|
19034
20184
|
async ensureUniqueFolderName(baseFolderName) {
|
|
19035
20185
|
let candidate = baseFolderName;
|
|
19036
20186
|
let counter = 1;
|
|
19037
|
-
while (await this.folderExists(
|
|
20187
|
+
while (await this.folderExists(join25(this.rootFolder, candidate))) {
|
|
19038
20188
|
candidate = `${baseFolderName}-${counter}`;
|
|
19039
20189
|
counter++;
|
|
19040
20190
|
}
|
|
@@ -19066,8 +20216,8 @@ var ProjectCreator = class {
|
|
|
19066
20216
|
yield { type: "error", message: `Cannot access folder: ${folderPath}` };
|
|
19067
20217
|
return;
|
|
19068
20218
|
}
|
|
19069
|
-
const projectId =
|
|
19070
|
-
const initialThreadId =
|
|
20219
|
+
const projectId = randomUUID10();
|
|
20220
|
+
const initialThreadId = randomUUID10();
|
|
19071
20221
|
const initialThreadTitle = generateRandomThreadName();
|
|
19072
20222
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
19073
20223
|
try {
|
|
@@ -19188,19 +20338,19 @@ var ProjectCreator = class {
|
|
|
19188
20338
|
}
|
|
19189
20339
|
async findAllPackageJsonFiles(rootPath, currentPath, setupScripts) {
|
|
19190
20340
|
try {
|
|
19191
|
-
await access3(
|
|
20341
|
+
await access3(join25(currentPath, "package.json"));
|
|
19192
20342
|
const relativePath = relative5(rootPath, currentPath);
|
|
19193
20343
|
let installCommand = "";
|
|
19194
20344
|
try {
|
|
19195
|
-
await access3(
|
|
20345
|
+
await access3(join25(currentPath, "yarn.lock"));
|
|
19196
20346
|
installCommand = "yarn install";
|
|
19197
20347
|
} catch {
|
|
19198
20348
|
try {
|
|
19199
|
-
await access3(
|
|
20349
|
+
await access3(join25(currentPath, "bun.lock"));
|
|
19200
20350
|
installCommand = "bun install";
|
|
19201
20351
|
} catch {
|
|
19202
20352
|
try {
|
|
19203
|
-
await access3(
|
|
20353
|
+
await access3(join25(currentPath, "pnpm-lock.yaml"));
|
|
19204
20354
|
installCommand = "pnpm install";
|
|
19205
20355
|
} catch {
|
|
19206
20356
|
installCommand = "npm install";
|
|
@@ -19217,7 +20367,7 @@ var ProjectCreator = class {
|
|
|
19217
20367
|
try {
|
|
19218
20368
|
const entries = await readdir8(currentPath);
|
|
19219
20369
|
for (const entry of entries) {
|
|
19220
|
-
const entryPath =
|
|
20370
|
+
const entryPath = join25(currentPath, entry);
|
|
19221
20371
|
try {
|
|
19222
20372
|
const stats = await stat3(entryPath);
|
|
19223
20373
|
if (stats.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
|
|
@@ -19563,7 +20713,7 @@ var ProjectManagerImpl = class {
|
|
|
19563
20713
|
const session = this.terminalSessionManager.getOrCreateSession(threadId, thread.path);
|
|
19564
20714
|
let workingDir;
|
|
19565
20715
|
if (cwd) {
|
|
19566
|
-
workingDir = cwd.startsWith("/") ? cwd :
|
|
20716
|
+
workingDir = cwd.startsWith("/") ? cwd : join26(thread.path, cwd);
|
|
19567
20717
|
this.terminalSessionManager.updateWorkingDirectory(threadId, workingDir);
|
|
19568
20718
|
} else {
|
|
19569
20719
|
workingDir = session.currentWorkingDirectory;
|
|
@@ -19619,7 +20769,7 @@ ___CWD___%s
|
|
|
19619
20769
|
if (outputBuffer.length > 0) {
|
|
19620
20770
|
yield outputBuffer.shift();
|
|
19621
20771
|
} else {
|
|
19622
|
-
await new Promise((
|
|
20772
|
+
await new Promise((resolve7) => setTimeout(resolve7, 50));
|
|
19623
20773
|
}
|
|
19624
20774
|
}
|
|
19625
20775
|
}
|
|
@@ -20354,8 +21504,8 @@ function createScaffoldRoutes(projectManager) {
|
|
|
20354
21504
|
}
|
|
20355
21505
|
|
|
20356
21506
|
// src/features/slash-commands/slash-commands.manager.ts
|
|
20357
|
-
import { readdir as readdir9, readFile as
|
|
20358
|
-
import { join as
|
|
21507
|
+
import { readdir as readdir9, readFile as readFile13, mkdir as mkdir5, writeFile as writeFile5, unlink as unlink3 } from "fs/promises";
|
|
21508
|
+
import { join as join27, basename as basename3, extname as extname5, relative as relative6 } from "path";
|
|
20359
21509
|
import { existsSync as existsSync16 } from "fs";
|
|
20360
21510
|
import { homedir as homedir8 } from "os";
|
|
20361
21511
|
function slugify(filename) {
|
|
@@ -20401,10 +21551,10 @@ function parseFrontmatter3(markdown) {
|
|
|
20401
21551
|
return { metadata, content };
|
|
20402
21552
|
}
|
|
20403
21553
|
function getGlobalCommandsDir() {
|
|
20404
|
-
return
|
|
21554
|
+
return join27(homedir8(), ".agents", "commands");
|
|
20405
21555
|
}
|
|
20406
21556
|
function getProjectCommandsDir(threadPath) {
|
|
20407
|
-
return
|
|
21557
|
+
return join27(threadPath, ".agents", "commands");
|
|
20408
21558
|
}
|
|
20409
21559
|
var SlashCommandManager = class _SlashCommandManager {
|
|
20410
21560
|
/**
|
|
@@ -20477,8 +21627,8 @@ var SlashCommandManager = class _SlashCommandManager {
|
|
|
20477
21627
|
const files = await readdir9(dir);
|
|
20478
21628
|
for (const file of files) {
|
|
20479
21629
|
if (!file.endsWith(".md")) continue;
|
|
20480
|
-
const filePath =
|
|
20481
|
-
const fileContent = await
|
|
21630
|
+
const filePath = join27(dir, file);
|
|
21631
|
+
const fileContent = await readFile13(filePath, "utf-8");
|
|
20482
21632
|
const { metadata, content } = parseFrontmatter3(fileContent);
|
|
20483
21633
|
const nameWithoutExt = basename3(file, extname5(file));
|
|
20484
21634
|
const commandName = slugify(nameWithoutExt);
|
|
@@ -20512,7 +21662,7 @@ var SlashCommandManager = class _SlashCommandManager {
|
|
|
20512
21662
|
await mkdir5(dir, { recursive: true });
|
|
20513
21663
|
}
|
|
20514
21664
|
const filename = `${name}.md`;
|
|
20515
|
-
const filePath =
|
|
21665
|
+
const filePath = join27(dir, filename);
|
|
20516
21666
|
if (existsSync16(filePath)) {
|
|
20517
21667
|
throw new Error(`Command already exists: ${name}`);
|
|
20518
21668
|
}
|
|
@@ -20577,7 +21727,7 @@ var SlashCommandManager = class _SlashCommandManager {
|
|
|
20577
21727
|
if (!existsSync16(filePath)) {
|
|
20578
21728
|
throw new Error(`Command file not found: ${filePath}`);
|
|
20579
21729
|
}
|
|
20580
|
-
await
|
|
21730
|
+
await unlink3(filePath);
|
|
20581
21731
|
}
|
|
20582
21732
|
/**
|
|
20583
21733
|
* Get a specific command by name
|
|
@@ -21106,8 +22256,8 @@ function createRuleRoutes(router, projectManager) {
|
|
|
21106
22256
|
|
|
21107
22257
|
// src/features/threads/threads.manager.ts
|
|
21108
22258
|
init_utils();
|
|
21109
|
-
import { randomUUID as
|
|
21110
|
-
import { join as
|
|
22259
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
22260
|
+
import { join as join28 } from "path";
|
|
21111
22261
|
import { execSync as execSync3 } from "child_process";
|
|
21112
22262
|
import { rm as rm5, stat as stat4, mkdir as mkdir6 } from "fs/promises";
|
|
21113
22263
|
init_database();
|
|
@@ -21173,7 +22323,7 @@ var ThreadManagerImpl = class {
|
|
|
21173
22323
|
} catch {
|
|
21174
22324
|
await mkdir6(project.path, { recursive: true });
|
|
21175
22325
|
}
|
|
21176
|
-
threadId =
|
|
22326
|
+
threadId = randomUUID11();
|
|
21177
22327
|
const threadPath = this.generateThreadPath(projectId, threadId);
|
|
21178
22328
|
const existingThreads = await this.metadataManager.loadThreads();
|
|
21179
22329
|
const existingTitles = new Set(
|
|
@@ -21497,8 +22647,8 @@ var ThreadManagerImpl = class {
|
|
|
21497
22647
|
isDone = true;
|
|
21498
22648
|
resolveNext();
|
|
21499
22649
|
});
|
|
21500
|
-
const waitForData = () => new Promise((
|
|
21501
|
-
resolveNext =
|
|
22650
|
+
const waitForData = () => new Promise((resolve7) => {
|
|
22651
|
+
resolveNext = resolve7;
|
|
21502
22652
|
});
|
|
21503
22653
|
while (!isDone || outputQueue.length > 0 || errorQueue.length > 0) {
|
|
21504
22654
|
if (outputQueue.length > 0) {
|
|
@@ -21531,7 +22681,7 @@ var ThreadManagerImpl = class {
|
|
|
21531
22681
|
* - 7.4 - THE CLI SHALL ensure each Thread clone is stored in a unique directory path
|
|
21532
22682
|
*/
|
|
21533
22683
|
generateThreadPath(_projectId, threadId) {
|
|
21534
|
-
return
|
|
22684
|
+
return join28(this.rootFolder, threadId);
|
|
21535
22685
|
}
|
|
21536
22686
|
/**
|
|
21537
22687
|
* Generates a thread title if not provided
|
|
@@ -21561,7 +22711,7 @@ var ThreadManagerImpl = class {
|
|
|
21561
22711
|
if (!thread) {
|
|
21562
22712
|
throw new Error(`Thread not found: ${threadId}`);
|
|
21563
22713
|
}
|
|
21564
|
-
return new Promise((
|
|
22714
|
+
return new Promise((resolve7, reject) => {
|
|
21565
22715
|
const gitProcess = spawnProcess(
|
|
21566
22716
|
"git",
|
|
21567
22717
|
["ls-files", "--cached", "--others", "--exclude-standard"],
|
|
@@ -21580,7 +22730,7 @@ var ThreadManagerImpl = class {
|
|
|
21580
22730
|
gitProcess.on("close", (code) => {
|
|
21581
22731
|
if (code === 0) {
|
|
21582
22732
|
const files = output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
21583
|
-
|
|
22733
|
+
resolve7(files);
|
|
21584
22734
|
} else {
|
|
21585
22735
|
reject(new Error(`git ls-files failed with code ${code}: ${errorOutput}`));
|
|
21586
22736
|
}
|
|
@@ -21724,14 +22874,14 @@ async function handleDeleteThread(c, threadManager) {
|
|
|
21724
22874
|
}
|
|
21725
22875
|
|
|
21726
22876
|
// src/core/project-inspector.ts
|
|
21727
|
-
import { readFile as
|
|
22877
|
+
import { readFile as readFile14, readdir as readdir10 } from "fs/promises";
|
|
21728
22878
|
import { existsSync as existsSync17 } from "fs";
|
|
21729
|
-
import { join as
|
|
22879
|
+
import { join as join29, basename as basename4, relative as relative7 } from "path";
|
|
21730
22880
|
import { glob as glob2 } from "glob";
|
|
21731
22881
|
|
|
21732
22882
|
// src/features/project-scripts/project-scripts.database.ts
|
|
21733
22883
|
init_database();
|
|
21734
|
-
import { randomUUID as
|
|
22884
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
21735
22885
|
async function getScriptsByProject(db, projectId) {
|
|
21736
22886
|
const result = await db.execute({
|
|
21737
22887
|
sql: `SELECT id, projectId, workspace, name, command, friendlyName, updatedAt
|
|
@@ -21748,7 +22898,7 @@ async function upsertProjectScripts(projectId, scripts) {
|
|
|
21748
22898
|
sql: `INSERT OR REPLACE INTO project_scripts (id, projectId, workspace, name, command, friendlyName, updatedAt)
|
|
21749
22899
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
21750
22900
|
args: [
|
|
21751
|
-
|
|
22901
|
+
randomUUID12(),
|
|
21752
22902
|
projectId,
|
|
21753
22903
|
script.workspace,
|
|
21754
22904
|
script.name,
|
|
@@ -21770,13 +22920,13 @@ async function inspectProject(projectPath, projectId) {
|
|
|
21770
22920
|
return scripts;
|
|
21771
22921
|
}
|
|
21772
22922
|
async function detectPackageManager2(projectPath) {
|
|
21773
|
-
if (existsSync17(
|
|
22923
|
+
if (existsSync17(join29(projectPath, "bun.lockb")) || existsSync17(join29(projectPath, "bun.lock"))) {
|
|
21774
22924
|
return "bun";
|
|
21775
22925
|
}
|
|
21776
|
-
if (existsSync17(
|
|
22926
|
+
if (existsSync17(join29(projectPath, "pnpm-lock.yaml"))) {
|
|
21777
22927
|
return "pnpm";
|
|
21778
22928
|
}
|
|
21779
|
-
if (existsSync17(
|
|
22929
|
+
if (existsSync17(join29(projectPath, "yarn.lock"))) {
|
|
21780
22930
|
return "yarn";
|
|
21781
22931
|
}
|
|
21782
22932
|
try {
|
|
@@ -21795,8 +22945,8 @@ async function detectPackageManager2(projectPath) {
|
|
|
21795
22945
|
return "npm";
|
|
21796
22946
|
}
|
|
21797
22947
|
async function detectMonoRepoType(projectPath) {
|
|
21798
|
-
const hasPnpmWorkspace = existsSync17(
|
|
21799
|
-
if (existsSync17(
|
|
22948
|
+
const hasPnpmWorkspace = existsSync17(join29(projectPath, "pnpm-workspace.yaml"));
|
|
22949
|
+
if (existsSync17(join29(projectPath, "nx.json"))) {
|
|
21800
22950
|
return "nx";
|
|
21801
22951
|
}
|
|
21802
22952
|
const pkg = await readPackageJson(projectPath);
|
|
@@ -21810,10 +22960,10 @@ async function detectMonoRepoType(projectPath) {
|
|
|
21810
22960
|
if (hasPnpmWorkspace) {
|
|
21811
22961
|
return "pnpm";
|
|
21812
22962
|
}
|
|
21813
|
-
if (existsSync17(
|
|
22963
|
+
if (existsSync17(join29(projectPath, "lerna.json"))) {
|
|
21814
22964
|
return "lerna";
|
|
21815
22965
|
}
|
|
21816
|
-
if (existsSync17(
|
|
22966
|
+
if (existsSync17(join29(projectPath, "turbo.json"))) {
|
|
21817
22967
|
return "turbo";
|
|
21818
22968
|
}
|
|
21819
22969
|
const folderWorkspaces = await detectFolderBasedWorkspaces(projectPath);
|
|
@@ -21854,8 +23004,8 @@ async function resolvePackageJsonWorkspaces(projectPath) {
|
|
|
21854
23004
|
});
|
|
21855
23005
|
const workspaces = [];
|
|
21856
23006
|
for (const folder of folders) {
|
|
21857
|
-
const absPath =
|
|
21858
|
-
if (existsSync17(
|
|
23007
|
+
const absPath = join29(projectPath, folder);
|
|
23008
|
+
if (existsSync17(join29(absPath, "package.json"))) {
|
|
21859
23009
|
workspaces.push({
|
|
21860
23010
|
name: basename4(folder),
|
|
21861
23011
|
folder: absPath,
|
|
@@ -21870,12 +23020,12 @@ async function resolvePackageJsonWorkspaces(projectPath) {
|
|
|
21870
23020
|
return workspaces;
|
|
21871
23021
|
}
|
|
21872
23022
|
async function resolvePnpmWorkspaces(projectPath) {
|
|
21873
|
-
const yamlPath =
|
|
23023
|
+
const yamlPath = join29(projectPath, "pnpm-workspace.yaml");
|
|
21874
23024
|
if (!existsSync17(yamlPath)) {
|
|
21875
23025
|
return [{ name: "root", folder: projectPath, relativePath: "." }];
|
|
21876
23026
|
}
|
|
21877
23027
|
try {
|
|
21878
|
-
const yaml = await
|
|
23028
|
+
const yaml = await readFile14(yamlPath, "utf-8");
|
|
21879
23029
|
const patterns = [];
|
|
21880
23030
|
for (const line of yaml.split("\n")) {
|
|
21881
23031
|
const trimmed = line.trim();
|
|
@@ -21896,8 +23046,8 @@ async function resolvePnpmWorkspaces(projectPath) {
|
|
|
21896
23046
|
});
|
|
21897
23047
|
const workspaces = [];
|
|
21898
23048
|
for (const folder of folders) {
|
|
21899
|
-
const absPath =
|
|
21900
|
-
if (existsSync17(
|
|
23049
|
+
const absPath = join29(projectPath, folder);
|
|
23050
|
+
if (existsSync17(join29(absPath, "package.json"))) {
|
|
21901
23051
|
const wsPkg = await readPackageJson(absPath);
|
|
21902
23052
|
workspaces.push({
|
|
21903
23053
|
name: wsPkg?.name ?? basename4(folder),
|
|
@@ -21916,12 +23066,12 @@ async function resolvePnpmWorkspaces(projectPath) {
|
|
|
21916
23066
|
}
|
|
21917
23067
|
}
|
|
21918
23068
|
async function resolveLernaWorkspaces(projectPath) {
|
|
21919
|
-
const lernaPath =
|
|
23069
|
+
const lernaPath = join29(projectPath, "lerna.json");
|
|
21920
23070
|
if (!existsSync17(lernaPath)) {
|
|
21921
23071
|
return [{ name: "root", folder: projectPath, relativePath: "." }];
|
|
21922
23072
|
}
|
|
21923
23073
|
try {
|
|
21924
|
-
const content = await
|
|
23074
|
+
const content = await readFile14(lernaPath, "utf-8");
|
|
21925
23075
|
const lerna = JSON.parse(content);
|
|
21926
23076
|
const patterns = lerna.packages ?? ["packages/*"];
|
|
21927
23077
|
const folders = await glob2(patterns, {
|
|
@@ -21930,8 +23080,8 @@ async function resolveLernaWorkspaces(projectPath) {
|
|
|
21930
23080
|
});
|
|
21931
23081
|
const workspaces = [];
|
|
21932
23082
|
for (const folder of folders) {
|
|
21933
|
-
const absPath =
|
|
21934
|
-
if (existsSync17(
|
|
23083
|
+
const absPath = join29(projectPath, folder);
|
|
23084
|
+
if (existsSync17(join29(absPath, "package.json"))) {
|
|
21935
23085
|
const wsPkg = await readPackageJson(absPath);
|
|
21936
23086
|
workspaces.push({
|
|
21937
23087
|
name: wsPkg?.name ?? basename4(folder),
|
|
@@ -21951,17 +23101,17 @@ async function resolveLernaWorkspaces(projectPath) {
|
|
|
21951
23101
|
}
|
|
21952
23102
|
async function resolveNxWorkspaces(projectPath) {
|
|
21953
23103
|
const workspaces = [];
|
|
21954
|
-
const workspaceJsonPath =
|
|
23104
|
+
const workspaceJsonPath = join29(projectPath, "workspace.json");
|
|
21955
23105
|
if (existsSync17(workspaceJsonPath)) {
|
|
21956
23106
|
try {
|
|
21957
|
-
const content = await
|
|
23107
|
+
const content = await readFile14(workspaceJsonPath, "utf-8");
|
|
21958
23108
|
const wsJson = JSON.parse(content);
|
|
21959
23109
|
for (const [name, value] of Object.entries(wsJson.projects ?? {})) {
|
|
21960
23110
|
const folder = typeof value === "string" ? value : value.root;
|
|
21961
23111
|
if (folder) {
|
|
21962
23112
|
workspaces.push({
|
|
21963
23113
|
name,
|
|
21964
|
-
folder:
|
|
23114
|
+
folder: join29(projectPath, folder),
|
|
21965
23115
|
relativePath: folder
|
|
21966
23116
|
});
|
|
21967
23117
|
}
|
|
@@ -21976,13 +23126,13 @@ async function resolveNxWorkspaces(projectPath) {
|
|
|
21976
23126
|
});
|
|
21977
23127
|
for (const file of projectJsonFiles) {
|
|
21978
23128
|
try {
|
|
21979
|
-
const content = await
|
|
23129
|
+
const content = await readFile14(join29(projectPath, file), "utf-8");
|
|
21980
23130
|
const project = JSON.parse(content);
|
|
21981
23131
|
if (project.name) {
|
|
21982
23132
|
const folder = file.replace(/\/project\.json$/, "");
|
|
21983
23133
|
workspaces.push({
|
|
21984
23134
|
name: project.name,
|
|
21985
|
-
folder:
|
|
23135
|
+
folder: join29(projectPath, folder),
|
|
21986
23136
|
relativePath: folder
|
|
21987
23137
|
});
|
|
21988
23138
|
}
|
|
@@ -21990,15 +23140,15 @@ async function resolveNxWorkspaces(projectPath) {
|
|
|
21990
23140
|
}
|
|
21991
23141
|
}
|
|
21992
23142
|
if (workspaces.length > 0) return workspaces;
|
|
21993
|
-
const appsDir =
|
|
23143
|
+
const appsDir = join29(projectPath, "apps");
|
|
21994
23144
|
if (existsSync17(appsDir)) {
|
|
21995
23145
|
const entries = await readdir10(appsDir, { withFileTypes: true });
|
|
21996
23146
|
for (const entry of entries) {
|
|
21997
23147
|
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
21998
|
-
const folder =
|
|
23148
|
+
const folder = join29("apps", entry.name);
|
|
21999
23149
|
workspaces.push({
|
|
22000
23150
|
name: entry.name,
|
|
22001
|
-
folder:
|
|
23151
|
+
folder: join29(projectPath, folder),
|
|
22002
23152
|
relativePath: folder
|
|
22003
23153
|
});
|
|
22004
23154
|
}
|
|
@@ -22032,13 +23182,13 @@ async function detectFolderBasedWorkspaces(projectPath) {
|
|
|
22032
23182
|
const results = [];
|
|
22033
23183
|
for (const entry of entries) {
|
|
22034
23184
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "build") {
|
|
22035
|
-
const pkgPath =
|
|
23185
|
+
const pkgPath = join29(projectPath, entry.name, "package.json");
|
|
22036
23186
|
if (existsSync17(pkgPath)) {
|
|
22037
23187
|
try {
|
|
22038
|
-
const content = await
|
|
23188
|
+
const content = await readFile14(pkgPath, "utf-8");
|
|
22039
23189
|
const pkg = JSON.parse(content);
|
|
22040
23190
|
if (pkg.dependencies || pkg.devDependencies || pkg.scripts) {
|
|
22041
|
-
results.push({ name: entry.name, absPath:
|
|
23191
|
+
results.push({ name: entry.name, absPath: join29(projectPath, entry.name) });
|
|
22042
23192
|
}
|
|
22043
23193
|
} catch {
|
|
22044
23194
|
}
|
|
@@ -22094,7 +23244,7 @@ function pmRunCommand(packageManager, scriptName) {
|
|
|
22094
23244
|
}
|
|
22095
23245
|
async function readPackageJson(dir) {
|
|
22096
23246
|
try {
|
|
22097
|
-
const content = await
|
|
23247
|
+
const content = await readFile14(join29(dir, "package.json"), "utf-8");
|
|
22098
23248
|
return JSON.parse(content);
|
|
22099
23249
|
} catch {
|
|
22100
23250
|
return null;
|
|
@@ -22286,8 +23436,8 @@ async function handleListThreadFiles(c, threadManager) {
|
|
|
22286
23436
|
}
|
|
22287
23437
|
|
|
22288
23438
|
// src/features/threads/threads-explorer.route.ts
|
|
22289
|
-
import { readdir as readdir11, readFile as
|
|
22290
|
-
import { join as
|
|
23439
|
+
import { readdir as readdir11, readFile as readFile15, rename as rename2, writeFile as writeFile6, mkdir as mkdir7, rm as rm6, access as access4 } from "fs/promises";
|
|
23440
|
+
import { join as join30 } from "path";
|
|
22291
23441
|
import { spawn as spawn2 } from "child_process";
|
|
22292
23442
|
var Utils3 = null;
|
|
22293
23443
|
var utilsLoaded3 = false;
|
|
@@ -22302,7 +23452,7 @@ async function loadUtils3() {
|
|
|
22302
23452
|
}
|
|
22303
23453
|
async function getIgnoredPaths(repoPath, relativePaths) {
|
|
22304
23454
|
if (relativePaths.length === 0) return /* @__PURE__ */ new Set();
|
|
22305
|
-
return new Promise((
|
|
23455
|
+
return new Promise((resolve7) => {
|
|
22306
23456
|
const proc = spawn2("git", ["check-ignore", "--stdin"], { cwd: repoPath });
|
|
22307
23457
|
let output = "";
|
|
22308
23458
|
proc.stdout?.on("data", (d) => {
|
|
@@ -22314,16 +23464,16 @@ async function getIgnoredPaths(repoPath, relativePaths) {
|
|
|
22314
23464
|
const ignored = new Set(
|
|
22315
23465
|
output.split("\n").map((l) => l.trim()).filter(Boolean)
|
|
22316
23466
|
);
|
|
22317
|
-
|
|
23467
|
+
resolve7(ignored);
|
|
22318
23468
|
});
|
|
22319
|
-
proc.on("error", () =>
|
|
23469
|
+
proc.on("error", () => resolve7(/* @__PURE__ */ new Set()));
|
|
22320
23470
|
});
|
|
22321
23471
|
}
|
|
22322
23472
|
async function listExplorerEntriesRecursive(rootPath, currentPath, relativePath) {
|
|
22323
23473
|
const dirents = await readdir11(currentPath, { withFileTypes: true });
|
|
22324
23474
|
const isRoot = relativePath.length === 0;
|
|
22325
23475
|
const entries = dirents.filter((d) => !(isRoot && d.name === ".git") && d.name !== "node_modules").map((d) => {
|
|
22326
|
-
const entryPath = relativePath ?
|
|
23476
|
+
const entryPath = relativePath ? join30(relativePath, d.name) : d.name;
|
|
22327
23477
|
return {
|
|
22328
23478
|
name: d.name,
|
|
22329
23479
|
type: d.isDirectory() ? "folder" : "file",
|
|
@@ -22335,7 +23485,7 @@ async function listExplorerEntriesRecursive(rootPath, currentPath, relativePath)
|
|
|
22335
23485
|
});
|
|
22336
23486
|
const nestedEntries = await Promise.all(
|
|
22337
23487
|
entries.filter((entry) => entry.type === "folder").map(
|
|
22338
|
-
(entry) => listExplorerEntriesRecursive(rootPath,
|
|
23488
|
+
(entry) => listExplorerEntriesRecursive(rootPath, join30(rootPath, entry.path), entry.path)
|
|
22339
23489
|
)
|
|
22340
23490
|
);
|
|
22341
23491
|
return [...entries, ...nestedEntries.flat()];
|
|
@@ -22371,7 +23521,7 @@ async function handleListThreadExplorerDir(c, threadManager) {
|
|
|
22371
23521
|
const entries = recursive ? await listExplorerEntriesRecursive(thread.path, absDir, queryPath) : dirents.filter((d) => !(!queryPath && d.name === ".git")).map((d) => ({
|
|
22372
23522
|
name: d.name,
|
|
22373
23523
|
type: d.isDirectory() ? "folder" : "file",
|
|
22374
|
-
path: queryPath ?
|
|
23524
|
+
path: queryPath ? join30(queryPath, d.name) : d.name
|
|
22375
23525
|
})).sort((a, b) => {
|
|
22376
23526
|
if (a.type !== b.type) return a.type === "folder" ? -1 : 1;
|
|
22377
23527
|
return a.name.localeCompare(b.name);
|
|
@@ -22517,7 +23667,7 @@ async function handleCreateExplorerFile(c, threadManager) {
|
|
|
22517
23667
|
absParent = validated;
|
|
22518
23668
|
}
|
|
22519
23669
|
await mkdir7(absParent, { recursive: true });
|
|
22520
|
-
await writeFile6(
|
|
23670
|
+
await writeFile6(join30(absParent, name), "", "utf-8");
|
|
22521
23671
|
const relPath = dirPath ? `${dirPath}/${name}` : name;
|
|
22522
23672
|
return c.json({ success: true, path: relPath });
|
|
22523
23673
|
} catch (error) {
|
|
@@ -22559,7 +23709,7 @@ async function handleGetExplorerMedia(c, threadManager) {
|
|
|
22559
23709
|
}
|
|
22560
23710
|
const ext = getFileExtension(filePath);
|
|
22561
23711
|
const contentType = MEDIA_MIME_TYPES[ext] ?? "application/octet-stream";
|
|
22562
|
-
const data = await
|
|
23712
|
+
const data = await readFile15(absPath);
|
|
22563
23713
|
return new Response(data, {
|
|
22564
23714
|
headers: {
|
|
22565
23715
|
"Content-Type": contentType,
|
|
@@ -22609,7 +23759,7 @@ async function handleCreateExplorerFolder(c, threadManager) {
|
|
|
22609
23759
|
}
|
|
22610
23760
|
absParent = validated;
|
|
22611
23761
|
}
|
|
22612
|
-
await mkdir7(
|
|
23762
|
+
await mkdir7(join30(absParent, name), { recursive: true });
|
|
22613
23763
|
const relPath = dirPath ? `${dirPath}/${name}` : name;
|
|
22614
23764
|
return c.json({ success: true, path: relPath });
|
|
22615
23765
|
} catch (error) {
|
|
@@ -22782,13 +23932,13 @@ async function handleFixComments(c, threadManager) {
|
|
|
22782
23932
|
}
|
|
22783
23933
|
|
|
22784
23934
|
// src/features/threads/threads-ai-files.route.ts
|
|
22785
|
-
import { readFile as
|
|
22786
|
-
import { join as
|
|
23935
|
+
import { readFile as readFile16, writeFile as writeFile8, mkdir as mkdir9, access as access5, rm as rm7, stat as stat5 } from "fs/promises";
|
|
23936
|
+
import { join as join32 } from "path";
|
|
22787
23937
|
import { existsSync as existsSync18 } from "fs";
|
|
22788
23938
|
|
|
22789
23939
|
// src/features/git/git-download-folder.ts
|
|
22790
23940
|
import { mkdir as mkdir8, writeFile as writeFile7 } from "fs/promises";
|
|
22791
|
-
import { join as
|
|
23941
|
+
import { join as join31, dirname as dirname5 } from "path";
|
|
22792
23942
|
async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
22793
23943
|
const { token, ref } = options;
|
|
22794
23944
|
const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
@@ -22824,7 +23974,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
|
22824
23974
|
await Promise.all(
|
|
22825
23975
|
files.map(async (file) => {
|
|
22826
23976
|
const relativePath = file.path.slice(prefix.length);
|
|
22827
|
-
const localPath =
|
|
23977
|
+
const localPath = join31(destPath, relativePath);
|
|
22828
23978
|
await mkdir8(dirname5(localPath), { recursive: true });
|
|
22829
23979
|
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${file.path}`;
|
|
22830
23980
|
const fileRes = await fetch(rawUrl, { headers });
|
|
@@ -22942,7 +24092,7 @@ Links to important documentation, tools, or references.
|
|
|
22942
24092
|
400
|
|
22943
24093
|
);
|
|
22944
24094
|
}
|
|
22945
|
-
const content = await
|
|
24095
|
+
const content = await readFile16(absPath, "utf-8");
|
|
22946
24096
|
return c.json({ content, path: filePath });
|
|
22947
24097
|
} catch (error) {
|
|
22948
24098
|
return errorResponse(
|
|
@@ -22981,7 +24131,7 @@ async function handleSaveThreadAIFile(c, threadManager) {
|
|
|
22981
24131
|
if (!absPath) {
|
|
22982
24132
|
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
|
|
22983
24133
|
}
|
|
22984
|
-
const parentDir =
|
|
24134
|
+
const parentDir = join32(absPath, "..");
|
|
22985
24135
|
await mkdir9(parentDir, { recursive: true });
|
|
22986
24136
|
await writeFile8(absPath, content, "utf-8");
|
|
22987
24137
|
return c.json({ success: true, path: filePath });
|
|
@@ -23070,10 +24220,10 @@ async function handleCreateThreadAgent(c, threadManager) {
|
|
|
23070
24220
|
if (!thread) {
|
|
23071
24221
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
23072
24222
|
}
|
|
23073
|
-
const agentRelPath =
|
|
23074
|
-
const agentAbsPath =
|
|
23075
|
-
const agentFileRelPath =
|
|
23076
|
-
const agentFileAbsPath =
|
|
24223
|
+
const agentRelPath = join32(".agents", "agents", name);
|
|
24224
|
+
const agentAbsPath = join32(thread.path, agentRelPath);
|
|
24225
|
+
const agentFileRelPath = join32(agentRelPath, "AGENT.md");
|
|
24226
|
+
const agentFileAbsPath = join32(agentAbsPath, "AGENT.md");
|
|
23077
24227
|
if (existsSync18(agentAbsPath)) {
|
|
23078
24228
|
return c.json(
|
|
23079
24229
|
{ error: { code: "CONFLICT", message: `Agent '${name}' already exists` } },
|
|
@@ -23140,10 +24290,10 @@ async function handleCreateThreadSkill(c, threadManager) {
|
|
|
23140
24290
|
if (!thread) {
|
|
23141
24291
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
23142
24292
|
}
|
|
23143
|
-
const skillRelPath =
|
|
23144
|
-
const skillAbsPath =
|
|
23145
|
-
const skillFileRelPath =
|
|
23146
|
-
const skillFileAbsPath =
|
|
24293
|
+
const skillRelPath = join32(".agents", "skills", name);
|
|
24294
|
+
const skillAbsPath = join32(thread.path, skillRelPath);
|
|
24295
|
+
const skillFileRelPath = join32(skillRelPath, "SKILL.md");
|
|
24296
|
+
const skillFileAbsPath = join32(skillAbsPath, "SKILL.md");
|
|
23147
24297
|
if (existsSync18(skillAbsPath)) {
|
|
23148
24298
|
return c.json(
|
|
23149
24299
|
{ error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
|
|
@@ -23274,7 +24424,7 @@ var ScriptProcessManager = class {
|
|
|
23274
24424
|
if (outputBuffer.length > 0) {
|
|
23275
24425
|
yield outputBuffer.shift();
|
|
23276
24426
|
} else {
|
|
23277
|
-
await new Promise((
|
|
24427
|
+
await new Promise((resolve7) => setTimeout(resolve7, 50));
|
|
23278
24428
|
}
|
|
23279
24429
|
}
|
|
23280
24430
|
} finally {
|
|
@@ -23608,7 +24758,7 @@ var GitManagerImpl = class {
|
|
|
23608
24758
|
yield event;
|
|
23609
24759
|
}
|
|
23610
24760
|
} else {
|
|
23611
|
-
await new Promise((
|
|
24761
|
+
await new Promise((resolve7) => setTimeout(resolve7, 10));
|
|
23612
24762
|
}
|
|
23613
24763
|
}
|
|
23614
24764
|
if (processError) {
|
|
@@ -23671,7 +24821,7 @@ var GitManagerImpl = class {
|
|
|
23671
24821
|
* @returns true if the branch exists, false otherwise
|
|
23672
24822
|
*/
|
|
23673
24823
|
async checkBranchExists(repoPath, branchName) {
|
|
23674
|
-
return new Promise((
|
|
24824
|
+
return new Promise((resolve7) => {
|
|
23675
24825
|
const gitProcess = spawnProcess(
|
|
23676
24826
|
"git",
|
|
23677
24827
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
@@ -23680,10 +24830,10 @@ var GitManagerImpl = class {
|
|
|
23680
24830
|
}
|
|
23681
24831
|
);
|
|
23682
24832
|
gitProcess.on("close", (code) => {
|
|
23683
|
-
|
|
24833
|
+
resolve7(code === 0);
|
|
23684
24834
|
});
|
|
23685
24835
|
gitProcess.on("error", () => {
|
|
23686
|
-
|
|
24836
|
+
resolve7(false);
|
|
23687
24837
|
});
|
|
23688
24838
|
});
|
|
23689
24839
|
}
|
|
@@ -23741,7 +24891,7 @@ var GitManagerImpl = class {
|
|
|
23741
24891
|
yield event;
|
|
23742
24892
|
}
|
|
23743
24893
|
} else {
|
|
23744
|
-
await new Promise((
|
|
24894
|
+
await new Promise((resolve7) => setTimeout(resolve7, 10));
|
|
23745
24895
|
}
|
|
23746
24896
|
}
|
|
23747
24897
|
if (processError) {
|
|
@@ -23797,7 +24947,7 @@ import { Hono as Hono13 } from "hono";
|
|
|
23797
24947
|
// src/features/git/git-user.route.ts
|
|
23798
24948
|
init_utils();
|
|
23799
24949
|
function readGitConfigValue(key) {
|
|
23800
|
-
return new Promise((
|
|
24950
|
+
return new Promise((resolve7) => {
|
|
23801
24951
|
const proc = spawnProcess("git", ["config", key]);
|
|
23802
24952
|
let out = "";
|
|
23803
24953
|
proc.stdout?.on("data", (d) => {
|
|
@@ -23805,12 +24955,12 @@ function readGitConfigValue(key) {
|
|
|
23805
24955
|
});
|
|
23806
24956
|
proc.on("close", (code) => {
|
|
23807
24957
|
if (code === 0) {
|
|
23808
|
-
|
|
24958
|
+
resolve7(out.trim());
|
|
23809
24959
|
} else {
|
|
23810
|
-
|
|
24960
|
+
resolve7("");
|
|
23811
24961
|
}
|
|
23812
24962
|
});
|
|
23813
|
-
proc.on("error", () =>
|
|
24963
|
+
proc.on("error", () => resolve7(""));
|
|
23814
24964
|
});
|
|
23815
24965
|
}
|
|
23816
24966
|
async function gitUserHandler(c) {
|
|
@@ -23855,14 +25005,14 @@ async function gitStatusHandler(c, metadataManager) {
|
|
|
23855
25005
|
400
|
|
23856
25006
|
);
|
|
23857
25007
|
}
|
|
23858
|
-
const { hasChanges, changedFilesCount } = await new Promise((
|
|
25008
|
+
const { hasChanges, changedFilesCount } = await new Promise((resolve7) => {
|
|
23859
25009
|
getGitStatus(gitRoot).then((statusOutput) => {
|
|
23860
25010
|
const lines = statusOutput.trim().split("\n").filter((line) => line.length > 0);
|
|
23861
|
-
|
|
25011
|
+
resolve7({
|
|
23862
25012
|
hasChanges: lines.length > 0,
|
|
23863
25013
|
changedFilesCount: lines.length
|
|
23864
25014
|
});
|
|
23865
|
-
}).catch(() =>
|
|
25015
|
+
}).catch(() => resolve7({ hasChanges: false, changedFilesCount: 0 }));
|
|
23866
25016
|
});
|
|
23867
25017
|
const currentBranch = await getCurrentBranch(gitRoot);
|
|
23868
25018
|
const hasUnpushed = await hasUnpushedCommits(gitRoot, currentBranch);
|
|
@@ -23929,15 +25079,26 @@ async function gitDiffHandler(c, metadataManager) {
|
|
|
23929
25079
|
let oldContent = "";
|
|
23930
25080
|
let newContent = "";
|
|
23931
25081
|
const hunks = [];
|
|
25082
|
+
const isBinary = getExplorerFileMediaType(file.path) !== null;
|
|
25083
|
+
if (isBinary) {
|
|
25084
|
+
filesWithDiff.push({
|
|
25085
|
+
...file,
|
|
25086
|
+
oldContent: "",
|
|
25087
|
+
newContent: "",
|
|
25088
|
+
isBinary: true,
|
|
25089
|
+
hunks: []
|
|
25090
|
+
});
|
|
25091
|
+
continue;
|
|
25092
|
+
}
|
|
23932
25093
|
if (file.status === "deleted") {
|
|
23933
|
-
const content = await new Promise((
|
|
25094
|
+
const content = await new Promise((resolve7) => {
|
|
23934
25095
|
const proc = spawnProcess("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
|
|
23935
25096
|
let out = "";
|
|
23936
25097
|
proc.stdout?.on("data", (d) => {
|
|
23937
25098
|
out += d.toString();
|
|
23938
25099
|
});
|
|
23939
|
-
proc.on("close", () =>
|
|
23940
|
-
proc.on("error", () =>
|
|
25100
|
+
proc.on("close", () => resolve7(out));
|
|
25101
|
+
proc.on("error", () => resolve7(""));
|
|
23941
25102
|
});
|
|
23942
25103
|
oldContent = content;
|
|
23943
25104
|
} else if (file.status === "added" && file.path.startsWith("(new)")) {
|
|
@@ -23947,7 +25108,7 @@ async function gitDiffHandler(c, metadataManager) {
|
|
|
23947
25108
|
newContent = "";
|
|
23948
25109
|
}
|
|
23949
25110
|
} else {
|
|
23950
|
-
const diffOutput = await new Promise((
|
|
25111
|
+
const diffOutput = await new Promise((resolve7) => {
|
|
23951
25112
|
const proc = spawnProcess(
|
|
23952
25113
|
"git",
|
|
23953
25114
|
file.status === "added" ? ["diff", "--cached", "--", file.path] : ["diff", "HEAD", "--", file.path],
|
|
@@ -23957,21 +25118,21 @@ async function gitDiffHandler(c, metadataManager) {
|
|
|
23957
25118
|
proc.stdout?.on("data", (d) => {
|
|
23958
25119
|
out += d.toString();
|
|
23959
25120
|
});
|
|
23960
|
-
proc.on("close", () =>
|
|
23961
|
-
proc.on("error", () =>
|
|
25121
|
+
proc.on("close", () => resolve7(out));
|
|
25122
|
+
proc.on("error", () => resolve7(""));
|
|
23962
25123
|
});
|
|
23963
25124
|
const lines = diffOutput.split("\n");
|
|
23964
25125
|
let currentHunk = null;
|
|
23965
25126
|
let oldLineNum = 0;
|
|
23966
25127
|
let newLineNum = 0;
|
|
23967
|
-
const oldContentOutput = await new Promise((
|
|
25128
|
+
const oldContentOutput = await new Promise((resolve7) => {
|
|
23968
25129
|
const proc = spawnProcess("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
|
|
23969
25130
|
let out = "";
|
|
23970
25131
|
proc.stdout?.on("data", (d) => {
|
|
23971
25132
|
out += d.toString();
|
|
23972
25133
|
});
|
|
23973
|
-
proc.on("close", () =>
|
|
23974
|
-
proc.on("error", () =>
|
|
25134
|
+
proc.on("close", () => resolve7(out));
|
|
25135
|
+
proc.on("error", () => resolve7(""));
|
|
23975
25136
|
});
|
|
23976
25137
|
oldContent = oldContentOutput;
|
|
23977
25138
|
try {
|
|
@@ -24028,6 +25189,7 @@ async function gitDiffHandler(c, metadataManager) {
|
|
|
24028
25189
|
...file,
|
|
24029
25190
|
oldContent,
|
|
24030
25191
|
newContent,
|
|
25192
|
+
isBinary: false,
|
|
24031
25193
|
hunks
|
|
24032
25194
|
});
|
|
24033
25195
|
}
|
|
@@ -24807,7 +25969,7 @@ function sanitizeBranchName(name) {
|
|
|
24807
25969
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "branch";
|
|
24808
25970
|
}
|
|
24809
25971
|
function checkBranchExists(gitRoot, branchName) {
|
|
24810
|
-
return new Promise((
|
|
25972
|
+
return new Promise((resolve7) => {
|
|
24811
25973
|
const proc = spawnProcess(
|
|
24812
25974
|
"git",
|
|
24813
25975
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
@@ -24816,15 +25978,15 @@ function checkBranchExists(gitRoot, branchName) {
|
|
|
24816
25978
|
}
|
|
24817
25979
|
);
|
|
24818
25980
|
proc.on("close", (code) => {
|
|
24819
|
-
|
|
25981
|
+
resolve7(code === 0);
|
|
24820
25982
|
});
|
|
24821
25983
|
proc.on("error", () => {
|
|
24822
|
-
|
|
25984
|
+
resolve7(false);
|
|
24823
25985
|
});
|
|
24824
25986
|
});
|
|
24825
25987
|
}
|
|
24826
25988
|
function createAndCheckoutBranch(gitRoot, branchName) {
|
|
24827
|
-
return new Promise((
|
|
25989
|
+
return new Promise((resolve7, reject) => {
|
|
24828
25990
|
const proc = spawnProcess("git", ["checkout", "-b", branchName], {
|
|
24829
25991
|
cwd: gitRoot
|
|
24830
25992
|
});
|
|
@@ -24836,7 +25998,7 @@ function createAndCheckoutBranch(gitRoot, branchName) {
|
|
|
24836
25998
|
}
|
|
24837
25999
|
proc.on("close", (code) => {
|
|
24838
26000
|
if (code === 0) {
|
|
24839
|
-
|
|
26001
|
+
resolve7();
|
|
24840
26002
|
} else {
|
|
24841
26003
|
reject(new Error(err || `Failed to create branch "${branchName}"`));
|
|
24842
26004
|
}
|
|
@@ -24929,7 +26091,7 @@ function sanitizeBranchName2(name) {
|
|
|
24929
26091
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "branch";
|
|
24930
26092
|
}
|
|
24931
26093
|
function checkBranchExists2(gitRoot, branchName) {
|
|
24932
|
-
return new Promise((
|
|
26094
|
+
return new Promise((resolve7) => {
|
|
24933
26095
|
const proc = spawnProcess(
|
|
24934
26096
|
"git",
|
|
24935
26097
|
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
@@ -24938,16 +26100,16 @@ function checkBranchExists2(gitRoot, branchName) {
|
|
|
24938
26100
|
}
|
|
24939
26101
|
);
|
|
24940
26102
|
proc.on("close", (code) => {
|
|
24941
|
-
|
|
26103
|
+
resolve7(code === 0);
|
|
24942
26104
|
});
|
|
24943
26105
|
proc.on("error", () => {
|
|
24944
|
-
|
|
26106
|
+
resolve7(false);
|
|
24945
26107
|
});
|
|
24946
26108
|
});
|
|
24947
26109
|
}
|
|
24948
26110
|
function createAndCheckoutBranch2(gitRoot, branchName, baseBranch) {
|
|
24949
26111
|
const args2 = baseBranch ? ["checkout", "-b", branchName, `origin/${baseBranch}`] : ["checkout", "-b", branchName];
|
|
24950
|
-
return new Promise((
|
|
26112
|
+
return new Promise((resolve7, reject) => {
|
|
24951
26113
|
const proc = spawnProcess("git", args2, {
|
|
24952
26114
|
cwd: gitRoot
|
|
24953
26115
|
});
|
|
@@ -24959,7 +26121,7 @@ function createAndCheckoutBranch2(gitRoot, branchName, baseBranch) {
|
|
|
24959
26121
|
}
|
|
24960
26122
|
proc.on("close", (code) => {
|
|
24961
26123
|
if (code === 0) {
|
|
24962
|
-
|
|
26124
|
+
resolve7();
|
|
24963
26125
|
} else {
|
|
24964
26126
|
reject(new Error(err || `Failed to create branch "${branchName}"`));
|
|
24965
26127
|
}
|
|
@@ -25074,14 +26236,14 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25074
26236
|
);
|
|
25075
26237
|
}
|
|
25076
26238
|
const { spawnProcess: spawnProcess2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
|
|
25077
|
-
return new Promise((
|
|
26239
|
+
return new Promise((resolve7) => {
|
|
25078
26240
|
console.log(`[sync-branch] Checking branch status...`);
|
|
25079
26241
|
const statusProc = spawnProcess2("git", ["status", "-sb"], { cwd: gitRoot });
|
|
25080
26242
|
let statusOutput = "";
|
|
25081
26243
|
statusProc.on("close", (statusCode) => {
|
|
25082
26244
|
if (statusCode !== 0) {
|
|
25083
26245
|
console.log(`[sync-branch] Failed to get git status: code ${statusCode}`);
|
|
25084
|
-
|
|
26246
|
+
resolve7(
|
|
25085
26247
|
c.json(
|
|
25086
26248
|
{
|
|
25087
26249
|
error: "Failed to check git status",
|
|
@@ -25115,7 +26277,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25115
26277
|
pushProc.on("close", (pushCode) => {
|
|
25116
26278
|
if (pushCode !== 0) {
|
|
25117
26279
|
console.log(`[sync-branch] Failed to push commits: code ${pushCode}`);
|
|
25118
|
-
|
|
26280
|
+
resolve7(
|
|
25119
26281
|
c.json(
|
|
25120
26282
|
{
|
|
25121
26283
|
error: `Failed to push commits before sync: ${pushErr || pushOut}`,
|
|
@@ -25133,7 +26295,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25133
26295
|
console.log(
|
|
25134
26296
|
`[sync-branch] \u2717 Push process error: ${error instanceof Error ? error.message : String(error)}`
|
|
25135
26297
|
);
|
|
25136
|
-
|
|
26298
|
+
resolve7(
|
|
25137
26299
|
c.json(
|
|
25138
26300
|
{
|
|
25139
26301
|
error: `Failed to push commits: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -25161,7 +26323,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25161
26323
|
console.log(
|
|
25162
26324
|
`[sync-branch] \u2717 Status process error: ${error instanceof Error ? error.message : String(error)}`
|
|
25163
26325
|
);
|
|
25164
|
-
|
|
26326
|
+
resolve7(
|
|
25165
26327
|
c.json(
|
|
25166
26328
|
{
|
|
25167
26329
|
error: `Failed to check git status: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -25176,7 +26338,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25176
26338
|
configProc.on("close", (configCode) => {
|
|
25177
26339
|
if (configCode !== 0) {
|
|
25178
26340
|
console.log(`[sync-branch] Failed to configure git: code ${configCode}`);
|
|
25179
|
-
|
|
26341
|
+
resolve7(
|
|
25180
26342
|
c.json(
|
|
25181
26343
|
{
|
|
25182
26344
|
error: "Failed to configure git pull strategy",
|
|
@@ -25210,7 +26372,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25210
26372
|
if (code === 0) {
|
|
25211
26373
|
const message = currentBranch === defaultBranch ? `Successfully pulled latest changes from origin/${defaultBranch}` : `Successfully synced ${currentBranch} with ${defaultBranch}`;
|
|
25212
26374
|
console.log(`[sync-branch] \u2713 ${message}`);
|
|
25213
|
-
|
|
26375
|
+
resolve7(
|
|
25214
26376
|
c.json({
|
|
25215
26377
|
success: true,
|
|
25216
26378
|
message,
|
|
@@ -25221,7 +26383,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25221
26383
|
} else {
|
|
25222
26384
|
console.log(`[sync-branch] \u2717 Failed to sync with exit code ${code}`);
|
|
25223
26385
|
console.log(`[sync-branch] Error output: ${err || out}`);
|
|
25224
|
-
|
|
26386
|
+
resolve7(
|
|
25225
26387
|
c.json(
|
|
25226
26388
|
{
|
|
25227
26389
|
error: `Failed to sync with ${defaultBranch}: ${err || out}`,
|
|
@@ -25236,7 +26398,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25236
26398
|
console.log(
|
|
25237
26399
|
`[sync-branch] \u2717 Process error: ${error instanceof Error ? error.message : String(error)}`
|
|
25238
26400
|
);
|
|
25239
|
-
|
|
26401
|
+
resolve7(
|
|
25240
26402
|
c.json(
|
|
25241
26403
|
{
|
|
25242
26404
|
error: `Failed to sync branch: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -25250,7 +26412,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25250
26412
|
console.log(
|
|
25251
26413
|
`[sync-branch] \u2717 Config process error: ${error instanceof Error ? error.message : String(error)}`
|
|
25252
26414
|
);
|
|
25253
|
-
|
|
26415
|
+
resolve7(
|
|
25254
26416
|
c.json(
|
|
25255
26417
|
{
|
|
25256
26418
|
error: `Failed to configure git: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -25271,7 +26433,7 @@ async function gitSyncBranchHandler(c, metadataManager) {
|
|
|
25271
26433
|
// src/features/git/git-list-branches.route.ts
|
|
25272
26434
|
init_utils();
|
|
25273
26435
|
function listLocalBranches(gitRoot) {
|
|
25274
|
-
return new Promise((
|
|
26436
|
+
return new Promise((resolve7) => {
|
|
25275
26437
|
const proc = spawnProcess("git", ["branch", "--format=%(refname:short)"], { cwd: gitRoot });
|
|
25276
26438
|
let out = "";
|
|
25277
26439
|
if (proc.stdout) {
|
|
@@ -25281,9 +26443,9 @@ function listLocalBranches(gitRoot) {
|
|
|
25281
26443
|
}
|
|
25282
26444
|
proc.on("close", () => {
|
|
25283
26445
|
const branches = out.split("\n").map((b) => b.trim()).filter(Boolean);
|
|
25284
|
-
|
|
26446
|
+
resolve7(branches);
|
|
25285
26447
|
});
|
|
25286
|
-
proc.on("error", () =>
|
|
26448
|
+
proc.on("error", () => resolve7([]));
|
|
25287
26449
|
});
|
|
25288
26450
|
}
|
|
25289
26451
|
async function gitListBranchesHandler(c, metadataManager) {
|
|
@@ -25315,7 +26477,7 @@ async function gitListBranchesHandler(c, metadataManager) {
|
|
|
25315
26477
|
// src/features/git/git-checkout-branch.route.ts
|
|
25316
26478
|
init_utils();
|
|
25317
26479
|
function checkoutBranch(gitRoot, branchName) {
|
|
25318
|
-
return new Promise((
|
|
26480
|
+
return new Promise((resolve7, reject) => {
|
|
25319
26481
|
const proc = spawnProcess("git", ["checkout", branchName], { cwd: gitRoot });
|
|
25320
26482
|
let err = "";
|
|
25321
26483
|
if (proc.stderr) {
|
|
@@ -25325,7 +26487,7 @@ function checkoutBranch(gitRoot, branchName) {
|
|
|
25325
26487
|
}
|
|
25326
26488
|
proc.on("close", (code) => {
|
|
25327
26489
|
if (code === 0) {
|
|
25328
|
-
|
|
26490
|
+
resolve7();
|
|
25329
26491
|
} else {
|
|
25330
26492
|
reject(new Error(err.trim() || `Failed to checkout branch "${branchName}"`));
|
|
25331
26493
|
}
|
|
@@ -25375,7 +26537,7 @@ import { unlinkSync } from "fs";
|
|
|
25375
26537
|
import { resolve as resolve5 } from "path";
|
|
25376
26538
|
init_utils();
|
|
25377
26539
|
function runGit2(args2, cwd) {
|
|
25378
|
-
return new Promise((
|
|
26540
|
+
return new Promise((resolve7, reject) => {
|
|
25379
26541
|
const proc = spawnProcess("git", args2, { cwd });
|
|
25380
26542
|
let out = "";
|
|
25381
26543
|
let err = "";
|
|
@@ -25386,7 +26548,7 @@ function runGit2(args2, cwd) {
|
|
|
25386
26548
|
err += d.toString();
|
|
25387
26549
|
});
|
|
25388
26550
|
proc.on("close", (code) => {
|
|
25389
|
-
if (code === 0)
|
|
26551
|
+
if (code === 0) resolve7(out.trim());
|
|
25390
26552
|
else reject(new Error(err.trim() || `git ${args2[0]} failed with code ${code}`));
|
|
25391
26553
|
});
|
|
25392
26554
|
proc.on("error", reject);
|
|
@@ -25438,7 +26600,7 @@ async function gitRevertFileHandler(c, metadataManager) {
|
|
|
25438
26600
|
// src/features/git/git-checkpoint.route.ts
|
|
25439
26601
|
init_utils();
|
|
25440
26602
|
function stashWithLabel(gitRoot, label) {
|
|
25441
|
-
return new Promise((
|
|
26603
|
+
return new Promise((resolve7, reject) => {
|
|
25442
26604
|
const proc = spawnProcess("git", ["stash", "push", "--include-untracked", "-m", label], {
|
|
25443
26605
|
cwd: gitRoot
|
|
25444
26606
|
});
|
|
@@ -25458,7 +26620,7 @@ function stashWithLabel(gitRoot, label) {
|
|
|
25458
26620
|
if (code === 0) {
|
|
25459
26621
|
const nothingSaved = out.includes("No local changes to save");
|
|
25460
26622
|
if (nothingSaved) {
|
|
25461
|
-
|
|
26623
|
+
resolve7(false);
|
|
25462
26624
|
return;
|
|
25463
26625
|
}
|
|
25464
26626
|
const apply = spawnProcess("git", ["stash", "apply", "--index"], { cwd: gitRoot });
|
|
@@ -25470,7 +26632,7 @@ function stashWithLabel(gitRoot, label) {
|
|
|
25470
26632
|
}
|
|
25471
26633
|
apply.on("close", (applyCode) => {
|
|
25472
26634
|
if (applyCode === 0) {
|
|
25473
|
-
|
|
26635
|
+
resolve7(true);
|
|
25474
26636
|
} else {
|
|
25475
26637
|
reject(new Error(applyErr || "Failed to re-apply stash after checkpoint"));
|
|
25476
26638
|
}
|
|
@@ -25519,7 +26681,7 @@ async function gitCheckpointHandler(c, metadataManager) {
|
|
|
25519
26681
|
// src/features/git/git-checkpoint-restore.route.ts
|
|
25520
26682
|
init_utils();
|
|
25521
26683
|
function listStashes(gitRoot) {
|
|
25522
|
-
return new Promise((
|
|
26684
|
+
return new Promise((resolve7, reject) => {
|
|
25523
26685
|
const proc = spawnProcess("git", ["stash", "list", "--format=%gd %s"], { cwd: gitRoot });
|
|
25524
26686
|
let out = "";
|
|
25525
26687
|
let err = "";
|
|
@@ -25543,18 +26705,18 @@ function listStashes(gitRoot) {
|
|
|
25543
26705
|
if (!match) return null;
|
|
25544
26706
|
return { index: parseInt(match[1], 10), label: match[2].trim() };
|
|
25545
26707
|
}).filter((e) => e !== null);
|
|
25546
|
-
|
|
26708
|
+
resolve7(entries);
|
|
25547
26709
|
});
|
|
25548
26710
|
proc.on("error", reject);
|
|
25549
26711
|
});
|
|
25550
26712
|
}
|
|
25551
26713
|
function discardWorkingChanges(gitRoot) {
|
|
25552
|
-
return new Promise((
|
|
26714
|
+
return new Promise((resolve7, reject) => {
|
|
25553
26715
|
const proc = spawnProcess("git", ["checkout", "--", "."], { cwd: gitRoot });
|
|
25554
26716
|
proc.on("close", () => {
|
|
25555
26717
|
const clean = spawnProcess("git", ["clean", "-fd"], { cwd: gitRoot });
|
|
25556
26718
|
clean.on("close", (code) => {
|
|
25557
|
-
if (code === 0)
|
|
26719
|
+
if (code === 0) resolve7();
|
|
25558
26720
|
else reject(new Error("Failed to clean working tree"));
|
|
25559
26721
|
});
|
|
25560
26722
|
clean.on("error", reject);
|
|
@@ -25563,7 +26725,7 @@ function discardWorkingChanges(gitRoot) {
|
|
|
25563
26725
|
});
|
|
25564
26726
|
}
|
|
25565
26727
|
function applyStash(gitRoot, index) {
|
|
25566
|
-
return new Promise((
|
|
26728
|
+
return new Promise((resolve7, reject) => {
|
|
25567
26729
|
const proc = spawnProcess("git", ["stash", "apply", `stash@{${index}}`], { cwd: gitRoot });
|
|
25568
26730
|
let err = "";
|
|
25569
26731
|
if (proc.stderr) {
|
|
@@ -25572,7 +26734,7 @@ function applyStash(gitRoot, index) {
|
|
|
25572
26734
|
});
|
|
25573
26735
|
}
|
|
25574
26736
|
proc.on("close", (code) => {
|
|
25575
|
-
if (code === 0)
|
|
26737
|
+
if (code === 0) resolve7();
|
|
25576
26738
|
else reject(new Error(err || "Failed to apply stash"));
|
|
25577
26739
|
});
|
|
25578
26740
|
proc.on("error", reject);
|
|
@@ -25616,6 +26778,175 @@ async function gitCheckpointRestoreHandler(c, metadataManager) {
|
|
|
25616
26778
|
}
|
|
25617
26779
|
}
|
|
25618
26780
|
|
|
26781
|
+
// src/features/git/git-rename-branch.route.ts
|
|
26782
|
+
init_utils();
|
|
26783
|
+
function sanitizeBranchName3(name) {
|
|
26784
|
+
if (!name || typeof name !== "string") {
|
|
26785
|
+
return "";
|
|
26786
|
+
}
|
|
26787
|
+
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "";
|
|
26788
|
+
}
|
|
26789
|
+
function checkBranchExists3(gitRoot, branchName) {
|
|
26790
|
+
return new Promise((resolve7) => {
|
|
26791
|
+
const proc = spawnProcess(
|
|
26792
|
+
"git",
|
|
26793
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
26794
|
+
{
|
|
26795
|
+
cwd: gitRoot
|
|
26796
|
+
}
|
|
26797
|
+
);
|
|
26798
|
+
proc.on("close", (code) => {
|
|
26799
|
+
resolve7(code === 0);
|
|
26800
|
+
});
|
|
26801
|
+
proc.on("error", () => {
|
|
26802
|
+
resolve7(false);
|
|
26803
|
+
});
|
|
26804
|
+
});
|
|
26805
|
+
}
|
|
26806
|
+
function renameBranch(gitRoot, newBranchName) {
|
|
26807
|
+
return new Promise((resolve7, reject) => {
|
|
26808
|
+
const proc = spawnProcess("git", ["branch", "-m", newBranchName], {
|
|
26809
|
+
cwd: gitRoot
|
|
26810
|
+
});
|
|
26811
|
+
let err = "";
|
|
26812
|
+
if (proc.stderr) {
|
|
26813
|
+
proc.stderr.on("data", (d) => {
|
|
26814
|
+
err += d.toString();
|
|
26815
|
+
});
|
|
26816
|
+
}
|
|
26817
|
+
proc.on("close", (code) => {
|
|
26818
|
+
if (code === 0) {
|
|
26819
|
+
resolve7();
|
|
26820
|
+
} else {
|
|
26821
|
+
reject(new Error(err || `Failed to rename branch to "${newBranchName}"`));
|
|
26822
|
+
}
|
|
26823
|
+
});
|
|
26824
|
+
proc.on("error", reject);
|
|
26825
|
+
});
|
|
26826
|
+
}
|
|
26827
|
+
async function gitRenameBranchHandler(c, metadataManager) {
|
|
26828
|
+
try {
|
|
26829
|
+
const threadId = c.req.param("threadId");
|
|
26830
|
+
const thread = await metadataManager.loadThreads().then((threads2) => threads2.find((t) => t.id === threadId));
|
|
26831
|
+
if (!thread) {
|
|
26832
|
+
return c.json({ error: "Thread not found" }, 404);
|
|
26833
|
+
}
|
|
26834
|
+
const repoPath = thread.path;
|
|
26835
|
+
if (!repoPath) {
|
|
26836
|
+
return c.json({ error: "Thread path not found" }, 404);
|
|
26837
|
+
}
|
|
26838
|
+
const body = await c.req.json().catch(() => ({}));
|
|
26839
|
+
const rawName = body.newBranchName?.trim();
|
|
26840
|
+
if (!rawName) {
|
|
26841
|
+
return c.json({ error: "newBranchName is required" }, 400);
|
|
26842
|
+
}
|
|
26843
|
+
const newBranchName = sanitizeBranchName3(rawName);
|
|
26844
|
+
if (!newBranchName) {
|
|
26845
|
+
return c.json({ error: "Invalid branch name" }, 400);
|
|
26846
|
+
}
|
|
26847
|
+
const absolutePath = resolveThreadPath(repoPath);
|
|
26848
|
+
let gitRoot;
|
|
26849
|
+
try {
|
|
26850
|
+
gitRoot = await getGitRoot(absolutePath);
|
|
26851
|
+
} catch {
|
|
26852
|
+
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
26853
|
+
}
|
|
26854
|
+
const branchExists = await checkBranchExists3(gitRoot, newBranchName);
|
|
26855
|
+
if (branchExists) {
|
|
26856
|
+
return c.json({ error: `Branch "${newBranchName}" already exists` }, 409);
|
|
26857
|
+
}
|
|
26858
|
+
await renameBranch(gitRoot, newBranchName);
|
|
26859
|
+
const threads = await metadataManager.loadThreads();
|
|
26860
|
+
const threadIndex = threads.findIndex((t) => t.id === threadId);
|
|
26861
|
+
if (threadIndex !== -1) {
|
|
26862
|
+
threads[threadIndex].currentBranch = newBranchName;
|
|
26863
|
+
await metadataManager.saveThreads(threads);
|
|
26864
|
+
}
|
|
26865
|
+
return c.json({ success: true, branchName: newBranchName });
|
|
26866
|
+
} catch (error) {
|
|
26867
|
+
const message = error instanceof Error ? error.message : "Failed to rename branch";
|
|
26868
|
+
return c.json({ error: message }, 500);
|
|
26869
|
+
}
|
|
26870
|
+
}
|
|
26871
|
+
|
|
26872
|
+
// src/features/git/git-media.route.ts
|
|
26873
|
+
import { readFileSync as readFileSync8, existsSync as existsSync23 } from "fs";
|
|
26874
|
+
import { resolve as resolve6 } from "path";
|
|
26875
|
+
import { spawn as spawn3 } from "child_process";
|
|
26876
|
+
function spawnBinary(command, args2, cwd) {
|
|
26877
|
+
return new Promise((resolve7, reject) => {
|
|
26878
|
+
const proc = spawn3(command, args2, { cwd });
|
|
26879
|
+
const chunks = [];
|
|
26880
|
+
proc.stdout?.on("data", (d) => {
|
|
26881
|
+
chunks.push(Buffer.from(d));
|
|
26882
|
+
});
|
|
26883
|
+
proc.on("close", (code) => {
|
|
26884
|
+
if (code === 0) {
|
|
26885
|
+
resolve7(Buffer.concat(chunks));
|
|
26886
|
+
} else {
|
|
26887
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
26888
|
+
}
|
|
26889
|
+
});
|
|
26890
|
+
proc.on("error", reject);
|
|
26891
|
+
});
|
|
26892
|
+
}
|
|
26893
|
+
async function gitMediaHandler(c, metadataManager) {
|
|
26894
|
+
try {
|
|
26895
|
+
const threadId = c.req.param("threadId");
|
|
26896
|
+
const filePath = c.req.query("path");
|
|
26897
|
+
const version = c.req.query("version");
|
|
26898
|
+
if (!threadId) {
|
|
26899
|
+
return c.json({ error: "Thread ID is required" }, 400);
|
|
26900
|
+
}
|
|
26901
|
+
if (!filePath) {
|
|
26902
|
+
return c.json({ error: "path query parameter is required" }, 400);
|
|
26903
|
+
}
|
|
26904
|
+
if (version !== "old" && version !== "new") {
|
|
26905
|
+
return c.json({ error: "version query parameter must be 'old' or 'new'" }, 400);
|
|
26906
|
+
}
|
|
26907
|
+
const thread = await metadataManager.loadThreads().then((threads) => threads.find((t) => t.id === threadId));
|
|
26908
|
+
if (!thread) {
|
|
26909
|
+
return c.json({ error: "Thread not found" }, 404);
|
|
26910
|
+
}
|
|
26911
|
+
const repoPath = thread.path;
|
|
26912
|
+
if (!repoPath) {
|
|
26913
|
+
return c.json({ error: "Thread path not found" }, 404);
|
|
26914
|
+
}
|
|
26915
|
+
const absolutePath = resolveThreadPath(repoPath);
|
|
26916
|
+
let gitRoot;
|
|
26917
|
+
try {
|
|
26918
|
+
gitRoot = await getGitRoot(absolutePath);
|
|
26919
|
+
} catch {
|
|
26920
|
+
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
26921
|
+
}
|
|
26922
|
+
const ext = getFileExtension(filePath);
|
|
26923
|
+
const contentType = EXPLORER_MEDIA_MIME_TYPES[ext] ?? "application/octet-stream";
|
|
26924
|
+
let data;
|
|
26925
|
+
if (version === "old") {
|
|
26926
|
+
try {
|
|
26927
|
+
data = await spawnBinary("git", ["show", `HEAD:${filePath}`], gitRoot);
|
|
26928
|
+
} catch {
|
|
26929
|
+
return c.json({ error: `File not found in HEAD: ${filePath}` }, 404);
|
|
26930
|
+
}
|
|
26931
|
+
} else {
|
|
26932
|
+
const absFilePath = resolve6(gitRoot, filePath);
|
|
26933
|
+
if (!existsSync23(absFilePath)) {
|
|
26934
|
+
return c.json({ error: `File not found in working directory: ${filePath}` }, 404);
|
|
26935
|
+
}
|
|
26936
|
+
data = readFileSync8(absFilePath);
|
|
26937
|
+
}
|
|
26938
|
+
return new Response(data.buffer, {
|
|
26939
|
+
headers: {
|
|
26940
|
+
"Content-Type": contentType,
|
|
26941
|
+
"Cross-Origin-Resource-Policy": "cross-origin"
|
|
26942
|
+
}
|
|
26943
|
+
});
|
|
26944
|
+
} catch (error) {
|
|
26945
|
+
const message = error instanceof Error ? error.message : "Failed to get git media";
|
|
26946
|
+
return c.json({ error: message }, 500);
|
|
26947
|
+
}
|
|
26948
|
+
}
|
|
26949
|
+
|
|
25619
26950
|
// src/features/git/git.routes.ts
|
|
25620
26951
|
function createGitRoutes(metadataManager) {
|
|
25621
26952
|
const router = new Hono13();
|
|
@@ -25638,6 +26969,9 @@ function createGitRoutes(metadataManager) {
|
|
|
25638
26969
|
router.get("/diff/:threadId", async (c) => {
|
|
25639
26970
|
return gitDiffHandler(c, metadataManager);
|
|
25640
26971
|
});
|
|
26972
|
+
router.get("/media/:threadId", async (c) => {
|
|
26973
|
+
return gitMediaHandler(c, metadataManager);
|
|
26974
|
+
});
|
|
25641
26975
|
router.post("/generate-commit-message/:threadId", async (c) => {
|
|
25642
26976
|
return gitGenerateCommitMessageHandler(c, metadataManager);
|
|
25643
26977
|
});
|
|
@@ -25702,6 +27036,11 @@ function createGitRoutes(metadataManager) {
|
|
|
25702
27036
|
await invalidateGitStatusCache(db, c.req.param("threadId"));
|
|
25703
27037
|
return gitCheckoutBranchHandler(c, metadataManager);
|
|
25704
27038
|
});
|
|
27039
|
+
router.post("/rename-branch/:threadId", async (c) => {
|
|
27040
|
+
const db = await getDatabase();
|
|
27041
|
+
await invalidateGitStatusCache(db, c.req.param("threadId"));
|
|
27042
|
+
return gitRenameBranchHandler(c, metadataManager);
|
|
27043
|
+
});
|
|
25705
27044
|
router.post("/revert-file/:threadId", async (c) => {
|
|
25706
27045
|
const db = await getDatabase();
|
|
25707
27046
|
await invalidateGitStatusCache(db, c.req.param("threadId"));
|
|
@@ -25757,8 +27096,8 @@ function createUpdateRoutes(updater) {
|
|
|
25757
27096
|
|
|
25758
27097
|
// src/features/mcp/mcp.routes.ts
|
|
25759
27098
|
import { Hono as Hono15 } from "hono";
|
|
25760
|
-
import { readFile as
|
|
25761
|
-
import { join as
|
|
27099
|
+
import { readFile as readFile17, writeFile as writeFile9, mkdir as mkdir11, access as access6 } from "fs/promises";
|
|
27100
|
+
import { join as join33, dirname as dirname7 } from "path";
|
|
25762
27101
|
|
|
25763
27102
|
// src/features/mcp/mcp.popular.json
|
|
25764
27103
|
var mcp_popular_default = [
|
|
@@ -25880,10 +27219,10 @@ var mcp_popular_default = [
|
|
|
25880
27219
|
var MCP_CONFIG_PATHS2 = [".agents/mcp.json", "mcp.json"];
|
|
25881
27220
|
async function readMCPConfig(projectPath) {
|
|
25882
27221
|
for (const configPath of MCP_CONFIG_PATHS2) {
|
|
25883
|
-
const fullPath =
|
|
27222
|
+
const fullPath = join33(projectPath, configPath);
|
|
25884
27223
|
try {
|
|
25885
27224
|
await access6(fullPath);
|
|
25886
|
-
const content = await
|
|
27225
|
+
const content = await readFile17(fullPath, "utf-8");
|
|
25887
27226
|
const config = JSON.parse(content);
|
|
25888
27227
|
return { config, filePath: fullPath };
|
|
25889
27228
|
} catch {
|
|
@@ -25894,7 +27233,7 @@ async function readMCPConfig(projectPath) {
|
|
|
25894
27233
|
}
|
|
25895
27234
|
async function writeMCPConfig(projectPath, config) {
|
|
25896
27235
|
const existing = await readMCPConfig(projectPath);
|
|
25897
|
-
const targetPath = existing?.filePath ??
|
|
27236
|
+
const targetPath = existing?.filePath ?? join33(projectPath, ".agents/mcp.json");
|
|
25898
27237
|
await mkdir11(dirname7(targetPath), { recursive: true });
|
|
25899
27238
|
await writeFile9(targetPath, JSON.stringify(config, null, 2), "utf-8");
|
|
25900
27239
|
}
|
|
@@ -26310,8 +27649,8 @@ function getLocalNetworkAddresses() {
|
|
|
26310
27649
|
}
|
|
26311
27650
|
|
|
26312
27651
|
// src/core/server-bind-mode-store.ts
|
|
26313
|
-
import { existsSync as
|
|
26314
|
-
import { join as
|
|
27652
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync } from "fs";
|
|
27653
|
+
import { join as join34 } from "path";
|
|
26315
27654
|
|
|
26316
27655
|
// src/core/server-bind-mode.ts
|
|
26317
27656
|
var DEFAULT_SERVER_BIND_MODE = "local";
|
|
@@ -26323,16 +27662,16 @@ function getServerBindHostname(mode) {
|
|
|
26323
27662
|
}
|
|
26324
27663
|
|
|
26325
27664
|
// src/core/server-bind-mode-store.ts
|
|
26326
|
-
var SERVER_BIND_MODE_FILE =
|
|
27665
|
+
var SERVER_BIND_MODE_FILE = join34(DATA_DIR, "server-bind-mode.json");
|
|
26327
27666
|
function hasServerBindModeFile() {
|
|
26328
|
-
return
|
|
27667
|
+
return existsSync24(SERVER_BIND_MODE_FILE);
|
|
26329
27668
|
}
|
|
26330
27669
|
function readServerBindMode() {
|
|
26331
27670
|
try {
|
|
26332
|
-
if (!
|
|
27671
|
+
if (!existsSync24(SERVER_BIND_MODE_FILE)) {
|
|
26333
27672
|
return DEFAULT_SERVER_BIND_MODE;
|
|
26334
27673
|
}
|
|
26335
|
-
const raw =
|
|
27674
|
+
const raw = readFileSync9(SERVER_BIND_MODE_FILE, "utf8");
|
|
26336
27675
|
const parsed = JSON.parse(raw);
|
|
26337
27676
|
return normalizeServerBindMode(parsed.serverBindMode);
|
|
26338
27677
|
} catch {
|
|
@@ -26656,7 +27995,8 @@ async function clipboardWriteImage(c) {
|
|
|
26656
27995
|
pngData[i] = binary.charCodeAt(i);
|
|
26657
27996
|
}
|
|
26658
27997
|
} else if (imageUrl) {
|
|
26659
|
-
const
|
|
27998
|
+
const fetchUrl2 = resolveAbsoluteImageUrl(imageUrl, new URL(c.req.url).origin);
|
|
27999
|
+
const res = await fetch(fetchUrl2);
|
|
26660
28000
|
if (!res.ok) {
|
|
26661
28001
|
return c.json({ error: `Failed to fetch image: ${res.status}` }, 400);
|
|
26662
28002
|
}
|
|
@@ -26673,7 +28013,7 @@ async function clipboardWriteImage(c) {
|
|
|
26673
28013
|
}
|
|
26674
28014
|
|
|
26675
28015
|
// src/features/image/image-save.route.ts
|
|
26676
|
-
import { join as
|
|
28016
|
+
import { join as join35 } from "path";
|
|
26677
28017
|
var Utils5 = null;
|
|
26678
28018
|
var utilsLoaded5 = false;
|
|
26679
28019
|
async function loadUtils5() {
|
|
@@ -26701,7 +28041,8 @@ async function saveImage(c) {
|
|
|
26701
28041
|
data[i] = binary.charCodeAt(i);
|
|
26702
28042
|
}
|
|
26703
28043
|
} else if (imageUrl) {
|
|
26704
|
-
const
|
|
28044
|
+
const fetchUrl2 = resolveAbsoluteImageUrl(imageUrl, new URL(c.req.url).origin);
|
|
28045
|
+
const res = await fetch(fetchUrl2);
|
|
26705
28046
|
if (!res.ok) {
|
|
26706
28047
|
return c.json({ error: `Failed to fetch image: ${res.status}` }, 400);
|
|
26707
28048
|
}
|
|
@@ -26710,7 +28051,7 @@ async function saveImage(c) {
|
|
|
26710
28051
|
return c.json({ error: "imageUrl or imageData is required" }, 400);
|
|
26711
28052
|
}
|
|
26712
28053
|
const name = filename ?? `generated-image-${Date.now()}.png`;
|
|
26713
|
-
const savePath =
|
|
28054
|
+
const savePath = join35(Utils5.paths.downloads, name);
|
|
26714
28055
|
await Bun.write(savePath, data);
|
|
26715
28056
|
return c.json({ success: true, path: savePath });
|
|
26716
28057
|
} catch (error) {
|
|
@@ -26802,7 +28143,7 @@ function createBrowserJsRoutes(threadManager, projectManager, processingStateMan
|
|
|
26802
28143
|
});
|
|
26803
28144
|
}
|
|
26804
28145
|
}) : void 0;
|
|
26805
|
-
const tools = createAllTools(thread.path, { shellPermissionGate });
|
|
28146
|
+
const tools = createAllTools(thread.path, { shellPermissionGate, threadId });
|
|
26806
28147
|
const tool = tools[toolName];
|
|
26807
28148
|
if (!tool) {
|
|
26808
28149
|
return errorResponse(c, ErrorCodes.INVALID_REQUEST, `Tool ${toolName} not found`, 404);
|
|
@@ -26818,8 +28159,79 @@ function createBrowserJsRoutes(threadManager, projectManager, processingStateMan
|
|
|
26818
28159
|
return router;
|
|
26819
28160
|
}
|
|
26820
28161
|
|
|
26821
|
-
// src/features/
|
|
28162
|
+
// src/features/browser/browser.routes.ts
|
|
26822
28163
|
import { Hono as Hono23 } from "hono";
|
|
28164
|
+
function createBrowserRoutes() {
|
|
28165
|
+
const router = new Hono23();
|
|
28166
|
+
router.post("/ready", async (c) => {
|
|
28167
|
+
try {
|
|
28168
|
+
const body = await c.req.json();
|
|
28169
|
+
const { toolCallId, webviewId } = body;
|
|
28170
|
+
if (!toolCallId || typeof toolCallId !== "string") {
|
|
28171
|
+
return errorResponse(
|
|
28172
|
+
c,
|
|
28173
|
+
ErrorCodes.INVALID_REQUEST,
|
|
28174
|
+
"toolCallId is required and must be a string",
|
|
28175
|
+
400
|
|
28176
|
+
);
|
|
28177
|
+
}
|
|
28178
|
+
if (typeof webviewId !== "number" || !Number.isFinite(webviewId)) {
|
|
28179
|
+
return errorResponse(
|
|
28180
|
+
c,
|
|
28181
|
+
ErrorCodes.INVALID_REQUEST,
|
|
28182
|
+
"webviewId is required and must be a number",
|
|
28183
|
+
400
|
|
28184
|
+
);
|
|
28185
|
+
}
|
|
28186
|
+
submitBrowserWebviewReady(toolCallId, webviewId);
|
|
28187
|
+
return c.json({ success: true });
|
|
28188
|
+
} catch (error) {
|
|
28189
|
+
return errorResponse(
|
|
28190
|
+
c,
|
|
28191
|
+
ErrorCodes.REQUEST_PARSE_ERROR,
|
|
28192
|
+
"Failed to parse request body",
|
|
28193
|
+
400,
|
|
28194
|
+
error instanceof Error ? error.message : String(error)
|
|
28195
|
+
);
|
|
28196
|
+
}
|
|
28197
|
+
});
|
|
28198
|
+
router.post("/action-result", async (c) => {
|
|
28199
|
+
try {
|
|
28200
|
+
const body = await c.req.json();
|
|
28201
|
+
const { toolCallId, result } = body;
|
|
28202
|
+
if (!toolCallId || typeof toolCallId !== "string") {
|
|
28203
|
+
return errorResponse(
|
|
28204
|
+
c,
|
|
28205
|
+
ErrorCodes.INVALID_REQUEST,
|
|
28206
|
+
"toolCallId is required and must be a string",
|
|
28207
|
+
400
|
|
28208
|
+
);
|
|
28209
|
+
}
|
|
28210
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
28211
|
+
return errorResponse(
|
|
28212
|
+
c,
|
|
28213
|
+
ErrorCodes.INVALID_REQUEST,
|
|
28214
|
+
"result is required and must be an object",
|
|
28215
|
+
400
|
|
28216
|
+
);
|
|
28217
|
+
}
|
|
28218
|
+
submitBrowserActionResult(toolCallId, result);
|
|
28219
|
+
return c.json({ success: true });
|
|
28220
|
+
} catch (error) {
|
|
28221
|
+
return errorResponse(
|
|
28222
|
+
c,
|
|
28223
|
+
ErrorCodes.REQUEST_PARSE_ERROR,
|
|
28224
|
+
"Failed to parse request body",
|
|
28225
|
+
400,
|
|
28226
|
+
error instanceof Error ? error.message : String(error)
|
|
28227
|
+
);
|
|
28228
|
+
}
|
|
28229
|
+
});
|
|
28230
|
+
return router;
|
|
28231
|
+
}
|
|
28232
|
+
|
|
28233
|
+
// src/features/voice-model/voice-model.routes.ts
|
|
28234
|
+
import { Hono as Hono24 } from "hono";
|
|
26823
28235
|
var VOICE_MODEL_URLS = {
|
|
26824
28236
|
default: "https://install.tarsk.io/voice-models/ggml-tiny.en.bin",
|
|
26825
28237
|
tiny: "https://install.tarsk.io/voice-models/ggml-tiny.en-q5_1.bin"
|
|
@@ -26831,7 +28243,7 @@ function getVoiceModelUrl(model) {
|
|
|
26831
28243
|
return VOICE_MODEL_URLS.default;
|
|
26832
28244
|
}
|
|
26833
28245
|
function createVoiceModelRoutes() {
|
|
26834
|
-
const router = new
|
|
28246
|
+
const router = new Hono24();
|
|
26835
28247
|
router.get("/download", async (c) => {
|
|
26836
28248
|
const selectedModel = c.req.query("model");
|
|
26837
28249
|
const modelUrl = getVoiceModelUrl(selectedModel);
|
|
@@ -26872,9 +28284,9 @@ function createVoiceModelRoutes() {
|
|
|
26872
28284
|
}
|
|
26873
28285
|
|
|
26874
28286
|
// src/features/logs/logs.routes.ts
|
|
26875
|
-
import { Hono as
|
|
28287
|
+
import { Hono as Hono25 } from "hono";
|
|
26876
28288
|
function createLogsRoutes() {
|
|
26877
|
-
const router = new
|
|
28289
|
+
const router = new Hono25();
|
|
26878
28290
|
router.post("/", async (c) => {
|
|
26879
28291
|
let body = {};
|
|
26880
28292
|
try {
|
|
@@ -26912,11 +28324,11 @@ function createLogsRoutes() {
|
|
|
26912
28324
|
|
|
26913
28325
|
// src/features/user-tasks/user-tasks.routes.ts
|
|
26914
28326
|
init_database();
|
|
26915
|
-
import { Hono as
|
|
28327
|
+
import { Hono as Hono26 } from "hono";
|
|
26916
28328
|
|
|
26917
28329
|
// src/features/user-tasks/user-tasks.database.ts
|
|
26918
28330
|
init_database();
|
|
26919
|
-
import { randomUUID as
|
|
28331
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
26920
28332
|
async function getUserTasksByThread(db, threadId) {
|
|
26921
28333
|
const result = await db.execute({
|
|
26922
28334
|
sql: `SELECT id, threadId, content, createdAt, updatedAt
|
|
@@ -26929,7 +28341,7 @@ async function getUserTasksByThread(db, threadId) {
|
|
|
26929
28341
|
}
|
|
26930
28342
|
async function insertUserTask(threadId, content) {
|
|
26931
28343
|
const db = await getDatabase();
|
|
26932
|
-
const id =
|
|
28344
|
+
const id = randomUUID13();
|
|
26933
28345
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26934
28346
|
await db.execute({
|
|
26935
28347
|
sql: `INSERT INTO user_tasks (id, threadId, content, createdAt, updatedAt)
|
|
@@ -26953,7 +28365,7 @@ async function deleteUserTaskById(db, id) {
|
|
|
26953
28365
|
|
|
26954
28366
|
// src/features/user-tasks/user-tasks.routes.ts
|
|
26955
28367
|
function createUserTaskRoutes() {
|
|
26956
|
-
const router = new
|
|
28368
|
+
const router = new Hono26();
|
|
26957
28369
|
router.get("/:threadId/user-tasks", async (c) => {
|
|
26958
28370
|
const threadId = c.req.param("threadId");
|
|
26959
28371
|
const db = await getDatabase();
|
|
@@ -26993,7 +28405,7 @@ async function startTarskServer(options) {
|
|
|
26993
28405
|
async function startTarskServerInternal(options) {
|
|
26994
28406
|
const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
|
|
26995
28407
|
const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
|
|
26996
|
-
const app = new
|
|
28408
|
+
const app = new Hono27();
|
|
26997
28409
|
app.use("/*", cors());
|
|
26998
28410
|
app.use("/*", async (c, next) => {
|
|
26999
28411
|
c.header("Cross-Origin-Opener-Policy", "same-origin");
|
|
@@ -27086,6 +28498,7 @@ async function startTarskServerInternal(options) {
|
|
|
27086
28498
|
"/api/browser-js",
|
|
27087
28499
|
createBrowserJsRoutes(threadManager, projectManager, processingStateManager)
|
|
27088
28500
|
);
|
|
28501
|
+
app.route("/api/browser", createBrowserRoutes());
|
|
27089
28502
|
app.route("/api/voice-model", createVoiceModelRoutes());
|
|
27090
28503
|
app.route("/api/logs", createLogsRoutes());
|
|
27091
28504
|
app.route("/api/update", createUpdateRoutes(options.updater));
|
|
@@ -27157,10 +28570,10 @@ async function listenForTarskServer(options) {
|
|
|
27157
28570
|
hostname: options.hostname,
|
|
27158
28571
|
port: options.port
|
|
27159
28572
|
});
|
|
27160
|
-
return new Promise((
|
|
28573
|
+
return new Promise((resolve7, reject) => {
|
|
27161
28574
|
server.once("error", (error) => {
|
|
27162
28575
|
if (error.code === "EADDRINUSE") {
|
|
27163
|
-
|
|
28576
|
+
resolve7(false);
|
|
27164
28577
|
return;
|
|
27165
28578
|
}
|
|
27166
28579
|
startPromise = null;
|
|
@@ -27168,7 +28581,7 @@ async function listenForTarskServer(options) {
|
|
|
27168
28581
|
});
|
|
27169
28582
|
function onListening() {
|
|
27170
28583
|
options.onListening();
|
|
27171
|
-
|
|
28584
|
+
resolve7(true);
|
|
27172
28585
|
}
|
|
27173
28586
|
if (options.hostname) {
|
|
27174
28587
|
server.listen(options.port, options.hostname, onListening);
|