tarsk 0.5.34 → 0.5.35
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/README.md +2 -0
- package/dist/index.js +1283 -789
- package/dist/public/assets/account-view-k5jIArY-.js +1 -0
- package/dist/public/assets/api-OCjPhv01.js +1 -0
- package/dist/public/assets/browser-tab-C9kJT-C5.js +1 -0
- package/dist/public/assets/commit-dialog-8Ce6d3H8.js +1 -0
- package/dist/public/assets/context-menu-CMsWrzLD.js +1 -0
- package/dist/public/assets/create-repo-dialog-CVUtteNS.js +1 -0
- package/dist/public/assets/{dialogs-config-Ct6i8Iij.js → dialogs-config-B4TDFSrS.js} +14 -14
- package/dist/public/assets/diff-view-DGfmGlB_.js +3 -0
- package/dist/public/assets/explorer-tab-view-CCXVTkVu.js +2 -0
- package/dist/public/assets/explorer-tree-uXbr6cCI.js +1 -0
- package/dist/public/assets/explorer-view-CupxzDId.js +1 -0
- package/dist/public/assets/git-history-dialog-BCGOZ6ua.js +1 -0
- package/dist/public/assets/git-ops-button-BV9X3fo-.js +2 -0
- package/dist/public/assets/history-view-Ds_0e_bx.js +7 -0
- package/dist/public/assets/index-C0lTt0ZK.css +1 -0
- package/dist/public/assets/index-DOsSr4dl.js +65 -0
- package/dist/public/assets/mcp-server-card-DrUUKNXv.js +1 -0
- package/dist/public/assets/merged-pr-dialog-DqWSFdCH.js +1 -0
- package/dist/public/assets/onboarding-DfL8a0LZ.js +1 -0
- package/dist/public/assets/project-settings-view-Cd_l3Lfy.js +1 -0
- package/dist/public/assets/providers-list-view-VXhgM3vp.js +1 -0
- package/dist/public/assets/pull-request-dialog-DZXTHHuh.js +1 -0
- package/dist/public/assets/pull-with-changes-dialog-CYQrwzcw.js +1 -0
- package/dist/public/assets/push-before-pr-dialog-D-ZnRpJi.js +1 -0
- package/dist/public/assets/radio-group-c0sU992r.js +1 -0
- package/dist/public/assets/react-vendor-unlDVFpU.js +16 -0
- package/dist/public/assets/settings-general-view-DI-lvP0L.js +1 -0
- package/dist/public/assets/settings-instructions-view-kUG32_dT.js +1 -0
- package/dist/public/assets/settings-list-CUbZrggr.js +1 -0
- package/dist/public/assets/settings-mcp-servers-view-DmxUO42I.js +5 -0
- package/dist/public/assets/{settings-models-skeleton-CN2JkqZh.js → settings-models-skeleton-B_56ozbA.js} +1 -1
- package/dist/public/assets/settings-models-view-ms828HDa.js +1 -0
- package/dist/public/assets/settings-rules-view-Xx0ymfnQ.js +8 -0
- package/dist/public/assets/settings-skills-view-DvPPdsi_.js +2 -0
- package/dist/public/assets/settings-slash-commands-view-BULIAW2o.js +1 -0
- package/dist/public/assets/settings-subagents-view-B7FcLaZT.js +2 -0
- package/dist/public/assets/{settings-system-prompt-view-BNM_15ry.js → settings-system-prompt-view-66zf0gRc.js} +1 -1
- package/dist/public/assets/settings-view-B4CMq7it.js +2 -0
- package/dist/public/assets/skeleton-BvSNJL9q.js +1 -0
- package/dist/public/assets/{terminal-panel-Ds1tCgQA.js → terminal-panel-Bm5ECpK_.js} +2 -2
- package/dist/public/assets/{ui-components-Di-9yNr4.js → ui-components-BZYnvMGc.js} +1 -1
- package/dist/public/assets/{utils-DDDR42pW.js → utils-umVhs9to.js} +1 -1
- package/dist/public/index.html +7 -7
- package/package.json +1 -1
- package/dist/public/assets/account-view-Ds03urJE.js +0 -1
- package/dist/public/assets/api-BHfaVqlR.js +0 -1
- package/dist/public/assets/browser-tab-Qs8cnQkf.js +0 -1
- package/dist/public/assets/commit-dialog-D4suPxEe.js +0 -1
- package/dist/public/assets/context-menu-S8y0c1Fx.js +0 -1
- package/dist/public/assets/create-repo-dialog-DPKIg1mY.js +0 -1
- package/dist/public/assets/diff-view-rKDEnBLg.js +0 -3
- package/dist/public/assets/explorer-tab-view-JbLbPVAn.js +0 -2
- package/dist/public/assets/explorer-tree-Dq44yA1j.js +0 -1
- package/dist/public/assets/explorer-view-BMrlX2hb.js +0 -1
- package/dist/public/assets/git-history-dialog-CDwIqSZQ.js +0 -1
- package/dist/public/assets/git-ops-button-B6rEla5u.js +0 -2
- package/dist/public/assets/history-view-CFot099O.js +0 -7
- package/dist/public/assets/index-6DhhjIBL.js +0 -65
- package/dist/public/assets/index-sEZ3wJXu.css +0 -1
- package/dist/public/assets/mcp-server-card-DQY1RUPX.js +0 -1
- package/dist/public/assets/merged-pr-dialog-H_fuSmiF.js +0 -1
- package/dist/public/assets/onboarding-DHCR18iU.js +0 -1
- package/dist/public/assets/project-settings-view-CiefnK73.js +0 -1
- package/dist/public/assets/providers-list-view-DVopskGo.js +0 -1
- package/dist/public/assets/pull-request-dialog-Csql6eob.js +0 -1
- package/dist/public/assets/pull-with-changes-dialog-DN0kE6os.js +0 -1
- package/dist/public/assets/push-before-pr-dialog-IhdlLvWt.js +0 -1
- package/dist/public/assets/radio-group-BaKkAEZR.js +0 -1
- package/dist/public/assets/react-vendor-CjZZeCRj.js +0 -16
- package/dist/public/assets/settings-general-view-BMTxN35t.js +0 -1
- package/dist/public/assets/settings-instructions-view-DULFg6dU.js +0 -1
- package/dist/public/assets/settings-list-Bdeuh4bJ.js +0 -1
- package/dist/public/assets/settings-mcp-servers-view-CRCtWjP2.js +0 -5
- package/dist/public/assets/settings-models-view-DosllhVB.js +0 -1
- package/dist/public/assets/settings-rules-view-BVWuGrnB.js +0 -8
- package/dist/public/assets/settings-skills-view-DYtcAu9-.js +0 -2
- package/dist/public/assets/settings-slash-commands-view-DFyL1W2n.js +0 -1
- package/dist/public/assets/settings-subagents-view-aCbK95Sk.js +0 -2
- package/dist/public/assets/settings-view-D5GE_CYR.js +0 -2
- package/dist/public/assets/skeleton-pSKdmWyJ.js +0 -1
package/dist/index.js
CHANGED
|
@@ -191,18 +191,18 @@ __export(database_exports, {
|
|
|
191
191
|
initializeSchema: () => initializeSchema
|
|
192
192
|
});
|
|
193
193
|
import { createClient } from "@libsql/client";
|
|
194
|
-
import { join as
|
|
195
|
-
import { homedir as
|
|
194
|
+
import { join as join2 } from "path";
|
|
195
|
+
import { homedir as homedir2 } from "os";
|
|
196
196
|
import { mkdirSync } from "fs";
|
|
197
197
|
function getDatabasePath() {
|
|
198
|
-
const appSupportDir =
|
|
199
|
-
const dataDir =
|
|
200
|
-
return
|
|
198
|
+
const appSupportDir = join2(homedir2(), "Library", "Application Support", "Tarsk");
|
|
199
|
+
const dataDir = join2(appSupportDir, "data");
|
|
200
|
+
return join2(dataDir, "tarsk.db");
|
|
201
201
|
}
|
|
202
202
|
async function initializeDatabase() {
|
|
203
203
|
const dbPath = getDatabasePath();
|
|
204
204
|
try {
|
|
205
|
-
const dataDir =
|
|
205
|
+
const dataDir = join2(homedir2(), "Library", "Application Support", "Tarsk", "data");
|
|
206
206
|
mkdirSync(dataDir, { recursive: true });
|
|
207
207
|
const db = createClient({
|
|
208
208
|
url: `file:${dbPath}`
|
|
@@ -900,6 +900,27 @@ async function runMigrations(db) {
|
|
|
900
900
|
CREATE INDEX idx_project_scripts_projectId ON project_scripts(projectId)
|
|
901
901
|
`);
|
|
902
902
|
}
|
|
903
|
+
const projectEnvVarsExists = await db.execute(
|
|
904
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name='project_env_vars'`
|
|
905
|
+
);
|
|
906
|
+
if (projectEnvVarsExists.rows.length === 0) {
|
|
907
|
+
tarskDebugLog("[db] Running migration: Creating project_env_vars table");
|
|
908
|
+
await db.execute(`
|
|
909
|
+
CREATE TABLE project_env_vars (
|
|
910
|
+
id TEXT PRIMARY KEY,
|
|
911
|
+
projectId TEXT NOT NULL,
|
|
912
|
+
name TEXT NOT NULL,
|
|
913
|
+
encryptedValue TEXT NOT NULL,
|
|
914
|
+
createdAt TEXT NOT NULL,
|
|
915
|
+
updatedAt TEXT NOT NULL,
|
|
916
|
+
FOREIGN KEY (projectId) REFERENCES projects(id) ON DELETE CASCADE,
|
|
917
|
+
UNIQUE(projectId, name)
|
|
918
|
+
)
|
|
919
|
+
`);
|
|
920
|
+
await db.execute(`
|
|
921
|
+
CREATE INDEX idx_project_env_vars_projectId ON project_env_vars(projectId)
|
|
922
|
+
`);
|
|
923
|
+
}
|
|
903
924
|
} catch (error) {
|
|
904
925
|
console.error("Failed to run migrations:", error);
|
|
905
926
|
throw error;
|
|
@@ -927,7 +948,7 @@ var init_database = __esm({
|
|
|
927
948
|
});
|
|
928
949
|
|
|
929
950
|
// src/server.ts
|
|
930
|
-
import {
|
|
951
|
+
import { createAdaptorServer } from "@hono/node-server";
|
|
931
952
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
932
953
|
|
|
933
954
|
// ../shared/dist/programs.js
|
|
@@ -1554,6 +1575,59 @@ function isSameFetchHost(a, b) {
|
|
|
1554
1575
|
return normalizeFetchHostname(a) === normalizeFetchHostname(b);
|
|
1555
1576
|
}
|
|
1556
1577
|
|
|
1578
|
+
// ../shared/dist/explorer-media.js
|
|
1579
|
+
var EXPLORER_IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1580
|
+
"png",
|
|
1581
|
+
"jpg",
|
|
1582
|
+
"jpeg",
|
|
1583
|
+
"gif",
|
|
1584
|
+
"webp",
|
|
1585
|
+
"svg",
|
|
1586
|
+
"ico",
|
|
1587
|
+
"bmp",
|
|
1588
|
+
"tiff",
|
|
1589
|
+
"tif"
|
|
1590
|
+
]);
|
|
1591
|
+
var EXPLORER_VIDEO_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "avi"]);
|
|
1592
|
+
var EXPLORER_MEDIA_MIME_TYPES = {
|
|
1593
|
+
png: "image/png",
|
|
1594
|
+
jpg: "image/jpeg",
|
|
1595
|
+
jpeg: "image/jpeg",
|
|
1596
|
+
gif: "image/gif",
|
|
1597
|
+
webp: "image/webp",
|
|
1598
|
+
svg: "image/svg+xml",
|
|
1599
|
+
ico: "image/x-icon",
|
|
1600
|
+
bmp: "image/bmp",
|
|
1601
|
+
tiff: "image/tiff",
|
|
1602
|
+
tif: "image/tiff",
|
|
1603
|
+
mp4: "video/mp4",
|
|
1604
|
+
webm: "video/webm",
|
|
1605
|
+
mov: "video/quicktime",
|
|
1606
|
+
avi: "video/x-msvideo"
|
|
1607
|
+
};
|
|
1608
|
+
function getFileExtension(filename) {
|
|
1609
|
+
const trimmed = filename.trim();
|
|
1610
|
+
const basename5 = trimmed.split(/[/\\]/).pop() ?? trimmed;
|
|
1611
|
+
const dotIndex = basename5.lastIndexOf(".");
|
|
1612
|
+
if (dotIndex <= 0 || dotIndex === basename5.length - 1) {
|
|
1613
|
+
return "";
|
|
1614
|
+
}
|
|
1615
|
+
return basename5.slice(dotIndex + 1).toLowerCase();
|
|
1616
|
+
}
|
|
1617
|
+
function getExplorerFileMediaType(path7, name) {
|
|
1618
|
+
const ext = getFileExtension(path7) || (name ? getFileExtension(name) : "");
|
|
1619
|
+
if (!ext) {
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
if (EXPLORER_IMAGE_EXTENSIONS.has(ext)) {
|
|
1623
|
+
return "image";
|
|
1624
|
+
}
|
|
1625
|
+
if (EXPLORER_VIDEO_EXTENSIONS.has(ext)) {
|
|
1626
|
+
return "video";
|
|
1627
|
+
}
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1557
1631
|
// src/server.ts
|
|
1558
1632
|
import fs3 from "fs";
|
|
1559
1633
|
import { Hono as Hono26 } from "hono";
|
|
@@ -1580,10 +1654,10 @@ function createMcpThinkingEvent(content) {
|
|
|
1580
1654
|
// src/tools/bash.ts
|
|
1581
1655
|
init_tarsk_debug();
|
|
1582
1656
|
init_utils();
|
|
1583
|
-
import { randomBytes } from "node:crypto";
|
|
1657
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1584
1658
|
import { createWriteStream, existsSync as existsSync3 } from "node:fs";
|
|
1585
1659
|
import { tmpdir } from "node:os";
|
|
1586
|
-
import { join as
|
|
1660
|
+
import { join as join3 } from "node:path";
|
|
1587
1661
|
import { Type } from "@sinclair/typebox";
|
|
1588
1662
|
|
|
1589
1663
|
// src/tools/shell.ts
|
|
@@ -1718,172 +1792,649 @@ function killProcessTree(pid) {
|
|
|
1718
1792
|
}
|
|
1719
1793
|
}
|
|
1720
1794
|
|
|
1721
|
-
// src/
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
totalLines,
|
|
1746
|
-
totalBytes,
|
|
1747
|
-
outputLines: totalLines,
|
|
1748
|
-
outputBytes: totalBytes,
|
|
1749
|
-
lastLinePartial: false,
|
|
1750
|
-
firstLineExceedsLimit: false,
|
|
1751
|
-
maxLines,
|
|
1752
|
-
maxBytes
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
|
-
const firstLineBytes = Buffer.byteLength(lines[0], "utf-8");
|
|
1756
|
-
if (firstLineBytes > maxBytes) {
|
|
1757
|
-
return {
|
|
1758
|
-
content: "",
|
|
1759
|
-
truncated: true,
|
|
1760
|
-
truncatedBy: "bytes",
|
|
1761
|
-
totalLines,
|
|
1762
|
-
totalBytes,
|
|
1763
|
-
outputLines: 0,
|
|
1764
|
-
outputBytes: 0,
|
|
1765
|
-
lastLinePartial: false,
|
|
1766
|
-
firstLineExceedsLimit: true,
|
|
1767
|
-
maxLines,
|
|
1768
|
-
maxBytes
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
const outputLinesArr = [];
|
|
1772
|
-
let outputBytesCount = 0;
|
|
1773
|
-
let truncatedBy = "lines";
|
|
1774
|
-
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
1775
|
-
const line = lines[i];
|
|
1776
|
-
const lineBytes = Buffer.byteLength(line, "utf-8") + (i > 0 ? 1 : 0);
|
|
1777
|
-
if (outputBytesCount + lineBytes > maxBytes) {
|
|
1778
|
-
truncatedBy = "bytes";
|
|
1779
|
-
break;
|
|
1795
|
+
// src/features/projects/project-env-vars.ts
|
|
1796
|
+
init_database();
|
|
1797
|
+
|
|
1798
|
+
// src/core/crypto.ts
|
|
1799
|
+
init_database();
|
|
1800
|
+
import { createCipheriv, createDecipheriv, randomBytes, createHash } from "crypto";
|
|
1801
|
+
import { networkInterfaces, hostname } from "os";
|
|
1802
|
+
var ALGORITHM = "aes-256-gcm";
|
|
1803
|
+
var IV_LENGTH = 16;
|
|
1804
|
+
function getMachineKey() {
|
|
1805
|
+
try {
|
|
1806
|
+
const interfaces = networkInterfaces();
|
|
1807
|
+
let macAddress = "";
|
|
1808
|
+
for (const name of Object.keys(interfaces)) {
|
|
1809
|
+
const iface = interfaces[name];
|
|
1810
|
+
if (iface) {
|
|
1811
|
+
for (const addr of iface) {
|
|
1812
|
+
if (addr.mac && addr.mac !== "00:00:00:00:00:00" && !addr.internal) {
|
|
1813
|
+
macAddress = addr.mac;
|
|
1814
|
+
break;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (macAddress) break;
|
|
1818
|
+
}
|
|
1780
1819
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1820
|
+
const host = hostname();
|
|
1821
|
+
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
1822
|
+
const machineId = `${macAddress}-${host}-${homeDir}`;
|
|
1823
|
+
return createHash("sha256").update(machineId).digest();
|
|
1824
|
+
} catch {
|
|
1825
|
+
return Buffer.from("Invalid", "utf8");
|
|
1786
1826
|
}
|
|
1787
|
-
const outputContent = outputLinesArr.join("\n");
|
|
1788
|
-
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
1789
|
-
return {
|
|
1790
|
-
content: outputContent,
|
|
1791
|
-
truncated: true,
|
|
1792
|
-
truncatedBy,
|
|
1793
|
-
totalLines,
|
|
1794
|
-
totalBytes,
|
|
1795
|
-
outputLines: outputLinesArr.length,
|
|
1796
|
-
outputBytes: finalOutputBytes,
|
|
1797
|
-
lastLinePartial: false,
|
|
1798
|
-
firstLineExceedsLimit: false,
|
|
1799
|
-
maxLines,
|
|
1800
|
-
maxBytes
|
|
1801
|
-
};
|
|
1802
1827
|
}
|
|
1803
|
-
function
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
outputBytes: totalBytes,
|
|
1818
|
-
lastLinePartial: false,
|
|
1819
|
-
firstLineExceedsLimit: false,
|
|
1820
|
-
maxLines,
|
|
1821
|
-
maxBytes
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
const outputLinesArr = [];
|
|
1825
|
-
let outputBytesCount = 0;
|
|
1826
|
-
let truncatedBy = "lines";
|
|
1827
|
-
let lastLinePartial = false;
|
|
1828
|
-
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
1829
|
-
const line = lines[i];
|
|
1830
|
-
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
|
|
1831
|
-
if (outputBytesCount + lineBytes > maxBytes) {
|
|
1832
|
-
truncatedBy = "bytes";
|
|
1833
|
-
if (outputLinesArr.length === 0) {
|
|
1834
|
-
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
1835
|
-
outputLinesArr.unshift(truncatedLine);
|
|
1836
|
-
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
1837
|
-
lastLinePartial = true;
|
|
1828
|
+
async function getEncryptionKey() {
|
|
1829
|
+
try {
|
|
1830
|
+
const db = await getDatabase();
|
|
1831
|
+
const result = await db.execute({
|
|
1832
|
+
sql: "SELECT value FROM state WHERE key = ?",
|
|
1833
|
+
args: ["encryption_key"]
|
|
1834
|
+
});
|
|
1835
|
+
if (result.rows.length > 0 && result.rows[0].value) {
|
|
1836
|
+
const storedValue = result.rows[0].value;
|
|
1837
|
+
try {
|
|
1838
|
+
const parsed = JSON.parse(storedValue);
|
|
1839
|
+
return Buffer.from(parsed, "base64");
|
|
1840
|
+
} catch {
|
|
1841
|
+
return Buffer.from(storedValue, "base64");
|
|
1838
1842
|
}
|
|
1839
|
-
break;
|
|
1840
1843
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1844
|
+
const machineKey = getMachineKey();
|
|
1845
|
+
await db.execute({
|
|
1846
|
+
sql: "INSERT OR REPLACE INTO state (key, value) VALUES (?, ?)",
|
|
1847
|
+
args: ["encryption_key", machineKey.toString("base64")]
|
|
1848
|
+
});
|
|
1849
|
+
return machineKey;
|
|
1850
|
+
} catch {
|
|
1851
|
+
return getMachineKey();
|
|
1846
1852
|
}
|
|
1847
|
-
const outputContent = outputLinesArr.join("\n");
|
|
1848
|
-
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
1849
|
-
return {
|
|
1850
|
-
content: outputContent,
|
|
1851
|
-
truncated: true,
|
|
1852
|
-
truncatedBy,
|
|
1853
|
-
totalLines,
|
|
1854
|
-
totalBytes,
|
|
1855
|
-
outputLines: outputLinesArr.length,
|
|
1856
|
-
outputBytes: finalOutputBytes,
|
|
1857
|
-
lastLinePartial,
|
|
1858
|
-
firstLineExceedsLimit: false,
|
|
1859
|
-
maxLines,
|
|
1860
|
-
maxBytes
|
|
1861
|
-
};
|
|
1862
1853
|
}
|
|
1863
|
-
function
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
return str;
|
|
1867
|
-
}
|
|
1868
|
-
let start = buf.length - maxBytes;
|
|
1869
|
-
while (start < buf.length && (buf[start] & 192) === 128) {
|
|
1870
|
-
start++;
|
|
1854
|
+
async function encrypt(plaintext) {
|
|
1855
|
+
if (!plaintext) {
|
|
1856
|
+
return "";
|
|
1871
1857
|
}
|
|
1872
|
-
|
|
1858
|
+
const key = await getEncryptionKey();
|
|
1859
|
+
const iv = randomBytes(IV_LENGTH);
|
|
1860
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
1861
|
+
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
1862
|
+
encrypted += cipher.final("base64");
|
|
1863
|
+
const authTag = cipher.getAuthTag();
|
|
1864
|
+
const result = `${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
1865
|
+
return result;
|
|
1873
1866
|
}
|
|
1874
|
-
function
|
|
1875
|
-
if (
|
|
1876
|
-
return
|
|
1867
|
+
async function decrypt(encryptedData) {
|
|
1868
|
+
if (!encryptedData) {
|
|
1869
|
+
return "";
|
|
1877
1870
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1871
|
+
try {
|
|
1872
|
+
const parts = encryptedData.split(":");
|
|
1873
|
+
if (parts.length !== 3) {
|
|
1874
|
+
throw new Error("Invalid encrypted data format");
|
|
1875
|
+
}
|
|
1876
|
+
const [ivBase64, authTagBase64, encrypted] = parts;
|
|
1877
|
+
const key = await getEncryptionKey();
|
|
1878
|
+
const iv = Buffer.from(ivBase64, "base64");
|
|
1879
|
+
const authTag = Buffer.from(authTagBase64, "base64");
|
|
1880
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
1881
|
+
decipher.setAuthTag(authTag);
|
|
1882
|
+
let decrypted = decipher.update(encrypted, "base64", "utf8");
|
|
1883
|
+
decrypted += decipher.final("utf8");
|
|
1884
|
+
return decrypted;
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
throw new Error(
|
|
1887
|
+
`Decryption failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function isEncrypted(value) {
|
|
1892
|
+
if (!value) {
|
|
1893
|
+
return false;
|
|
1894
|
+
}
|
|
1895
|
+
const parts = value.split(":");
|
|
1896
|
+
if (parts.length !== 3) {
|
|
1897
|
+
return false;
|
|
1898
|
+
}
|
|
1899
|
+
const base64Regex = /^[A-Za-z0-9+/]+=*$/;
|
|
1900
|
+
return parts.every((part) => base64Regex.test(part));
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// src/database/database.encryption.ts
|
|
1904
|
+
async function encryptProviderKey(key) {
|
|
1905
|
+
if (!key) {
|
|
1906
|
+
return "";
|
|
1907
|
+
}
|
|
1908
|
+
return await encrypt(key);
|
|
1909
|
+
}
|
|
1910
|
+
async function decryptProviderKey(encrypted) {
|
|
1911
|
+
if (!encrypted) {
|
|
1912
|
+
return "";
|
|
1913
|
+
}
|
|
1914
|
+
try {
|
|
1915
|
+
return await decrypt(encrypted);
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
console.error("Failed to decrypt provider key:", error);
|
|
1918
|
+
throw error;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
function isEncryptedKey(value) {
|
|
1922
|
+
return isEncrypted(value);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
// src/features/projects/project-env-vars.database.ts
|
|
1926
|
+
import { randomUUID } from "node:crypto";
|
|
1927
|
+
async function listProjectEnvVarNames(db, projectId) {
|
|
1928
|
+
const result = await db.execute(
|
|
1929
|
+
`SELECT name FROM project_env_vars WHERE projectId = ? ORDER BY name ASC`,
|
|
1930
|
+
[projectId]
|
|
1931
|
+
);
|
|
1932
|
+
return result.rows;
|
|
1933
|
+
}
|
|
1934
|
+
async function listProjectEnvVars(db, projectId) {
|
|
1935
|
+
const result = await db.execute(
|
|
1936
|
+
`SELECT id, projectId, name, encryptedValue, createdAt, updatedAt
|
|
1937
|
+
FROM project_env_vars WHERE projectId = ? ORDER BY name ASC`,
|
|
1938
|
+
[projectId]
|
|
1939
|
+
);
|
|
1940
|
+
return result.rows;
|
|
1941
|
+
}
|
|
1942
|
+
async function upsertProjectEnvVar(db, projectId, name, value) {
|
|
1943
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1944
|
+
const encryptedValue = await encryptProviderKey(value);
|
|
1945
|
+
const existing = await db.execute(
|
|
1946
|
+
`SELECT id FROM project_env_vars WHERE projectId = ? AND name = ?`,
|
|
1947
|
+
[projectId, name]
|
|
1948
|
+
);
|
|
1949
|
+
if (existing.rows.length > 0) {
|
|
1950
|
+
await db.execute(
|
|
1951
|
+
`UPDATE project_env_vars SET encryptedValue = ?, updatedAt = ? WHERE projectId = ? AND name = ?`,
|
|
1952
|
+
[encryptedValue, now, projectId, name]
|
|
1953
|
+
);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
await db.execute(
|
|
1957
|
+
`INSERT INTO project_env_vars (id, projectId, name, encryptedValue, createdAt, updatedAt)
|
|
1958
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
1959
|
+
[randomUUID(), projectId, name, encryptedValue, now, now]
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
async function deleteProjectEnvVarsNotIn(db, projectId, names) {
|
|
1963
|
+
if (names.length === 0) {
|
|
1964
|
+
await db.execute(`DELETE FROM project_env_vars WHERE projectId = ?`, [projectId]);
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
const placeholders = names.map(() => "?").join(", ");
|
|
1968
|
+
await db.execute(
|
|
1969
|
+
`DELETE FROM project_env_vars WHERE projectId = ? AND name NOT IN (${placeholders})`,
|
|
1970
|
+
[projectId, ...names]
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// src/features/projects/project-env-vars.ts
|
|
1975
|
+
function isValidProjectEnvVarName(name) {
|
|
1976
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(name);
|
|
1977
|
+
}
|
|
1978
|
+
async function getDecryptedProjectEnvVars(projectId) {
|
|
1979
|
+
const db = await getDatabase();
|
|
1980
|
+
const rows = await listProjectEnvVars(db, projectId);
|
|
1981
|
+
const env = {};
|
|
1982
|
+
for (const row of rows) {
|
|
1983
|
+
env[row.name] = await decryptProviderKey(row.encryptedValue);
|
|
1984
|
+
}
|
|
1985
|
+
return env;
|
|
1986
|
+
}
|
|
1987
|
+
function mergeEnvVars(baseEnv, projectEnv) {
|
|
1988
|
+
if (!projectEnv || Object.keys(projectEnv).length === 0) {
|
|
1989
|
+
return baseEnv;
|
|
1990
|
+
}
|
|
1991
|
+
return { ...baseEnv, ...projectEnv };
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// src/features/shell-permissions/command-pattern.ts
|
|
1995
|
+
var ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
1996
|
+
function stripLeadingEnvAssignments(command) {
|
|
1997
|
+
let remaining = command.trim();
|
|
1998
|
+
while (remaining.length > 0) {
|
|
1999
|
+
const tokenMatch = remaining.match(/^(\S+)\s*/);
|
|
2000
|
+
if (!tokenMatch) {
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
const token = tokenMatch[1];
|
|
2004
|
+
if (!ENV_ASSIGNMENT_PATTERN.test(token)) {
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
2007
|
+
remaining = remaining.slice(tokenMatch[0].length).trimStart();
|
|
2008
|
+
}
|
|
2009
|
+
return remaining;
|
|
2010
|
+
}
|
|
2011
|
+
function tokenizeCommand(command) {
|
|
2012
|
+
const tokens = [];
|
|
2013
|
+
let current = "";
|
|
2014
|
+
let quote = null;
|
|
2015
|
+
let escaped = false;
|
|
2016
|
+
for (const char of command) {
|
|
2017
|
+
if (escaped) {
|
|
2018
|
+
current += char;
|
|
2019
|
+
escaped = false;
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
if (char === "\\") {
|
|
2023
|
+
escaped = true;
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
if (quote) {
|
|
2027
|
+
if (char === quote) {
|
|
2028
|
+
quote = null;
|
|
2029
|
+
} else {
|
|
2030
|
+
current += char;
|
|
2031
|
+
}
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (char === "'" || char === '"') {
|
|
2035
|
+
quote = char;
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (/\s/.test(char)) {
|
|
2039
|
+
if (current.length > 0) {
|
|
2040
|
+
tokens.push(current);
|
|
2041
|
+
current = "";
|
|
2042
|
+
}
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
current += char;
|
|
2046
|
+
}
|
|
2047
|
+
if (current.length > 0) {
|
|
2048
|
+
tokens.push(current);
|
|
2049
|
+
}
|
|
2050
|
+
return tokens;
|
|
2051
|
+
}
|
|
2052
|
+
function getCommandTokens(command) {
|
|
2053
|
+
return tokenizeCommand(stripLeadingEnvAssignments(command));
|
|
2054
|
+
}
|
|
2055
|
+
function buildWildcardPattern(tokens, wildcardTokenCount) {
|
|
2056
|
+
const prefix = tokens.slice(0, wildcardTokenCount);
|
|
2057
|
+
if (prefix.length === 0) {
|
|
2058
|
+
return commandToPatternString(tokens);
|
|
2059
|
+
}
|
|
2060
|
+
return `${prefix.join(" ")} *`;
|
|
2061
|
+
}
|
|
2062
|
+
function commandToPatternString(tokens) {
|
|
2063
|
+
return tokens.join(" ");
|
|
2064
|
+
}
|
|
2065
|
+
function buildWildcardOptions(command) {
|
|
2066
|
+
const tokens = getCommandTokens(command);
|
|
2067
|
+
const options = [];
|
|
2068
|
+
if (tokens.length >= 1) {
|
|
2069
|
+
const oneTokenPattern = buildWildcardPattern(tokens, 1);
|
|
2070
|
+
options.push(
|
|
2071
|
+
{
|
|
2072
|
+
label: `Allow ${oneTokenPattern} (this session)`,
|
|
2073
|
+
pattern: oneTokenPattern,
|
|
2074
|
+
scope: "session"
|
|
2075
|
+
},
|
|
2076
|
+
{
|
|
2077
|
+
label: `Allow ${oneTokenPattern} (always for this project)`,
|
|
2078
|
+
pattern: oneTokenPattern,
|
|
2079
|
+
scope: "project"
|
|
2080
|
+
}
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
if (tokens.length >= 2) {
|
|
2084
|
+
const twoTokenPattern = buildWildcardPattern(tokens, 2);
|
|
2085
|
+
options.push(
|
|
2086
|
+
{
|
|
2087
|
+
label: `Allow ${twoTokenPattern} (this session)`,
|
|
2088
|
+
pattern: twoTokenPattern,
|
|
2089
|
+
scope: "session"
|
|
2090
|
+
},
|
|
2091
|
+
{
|
|
2092
|
+
label: `Allow ${twoTokenPattern} (always for this project)`,
|
|
2093
|
+
pattern: twoTokenPattern,
|
|
2094
|
+
scope: "project"
|
|
2095
|
+
}
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
return options;
|
|
2099
|
+
}
|
|
2100
|
+
function parsePattern(pattern) {
|
|
2101
|
+
const trimmed = pattern.trim();
|
|
2102
|
+
if (trimmed.endsWith(" *")) {
|
|
2103
|
+
return {
|
|
2104
|
+
tokens: tokenizeCommand(trimmed.slice(0, -2).trim()),
|
|
2105
|
+
hasWildcard: true
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
return {
|
|
2109
|
+
tokens: tokenizeCommand(trimmed),
|
|
2110
|
+
hasWildcard: false
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
function commandMatchesPattern(command, pattern) {
|
|
2114
|
+
const commandTokens = getCommandTokens(command);
|
|
2115
|
+
const parsedPattern = parsePattern(pattern);
|
|
2116
|
+
if (!parsedPattern.hasWildcard) {
|
|
2117
|
+
return command.trim() === pattern.trim();
|
|
2118
|
+
}
|
|
2119
|
+
if (parsedPattern.tokens.length === 0) {
|
|
2120
|
+
return false;
|
|
2121
|
+
}
|
|
2122
|
+
if (commandTokens.length < parsedPattern.tokens.length) {
|
|
2123
|
+
return false;
|
|
2124
|
+
}
|
|
2125
|
+
for (let index = 0; index < parsedPattern.tokens.length; index++) {
|
|
2126
|
+
if (commandTokens[index] !== parsedPattern.tokens[index]) {
|
|
2127
|
+
return false;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return true;
|
|
2131
|
+
}
|
|
2132
|
+
function commandMatchesAnyPattern(command, patterns) {
|
|
2133
|
+
return patterns.some((pattern) => commandMatchesPattern(command, pattern));
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// src/tools/path-utils.ts
|
|
2137
|
+
import { accessSync, constants } from "node:fs";
|
|
2138
|
+
import * as os from "node:os";
|
|
2139
|
+
import { isAbsolute, resolve as resolvePath, relative } from "node:path";
|
|
2140
|
+
var UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
2141
|
+
var NARROW_NO_BREAK_SPACE = "\u202F";
|
|
2142
|
+
function normalizeUnicodeSpaces(str) {
|
|
2143
|
+
return str.replace(UNICODE_SPACES, " ");
|
|
2144
|
+
}
|
|
2145
|
+
function tryMacOSScreenshotPath(filePath) {
|
|
2146
|
+
return filePath.replace(/ (AM|PM)\./g, `${NARROW_NO_BREAK_SPACE}$1.`);
|
|
2147
|
+
}
|
|
2148
|
+
function tryNFDVariant(filePath) {
|
|
2149
|
+
return filePath.normalize("NFD");
|
|
2150
|
+
}
|
|
2151
|
+
function tryCurlyQuoteVariant(filePath) {
|
|
2152
|
+
return filePath.replace(/'/g, "\u2019");
|
|
2153
|
+
}
|
|
2154
|
+
function fileExists(filePath) {
|
|
2155
|
+
try {
|
|
2156
|
+
accessSync(filePath, constants.F_OK);
|
|
2157
|
+
return true;
|
|
2158
|
+
} catch {
|
|
2159
|
+
return false;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
function normalizeAtPrefix(filePath) {
|
|
2163
|
+
return filePath.startsWith("@") ? filePath.slice(1) : filePath;
|
|
2164
|
+
}
|
|
2165
|
+
function expandPath(filePath) {
|
|
2166
|
+
const normalized = normalizeUnicodeSpaces(normalizeAtPrefix(filePath));
|
|
2167
|
+
if (normalized === "~") {
|
|
2168
|
+
return os.homedir();
|
|
2169
|
+
}
|
|
2170
|
+
if (normalized.startsWith("~/")) {
|
|
2171
|
+
return os.homedir() + normalized.slice(1);
|
|
2172
|
+
}
|
|
2173
|
+
return normalized;
|
|
2174
|
+
}
|
|
2175
|
+
function resolveToCwd(filePath, cwd) {
|
|
2176
|
+
const expanded = expandPath(filePath);
|
|
2177
|
+
if (isAbsolute(expanded)) {
|
|
2178
|
+
return expanded;
|
|
2179
|
+
}
|
|
2180
|
+
return resolvePath(cwd, expanded);
|
|
2181
|
+
}
|
|
2182
|
+
function validatePathWithinCwd(resolvedPath2, cwd) {
|
|
2183
|
+
const relativePath = relative(cwd, resolvedPath2);
|
|
2184
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2185
|
+
throw new Error(
|
|
2186
|
+
`Access denied: Path is outside the allowed directory. Requested: ${resolvedPath2}, Allowed root: ${cwd}`
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function resolveReadPath(filePath, cwd) {
|
|
2191
|
+
const resolved = resolveToCwd(filePath, cwd);
|
|
2192
|
+
if (fileExists(resolved)) {
|
|
2193
|
+
return resolved;
|
|
2194
|
+
}
|
|
2195
|
+
const amPmVariant = tryMacOSScreenshotPath(resolved);
|
|
2196
|
+
if (amPmVariant !== resolved && fileExists(amPmVariant)) {
|
|
2197
|
+
return amPmVariant;
|
|
2198
|
+
}
|
|
2199
|
+
const nfdVariant = tryNFDVariant(resolved);
|
|
2200
|
+
if (nfdVariant !== resolved && fileExists(nfdVariant)) {
|
|
2201
|
+
return nfdVariant;
|
|
2202
|
+
}
|
|
2203
|
+
const curlyVariant = tryCurlyQuoteVariant(resolved);
|
|
2204
|
+
if (curlyVariant !== resolved && fileExists(curlyVariant)) {
|
|
2205
|
+
return curlyVariant;
|
|
2206
|
+
}
|
|
2207
|
+
const nfdCurlyVariant = tryCurlyQuoteVariant(nfdVariant);
|
|
2208
|
+
if (nfdCurlyVariant !== resolved && fileExists(nfdCurlyVariant)) {
|
|
2209
|
+
return nfdCurlyVariant;
|
|
2210
|
+
}
|
|
2211
|
+
return resolved;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
// src/tools/bash-command-guard.ts
|
|
2215
|
+
var BROAD_SEARCH_ROOTS = /* @__PURE__ */ new Set(["/", "~"]);
|
|
2216
|
+
var FIND_OPTIONS_WITH_ARG = /* @__PURE__ */ new Set([
|
|
2217
|
+
"-name",
|
|
2218
|
+
"-iname",
|
|
2219
|
+
"-path",
|
|
2220
|
+
"-ipath",
|
|
2221
|
+
"-wholename",
|
|
2222
|
+
"-iwholename",
|
|
2223
|
+
"-perm",
|
|
2224
|
+
"-user",
|
|
2225
|
+
"-group",
|
|
2226
|
+
"-size",
|
|
2227
|
+
"-type",
|
|
2228
|
+
"-maxdepth",
|
|
2229
|
+
"-mindepth",
|
|
2230
|
+
"-mtime",
|
|
2231
|
+
"-atime",
|
|
2232
|
+
"-ctime",
|
|
2233
|
+
"-newer",
|
|
2234
|
+
"-anewer",
|
|
2235
|
+
"-cnewer"
|
|
2236
|
+
]);
|
|
2237
|
+
function getFindSearchRoot(tokens) {
|
|
2238
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
2239
|
+
const token = tokens[i];
|
|
2240
|
+
if (!token.startsWith("-")) {
|
|
2241
|
+
return token;
|
|
2242
|
+
}
|
|
2243
|
+
if (FIND_OPTIONS_WITH_ARG.has(token)) {
|
|
2244
|
+
i++;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return void 0;
|
|
2248
|
+
}
|
|
2249
|
+
function validateBashFilesystemSearch(command, cwd) {
|
|
2250
|
+
const tokens = getCommandTokens(command);
|
|
2251
|
+
if (tokens[0] !== "find") {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
const searchRoot = getFindSearchRoot(tokens);
|
|
2255
|
+
if (!searchRoot) {
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (BROAD_SEARCH_ROOTS.has(searchRoot) || searchRoot === "$HOME" || searchRoot.startsWith("~/")) {
|
|
2259
|
+
throw new Error(
|
|
2260
|
+
'Searching outside the project directory is not allowed. Use the find tool with a glob pattern (e.g. **/filename.png), or run find . -name "filename" from the project root.'
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
try {
|
|
2264
|
+
validatePathWithinCwd(resolveToCwd(searchRoot, cwd), cwd);
|
|
2265
|
+
} catch {
|
|
2266
|
+
throw new Error(
|
|
2267
|
+
"find search path must stay within the project directory. Use the find tool with a glob pattern instead."
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// src/tools/truncate.ts
|
|
2273
|
+
var DEFAULT_MAX_LINES = 2e3;
|
|
2274
|
+
var DEFAULT_MAX_BYTES = 50 * 1024;
|
|
2275
|
+
var GREP_MAX_LINE_LENGTH = 500;
|
|
2276
|
+
function formatSize(bytes) {
|
|
2277
|
+
if (bytes < 1024) {
|
|
2278
|
+
return `${bytes}B`;
|
|
2279
|
+
} else if (bytes < 1024 * 1024) {
|
|
2280
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
2281
|
+
} else {
|
|
2282
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
function truncateHead(content, options = {}) {
|
|
2286
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
2287
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
2288
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
2289
|
+
const lines = content.split("\n");
|
|
2290
|
+
const totalLines = lines.length;
|
|
2291
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
2292
|
+
return {
|
|
2293
|
+
content,
|
|
2294
|
+
truncated: false,
|
|
2295
|
+
truncatedBy: null,
|
|
2296
|
+
totalLines,
|
|
2297
|
+
totalBytes,
|
|
2298
|
+
outputLines: totalLines,
|
|
2299
|
+
outputBytes: totalBytes,
|
|
2300
|
+
lastLinePartial: false,
|
|
2301
|
+
firstLineExceedsLimit: false,
|
|
2302
|
+
maxLines,
|
|
2303
|
+
maxBytes
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
const firstLineBytes = Buffer.byteLength(lines[0], "utf-8");
|
|
2307
|
+
if (firstLineBytes > maxBytes) {
|
|
2308
|
+
return {
|
|
2309
|
+
content: "",
|
|
2310
|
+
truncated: true,
|
|
2311
|
+
truncatedBy: "bytes",
|
|
2312
|
+
totalLines,
|
|
2313
|
+
totalBytes,
|
|
2314
|
+
outputLines: 0,
|
|
2315
|
+
outputBytes: 0,
|
|
2316
|
+
lastLinePartial: false,
|
|
2317
|
+
firstLineExceedsLimit: true,
|
|
2318
|
+
maxLines,
|
|
2319
|
+
maxBytes
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
const outputLinesArr = [];
|
|
2323
|
+
let outputBytesCount = 0;
|
|
2324
|
+
let truncatedBy = "lines";
|
|
2325
|
+
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
2326
|
+
const line = lines[i];
|
|
2327
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (i > 0 ? 1 : 0);
|
|
2328
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
2329
|
+
truncatedBy = "bytes";
|
|
2330
|
+
break;
|
|
2331
|
+
}
|
|
2332
|
+
outputLinesArr.push(line);
|
|
2333
|
+
outputBytesCount += lineBytes;
|
|
2334
|
+
}
|
|
2335
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
2336
|
+
truncatedBy = "lines";
|
|
2337
|
+
}
|
|
2338
|
+
const outputContent = outputLinesArr.join("\n");
|
|
2339
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
2340
|
+
return {
|
|
2341
|
+
content: outputContent,
|
|
2342
|
+
truncated: true,
|
|
2343
|
+
truncatedBy,
|
|
2344
|
+
totalLines,
|
|
2345
|
+
totalBytes,
|
|
2346
|
+
outputLines: outputLinesArr.length,
|
|
2347
|
+
outputBytes: finalOutputBytes,
|
|
2348
|
+
lastLinePartial: false,
|
|
2349
|
+
firstLineExceedsLimit: false,
|
|
2350
|
+
maxLines,
|
|
2351
|
+
maxBytes
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
function truncateTail(content, options = {}) {
|
|
2355
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
2356
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
2357
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
2358
|
+
const lines = content.split("\n");
|
|
2359
|
+
const totalLines = lines.length;
|
|
2360
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
2361
|
+
return {
|
|
2362
|
+
content,
|
|
2363
|
+
truncated: false,
|
|
2364
|
+
truncatedBy: null,
|
|
2365
|
+
totalLines,
|
|
2366
|
+
totalBytes,
|
|
2367
|
+
outputLines: totalLines,
|
|
2368
|
+
outputBytes: totalBytes,
|
|
2369
|
+
lastLinePartial: false,
|
|
2370
|
+
firstLineExceedsLimit: false,
|
|
2371
|
+
maxLines,
|
|
2372
|
+
maxBytes
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
const outputLinesArr = [];
|
|
2376
|
+
let outputBytesCount = 0;
|
|
2377
|
+
let truncatedBy = "lines";
|
|
2378
|
+
let lastLinePartial = false;
|
|
2379
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
2380
|
+
const line = lines[i];
|
|
2381
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
|
|
2382
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
2383
|
+
truncatedBy = "bytes";
|
|
2384
|
+
if (outputLinesArr.length === 0) {
|
|
2385
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
2386
|
+
outputLinesArr.unshift(truncatedLine);
|
|
2387
|
+
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
2388
|
+
lastLinePartial = true;
|
|
2389
|
+
}
|
|
2390
|
+
break;
|
|
2391
|
+
}
|
|
2392
|
+
outputLinesArr.unshift(line);
|
|
2393
|
+
outputBytesCount += lineBytes;
|
|
2394
|
+
}
|
|
2395
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
2396
|
+
truncatedBy = "lines";
|
|
2397
|
+
}
|
|
2398
|
+
const outputContent = outputLinesArr.join("\n");
|
|
2399
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
2400
|
+
return {
|
|
2401
|
+
content: outputContent,
|
|
2402
|
+
truncated: true,
|
|
2403
|
+
truncatedBy,
|
|
2404
|
+
totalLines,
|
|
2405
|
+
totalBytes,
|
|
2406
|
+
outputLines: outputLinesArr.length,
|
|
2407
|
+
outputBytes: finalOutputBytes,
|
|
2408
|
+
lastLinePartial,
|
|
2409
|
+
firstLineExceedsLimit: false,
|
|
2410
|
+
maxLines,
|
|
2411
|
+
maxBytes
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
2415
|
+
const buf = Buffer.from(str, "utf-8");
|
|
2416
|
+
if (buf.length <= maxBytes) {
|
|
2417
|
+
return str;
|
|
2418
|
+
}
|
|
2419
|
+
let start = buf.length - maxBytes;
|
|
2420
|
+
while (start < buf.length && (buf[start] & 192) === 128) {
|
|
2421
|
+
start++;
|
|
2422
|
+
}
|
|
2423
|
+
return buf.slice(start).toString("utf-8");
|
|
2424
|
+
}
|
|
2425
|
+
function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
|
|
2426
|
+
if (line.length <= maxChars) {
|
|
2427
|
+
return { text: line, wasTruncated: false };
|
|
2428
|
+
}
|
|
2429
|
+
return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// src/tools/bash.ts
|
|
2433
|
+
function getTempFilePath() {
|
|
2434
|
+
return join3(tmpdir(), `tarsk-bash-${randomBytes2(8).toString("hex")}.log`);
|
|
2435
|
+
}
|
|
2436
|
+
var bashSchema = Type.Object({
|
|
2437
|
+
command: Type.String({ description: "Bash command to execute" }),
|
|
1887
2438
|
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional)" }))
|
|
1888
2439
|
});
|
|
1889
2440
|
var defaultBashOperations = {
|
|
@@ -1946,15 +2497,17 @@ function createBashTool(cwd, options) {
|
|
|
1946
2497
|
const ops = options?.operations ?? defaultBashOperations;
|
|
1947
2498
|
const commandPrefix = options?.commandPrefix;
|
|
1948
2499
|
const shellPermissionGate = options?.shellPermissionGate;
|
|
2500
|
+
const projectEnv = options?.projectEnv;
|
|
1949
2501
|
return {
|
|
1950
2502
|
name: "bash",
|
|
1951
2503
|
label: "bash",
|
|
1952
|
-
description: `Execute a bash command in the current working directory. Output truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB. Optionally provide a timeout in seconds.`,
|
|
2504
|
+
description: `Execute a bash command in the current working directory. Do not search the filesystem from / or ~; use the find tool for file discovery. Output truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB. Optionally provide a timeout in seconds.`,
|
|
1953
2505
|
parameters: bashSchema,
|
|
1954
2506
|
execute: async (toolCallId, { command, timeout }, signal, onUpdate) => {
|
|
1955
2507
|
const resolvedCommand = commandPrefix ? `${commandPrefix}
|
|
1956
2508
|
${command}` : command;
|
|
1957
2509
|
tarskDebugLog(`[ai] bash-start: ${resolvedCommand}`);
|
|
2510
|
+
validateBashFilesystemSearch(resolvedCommand, cwd);
|
|
1958
2511
|
if (shellPermissionGate) {
|
|
1959
2512
|
await shellPermissionGate.ensureAllowed(toolCallId, resolvedCommand, "bash", signal);
|
|
1960
2513
|
}
|
|
@@ -1991,7 +2544,12 @@ ${command}` : command;
|
|
|
1991
2544
|
});
|
|
1992
2545
|
}
|
|
1993
2546
|
};
|
|
1994
|
-
ops.exec(resolvedCommand, cwd, {
|
|
2547
|
+
ops.exec(resolvedCommand, cwd, {
|
|
2548
|
+
onData: handleData,
|
|
2549
|
+
signal,
|
|
2550
|
+
timeout,
|
|
2551
|
+
env: mergeEnvVars(getShellEnv(), projectEnv)
|
|
2552
|
+
}).then(({ exitCode }) => {
|
|
1995
2553
|
tarskDebugLog(`[ai] bash-end: ${resolvedCommand}`);
|
|
1996
2554
|
if (tempFileStream) tempFileStream.end();
|
|
1997
2555
|
const fullOutput = Buffer.concat(chunks).toString("utf-8");
|
|
@@ -2101,157 +2659,79 @@ function fuzzyFindText(content, oldText) {
|
|
|
2101
2659
|
found: true,
|
|
2102
2660
|
index: fuzzyIndex,
|
|
2103
2661
|
matchLength: fuzzyOldText.length,
|
|
2104
|
-
usedFuzzyMatch: true,
|
|
2105
|
-
contentForReplacement: fuzzyContent
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
function stripBom(content) {
|
|
2109
|
-
return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
|
|
2110
|
-
}
|
|
2111
|
-
function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
2112
|
-
const parts = Diff.diffLines(oldContent, newContent);
|
|
2113
|
-
const output = [];
|
|
2114
|
-
const oldLines = oldContent.split("\n");
|
|
2115
|
-
const newLines = newContent.split("\n");
|
|
2116
|
-
const maxLineNum = Math.max(oldLines.length, newLines.length);
|
|
2117
|
-
const lineNumWidth = String(maxLineNum).length;
|
|
2118
|
-
let oldLineNum = 1;
|
|
2119
|
-
let newLineNum = 1;
|
|
2120
|
-
let lastWasChange = false;
|
|
2121
|
-
let firstChangedLine;
|
|
2122
|
-
for (let i = 0; i < parts.length; i++) {
|
|
2123
|
-
const part = parts[i];
|
|
2124
|
-
const raw = part.value.split("\n");
|
|
2125
|
-
if (raw[raw.length - 1] === "") {
|
|
2126
|
-
raw.pop();
|
|
2127
|
-
}
|
|
2128
|
-
if (part.added || part.removed) {
|
|
2129
|
-
firstChangedLine ??= newLineNum;
|
|
2130
|
-
for (const line of raw) {
|
|
2131
|
-
if (part.added) {
|
|
2132
|
-
output.push(`+${String(newLineNum).padStart(lineNumWidth, " ")} ${line}`);
|
|
2133
|
-
newLineNum++;
|
|
2134
|
-
} else {
|
|
2135
|
-
output.push(`-${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`);
|
|
2136
|
-
oldLineNum++;
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
lastWasChange = true;
|
|
2140
|
-
} else {
|
|
2141
|
-
const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
|
|
2142
|
-
if (lastWasChange || nextPartIsChange) {
|
|
2143
|
-
let linesToShow = raw;
|
|
2144
|
-
let skipStart = 0;
|
|
2145
|
-
let skipEnd = 0;
|
|
2146
|
-
if (!lastWasChange) {
|
|
2147
|
-
skipStart = Math.max(0, raw.length - contextLines);
|
|
2148
|
-
linesToShow = raw.slice(skipStart);
|
|
2149
|
-
}
|
|
2150
|
-
if (!nextPartIsChange && linesToShow.length > contextLines) {
|
|
2151
|
-
skipEnd = linesToShow.length - contextLines;
|
|
2152
|
-
linesToShow = linesToShow.slice(0, contextLines);
|
|
2153
|
-
}
|
|
2154
|
-
if (skipStart > 0) {
|
|
2155
|
-
output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
|
|
2156
|
-
oldLineNum += skipStart;
|
|
2157
|
-
newLineNum += skipStart;
|
|
2158
|
-
}
|
|
2159
|
-
for (const line of linesToShow) {
|
|
2160
|
-
output.push(` ${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`);
|
|
2161
|
-
oldLineNum++;
|
|
2162
|
-
newLineNum++;
|
|
2163
|
-
}
|
|
2164
|
-
if (skipEnd > 0) {
|
|
2165
|
-
output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
|
|
2166
|
-
oldLineNum += skipEnd;
|
|
2167
|
-
newLineNum += skipEnd;
|
|
2168
|
-
}
|
|
2169
|
-
} else {
|
|
2170
|
-
oldLineNum += raw.length;
|
|
2171
|
-
newLineNum += raw.length;
|
|
2172
|
-
}
|
|
2173
|
-
lastWasChange = false;
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
return { diff: output.join("\n"), firstChangedLine };
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
// src/tools/path-utils.ts
|
|
2180
|
-
import { accessSync, constants } from "node:fs";
|
|
2181
|
-
import * as os from "node:os";
|
|
2182
|
-
import { isAbsolute, resolve as resolvePath, relative } from "node:path";
|
|
2183
|
-
var UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
2184
|
-
var NARROW_NO_BREAK_SPACE = "\u202F";
|
|
2185
|
-
function normalizeUnicodeSpaces(str) {
|
|
2186
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
2187
|
-
}
|
|
2188
|
-
function tryMacOSScreenshotPath(filePath) {
|
|
2189
|
-
return filePath.replace(/ (AM|PM)\./g, `${NARROW_NO_BREAK_SPACE}$1.`);
|
|
2190
|
-
}
|
|
2191
|
-
function tryNFDVariant(filePath) {
|
|
2192
|
-
return filePath.normalize("NFD");
|
|
2193
|
-
}
|
|
2194
|
-
function tryCurlyQuoteVariant(filePath) {
|
|
2195
|
-
return filePath.replace(/'/g, "\u2019");
|
|
2196
|
-
}
|
|
2197
|
-
function fileExists(filePath) {
|
|
2198
|
-
try {
|
|
2199
|
-
accessSync(filePath, constants.F_OK);
|
|
2200
|
-
return true;
|
|
2201
|
-
} catch {
|
|
2202
|
-
return false;
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
function normalizeAtPrefix(filePath) {
|
|
2206
|
-
return filePath.startsWith("@") ? filePath.slice(1) : filePath;
|
|
2207
|
-
}
|
|
2208
|
-
function expandPath(filePath) {
|
|
2209
|
-
const normalized = normalizeUnicodeSpaces(normalizeAtPrefix(filePath));
|
|
2210
|
-
if (normalized === "~") {
|
|
2211
|
-
return os.homedir();
|
|
2212
|
-
}
|
|
2213
|
-
if (normalized.startsWith("~/")) {
|
|
2214
|
-
return os.homedir() + normalized.slice(1);
|
|
2215
|
-
}
|
|
2216
|
-
return normalized;
|
|
2217
|
-
}
|
|
2218
|
-
function resolveToCwd(filePath, cwd) {
|
|
2219
|
-
const expanded = expandPath(filePath);
|
|
2220
|
-
if (isAbsolute(expanded)) {
|
|
2221
|
-
return expanded;
|
|
2222
|
-
}
|
|
2223
|
-
return resolvePath(cwd, expanded);
|
|
2224
|
-
}
|
|
2225
|
-
function validatePathWithinCwd(resolvedPath2, cwd) {
|
|
2226
|
-
const relativePath = relative(cwd, resolvedPath2);
|
|
2227
|
-
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2228
|
-
throw new Error(
|
|
2229
|
-
`Access denied: Path is outside the allowed directory. Requested: ${resolvedPath2}, Allowed root: ${cwd}`
|
|
2230
|
-
);
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
function resolveReadPath(filePath, cwd) {
|
|
2234
|
-
const resolved = resolveToCwd(filePath, cwd);
|
|
2235
|
-
if (fileExists(resolved)) {
|
|
2236
|
-
return resolved;
|
|
2237
|
-
}
|
|
2238
|
-
const amPmVariant = tryMacOSScreenshotPath(resolved);
|
|
2239
|
-
if (amPmVariant !== resolved && fileExists(amPmVariant)) {
|
|
2240
|
-
return amPmVariant;
|
|
2241
|
-
}
|
|
2242
|
-
const nfdVariant = tryNFDVariant(resolved);
|
|
2243
|
-
if (nfdVariant !== resolved && fileExists(nfdVariant)) {
|
|
2244
|
-
return nfdVariant;
|
|
2245
|
-
}
|
|
2246
|
-
const curlyVariant = tryCurlyQuoteVariant(resolved);
|
|
2247
|
-
if (curlyVariant !== resolved && fileExists(curlyVariant)) {
|
|
2248
|
-
return curlyVariant;
|
|
2249
|
-
}
|
|
2250
|
-
const nfdCurlyVariant = tryCurlyQuoteVariant(nfdVariant);
|
|
2251
|
-
if (nfdCurlyVariant !== resolved && fileExists(nfdCurlyVariant)) {
|
|
2252
|
-
return nfdCurlyVariant;
|
|
2662
|
+
usedFuzzyMatch: true,
|
|
2663
|
+
contentForReplacement: fuzzyContent
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
function stripBom(content) {
|
|
2667
|
+
return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
|
|
2668
|
+
}
|
|
2669
|
+
function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
2670
|
+
const parts = Diff.diffLines(oldContent, newContent);
|
|
2671
|
+
const output = [];
|
|
2672
|
+
const oldLines = oldContent.split("\n");
|
|
2673
|
+
const newLines = newContent.split("\n");
|
|
2674
|
+
const maxLineNum = Math.max(oldLines.length, newLines.length);
|
|
2675
|
+
const lineNumWidth = String(maxLineNum).length;
|
|
2676
|
+
let oldLineNum = 1;
|
|
2677
|
+
let newLineNum = 1;
|
|
2678
|
+
let lastWasChange = false;
|
|
2679
|
+
let firstChangedLine;
|
|
2680
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2681
|
+
const part = parts[i];
|
|
2682
|
+
const raw = part.value.split("\n");
|
|
2683
|
+
if (raw[raw.length - 1] === "") {
|
|
2684
|
+
raw.pop();
|
|
2685
|
+
}
|
|
2686
|
+
if (part.added || part.removed) {
|
|
2687
|
+
firstChangedLine ??= newLineNum;
|
|
2688
|
+
for (const line of raw) {
|
|
2689
|
+
if (part.added) {
|
|
2690
|
+
output.push(`+${String(newLineNum).padStart(lineNumWidth, " ")} ${line}`);
|
|
2691
|
+
newLineNum++;
|
|
2692
|
+
} else {
|
|
2693
|
+
output.push(`-${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`);
|
|
2694
|
+
oldLineNum++;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
lastWasChange = true;
|
|
2698
|
+
} else {
|
|
2699
|
+
const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
|
|
2700
|
+
if (lastWasChange || nextPartIsChange) {
|
|
2701
|
+
let linesToShow = raw;
|
|
2702
|
+
let skipStart = 0;
|
|
2703
|
+
let skipEnd = 0;
|
|
2704
|
+
if (!lastWasChange) {
|
|
2705
|
+
skipStart = Math.max(0, raw.length - contextLines);
|
|
2706
|
+
linesToShow = raw.slice(skipStart);
|
|
2707
|
+
}
|
|
2708
|
+
if (!nextPartIsChange && linesToShow.length > contextLines) {
|
|
2709
|
+
skipEnd = linesToShow.length - contextLines;
|
|
2710
|
+
linesToShow = linesToShow.slice(0, contextLines);
|
|
2711
|
+
}
|
|
2712
|
+
if (skipStart > 0) {
|
|
2713
|
+
output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
|
|
2714
|
+
oldLineNum += skipStart;
|
|
2715
|
+
newLineNum += skipStart;
|
|
2716
|
+
}
|
|
2717
|
+
for (const line of linesToShow) {
|
|
2718
|
+
output.push(` ${String(oldLineNum).padStart(lineNumWidth, " ")} ${line}`);
|
|
2719
|
+
oldLineNum++;
|
|
2720
|
+
newLineNum++;
|
|
2721
|
+
}
|
|
2722
|
+
if (skipEnd > 0) {
|
|
2723
|
+
output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
|
|
2724
|
+
oldLineNum += skipEnd;
|
|
2725
|
+
newLineNum += skipEnd;
|
|
2726
|
+
}
|
|
2727
|
+
} else {
|
|
2728
|
+
oldLineNum += raw.length;
|
|
2729
|
+
newLineNum += raw.length;
|
|
2730
|
+
}
|
|
2731
|
+
lastWasChange = false;
|
|
2732
|
+
}
|
|
2253
2733
|
}
|
|
2254
|
-
return
|
|
2734
|
+
return { diff: output.join("\n"), firstChangedLine };
|
|
2255
2735
|
}
|
|
2256
2736
|
|
|
2257
2737
|
// src/tools/tool-helpers.ts
|
|
@@ -4360,7 +4840,7 @@ import { Type as Type13 } from "@sinclair/typebox";
|
|
|
4360
4840
|
|
|
4361
4841
|
// src/features/todos/todos.database.ts
|
|
4362
4842
|
init_database();
|
|
4363
|
-
import { randomUUID } from "crypto";
|
|
4843
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4364
4844
|
async function fetchAllTodos(db, threadId) {
|
|
4365
4845
|
const result = await db.execute({
|
|
4366
4846
|
sql: `SELECT id, threadId, description, status, assignedTo, passes, createdAt, updatedAt
|
|
@@ -4376,7 +4856,7 @@ async function addTodos(threadId, descriptions, assignedTo = "agent") {
|
|
|
4376
4856
|
const db = await getDatabase();
|
|
4377
4857
|
const baseTime = Date.now();
|
|
4378
4858
|
for (let i = 0; i < descriptions.length; i++) {
|
|
4379
|
-
const id =
|
|
4859
|
+
const id = randomUUID2();
|
|
4380
4860
|
const ts = new Date(baseTime + i).toISOString();
|
|
4381
4861
|
await db.execute({
|
|
4382
4862
|
sql: `INSERT INTO todos (id, threadId, description, status, assignedTo, passes, createdAt, updatedAt)
|
|
@@ -4850,7 +5330,7 @@ function describeMCPServerConnection(config) {
|
|
|
4850
5330
|
}
|
|
4851
5331
|
return "(invalid config)";
|
|
4852
5332
|
}
|
|
4853
|
-
function createMCPClientTransport(config) {
|
|
5333
|
+
function createMCPClientTransport(config, additionalEnv) {
|
|
4854
5334
|
if (isRemoteMCPServer(config)) {
|
|
4855
5335
|
const url = new URL(config.url.trim());
|
|
4856
5336
|
const transport = resolveRemoteTransport(config, url);
|
|
@@ -4869,7 +5349,7 @@ function createMCPClientTransport(config) {
|
|
|
4869
5349
|
return new StdioClientTransport({
|
|
4870
5350
|
command: config.command,
|
|
4871
5351
|
args: config.args,
|
|
4872
|
-
env: config.env
|
|
5352
|
+
env: additionalEnv && config.env ? { ...additionalEnv, ...config.env } : additionalEnv ?? config.env
|
|
4873
5353
|
});
|
|
4874
5354
|
}
|
|
4875
5355
|
|
|
@@ -4946,6 +5426,10 @@ var MCPManagerImpl = class {
|
|
|
4946
5426
|
configCache = /* @__PURE__ */ new Map();
|
|
4947
5427
|
serverInstances = /* @__PURE__ */ new Map();
|
|
4948
5428
|
toolWrappers = /* @__PURE__ */ new Map();
|
|
5429
|
+
projectEnv;
|
|
5430
|
+
constructor(options) {
|
|
5431
|
+
this.projectEnv = options?.projectEnv;
|
|
5432
|
+
}
|
|
4949
5433
|
/**
|
|
4950
5434
|
* Load MCP configuration from file
|
|
4951
5435
|
*/
|
|
@@ -5078,7 +5562,7 @@ var MCPManagerImpl = class {
|
|
|
5078
5562
|
const connection = describeMCPServerConnection(serverConfig);
|
|
5079
5563
|
logMCPConnecting(serverName, connection, options);
|
|
5080
5564
|
tarskDebugLog(`[MCP] Connecting to server ${serverName}: ${connection}`);
|
|
5081
|
-
const transport = createMCPClientTransport(serverConfig);
|
|
5565
|
+
const transport = createMCPClientTransport(serverConfig, this.projectEnv);
|
|
5082
5566
|
const client = new Client(
|
|
5083
5567
|
{
|
|
5084
5568
|
name: "tarsk-cli",
|
|
@@ -5199,7 +5683,7 @@ var MCPManagerImpl = class {
|
|
|
5199
5683
|
|
|
5200
5684
|
// src/tools/mcp-tools.ts
|
|
5201
5685
|
async function createMCPTools(projectPath, options) {
|
|
5202
|
-
const mcpManager = new MCPManagerImpl();
|
|
5686
|
+
const mcpManager = new MCPManagerImpl({ projectEnv: options?.projectEnv });
|
|
5203
5687
|
const logOptions = options?.onMcpStatus ? { onStatus: options.onMcpStatus } : void 0;
|
|
5204
5688
|
try {
|
|
5205
5689
|
const mcpTools = await mcpManager.createToolWrappers(projectPath, logOptions);
|
|
@@ -5223,6 +5707,10 @@ async function createMCPTools(projectPath, options) {
|
|
|
5223
5707
|
return [];
|
|
5224
5708
|
}
|
|
5225
5709
|
}
|
|
5710
|
+
function clearMCPToolCache(projectPath) {
|
|
5711
|
+
const mcpManager = new MCPManagerImpl();
|
|
5712
|
+
mcpManager.clearCache(projectPath);
|
|
5713
|
+
}
|
|
5226
5714
|
|
|
5227
5715
|
// src/tools/generate-image.ts
|
|
5228
5716
|
import { Type as Type14 } from "@sinclair/typebox";
|
|
@@ -7584,7 +8072,7 @@ function buildCrossHostRedirectResult(redirectUrl) {
|
|
|
7584
8072
|
// src/features/web-fetch/web-fetch.client.ts
|
|
7585
8073
|
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
7586
8074
|
import { join as join11, extname as extname3 } from "path";
|
|
7587
|
-
import { createHash } from "crypto";
|
|
8075
|
+
import { createHash as createHash2 } from "crypto";
|
|
7588
8076
|
|
|
7589
8077
|
// src/features/web-fetch/web-fetch-cache.ts
|
|
7590
8078
|
var WebFetchCache = class {
|
|
@@ -7690,7 +8178,7 @@ async function readResponseBody(response, signal) {
|
|
|
7690
8178
|
async function saveBinaryContent(body, contentType, url) {
|
|
7691
8179
|
const downloadsDir = join11(getDataDir(), "fetch-downloads");
|
|
7692
8180
|
await mkdir(downloadsDir, { recursive: true });
|
|
7693
|
-
const hash =
|
|
8181
|
+
const hash = createHash2("sha256").update(url).digest("hex").slice(0, 12);
|
|
7694
8182
|
let extension = extname3(new URL(url).pathname);
|
|
7695
8183
|
if (!extension && contentType) {
|
|
7696
8184
|
if (contentType.includes("pdf")) extension = ".pdf";
|
|
@@ -8218,7 +8706,7 @@ function isSessionDomainAllowed(projectId, hostname2) {
|
|
|
8218
8706
|
// src/features/web-fetch-permissions/web-fetch-permission.gate.ts
|
|
8219
8707
|
var WEB_FETCH_PERMISSION_SKIP_MESSAGE_PREFIX = "User declined to fetch from this domain:";
|
|
8220
8708
|
var pendingPermissions = /* @__PURE__ */ new Map();
|
|
8221
|
-
function
|
|
8709
|
+
function buildWildcardOptions2(hostname2) {
|
|
8222
8710
|
return [
|
|
8223
8711
|
{
|
|
8224
8712
|
label: `Always allow ${hostname2} (this project)`,
|
|
@@ -8271,7 +8759,7 @@ var WebFetchPermissionGate = class {
|
|
|
8271
8759
|
toolCallId,
|
|
8272
8760
|
hostname: normalized,
|
|
8273
8761
|
url,
|
|
8274
|
-
wildcardOptions:
|
|
8762
|
+
wildcardOptions: buildWildcardOptions2(normalized)
|
|
8275
8763
|
};
|
|
8276
8764
|
this.config.onPermissionRequired?.(request);
|
|
8277
8765
|
return new Promise((resolve6, reject) => {
|
|
@@ -8415,7 +8903,8 @@ function applyCodeSearchInvalidationHooks(tools, threadId) {
|
|
|
8415
8903
|
async function createCodingTools(cwd, options) {
|
|
8416
8904
|
const deferOption = options?.deferTools ?? true;
|
|
8417
8905
|
const shellPermissionGate = options?.shellPermissionGate;
|
|
8418
|
-
const
|
|
8906
|
+
const projectEnv = options?.projectId ? await getDecryptedProjectEnvVars(options.projectId) : void 0;
|
|
8907
|
+
const bashOptions = shellPermissionGate || projectEnv ? { ...options?.bash, shellPermissionGate, projectEnv } : options?.bash;
|
|
8419
8908
|
const coreTools = filterEnabledTools([
|
|
8420
8909
|
createReadTool(cwd, options?.read),
|
|
8421
8910
|
createBashTool(cwd, bashOptions),
|
|
@@ -8449,7 +8938,7 @@ async function createCodingTools(cwd, options) {
|
|
|
8449
8938
|
if (options?.loadMcpTools !== false && isToolEnabled("mcp")) {
|
|
8450
8939
|
try {
|
|
8451
8940
|
mcpTools = filterEnabledTools(
|
|
8452
|
-
await createMCPTools(cwd, { onMcpStatus: options?.onMcpStatus })
|
|
8941
|
+
await createMCPTools(cwd, { onMcpStatus: options?.onMcpStatus, projectEnv })
|
|
8453
8942
|
);
|
|
8454
8943
|
} catch (error) {
|
|
8455
8944
|
console.warn(`[Tools] Failed to load MCP tools for ${cwd}:`, error);
|
|
@@ -8586,6 +9075,16 @@ var EventQueue = class {
|
|
|
8586
9075
|
errorOccurred = false;
|
|
8587
9076
|
toolCallCount = 0;
|
|
8588
9077
|
lastErrorMessage;
|
|
9078
|
+
setLastErrorMessage(message) {
|
|
9079
|
+
if (typeof message !== "string") {
|
|
9080
|
+
return;
|
|
9081
|
+
}
|
|
9082
|
+
const trimmedMessage = message.trim();
|
|
9083
|
+
if (!trimmedMessage) {
|
|
9084
|
+
return;
|
|
9085
|
+
}
|
|
9086
|
+
this.lastErrorMessage = trimmedMessage;
|
|
9087
|
+
}
|
|
8589
9088
|
/**
|
|
8590
9089
|
* Processes Pi Agent events and transforms them into AgentEvent format
|
|
8591
9090
|
*/
|
|
@@ -8691,7 +9190,7 @@ var EventQueue = class {
|
|
|
8691
9190
|
const messages = event.messages;
|
|
8692
9191
|
const lastAssistant = [...messages].reverse().find((m) => "role" in m && m.role === "assistant");
|
|
8693
9192
|
if (lastAssistant && "errorMessage" in lastAssistant && typeof lastAssistant.errorMessage === "string") {
|
|
8694
|
-
this.
|
|
9193
|
+
this.setLastErrorMessage(lastAssistant.errorMessage);
|
|
8695
9194
|
}
|
|
8696
9195
|
if (lastAssistant) {
|
|
8697
9196
|
const extracted = extractTextFromAssistantMessage(lastAssistant);
|
|
@@ -9646,231 +10145,89 @@ async function loadPromptSections(threadPath, tools, skills, planMode, agents, d
|
|
|
9646
10145
|
}
|
|
9647
10146
|
function formatSkillsSection(skills) {
|
|
9648
10147
|
if (!skills || skills.length === 0) {
|
|
9649
|
-
return "";
|
|
9650
|
-
}
|
|
9651
|
-
let skillsSection = "\n\n# Available Skills\n\n";
|
|
9652
|
-
skillsSection += "The following skills are available for this session. Use them to enhance your capabilities:\n\n";
|
|
9653
|
-
for (const skill of skills) {
|
|
9654
|
-
skillsSection += `## ${skill.name}
|
|
9655
|
-
|
|
9656
|
-
`;
|
|
9657
|
-
skillsSection += `**Description**: ${skill.description}
|
|
9658
|
-
|
|
9659
|
-
`;
|
|
9660
|
-
if (skill.compatibility) {
|
|
9661
|
-
skillsSection += `**Compatibility**: ${skill.compatibility}
|
|
9662
|
-
|
|
9663
|
-
`;
|
|
9664
|
-
}
|
|
9665
|
-
skillsSection += skill.instructions;
|
|
9666
|
-
skillsSection += "\n\n---\n\n";
|
|
9667
|
-
}
|
|
9668
|
-
tarskDebugLog(
|
|
9669
|
-
`[ai] Injected ${skills.length} skill(s) into system prompt: ${skills.map((s) => s.name).join(", ")}`
|
|
9670
|
-
);
|
|
9671
|
-
return skillsSection;
|
|
9672
|
-
}
|
|
9673
|
-
function formatAgentsSection(agents) {
|
|
9674
|
-
const invocable = agents.filter((a) => !a.disableModelInvocation);
|
|
9675
|
-
if (invocable.length === 0) return "";
|
|
9676
|
-
let section = "\n\n# Available Agents\n\nUse the `agent` tool to delegate focused subtasks to these specialized agents. Each agent runs in an isolated context and returns its result.\n\n";
|
|
9677
|
-
for (const agent of invocable) {
|
|
9678
|
-
section += `## ${agent.name}
|
|
9679
|
-
|
|
9680
|
-
`;
|
|
9681
|
-
section += `**Description**: ${agent.description}
|
|
9682
|
-
`;
|
|
9683
|
-
section += `**Invoke as**: \`agentName: "${agent.name}"\`
|
|
9684
|
-
|
|
9685
|
-
`;
|
|
9686
|
-
}
|
|
9687
|
-
tarskDebugLog(
|
|
9688
|
-
`[ai] Injected ${invocable.length} agent(s) into system prompt: ${invocable.map((a) => a.name).join(", ")}`
|
|
9689
|
-
);
|
|
9690
|
-
return section;
|
|
9691
|
-
}
|
|
9692
|
-
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools, ralphMode, overrideOptions) {
|
|
9693
|
-
let prompt = resolveEffectiveSystemPrompt(buildDefaultPrompt(tools), overrideOptions);
|
|
9694
|
-
const agentsContent = loadDeveloperContext(threadPath);
|
|
9695
|
-
if (agentsContent) {
|
|
9696
|
-
prompt += "\n\n# Developer Context\n\n" + agentsContent;
|
|
9697
|
-
}
|
|
9698
|
-
const rulesSection = await loadRulesSection(threadPath);
|
|
9699
|
-
if (rulesSection) {
|
|
9700
|
-
prompt += rulesSection;
|
|
9701
|
-
}
|
|
9702
|
-
const projectAnalysisSection = loadProjectAnalysisSection(threadPath);
|
|
9703
|
-
if (projectAnalysisSection) {
|
|
9704
|
-
prompt += projectAnalysisSection;
|
|
9705
|
-
}
|
|
9706
|
-
const devServerSection = await loadDevServerSection(threadPath);
|
|
9707
|
-
if (devServerSection) {
|
|
9708
|
-
prompt += devServerSection;
|
|
9709
|
-
}
|
|
9710
|
-
if (planMode) {
|
|
9711
|
-
prompt += PLAN_MODE_INSTRUCTIONS;
|
|
9712
|
-
}
|
|
9713
|
-
if (ralphMode) {
|
|
9714
|
-
prompt += RALPH_MODE_INSTRUCTIONS;
|
|
9715
|
-
const progressContent = await readProgress(threadPath);
|
|
9716
|
-
if (progressContent.trim()) {
|
|
9717
|
-
prompt += "\n\n# Prior Learnings (from progress.txt)\n\n" + progressContent;
|
|
9718
|
-
}
|
|
9719
|
-
}
|
|
9720
|
-
const skillsSection = formatSkillsSection(skills ?? []);
|
|
9721
|
-
if (skillsSection) {
|
|
9722
|
-
prompt += skillsSection;
|
|
9723
|
-
}
|
|
9724
|
-
const agentsSection = formatAgentsSection(agents ?? []);
|
|
9725
|
-
if (agentsSection) {
|
|
9726
|
-
prompt += agentsSection;
|
|
9727
|
-
}
|
|
9728
|
-
if (deferredTools && deferredTools.size > 0) {
|
|
9729
|
-
prompt += formatDeferredToolsList(deferredTools);
|
|
9730
|
-
}
|
|
9731
|
-
return prompt;
|
|
9732
|
-
}
|
|
9733
|
-
|
|
9734
|
-
// src/features/shell-permissions/command-pattern.ts
|
|
9735
|
-
var ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
9736
|
-
function stripLeadingEnvAssignments(command) {
|
|
9737
|
-
let remaining = command.trim();
|
|
9738
|
-
while (remaining.length > 0) {
|
|
9739
|
-
const tokenMatch = remaining.match(/^(\S+)\s*/);
|
|
9740
|
-
if (!tokenMatch) {
|
|
9741
|
-
break;
|
|
9742
|
-
}
|
|
9743
|
-
const token = tokenMatch[1];
|
|
9744
|
-
if (!ENV_ASSIGNMENT_PATTERN.test(token)) {
|
|
9745
|
-
break;
|
|
9746
|
-
}
|
|
9747
|
-
remaining = remaining.slice(tokenMatch[0].length).trimStart();
|
|
9748
|
-
}
|
|
9749
|
-
return remaining;
|
|
9750
|
-
}
|
|
9751
|
-
function tokenizeCommand(command) {
|
|
9752
|
-
const tokens = [];
|
|
9753
|
-
let current = "";
|
|
9754
|
-
let quote = null;
|
|
9755
|
-
let escaped = false;
|
|
9756
|
-
for (const char of command) {
|
|
9757
|
-
if (escaped) {
|
|
9758
|
-
current += char;
|
|
9759
|
-
escaped = false;
|
|
9760
|
-
continue;
|
|
9761
|
-
}
|
|
9762
|
-
if (char === "\\") {
|
|
9763
|
-
escaped = true;
|
|
9764
|
-
continue;
|
|
9765
|
-
}
|
|
9766
|
-
if (quote) {
|
|
9767
|
-
if (char === quote) {
|
|
9768
|
-
quote = null;
|
|
9769
|
-
} else {
|
|
9770
|
-
current += char;
|
|
9771
|
-
}
|
|
9772
|
-
continue;
|
|
9773
|
-
}
|
|
9774
|
-
if (char === "'" || char === '"') {
|
|
9775
|
-
quote = char;
|
|
9776
|
-
continue;
|
|
9777
|
-
}
|
|
9778
|
-
if (/\s/.test(char)) {
|
|
9779
|
-
if (current.length > 0) {
|
|
9780
|
-
tokens.push(current);
|
|
9781
|
-
current = "";
|
|
9782
|
-
}
|
|
9783
|
-
continue;
|
|
9784
|
-
}
|
|
9785
|
-
current += char;
|
|
9786
|
-
}
|
|
9787
|
-
if (current.length > 0) {
|
|
9788
|
-
tokens.push(current);
|
|
9789
|
-
}
|
|
9790
|
-
return tokens;
|
|
9791
|
-
}
|
|
9792
|
-
function getCommandTokens(command) {
|
|
9793
|
-
return tokenizeCommand(stripLeadingEnvAssignments(command));
|
|
9794
|
-
}
|
|
9795
|
-
function buildWildcardPattern(tokens, wildcardTokenCount) {
|
|
9796
|
-
const prefix = tokens.slice(0, wildcardTokenCount);
|
|
9797
|
-
if (prefix.length === 0) {
|
|
9798
|
-
return commandToPatternString(tokens);
|
|
9799
|
-
}
|
|
9800
|
-
return `${prefix.join(" ")} *`;
|
|
9801
|
-
}
|
|
9802
|
-
function commandToPatternString(tokens) {
|
|
9803
|
-
return tokens.join(" ");
|
|
9804
|
-
}
|
|
9805
|
-
function buildWildcardOptions2(command) {
|
|
9806
|
-
const tokens = getCommandTokens(command);
|
|
9807
|
-
const options = [];
|
|
9808
|
-
if (tokens.length >= 1) {
|
|
9809
|
-
const oneTokenPattern = buildWildcardPattern(tokens, 1);
|
|
9810
|
-
options.push(
|
|
9811
|
-
{
|
|
9812
|
-
label: `Allow ${oneTokenPattern} (this session)`,
|
|
9813
|
-
pattern: oneTokenPattern,
|
|
9814
|
-
scope: "session"
|
|
9815
|
-
},
|
|
9816
|
-
{
|
|
9817
|
-
label: `Allow ${oneTokenPattern} (always for this project)`,
|
|
9818
|
-
pattern: oneTokenPattern,
|
|
9819
|
-
scope: "project"
|
|
9820
|
-
}
|
|
9821
|
-
);
|
|
10148
|
+
return "";
|
|
9822
10149
|
}
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
10150
|
+
let skillsSection = "\n\n# Available Skills\n\n";
|
|
10151
|
+
skillsSection += "The following skills are available for this session. Use them to enhance your capabilities:\n\n";
|
|
10152
|
+
for (const skill of skills) {
|
|
10153
|
+
skillsSection += `## ${skill.name}
|
|
10154
|
+
|
|
10155
|
+
`;
|
|
10156
|
+
skillsSection += `**Description**: ${skill.description}
|
|
10157
|
+
|
|
10158
|
+
`;
|
|
10159
|
+
if (skill.compatibility) {
|
|
10160
|
+
skillsSection += `**Compatibility**: ${skill.compatibility}
|
|
10161
|
+
|
|
10162
|
+
`;
|
|
10163
|
+
}
|
|
10164
|
+
skillsSection += skill.instructions;
|
|
10165
|
+
skillsSection += "\n\n---\n\n";
|
|
9837
10166
|
}
|
|
9838
|
-
|
|
10167
|
+
tarskDebugLog(
|
|
10168
|
+
`[ai] Injected ${skills.length} skill(s) into system prompt: ${skills.map((s) => s.name).join(", ")}`
|
|
10169
|
+
);
|
|
10170
|
+
return skillsSection;
|
|
9839
10171
|
}
|
|
9840
|
-
function
|
|
9841
|
-
const
|
|
9842
|
-
if (
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
10172
|
+
function formatAgentsSection(agents) {
|
|
10173
|
+
const invocable = agents.filter((a) => !a.disableModelInvocation);
|
|
10174
|
+
if (invocable.length === 0) return "";
|
|
10175
|
+
let section = "\n\n# Available Agents\n\nUse the `agent` tool to delegate focused subtasks to these specialized agents. Each agent runs in an isolated context and returns its result.\n\n";
|
|
10176
|
+
for (const agent of invocable) {
|
|
10177
|
+
section += `## ${agent.name}
|
|
10178
|
+
|
|
10179
|
+
`;
|
|
10180
|
+
section += `**Description**: ${agent.description}
|
|
10181
|
+
`;
|
|
10182
|
+
section += `**Invoke as**: \`agentName: "${agent.name}"\`
|
|
10183
|
+
|
|
10184
|
+
`;
|
|
9847
10185
|
}
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
10186
|
+
tarskDebugLog(
|
|
10187
|
+
`[ai] Injected ${invocable.length} agent(s) into system prompt: ${invocable.map((a) => a.name).join(", ")}`
|
|
10188
|
+
);
|
|
10189
|
+
return section;
|
|
9852
10190
|
}
|
|
9853
|
-
function
|
|
9854
|
-
|
|
9855
|
-
const
|
|
9856
|
-
if (
|
|
9857
|
-
|
|
10191
|
+
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools, ralphMode, overrideOptions) {
|
|
10192
|
+
let prompt = resolveEffectiveSystemPrompt(buildDefaultPrompt(tools), overrideOptions);
|
|
10193
|
+
const agentsContent = loadDeveloperContext(threadPath);
|
|
10194
|
+
if (agentsContent) {
|
|
10195
|
+
prompt += "\n\n# Developer Context\n\n" + agentsContent;
|
|
9858
10196
|
}
|
|
9859
|
-
|
|
9860
|
-
|
|
10197
|
+
const rulesSection = await loadRulesSection(threadPath);
|
|
10198
|
+
if (rulesSection) {
|
|
10199
|
+
prompt += rulesSection;
|
|
9861
10200
|
}
|
|
9862
|
-
|
|
9863
|
-
|
|
10201
|
+
const projectAnalysisSection = loadProjectAnalysisSection(threadPath);
|
|
10202
|
+
if (projectAnalysisSection) {
|
|
10203
|
+
prompt += projectAnalysisSection;
|
|
9864
10204
|
}
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
10205
|
+
const devServerSection = await loadDevServerSection(threadPath);
|
|
10206
|
+
if (devServerSection) {
|
|
10207
|
+
prompt += devServerSection;
|
|
10208
|
+
}
|
|
10209
|
+
if (planMode) {
|
|
10210
|
+
prompt += PLAN_MODE_INSTRUCTIONS;
|
|
10211
|
+
}
|
|
10212
|
+
if (ralphMode) {
|
|
10213
|
+
prompt += RALPH_MODE_INSTRUCTIONS;
|
|
10214
|
+
const progressContent = await readProgress(threadPath);
|
|
10215
|
+
if (progressContent.trim()) {
|
|
10216
|
+
prompt += "\n\n# Prior Learnings (from progress.txt)\n\n" + progressContent;
|
|
9868
10217
|
}
|
|
9869
10218
|
}
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
10219
|
+
const skillsSection = formatSkillsSection(skills ?? []);
|
|
10220
|
+
if (skillsSection) {
|
|
10221
|
+
prompt += skillsSection;
|
|
10222
|
+
}
|
|
10223
|
+
const agentsSection = formatAgentsSection(agents ?? []);
|
|
10224
|
+
if (agentsSection) {
|
|
10225
|
+
prompt += agentsSection;
|
|
10226
|
+
}
|
|
10227
|
+
if (deferredTools && deferredTools.size > 0) {
|
|
10228
|
+
prompt += formatDeferredToolsList(deferredTools);
|
|
10229
|
+
}
|
|
10230
|
+
return prompt;
|
|
9874
10231
|
}
|
|
9875
10232
|
|
|
9876
10233
|
// src/features/shell-permissions/shell-permission.store.ts
|
|
@@ -9946,7 +10303,7 @@ var ShellPermissionGate = class {
|
|
|
9946
10303
|
toolCallId,
|
|
9947
10304
|
command: command.trim(),
|
|
9948
10305
|
source,
|
|
9949
|
-
wildcardOptions:
|
|
10306
|
+
wildcardOptions: buildWildcardOptions(command)
|
|
9950
10307
|
};
|
|
9951
10308
|
this.config.onPermissionRequired?.(request);
|
|
9952
10309
|
return new Promise((resolve6, reject) => {
|
|
@@ -10219,6 +10576,7 @@ var PiExecutorImpl = class {
|
|
|
10219
10576
|
skills: context.skills,
|
|
10220
10577
|
threadId: context.threadId,
|
|
10221
10578
|
threadPath: cwd,
|
|
10579
|
+
projectId: context.projectId,
|
|
10222
10580
|
metadataManager: this.metadataManager,
|
|
10223
10581
|
shellPermissionGate,
|
|
10224
10582
|
webFetchPermissionGate
|
|
@@ -10368,6 +10726,7 @@ ${userPrompt}`;
|
|
|
10368
10726
|
eventQueue.notify();
|
|
10369
10727
|
}).catch((err) => {
|
|
10370
10728
|
const errMessage = err instanceof Error ? err.message : String(err);
|
|
10729
|
+
eventQueue.setLastErrorMessage(errMessage);
|
|
10371
10730
|
console.error(`[ai] Agent prompt failed:`, {
|
|
10372
10731
|
request: promptRequest,
|
|
10373
10732
|
error: serializeLogValue(err),
|
|
@@ -10428,7 +10787,7 @@ ${userPrompt}`;
|
|
|
10428
10787
|
console.log("[ai] \u26A0\uFE0F Agent ended with no text content and no tool calls.");
|
|
10429
10788
|
const upstreamError = eventQueue.lastErrorMessage ? { message: eventQueue.lastErrorMessage } : void 0;
|
|
10430
10789
|
const noResponseMessage = `The model ${model} from ${providerName} did not provide a response to your prompt. Check your API key and balance and try again.`;
|
|
10431
|
-
const content = upstreamError
|
|
10790
|
+
const content = upstreamError?.message ?? noResponseMessage;
|
|
10432
10791
|
console.error("[ai] Agent ended without text content:", {
|
|
10433
10792
|
request: promptRequest,
|
|
10434
10793
|
upstreamError,
|
|
@@ -10440,7 +10799,7 @@ ${userPrompt}`;
|
|
|
10440
10799
|
content,
|
|
10441
10800
|
error: {
|
|
10442
10801
|
code: "NO_CONTENT_NO_TOOLS",
|
|
10443
|
-
message:
|
|
10802
|
+
message: upstreamError?.message ?? noResponseMessage,
|
|
10444
10803
|
details: upstreamError
|
|
10445
10804
|
}
|
|
10446
10805
|
};
|
|
@@ -10610,7 +10969,7 @@ async function readEnvFile() {
|
|
|
10610
10969
|
import { Hono } from "hono";
|
|
10611
10970
|
|
|
10612
10971
|
// src/agent/agent-run-guard.ts
|
|
10613
|
-
import { randomUUID as
|
|
10972
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
10614
10973
|
var DEFAULT_LONG_RUNNING_TURN_LIMIT = 50;
|
|
10615
10974
|
var LONG_RUNNING_CONFIRMATION_QUESTION = "The agent has been working for a long time. Do you want to continue?";
|
|
10616
10975
|
var LONG_RUNNING_CONFIRMATION_OPTIONS = ["Yes", "No"];
|
|
@@ -10652,7 +11011,7 @@ function shouldPromptForLongRunningConfirmation(state) {
|
|
|
10652
11011
|
return state.turnCount >= state.maximumTurnCount;
|
|
10653
11012
|
}
|
|
10654
11013
|
function createLongRunningConfirmationRequest(maximumTurnCount) {
|
|
10655
|
-
const toolCallId = `long-running-confirmation-${
|
|
11014
|
+
const toolCallId = `long-running-confirmation-${randomUUID3()}`;
|
|
10656
11015
|
const event = {
|
|
10657
11016
|
type: "long_running_confirmation",
|
|
10658
11017
|
prompt: {
|
|
@@ -11028,7 +11387,7 @@ function createWebFetchPermissionRoutes() {
|
|
|
11028
11387
|
import { Hono as Hono4 } from "hono";
|
|
11029
11388
|
|
|
11030
11389
|
// src/features/chat/chat-post.route.ts
|
|
11031
|
-
import { randomUUID as
|
|
11390
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
11032
11391
|
|
|
11033
11392
|
// src/features/skills/skills.manager.ts
|
|
11034
11393
|
import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
|
|
@@ -11602,7 +11961,7 @@ import { join as join17 } from "path";
|
|
|
11602
11961
|
|
|
11603
11962
|
// src/features/project-todos/project-todos.database.ts
|
|
11604
11963
|
init_database();
|
|
11605
|
-
import { randomUUID as
|
|
11964
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
11606
11965
|
function serializeTodo(row) {
|
|
11607
11966
|
return {
|
|
11608
11967
|
...row,
|
|
@@ -11627,7 +11986,7 @@ async function getTodoById(db, todoId) {
|
|
|
11627
11986
|
}
|
|
11628
11987
|
async function insertTodo(projectId, title, description) {
|
|
11629
11988
|
const db = await getDatabase();
|
|
11630
|
-
const id =
|
|
11989
|
+
const id = randomUUID4();
|
|
11631
11990
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11632
11991
|
await db.execute({
|
|
11633
11992
|
sql: `INSERT INTO project_todos (id, projectId, title, description, status, working, createdAt, updatedAt)
|
|
@@ -11708,155 +12067,48 @@ async function getGitStatusCache(db, threadId) {
|
|
|
11708
12067
|
return null;
|
|
11709
12068
|
}
|
|
11710
12069
|
}
|
|
11711
|
-
async function setGitStatusCache(db, threadId, data) {
|
|
11712
|
-
await db.execute(
|
|
11713
|
-
"INSERT OR REPLACE INTO git_status_cache (threadId, data, cachedAt) VALUES (?, ?, ?)",
|
|
11714
|
-
[threadId, JSON.stringify(data), (/* @__PURE__ */ new Date()).toISOString()]
|
|
11715
|
-
);
|
|
11716
|
-
}
|
|
11717
|
-
async function getGitStatusCacheAnyAge(db, threadId) {
|
|
11718
|
-
const result = await db.execute("SELECT data FROM git_status_cache WHERE threadId = ?", [
|
|
11719
|
-
threadId
|
|
11720
|
-
]);
|
|
11721
|
-
const row = result.rows[0];
|
|
11722
|
-
if (!row) return null;
|
|
11723
|
-
try {
|
|
11724
|
-
return JSON.parse(row.data);
|
|
11725
|
-
} catch {
|
|
11726
|
-
return null;
|
|
11727
|
-
}
|
|
11728
|
-
}
|
|
11729
|
-
async function getGitStatusCacheBulk(db, threadIds) {
|
|
11730
|
-
if (threadIds.length === 0) return /* @__PURE__ */ new Map();
|
|
11731
|
-
const placeholders = threadIds.map(() => "?").join(", ");
|
|
11732
|
-
const result = await db.execute(
|
|
11733
|
-
`SELECT threadId, data FROM git_status_cache WHERE threadId IN (${placeholders})`,
|
|
11734
|
-
threadIds
|
|
11735
|
-
);
|
|
11736
|
-
const map = /* @__PURE__ */ new Map();
|
|
11737
|
-
for (const row of result.rows) {
|
|
11738
|
-
const r = row;
|
|
11739
|
-
try {
|
|
11740
|
-
map.set(r.threadId, JSON.parse(r.data));
|
|
11741
|
-
} catch {
|
|
11742
|
-
}
|
|
11743
|
-
}
|
|
11744
|
-
return map;
|
|
11745
|
-
}
|
|
11746
|
-
async function invalidateGitStatusCache(db, threadId) {
|
|
11747
|
-
await db.execute("DELETE FROM git_status_cache WHERE threadId = ?", [threadId]);
|
|
11748
|
-
}
|
|
11749
|
-
|
|
11750
|
-
// src/features/account/account-get-info.route.ts
|
|
11751
|
-
init_database();
|
|
11752
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
11753
|
-
|
|
11754
|
-
// src/core/crypto.ts
|
|
11755
|
-
init_database();
|
|
11756
|
-
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
|
|
11757
|
-
import { networkInterfaces, hostname } from "os";
|
|
11758
|
-
var ALGORITHM = "aes-256-gcm";
|
|
11759
|
-
var IV_LENGTH = 16;
|
|
11760
|
-
function getMachineKey() {
|
|
11761
|
-
try {
|
|
11762
|
-
const interfaces = networkInterfaces();
|
|
11763
|
-
let macAddress = "";
|
|
11764
|
-
for (const name of Object.keys(interfaces)) {
|
|
11765
|
-
const iface = interfaces[name];
|
|
11766
|
-
if (iface) {
|
|
11767
|
-
for (const addr of iface) {
|
|
11768
|
-
if (addr.mac && addr.mac !== "00:00:00:00:00:00" && !addr.internal) {
|
|
11769
|
-
macAddress = addr.mac;
|
|
11770
|
-
break;
|
|
11771
|
-
}
|
|
11772
|
-
}
|
|
11773
|
-
if (macAddress) break;
|
|
11774
|
-
}
|
|
11775
|
-
}
|
|
11776
|
-
const host = hostname();
|
|
11777
|
-
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11778
|
-
const machineId = `${macAddress}-${host}-${homeDir}`;
|
|
11779
|
-
return createHash2("sha256").update(machineId).digest();
|
|
11780
|
-
} catch {
|
|
11781
|
-
return Buffer.from("Invalid", "utf8");
|
|
11782
|
-
}
|
|
11783
|
-
}
|
|
11784
|
-
async function getEncryptionKey() {
|
|
11785
|
-
try {
|
|
11786
|
-
const db = await getDatabase();
|
|
11787
|
-
const result = await db.execute({
|
|
11788
|
-
sql: "SELECT value FROM state WHERE key = ?",
|
|
11789
|
-
args: ["encryption_key"]
|
|
11790
|
-
});
|
|
11791
|
-
if (result.rows.length > 0 && result.rows[0].value) {
|
|
11792
|
-
const storedValue = result.rows[0].value;
|
|
11793
|
-
try {
|
|
11794
|
-
const parsed = JSON.parse(storedValue);
|
|
11795
|
-
return Buffer.from(parsed, "base64");
|
|
11796
|
-
} catch {
|
|
11797
|
-
return Buffer.from(storedValue, "base64");
|
|
11798
|
-
}
|
|
11799
|
-
}
|
|
11800
|
-
const machineKey = getMachineKey();
|
|
11801
|
-
await db.execute({
|
|
11802
|
-
sql: "INSERT OR REPLACE INTO state (key, value) VALUES (?, ?)",
|
|
11803
|
-
args: ["encryption_key", machineKey.toString("base64")]
|
|
11804
|
-
});
|
|
11805
|
-
return machineKey;
|
|
11806
|
-
} catch {
|
|
11807
|
-
return getMachineKey();
|
|
11808
|
-
}
|
|
11809
|
-
}
|
|
11810
|
-
async function encrypt(plaintext) {
|
|
11811
|
-
if (!plaintext) {
|
|
11812
|
-
return "";
|
|
11813
|
-
}
|
|
11814
|
-
const key = await getEncryptionKey();
|
|
11815
|
-
const iv = randomBytes2(IV_LENGTH);
|
|
11816
|
-
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
11817
|
-
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
11818
|
-
encrypted += cipher.final("base64");
|
|
11819
|
-
const authTag = cipher.getAuthTag();
|
|
11820
|
-
const result = `${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
11821
|
-
return result;
|
|
11822
|
-
}
|
|
11823
|
-
async function decrypt(encryptedData) {
|
|
11824
|
-
if (!encryptedData) {
|
|
11825
|
-
return "";
|
|
11826
|
-
}
|
|
11827
|
-
try {
|
|
11828
|
-
const parts = encryptedData.split(":");
|
|
11829
|
-
if (parts.length !== 3) {
|
|
11830
|
-
throw new Error("Invalid encrypted data format");
|
|
11831
|
-
}
|
|
11832
|
-
const [ivBase64, authTagBase64, encrypted] = parts;
|
|
11833
|
-
const key = await getEncryptionKey();
|
|
11834
|
-
const iv = Buffer.from(ivBase64, "base64");
|
|
11835
|
-
const authTag = Buffer.from(authTagBase64, "base64");
|
|
11836
|
-
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
11837
|
-
decipher.setAuthTag(authTag);
|
|
11838
|
-
let decrypted = decipher.update(encrypted, "base64", "utf8");
|
|
11839
|
-
decrypted += decipher.final("utf8");
|
|
11840
|
-
return decrypted;
|
|
11841
|
-
} catch (error) {
|
|
11842
|
-
throw new Error(
|
|
11843
|
-
`Decryption failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
11844
|
-
);
|
|
11845
|
-
}
|
|
11846
|
-
}
|
|
11847
|
-
function isEncrypted(value) {
|
|
11848
|
-
if (!value) {
|
|
11849
|
-
return false;
|
|
12070
|
+
async function setGitStatusCache(db, threadId, data) {
|
|
12071
|
+
await db.execute(
|
|
12072
|
+
"INSERT OR REPLACE INTO git_status_cache (threadId, data, cachedAt) VALUES (?, ?, ?)",
|
|
12073
|
+
[threadId, JSON.stringify(data), (/* @__PURE__ */ new Date()).toISOString()]
|
|
12074
|
+
);
|
|
12075
|
+
}
|
|
12076
|
+
async function getGitStatusCacheAnyAge(db, threadId) {
|
|
12077
|
+
const result = await db.execute("SELECT data FROM git_status_cache WHERE threadId = ?", [
|
|
12078
|
+
threadId
|
|
12079
|
+
]);
|
|
12080
|
+
const row = result.rows[0];
|
|
12081
|
+
if (!row) return null;
|
|
12082
|
+
try {
|
|
12083
|
+
return JSON.parse(row.data);
|
|
12084
|
+
} catch {
|
|
12085
|
+
return null;
|
|
11850
12086
|
}
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
12087
|
+
}
|
|
12088
|
+
async function getGitStatusCacheBulk(db, threadIds) {
|
|
12089
|
+
if (threadIds.length === 0) return /* @__PURE__ */ new Map();
|
|
12090
|
+
const placeholders = threadIds.map(() => "?").join(", ");
|
|
12091
|
+
const result = await db.execute(
|
|
12092
|
+
`SELECT threadId, data FROM git_status_cache WHERE threadId IN (${placeholders})`,
|
|
12093
|
+
threadIds
|
|
12094
|
+
);
|
|
12095
|
+
const map = /* @__PURE__ */ new Map();
|
|
12096
|
+
for (const row of result.rows) {
|
|
12097
|
+
const r = row;
|
|
12098
|
+
try {
|
|
12099
|
+
map.set(r.threadId, JSON.parse(r.data));
|
|
12100
|
+
} catch {
|
|
12101
|
+
}
|
|
11854
12102
|
}
|
|
11855
|
-
|
|
11856
|
-
|
|
12103
|
+
return map;
|
|
12104
|
+
}
|
|
12105
|
+
async function invalidateGitStatusCache(db, threadId) {
|
|
12106
|
+
await db.execute("DELETE FROM git_status_cache WHERE threadId = ?", [threadId]);
|
|
11857
12107
|
}
|
|
11858
12108
|
|
|
11859
12109
|
// src/features/account/account-get-info.route.ts
|
|
12110
|
+
init_database();
|
|
12111
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
11860
12112
|
var KEY_BALANCE = "PromptBalance";
|
|
11861
12113
|
var KEY_VERIFICATION = "PromptBalanceVerification";
|
|
11862
12114
|
var KEY_PLAN = "Plan";
|
|
@@ -11902,7 +12154,7 @@ async function getOrCreateClientReferenceId() {
|
|
|
11902
12154
|
if (existing !== null && existing !== void 0) {
|
|
11903
12155
|
return existing;
|
|
11904
12156
|
}
|
|
11905
|
-
const id =
|
|
12157
|
+
const id = randomUUID5();
|
|
11906
12158
|
await setState(db, KEY_CLIENT_REFERENCE_ID, id);
|
|
11907
12159
|
return id;
|
|
11908
12160
|
}
|
|
@@ -12101,6 +12353,7 @@ async function deleteProject(db, id) {
|
|
|
12101
12353
|
);
|
|
12102
12354
|
await db.execute("DELETE FROM threads WHERE projectId = ?", [id]);
|
|
12103
12355
|
await db.execute("DELETE FROM project_todos WHERE projectId = ?", [id]);
|
|
12356
|
+
await db.execute("DELETE FROM project_env_vars WHERE projectId = ?", [id]);
|
|
12104
12357
|
await db.execute("DELETE FROM projects WHERE id = ?", [id]);
|
|
12105
12358
|
} finally {
|
|
12106
12359
|
await db.execute("PRAGMA foreign_keys = ON");
|
|
@@ -12318,7 +12571,7 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
12318
12571
|
}
|
|
12319
12572
|
let conversationId = thread.currentConversationId;
|
|
12320
12573
|
if (!conversationId) {
|
|
12321
|
-
conversationId =
|
|
12574
|
+
conversationId = randomUUID6();
|
|
12322
12575
|
const db2 = await getDatabase();
|
|
12323
12576
|
await db2.execute({
|
|
12324
12577
|
sql: "DELETE FROM todos WHERE threadId = ?",
|
|
@@ -12628,7 +12881,7 @@ ${result.output}`;
|
|
|
12628
12881
|
};
|
|
12629
12882
|
processingStateManager.pushEvent(threadId, iterationEvent);
|
|
12630
12883
|
yield iterationEvent;
|
|
12631
|
-
const newConversationId =
|
|
12884
|
+
const newConversationId = randomUUID6();
|
|
12632
12885
|
await threadManager.updateThread(threadId, {
|
|
12633
12886
|
currentConversationId: newConversationId
|
|
12634
12887
|
});
|
|
@@ -13061,7 +13314,7 @@ ${summary}`;
|
|
|
13061
13314
|
}
|
|
13062
13315
|
|
|
13063
13316
|
// src/features/chat/chat-delete.route.ts
|
|
13064
|
-
import { randomUUID as
|
|
13317
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
13065
13318
|
init_database();
|
|
13066
13319
|
async function deleteChat(c, threadManager) {
|
|
13067
13320
|
try {
|
|
@@ -13083,7 +13336,7 @@ async function deleteChat(c, threadManager) {
|
|
|
13083
13336
|
sql: "DELETE FROM todos WHERE threadId = ?",
|
|
13084
13337
|
args: [threadId]
|
|
13085
13338
|
});
|
|
13086
|
-
const newConversationId =
|
|
13339
|
+
const newConversationId = randomUUID7();
|
|
13087
13340
|
await threadManager.updateThread(threadId, { currentConversationId: newConversationId });
|
|
13088
13341
|
return successResponse(
|
|
13089
13342
|
c,
|
|
@@ -13207,10 +13460,10 @@ function createChatRoutes(threadManager, agentExecutor, conversationManager, pro
|
|
|
13207
13460
|
init_database();
|
|
13208
13461
|
|
|
13209
13462
|
// src/features/conversations/conversations.database.ts
|
|
13210
|
-
import { randomUUID as
|
|
13463
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
13211
13464
|
async function insertMessage(db, threadId, conversationId, message, model, attachments, planMode, imageMode, ralphMode, checkpointRef) {
|
|
13212
13465
|
try {
|
|
13213
|
-
const messageId =
|
|
13466
|
+
const messageId = randomUUID8();
|
|
13214
13467
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
13215
13468
|
await db.execute(
|
|
13216
13469
|
`
|
|
@@ -14717,28 +14970,6 @@ function deserializeThread(row) {
|
|
|
14717
14970
|
};
|
|
14718
14971
|
}
|
|
14719
14972
|
|
|
14720
|
-
// src/database/database.encryption.ts
|
|
14721
|
-
async function encryptProviderKey(key) {
|
|
14722
|
-
if (!key) {
|
|
14723
|
-
return "";
|
|
14724
|
-
}
|
|
14725
|
-
return await encrypt(key);
|
|
14726
|
-
}
|
|
14727
|
-
async function decryptProviderKey(encrypted) {
|
|
14728
|
-
if (!encrypted) {
|
|
14729
|
-
return "";
|
|
14730
|
-
}
|
|
14731
|
-
try {
|
|
14732
|
-
return await decrypt(encrypted);
|
|
14733
|
-
} catch (error) {
|
|
14734
|
-
console.error("Failed to decrypt provider key:", error);
|
|
14735
|
-
throw error;
|
|
14736
|
-
}
|
|
14737
|
-
}
|
|
14738
|
-
function isEncryptedKey(value) {
|
|
14739
|
-
return isEncrypted(value);
|
|
14740
|
-
}
|
|
14741
|
-
|
|
14742
14973
|
// src/features/metadata/metadata.manager.ts
|
|
14743
14974
|
init_tarsk_debug();
|
|
14744
14975
|
function normalizeProviderSlug(provider) {
|
|
@@ -16815,6 +17046,111 @@ async function handleGetSystemPrompt(c, projectManager) {
|
|
|
16815
17046
|
}
|
|
16816
17047
|
}
|
|
16817
17048
|
|
|
17049
|
+
// src/features/projects/projects-env-vars.route.ts
|
|
17050
|
+
init_database();
|
|
17051
|
+
async function handleGetProjectEnvVars(c, projectManager) {
|
|
17052
|
+
try {
|
|
17053
|
+
const projectId = c.req.param("id");
|
|
17054
|
+
if (!projectId) {
|
|
17055
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Project ID is required", 400);
|
|
17056
|
+
}
|
|
17057
|
+
const project = await projectManager.getProject(projectId);
|
|
17058
|
+
if (!project) {
|
|
17059
|
+
return errorResponse(c, ErrorCodes.PROJECT_NOT_FOUND, `Project not found: ${projectId}`, 404);
|
|
17060
|
+
}
|
|
17061
|
+
const db = await getDatabase();
|
|
17062
|
+
const rows = await listProjectEnvVarNames(db, projectId);
|
|
17063
|
+
const vars = rows.map((row) => ({
|
|
17064
|
+
name: row.name,
|
|
17065
|
+
hasValue: true
|
|
17066
|
+
}));
|
|
17067
|
+
return c.json({ vars });
|
|
17068
|
+
} catch (error) {
|
|
17069
|
+
return errorResponse(
|
|
17070
|
+
c,
|
|
17071
|
+
ErrorCodes.GET_PROJECT_ERROR,
|
|
17072
|
+
"Failed to get project environment variables",
|
|
17073
|
+
500,
|
|
17074
|
+
error instanceof Error ? error.message : String(error)
|
|
17075
|
+
);
|
|
17076
|
+
}
|
|
17077
|
+
}
|
|
17078
|
+
async function handleSyncProjectEnvVars(c, projectManager) {
|
|
17079
|
+
try {
|
|
17080
|
+
const projectId = c.req.param("id");
|
|
17081
|
+
if (!projectId) {
|
|
17082
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Project ID is required", 400);
|
|
17083
|
+
}
|
|
17084
|
+
const project = await projectManager.getProject(projectId);
|
|
17085
|
+
if (!project) {
|
|
17086
|
+
return errorResponse(c, ErrorCodes.PROJECT_NOT_FOUND, `Project not found: ${projectId}`, 404);
|
|
17087
|
+
}
|
|
17088
|
+
const body = await c.req.json();
|
|
17089
|
+
if (!body.vars || !Array.isArray(body.vars)) {
|
|
17090
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "vars array is required", 400);
|
|
17091
|
+
}
|
|
17092
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
17093
|
+
const namesToKeep = [];
|
|
17094
|
+
for (const entry of body.vars) {
|
|
17095
|
+
if (!entry || typeof entry.name !== "string") {
|
|
17096
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Each variable must have a name", 400);
|
|
17097
|
+
}
|
|
17098
|
+
const name = entry.name.trim();
|
|
17099
|
+
if (!name) {
|
|
17100
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Variable name cannot be empty", 400);
|
|
17101
|
+
}
|
|
17102
|
+
if (!isValidProjectEnvVarName(name)) {
|
|
17103
|
+
return errorResponse(
|
|
17104
|
+
c,
|
|
17105
|
+
ErrorCodes.INVALID_REQUEST,
|
|
17106
|
+
`Invalid environment variable name: ${name}`,
|
|
17107
|
+
400
|
|
17108
|
+
);
|
|
17109
|
+
}
|
|
17110
|
+
if (seenNames.has(name)) {
|
|
17111
|
+
return errorResponse(
|
|
17112
|
+
c,
|
|
17113
|
+
ErrorCodes.INVALID_REQUEST,
|
|
17114
|
+
`Duplicate environment variable name: ${name}`,
|
|
17115
|
+
400
|
|
17116
|
+
);
|
|
17117
|
+
}
|
|
17118
|
+
seenNames.add(name);
|
|
17119
|
+
namesToKeep.push(name);
|
|
17120
|
+
}
|
|
17121
|
+
const db = await getDatabase();
|
|
17122
|
+
const existingRows = await listProjectEnvVarNames(db, projectId);
|
|
17123
|
+
const existingNames = new Set(existingRows.map((row) => row.name));
|
|
17124
|
+
for (const entry of body.vars) {
|
|
17125
|
+
const name = entry.name.trim();
|
|
17126
|
+
const value = typeof entry.value === "string" ? entry.value : "";
|
|
17127
|
+
if (value) {
|
|
17128
|
+
await upsertProjectEnvVar(db, projectId, name, value);
|
|
17129
|
+
continue;
|
|
17130
|
+
}
|
|
17131
|
+
if (!existingNames.has(name)) {
|
|
17132
|
+
return errorResponse(
|
|
17133
|
+
c,
|
|
17134
|
+
ErrorCodes.INVALID_REQUEST,
|
|
17135
|
+
`Value is required for new environment variable: ${name}`,
|
|
17136
|
+
400
|
|
17137
|
+
);
|
|
17138
|
+
}
|
|
17139
|
+
}
|
|
17140
|
+
await deleteProjectEnvVarsNotIn(db, projectId, namesToKeep);
|
|
17141
|
+
clearMCPToolCache(project.path);
|
|
17142
|
+
return c.json({ success: true });
|
|
17143
|
+
} catch (error) {
|
|
17144
|
+
return errorResponse(
|
|
17145
|
+
c,
|
|
17146
|
+
ErrorCodes.UPDATE_PROJECT_ERROR,
|
|
17147
|
+
"Failed to save project environment variables",
|
|
17148
|
+
500,
|
|
17149
|
+
error instanceof Error ? error.message : String(error)
|
|
17150
|
+
);
|
|
17151
|
+
}
|
|
17152
|
+
}
|
|
17153
|
+
|
|
16818
17154
|
// src/features/projects/projects.routes.ts
|
|
16819
17155
|
function createProjectRoutes(projectManager, threadManager) {
|
|
16820
17156
|
const router = new Hono8();
|
|
@@ -16886,6 +17222,12 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
16886
17222
|
router.get("/:id/system-prompt", async (c) => {
|
|
16887
17223
|
return handleGetSystemPrompt(c, projectManager);
|
|
16888
17224
|
});
|
|
17225
|
+
router.get("/:id/env-vars", async (c) => {
|
|
17226
|
+
return handleGetProjectEnvVars(c, projectManager);
|
|
17227
|
+
});
|
|
17228
|
+
router.put("/:id/env-vars", async (c) => {
|
|
17229
|
+
return handleSyncProjectEnvVars(c, projectManager);
|
|
17230
|
+
});
|
|
16889
17231
|
return router;
|
|
16890
17232
|
}
|
|
16891
17233
|
|
|
@@ -17112,7 +17454,7 @@ var ProcessManager = class extends EventEmitter {
|
|
|
17112
17454
|
|
|
17113
17455
|
// src/features/projects/projects.creator.ts
|
|
17114
17456
|
init_utils();
|
|
17115
|
-
import { randomUUID as
|
|
17457
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
17116
17458
|
import { basename as basename2, join as join23, relative as relative5 } from "path";
|
|
17117
17459
|
import { access as access3, mkdir as mkdir4, rm as rm3, stat as stat3, readdir as readdir8 } from "fs/promises";
|
|
17118
17460
|
|
|
@@ -18298,16 +18640,25 @@ var ProjectCreator = class {
|
|
|
18298
18640
|
}
|
|
18299
18641
|
let initialThreadId;
|
|
18300
18642
|
try {
|
|
18301
|
-
const projectId =
|
|
18643
|
+
const projectId = randomUUID9();
|
|
18302
18644
|
const existingProjects = await this.metadataManager.loadProjects();
|
|
18303
18645
|
const existingNames = existingProjects.map((p) => p.name);
|
|
18304
18646
|
const projectName = this.ensureUniqueProjectName(
|
|
18305
18647
|
this.deriveProjectName(gitUrl),
|
|
18306
18648
|
existingNames
|
|
18307
18649
|
);
|
|
18308
|
-
initialThreadId =
|
|
18650
|
+
initialThreadId = randomUUID9();
|
|
18309
18651
|
const initialThreadTitle = generateRandomThreadName();
|
|
18310
|
-
const
|
|
18652
|
+
const baseFolderName = this.toFolderName(this.deriveProjectName(gitUrl));
|
|
18653
|
+
if (!baseFolderName) {
|
|
18654
|
+
yield {
|
|
18655
|
+
type: "error",
|
|
18656
|
+
message: "Git URL must produce a valid folder name"
|
|
18657
|
+
};
|
|
18658
|
+
return;
|
|
18659
|
+
}
|
|
18660
|
+
const uniqueFolderName = await this.ensureUniqueFolderName(baseFolderName);
|
|
18661
|
+
const firstThreadPath = join23(this.rootFolder, uniqueFolderName);
|
|
18311
18662
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18312
18663
|
yield {
|
|
18313
18664
|
type: "progress",
|
|
@@ -18445,8 +18796,8 @@ var ProjectCreator = class {
|
|
|
18445
18796
|
};
|
|
18446
18797
|
return;
|
|
18447
18798
|
}
|
|
18448
|
-
const projectId =
|
|
18449
|
-
const initialThreadId =
|
|
18799
|
+
const projectId = randomUUID9();
|
|
18800
|
+
const initialThreadId = randomUUID9();
|
|
18450
18801
|
const initialThreadTitle = generateRandomThreadName();
|
|
18451
18802
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18452
18803
|
try {
|
|
@@ -18557,20 +18908,14 @@ var ProjectCreator = class {
|
|
|
18557
18908
|
};
|
|
18558
18909
|
return;
|
|
18559
18910
|
}
|
|
18560
|
-
const projectId =
|
|
18561
|
-
const initialThreadId =
|
|
18911
|
+
const projectId = randomUUID9();
|
|
18912
|
+
const initialThreadId = randomUUID9();
|
|
18562
18913
|
const initialThreadTitle = generateRandomThreadName();
|
|
18563
18914
|
let folderPath;
|
|
18564
18915
|
let projectMetadataSaved = false;
|
|
18565
18916
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18566
18917
|
try {
|
|
18567
|
-
const
|
|
18568
|
-
const existingNames = existingProjects.map((p) => p.name);
|
|
18569
|
-
const uniqueProjectName = this.ensureUniqueProjectName(trimmedProjectName, existingNames);
|
|
18570
|
-
const uniqueFolderName = this.toFolderName(uniqueProjectName);
|
|
18571
|
-
if (!uniqueFolderName) {
|
|
18572
|
-
throw new Error("Unique project name must produce a valid folder name");
|
|
18573
|
-
}
|
|
18918
|
+
const uniqueFolderName = await this.ensureUniqueFolderName(folderName);
|
|
18574
18919
|
folderPath = join23(this.rootFolder, uniqueFolderName);
|
|
18575
18920
|
yield {
|
|
18576
18921
|
type: "progress",
|
|
@@ -18582,7 +18927,7 @@ var ProjectCreator = class {
|
|
|
18582
18927
|
await this.gitManager.initRepository(folderPath);
|
|
18583
18928
|
const project = {
|
|
18584
18929
|
id: projectId,
|
|
18585
|
-
name:
|
|
18930
|
+
name: trimmedProjectName,
|
|
18586
18931
|
gitUrl: "",
|
|
18587
18932
|
path: folderPath,
|
|
18588
18933
|
createdAt: /* @__PURE__ */ new Date(),
|
|
@@ -18607,7 +18952,7 @@ var ProjectCreator = class {
|
|
|
18607
18952
|
this.processingStateManager?.clearProcessing(initialThreadId);
|
|
18608
18953
|
yield {
|
|
18609
18954
|
type: "progress",
|
|
18610
|
-
message: `Folder project "${
|
|
18955
|
+
message: `Folder project "${trimmedProjectName}" created successfully`
|
|
18611
18956
|
};
|
|
18612
18957
|
yield {
|
|
18613
18958
|
type: "complete",
|
|
@@ -18663,6 +19008,23 @@ var ProjectCreator = class {
|
|
|
18663
19008
|
toFolderName(name) {
|
|
18664
19009
|
return name.trim().toLowerCase().replace(/[\\/]+/g, " ").replace(/[^a-z0-9._ -]+/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
18665
19010
|
}
|
|
19011
|
+
async folderExists(folderPath) {
|
|
19012
|
+
try {
|
|
19013
|
+
await stat3(folderPath);
|
|
19014
|
+
return true;
|
|
19015
|
+
} catch {
|
|
19016
|
+
return false;
|
|
19017
|
+
}
|
|
19018
|
+
}
|
|
19019
|
+
async ensureUniqueFolderName(baseFolderName) {
|
|
19020
|
+
let candidate = baseFolderName;
|
|
19021
|
+
let counter = 1;
|
|
19022
|
+
while (await this.folderExists(join23(this.rootFolder, candidate))) {
|
|
19023
|
+
candidate = `${baseFolderName}-${counter}`;
|
|
19024
|
+
counter++;
|
|
19025
|
+
}
|
|
19026
|
+
return candidate;
|
|
19027
|
+
}
|
|
18666
19028
|
async *createProjectFromFolder() {
|
|
18667
19029
|
await loadUtils();
|
|
18668
19030
|
if (!Utils) {
|
|
@@ -18689,8 +19051,8 @@ var ProjectCreator = class {
|
|
|
18689
19051
|
yield { type: "error", message: `Cannot access folder: ${folderPath}` };
|
|
18690
19052
|
return;
|
|
18691
19053
|
}
|
|
18692
|
-
const projectId =
|
|
18693
|
-
const initialThreadId =
|
|
19054
|
+
const projectId = randomUUID9();
|
|
19055
|
+
const initialThreadId = randomUUID9();
|
|
18694
19056
|
const initialThreadTitle = generateRandomThreadName();
|
|
18695
19057
|
this.processingStateManager?.setProcessing(initialThreadId);
|
|
18696
19058
|
try {
|
|
@@ -20729,7 +21091,7 @@ function createRuleRoutes(router, projectManager) {
|
|
|
20729
21091
|
|
|
20730
21092
|
// src/features/threads/threads.manager.ts
|
|
20731
21093
|
init_utils();
|
|
20732
|
-
import { randomUUID as
|
|
21094
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
20733
21095
|
import { join as join26 } from "path";
|
|
20734
21096
|
import { execSync as execSync3 } from "child_process";
|
|
20735
21097
|
import { rm as rm5, stat as stat4, mkdir as mkdir6 } from "fs/promises";
|
|
@@ -20796,7 +21158,7 @@ var ThreadManagerImpl = class {
|
|
|
20796
21158
|
} catch {
|
|
20797
21159
|
await mkdir6(project.path, { recursive: true });
|
|
20798
21160
|
}
|
|
20799
|
-
threadId =
|
|
21161
|
+
threadId = randomUUID10();
|
|
20800
21162
|
const threadPath = this.generateThreadPath(projectId, threadId);
|
|
20801
21163
|
const existingThreads = await this.metadataManager.loadThreads();
|
|
20802
21164
|
const existingTitles = new Set(
|
|
@@ -21354,7 +21716,7 @@ import { glob as glob2 } from "glob";
|
|
|
21354
21716
|
|
|
21355
21717
|
// src/features/project-scripts/project-scripts.database.ts
|
|
21356
21718
|
init_database();
|
|
21357
|
-
import { randomUUID as
|
|
21719
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
21358
21720
|
async function getScriptsByProject(db, projectId) {
|
|
21359
21721
|
const result = await db.execute({
|
|
21360
21722
|
sql: `SELECT id, projectId, workspace, name, command, friendlyName, updatedAt
|
|
@@ -21371,7 +21733,7 @@ async function upsertProjectScripts(projectId, scripts) {
|
|
|
21371
21733
|
sql: `INSERT OR REPLACE INTO project_scripts (id, projectId, workspace, name, command, friendlyName, updatedAt)
|
|
21372
21734
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
21373
21735
|
args: [
|
|
21374
|
-
|
|
21736
|
+
randomUUID11(),
|
|
21375
21737
|
projectId,
|
|
21376
21738
|
script.workspace,
|
|
21377
21739
|
script.name,
|
|
@@ -22153,22 +22515,7 @@ async function handleCreateExplorerFile(c, threadManager) {
|
|
|
22153
22515
|
);
|
|
22154
22516
|
}
|
|
22155
22517
|
}
|
|
22156
|
-
var MEDIA_MIME_TYPES =
|
|
22157
|
-
png: "image/png",
|
|
22158
|
-
jpg: "image/jpeg",
|
|
22159
|
-
jpeg: "image/jpeg",
|
|
22160
|
-
gif: "image/gif",
|
|
22161
|
-
webp: "image/webp",
|
|
22162
|
-
svg: "image/svg+xml",
|
|
22163
|
-
ico: "image/x-icon",
|
|
22164
|
-
bmp: "image/bmp",
|
|
22165
|
-
tiff: "image/tiff",
|
|
22166
|
-
tif: "image/tiff",
|
|
22167
|
-
mp4: "video/mp4",
|
|
22168
|
-
webm: "video/webm",
|
|
22169
|
-
mov: "video/quicktime",
|
|
22170
|
-
avi: "video/x-msvideo"
|
|
22171
|
-
};
|
|
22518
|
+
var MEDIA_MIME_TYPES = EXPLORER_MEDIA_MIME_TYPES;
|
|
22172
22519
|
async function handleGetExplorerMedia(c, threadManager) {
|
|
22173
22520
|
try {
|
|
22174
22521
|
const threadId = c.req.param("id");
|
|
@@ -22195,10 +22542,15 @@ async function handleGetExplorerMedia(c, threadManager) {
|
|
|
22195
22542
|
} catch {
|
|
22196
22543
|
return c.json({ error: { code: "NOT_FOUND", message: `File not found: ${filePath}` } }, 404);
|
|
22197
22544
|
}
|
|
22198
|
-
const ext = filePath
|
|
22545
|
+
const ext = getFileExtension(filePath);
|
|
22199
22546
|
const contentType = MEDIA_MIME_TYPES[ext] ?? "application/octet-stream";
|
|
22200
22547
|
const data = await readFile14(absPath);
|
|
22201
|
-
return new Response(data, {
|
|
22548
|
+
return new Response(data, {
|
|
22549
|
+
headers: {
|
|
22550
|
+
"Content-Type": contentType,
|
|
22551
|
+
"Cross-Origin-Resource-Policy": "cross-origin"
|
|
22552
|
+
}
|
|
22553
|
+
});
|
|
22202
22554
|
} catch (error) {
|
|
22203
22555
|
return errorResponse(
|
|
22204
22556
|
c,
|
|
@@ -22564,6 +22916,17 @@ Links to important documentation, tools, or references.
|
|
|
22564
22916
|
if (fileStat.isDirectory()) {
|
|
22565
22917
|
return c.json({ error: { code: "BAD_REQUEST", message: "Cannot read a directory" } }, 400);
|
|
22566
22918
|
}
|
|
22919
|
+
if (getExplorerFileMediaType(filePath)) {
|
|
22920
|
+
return c.json(
|
|
22921
|
+
{
|
|
22922
|
+
error: {
|
|
22923
|
+
code: "BAD_REQUEST",
|
|
22924
|
+
message: "Binary media files cannot be read as text. Use the explorer media endpoint."
|
|
22925
|
+
}
|
|
22926
|
+
},
|
|
22927
|
+
400
|
|
22928
|
+
);
|
|
22929
|
+
}
|
|
22567
22930
|
const content = await readFile15(absPath, "utf-8");
|
|
22568
22931
|
return c.json({ content, path: filePath });
|
|
22569
22932
|
} catch (error) {
|
|
@@ -25910,11 +26273,81 @@ function createProjectTodosRoutes(metadataManager) {
|
|
|
25910
26273
|
// src/features/account/account.routes.ts
|
|
25911
26274
|
import { Hono as Hono19 } from "hono";
|
|
25912
26275
|
|
|
26276
|
+
// src/core/network-addresses.ts
|
|
26277
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
26278
|
+
function isIPv4Address(family) {
|
|
26279
|
+
return family === "IPv4" || family === 4;
|
|
26280
|
+
}
|
|
26281
|
+
function getLocalNetworkAddresses() {
|
|
26282
|
+
const addresses = /* @__PURE__ */ new Set();
|
|
26283
|
+
for (const iface of Object.values(networkInterfaces2())) {
|
|
26284
|
+
if (!iface) {
|
|
26285
|
+
continue;
|
|
26286
|
+
}
|
|
26287
|
+
for (const config of iface) {
|
|
26288
|
+
if (config.internal || !isIPv4Address(config.family)) {
|
|
26289
|
+
continue;
|
|
26290
|
+
}
|
|
26291
|
+
addresses.add(config.address);
|
|
26292
|
+
}
|
|
26293
|
+
}
|
|
26294
|
+
return [...addresses].sort();
|
|
26295
|
+
}
|
|
26296
|
+
|
|
26297
|
+
// src/core/server-bind-mode-store.ts
|
|
26298
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync } from "fs";
|
|
26299
|
+
import { join as join32 } from "path";
|
|
26300
|
+
|
|
26301
|
+
// src/core/server-bind-mode.ts
|
|
26302
|
+
var DEFAULT_SERVER_BIND_MODE = "local";
|
|
26303
|
+
function normalizeServerBindMode(value) {
|
|
26304
|
+
return value === "network" ? "network" : "local";
|
|
26305
|
+
}
|
|
26306
|
+
function getServerBindHostname(mode) {
|
|
26307
|
+
return mode === "network" ? "0.0.0.0" : void 0;
|
|
26308
|
+
}
|
|
26309
|
+
|
|
26310
|
+
// src/core/server-bind-mode-store.ts
|
|
26311
|
+
var SERVER_BIND_MODE_FILE = join32(DATA_DIR, "server-bind-mode.json");
|
|
26312
|
+
function hasServerBindModeFile() {
|
|
26313
|
+
return existsSync23(SERVER_BIND_MODE_FILE);
|
|
26314
|
+
}
|
|
26315
|
+
function readServerBindMode() {
|
|
26316
|
+
try {
|
|
26317
|
+
if (!existsSync23(SERVER_BIND_MODE_FILE)) {
|
|
26318
|
+
return DEFAULT_SERVER_BIND_MODE;
|
|
26319
|
+
}
|
|
26320
|
+
const raw = readFileSync8(SERVER_BIND_MODE_FILE, "utf8");
|
|
26321
|
+
const parsed = JSON.parse(raw);
|
|
26322
|
+
return normalizeServerBindMode(parsed.serverBindMode);
|
|
26323
|
+
} catch {
|
|
26324
|
+
return DEFAULT_SERVER_BIND_MODE;
|
|
26325
|
+
}
|
|
26326
|
+
}
|
|
26327
|
+
function writeServerBindMode(mode) {
|
|
26328
|
+
mkdirSync2(DATA_DIR, { recursive: true });
|
|
26329
|
+
writeFileSync(
|
|
26330
|
+
SERVER_BIND_MODE_FILE,
|
|
26331
|
+
`${JSON.stringify({ serverBindMode: mode }, null, 2)}
|
|
26332
|
+
`,
|
|
26333
|
+
"utf8"
|
|
26334
|
+
);
|
|
26335
|
+
}
|
|
26336
|
+
|
|
25913
26337
|
// src/features/account/account-settings.route.ts
|
|
25914
26338
|
init_database();
|
|
25915
26339
|
var DEFAULT_MAXIMUM_TURN_COUNT = 50;
|
|
25916
26340
|
var KEY_MAXIMUM_TURN_COUNT = "MaximumTurnCount";
|
|
25917
26341
|
var KEY_USE_TOOLS_THROUGH_CODE = "UseToolsThroughCode";
|
|
26342
|
+
var LEGACY_KEY_SERVER_BIND_MODE = "ServerBindMode";
|
|
26343
|
+
function buildAccountSettingsResponse(rawMaximumTurnCount, rawUseToolsThroughCode, serverBindMode) {
|
|
26344
|
+
return {
|
|
26345
|
+
maximumTurnCount: normalizeMaximumTurnCount(rawMaximumTurnCount),
|
|
26346
|
+
useToolsThroughCode: rawUseToolsThroughCode !== false,
|
|
26347
|
+
serverBindMode,
|
|
26348
|
+
networkAddresses: serverBindMode === "network" ? getLocalNetworkAddresses() : []
|
|
26349
|
+
};
|
|
26350
|
+
}
|
|
25918
26351
|
function normalizeMaximumTurnCount(value) {
|
|
25919
26352
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
25920
26353
|
return DEFAULT_MAXIMUM_TURN_COUNT;
|
|
@@ -25925,14 +26358,27 @@ function normalizeMaximumTurnCount(value) {
|
|
|
25925
26358
|
}
|
|
25926
26359
|
return normalized;
|
|
25927
26360
|
}
|
|
26361
|
+
async function getServerBindModeSetting() {
|
|
26362
|
+
if (hasServerBindModeFile()) {
|
|
26363
|
+
return readServerBindMode();
|
|
26364
|
+
}
|
|
26365
|
+
const db = await getDatabase();
|
|
26366
|
+
const legacyValue = await getState(db, LEGACY_KEY_SERVER_BIND_MODE);
|
|
26367
|
+
if (legacyValue === null) {
|
|
26368
|
+
return DEFAULT_SERVER_BIND_MODE;
|
|
26369
|
+
}
|
|
26370
|
+
const migratedMode = normalizeServerBindMode(legacyValue);
|
|
26371
|
+
writeServerBindMode(migratedMode);
|
|
26372
|
+
return migratedMode;
|
|
26373
|
+
}
|
|
25928
26374
|
async function getAccountSettings(c) {
|
|
25929
26375
|
const db = await getDatabase();
|
|
25930
26376
|
const rawUseToolsThroughCode = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
|
|
25931
26377
|
const rawMaximumTurnCount = await getState(db, KEY_MAXIMUM_TURN_COUNT);
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
|
|
25935
|
-
|
|
26378
|
+
const serverBindMode = await getServerBindModeSetting();
|
|
26379
|
+
return c.json(
|
|
26380
|
+
buildAccountSettingsResponse(rawMaximumTurnCount, rawUseToolsThroughCode, serverBindMode)
|
|
26381
|
+
);
|
|
25936
26382
|
}
|
|
25937
26383
|
async function updateAccountSettings(c) {
|
|
25938
26384
|
const body = await c.req.json();
|
|
@@ -25943,12 +26389,15 @@ async function updateAccountSettings(c) {
|
|
|
25943
26389
|
if (body.maximumTurnCount !== void 0) {
|
|
25944
26390
|
await setState(db, KEY_MAXIMUM_TURN_COUNT, normalizeMaximumTurnCount(body.maximumTurnCount));
|
|
25945
26391
|
}
|
|
26392
|
+
if (body.serverBindMode !== void 0) {
|
|
26393
|
+
writeServerBindMode(normalizeServerBindMode(body.serverBindMode));
|
|
26394
|
+
}
|
|
25946
26395
|
const rawUseToolsThroughCode = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
|
|
25947
26396
|
const rawMaximumTurnCount = await getState(db, KEY_MAXIMUM_TURN_COUNT);
|
|
25948
|
-
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
|
|
26397
|
+
const serverBindMode = readServerBindMode();
|
|
26398
|
+
return c.json(
|
|
26399
|
+
buildAccountSettingsResponse(rawMaximumTurnCount, rawUseToolsThroughCode, serverBindMode)
|
|
26400
|
+
);
|
|
25952
26401
|
}
|
|
25953
26402
|
|
|
25954
26403
|
// src/features/account/account.routes.ts
|
|
@@ -26209,7 +26658,7 @@ async function clipboardWriteImage(c) {
|
|
|
26209
26658
|
}
|
|
26210
26659
|
|
|
26211
26660
|
// src/features/image/image-save.route.ts
|
|
26212
|
-
import { join as
|
|
26661
|
+
import { join as join33 } from "path";
|
|
26213
26662
|
var Utils5 = null;
|
|
26214
26663
|
var utilsLoaded5 = false;
|
|
26215
26664
|
async function loadUtils5() {
|
|
@@ -26246,7 +26695,7 @@ async function saveImage(c) {
|
|
|
26246
26695
|
return c.json({ error: "imageUrl or imageData is required" }, 400);
|
|
26247
26696
|
}
|
|
26248
26697
|
const name = filename ?? `generated-image-${Date.now()}.png`;
|
|
26249
|
-
const savePath =
|
|
26698
|
+
const savePath = join33(Utils5.paths.downloads, name);
|
|
26250
26699
|
await Bun.write(savePath, data);
|
|
26251
26700
|
return c.json({ success: true, path: savePath });
|
|
26252
26701
|
} catch (error) {
|
|
@@ -26452,7 +26901,7 @@ import { Hono as Hono25 } from "hono";
|
|
|
26452
26901
|
|
|
26453
26902
|
// src/features/user-tasks/user-tasks.database.ts
|
|
26454
26903
|
init_database();
|
|
26455
|
-
import { randomUUID as
|
|
26904
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
26456
26905
|
async function getUserTasksByThread(db, threadId) {
|
|
26457
26906
|
const result = await db.execute({
|
|
26458
26907
|
sql: `SELECT id, threadId, content, createdAt, updatedAt
|
|
@@ -26465,7 +26914,7 @@ async function getUserTasksByThread(db, threadId) {
|
|
|
26465
26914
|
}
|
|
26466
26915
|
async function insertUserTask(threadId, content) {
|
|
26467
26916
|
const db = await getDatabase();
|
|
26468
|
-
const id =
|
|
26917
|
+
const id = randomUUID12();
|
|
26469
26918
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26470
26919
|
await db.execute({
|
|
26471
26920
|
sql: `INSERT INTO user_tasks (id, threadId, content, createdAt, updatedAt)
|
|
@@ -26518,7 +26967,15 @@ function createUserTaskRoutes() {
|
|
|
26518
26967
|
// src/server.ts
|
|
26519
26968
|
var __filename = fileURLToPath2(import.meta.url);
|
|
26520
26969
|
var __dirname = path5.dirname(__filename);
|
|
26970
|
+
var startPromise = null;
|
|
26521
26971
|
async function startTarskServer(options) {
|
|
26972
|
+
if (startPromise) {
|
|
26973
|
+
return startPromise;
|
|
26974
|
+
}
|
|
26975
|
+
startPromise = startTarskServerInternal(options);
|
|
26976
|
+
return startPromise;
|
|
26977
|
+
}
|
|
26978
|
+
async function startTarskServerInternal(options) {
|
|
26522
26979
|
const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
|
|
26523
26980
|
const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
|
|
26524
26981
|
const app = new Hono26();
|
|
@@ -26652,22 +27109,58 @@ async function startTarskServer(options) {
|
|
|
26652
27109
|
};
|
|
26653
27110
|
return c.json(errorResponse2, 404);
|
|
26654
27111
|
});
|
|
27112
|
+
const serverBindMode = readServerBindMode();
|
|
27113
|
+
const hostname2 = getServerBindHostname(serverBindMode);
|
|
26655
27114
|
const url = `http://localhost:${port}`;
|
|
26656
|
-
|
|
27115
|
+
const bound = await listenForTarskServer({
|
|
27116
|
+
fetch: app.fetch,
|
|
27117
|
+
hostname: hostname2,
|
|
27118
|
+
onListening: () => {
|
|
27119
|
+
process.stdout.write(`Tarsk started on ${url}
|
|
26657
27120
|
`);
|
|
26658
|
-
|
|
26659
|
-
|
|
26660
|
-
|
|
26661
|
-
|
|
26662
|
-
|
|
26663
|
-
|
|
27121
|
+
if (serverBindMode === "network") {
|
|
27122
|
+
for (const address of getLocalNetworkAddresses()) {
|
|
27123
|
+
process.stdout.write(` Network: http://${address}:${port}
|
|
27124
|
+
`);
|
|
27125
|
+
}
|
|
27126
|
+
}
|
|
26664
27127
|
if (options.openBrowser) {
|
|
26665
27128
|
open3(url).catch(() => {
|
|
26666
27129
|
});
|
|
26667
27130
|
}
|
|
27131
|
+
},
|
|
27132
|
+
port
|
|
27133
|
+
});
|
|
27134
|
+
if (!bound) {
|
|
27135
|
+
return { url, port, bound: false };
|
|
27136
|
+
}
|
|
27137
|
+
return { url, port, bound: true };
|
|
27138
|
+
}
|
|
27139
|
+
async function listenForTarskServer(options) {
|
|
27140
|
+
const server = createAdaptorServer({
|
|
27141
|
+
fetch: options.fetch,
|
|
27142
|
+
hostname: options.hostname,
|
|
27143
|
+
port: options.port
|
|
27144
|
+
});
|
|
27145
|
+
return new Promise((resolve6, reject) => {
|
|
27146
|
+
server.once("error", (error) => {
|
|
27147
|
+
if (error.code === "EADDRINUSE") {
|
|
27148
|
+
resolve6(false);
|
|
27149
|
+
return;
|
|
27150
|
+
}
|
|
27151
|
+
startPromise = null;
|
|
27152
|
+
reject(error);
|
|
27153
|
+
});
|
|
27154
|
+
function onListening() {
|
|
27155
|
+
options.onListening();
|
|
27156
|
+
resolve6(true);
|
|
26668
27157
|
}
|
|
26669
|
-
|
|
26670
|
-
|
|
27158
|
+
if (options.hostname) {
|
|
27159
|
+
server.listen(options.port, options.hostname, onListening);
|
|
27160
|
+
} else {
|
|
27161
|
+
server.listen(options.port, onListening);
|
|
27162
|
+
}
|
|
27163
|
+
});
|
|
26671
27164
|
}
|
|
26672
27165
|
|
|
26673
27166
|
// src/index.ts
|
|
@@ -26675,6 +27168,7 @@ import fs4 from "fs";
|
|
|
26675
27168
|
import path6 from "path";
|
|
26676
27169
|
var args = process.argv.slice(2);
|
|
26677
27170
|
var isDebug = args.includes("--debug");
|
|
27171
|
+
var isServerOnly = args.includes("--server");
|
|
26678
27172
|
var shouldOpenBrowser = args.includes("--open");
|
|
26679
27173
|
if (!isDebug) {
|
|
26680
27174
|
console.log = () => {
|
|
@@ -26709,5 +27203,5 @@ if (!isDebug) {
|
|
|
26709
27203
|
var isDevelopment = process.env.MODE === "development";
|
|
26710
27204
|
await startTarskServer({
|
|
26711
27205
|
isDebug,
|
|
26712
|
-
openBrowser: shouldOpenBrowser || !isDevelopment
|
|
27206
|
+
openBrowser: !isServerOnly && (shouldOpenBrowser || !isDevelopment)
|
|
26713
27207
|
});
|