@deepagents/text2sql 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1060 -218
  4. package/dist/index.js.map +4 -4
  5. package/dist/lib/adapters/groundings/index.js +166 -154
  6. package/dist/lib/adapters/groundings/index.js.map +4 -4
  7. package/dist/lib/adapters/mysql/index.js +166 -154
  8. package/dist/lib/adapters/mysql/index.js.map +4 -4
  9. package/dist/lib/adapters/postgres/index.js +168 -155
  10. package/dist/lib/adapters/postgres/index.js.map +4 -4
  11. package/dist/lib/adapters/postgres/info.postgres.grounding.d.ts.map +1 -1
  12. package/dist/lib/adapters/spreadsheet/index.js +22 -2
  13. package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
  14. package/dist/lib/adapters/sqlite/index.js +166 -154
  15. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  16. package/dist/lib/adapters/sqlserver/index.js +166 -154
  17. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  18. package/dist/lib/agents/result-tools.d.ts +20 -23
  19. package/dist/lib/agents/result-tools.d.ts.map +1 -1
  20. package/dist/lib/fs/index.d.ts +4 -0
  21. package/dist/lib/fs/index.d.ts.map +1 -0
  22. package/dist/lib/fs/scoped-fs.d.ts +53 -0
  23. package/dist/lib/fs/scoped-fs.d.ts.map +1 -0
  24. package/dist/lib/fs/sqlite-fs.d.ts +66 -0
  25. package/dist/lib/fs/sqlite-fs.d.ts.map +1 -0
  26. package/dist/lib/fs/tracked-fs.d.ts +40 -0
  27. package/dist/lib/fs/tracked-fs.d.ts.map +1 -0
  28. package/dist/lib/sql.d.ts +3 -4
  29. package/dist/lib/sql.d.ts.map +1 -1
  30. package/dist/lib/synthesis/index.js +181 -181
  31. package/dist/lib/synthesis/index.js.map +4 -4
  32. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -387,7 +387,7 @@ function getTablesWithRelated(allTables, relationships, filter) {
387
387
 
388
388
  // packages/text2sql/src/lib/agents/developer.agent.ts
389
389
  import { tool } from "ai";
390
- import dedent from "dedent";
390
+ import dedent2 from "dedent";
391
391
  import z2 from "zod";
392
392
  import { toState } from "@deepagents/agent";
393
393
 
@@ -405,6 +405,7 @@ import spawn2 from "nano-spawn";
405
405
  import {
406
406
  createBashTool
407
407
  } from "bash-tool";
408
+ import dedent from "dedent";
408
409
  import YAML from "yaml";
409
410
  import { DatabaseSync } from "node:sqlite";
410
411
  import { groq } from "@ai-sdk/groq";
@@ -626,7 +627,7 @@ function assistantText(content, options) {
626
627
  parts: [{ type: "text", text: content }]
627
628
  });
628
629
  }
629
- var LAZY_ID = Symbol("lazy-id");
630
+ var LAZY_ID = Symbol.for("@deepagents/context:lazy-id");
630
631
  function isLazyFragment(fragment2) {
631
632
  return LAZY_ID in fragment2;
632
633
  }
@@ -1013,7 +1014,7 @@ var ContextEngine = class {
1013
1014
  async #createBranchFrom(messageId, switchTo) {
1014
1015
  const branches = await this.#store.listBranches(this.#chatId);
1015
1016
  const samePrefix = branches.filter(
1016
- (b) => b.name === this.#branchName || b.name.startsWith(`${this.#branchName}-v`)
1017
+ (it) => it.name === this.#branchName || it.name.startsWith(`${this.#branchName}-v`)
1017
1018
  );
1018
1019
  const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
1019
1020
  const newBranch = {
@@ -1041,6 +1042,15 @@ var ContextEngine = class {
1041
1042
  createdAt: newBranch.createdAt
1042
1043
  };
1043
1044
  }
1045
+ /**
1046
+ * Rewind to a message without clearing pending messages.
1047
+ * Used internally when saving an update to an existing message.
1048
+ */
1049
+ async #rewindForUpdate(messageId) {
1050
+ const pendingBackup = [...this.#pendingMessages];
1051
+ await this.rewind(messageId);
1052
+ this.#pendingMessages = pendingBackup;
1053
+ }
1044
1054
  /**
1045
1055
  * Get the current chat ID.
1046
1056
  */
@@ -1125,7 +1135,18 @@ var ContextEngine = class {
1125
1135
  messages.push(message(msg.data).codec?.decode());
1126
1136
  }
1127
1137
  }
1138
+ for (let i = 0; i < this.#pendingMessages.length; i++) {
1139
+ const fragment2 = this.#pendingMessages[i];
1140
+ if (isLazyFragment(fragment2)) {
1141
+ this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1142
+ }
1143
+ }
1128
1144
  for (const fragment2 of this.#pendingMessages) {
1145
+ if (!fragment2.codec) {
1146
+ throw new Error(
1147
+ `Fragment "${fragment2.name}" is missing codec. Lazy fragments must be resolved before decode.`
1148
+ );
1149
+ }
1129
1150
  const decoded = fragment2.codec.decode();
1130
1151
  messages.push(decoded);
1131
1152
  }
@@ -1156,9 +1177,24 @@ var ContextEngine = class {
1156
1177
  this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
1157
1178
  }
1158
1179
  }
1180
+ for (const fragment2 of this.#pendingMessages) {
1181
+ if (fragment2.id) {
1182
+ const existing = await this.#store.getMessage(fragment2.id);
1183
+ if (existing && existing.parentId) {
1184
+ await this.#rewindForUpdate(existing.parentId);
1185
+ fragment2.id = crypto.randomUUID();
1186
+ break;
1187
+ }
1188
+ }
1189
+ }
1159
1190
  let parentId = this.#branch.headMessageId;
1160
1191
  const now = Date.now();
1161
1192
  for (const fragment2 of this.#pendingMessages) {
1193
+ if (!fragment2.codec) {
1194
+ throw new Error(
1195
+ `Fragment "${fragment2.name}" is missing codec. Lazy fragments must be resolved before encode.`
1196
+ );
1197
+ }
1162
1198
  const messageData = {
1163
1199
  id: fragment2.id ?? crypto.randomUUID(),
1164
1200
  chatId: this.#chatId,
@@ -1518,33 +1554,28 @@ var ContextEngine = class {
1518
1554
  return void 0;
1519
1555
  }
1520
1556
  /**
1521
- * Extract skill path mappings from available_skills fragments.
1522
- * Returns array of { host, sandbox } for mounting in sandbox filesystem.
1523
- *
1524
- * Reads the original `paths` configuration stored in fragment metadata
1525
- * by the skills() fragment helper.
1557
+ * Extract skill mounts from available_skills fragments.
1558
+ * Returns unified mount array where entries with `name` are individual skills.
1526
1559
  *
1527
1560
  * @example
1528
1561
  * ```ts
1529
1562
  * const context = new ContextEngine({ store, chatId, userId })
1530
1563
  * .set(skills({ paths: [{ host: './skills', sandbox: '/skills' }] }));
1531
1564
  *
1532
- * const mounts = context.getSkillMounts();
1533
- * // [{ host: './skills', sandbox: '/skills' }]
1565
+ * const { mounts } = context.getSkillMounts();
1566
+ * // mounts: [{ name: 'bi-dashboards', host: './skills/bi-dashboards/SKILL.md', sandbox: '/skills/bi-dashboards/SKILL.md' }]
1567
+ *
1568
+ * // Extract skills only (entries with name)
1569
+ * const skills = mounts.filter(m => m.name);
1534
1570
  * ```
1535
1571
  */
1536
1572
  getSkillMounts() {
1537
- const mounts = [];
1538
1573
  for (const fragment2 of this.#fragments) {
1539
- if (fragment2.name === "available_skills" && fragment2.metadata && Array.isArray(fragment2.metadata.paths)) {
1540
- for (const mapping of fragment2.metadata.paths) {
1541
- if (typeof mapping === "object" && mapping !== null && typeof mapping.host === "string" && typeof mapping.sandbox === "string") {
1542
- mounts.push({ host: mapping.host, sandbox: mapping.sandbox });
1543
- }
1544
- }
1574
+ if (fragment2.name === "available_skills" && fragment2.metadata?.mounts) {
1575
+ return { mounts: fragment2.metadata.mounts };
1545
1576
  }
1546
1577
  }
1547
- return mounts;
1578
+ return { mounts: [] };
1548
1579
  }
1549
1580
  /**
1550
1581
  * Inspect the full context state for debugging.
@@ -1715,11 +1746,14 @@ function pass(part) {
1715
1746
  function fail(feedback) {
1716
1747
  return { type: "fail", feedback };
1717
1748
  }
1749
+ function stop(part) {
1750
+ return { type: "stop", part };
1751
+ }
1718
1752
  function runGuardrailChain(part, guardrails, context) {
1719
1753
  let currentPart = part;
1720
1754
  for (const guardrail2 of guardrails) {
1721
1755
  const result = guardrail2.handle(currentPart, context);
1722
- if (result.type === "fail") {
1756
+ if (result.type === "fail" || result.type === "stop") {
1723
1757
  return result;
1724
1758
  }
1725
1759
  currentPart = result.part;
@@ -1762,6 +1796,15 @@ var errorRecoveryGuardrail = {
1762
1796
  if (errorText.includes("not in request.tools") || errorText.includes("tool") && errorText.includes("not found")) {
1763
1797
  const toolMatch = errorText.match(/tool '([^']+)'/);
1764
1798
  const toolName = toolMatch ? toolMatch[1] : "unknown";
1799
+ const matchingSkill = context.availableSkills.find(
1800
+ (skill) => skill.name === toolName
1801
+ );
1802
+ if (matchingSkill) {
1803
+ return logAndFail(
1804
+ `Skill confused as tool: ${toolName}`,
1805
+ `"${toolName}" is a skill, not a tool. Read the skill at ${matchingSkill.sandbox} to use it.`
1806
+ );
1807
+ }
1765
1808
  if (context.availableTools.length > 0) {
1766
1809
  return logAndFail(
1767
1810
  `Unregistered tool: ${toolName}`,
@@ -1779,97 +1822,79 @@ var errorRecoveryGuardrail = {
1779
1822
  "I generated malformed JSON for the tool arguments. Let me format my tool call properly with valid JSON."
1780
1823
  );
1781
1824
  }
1825
+ if (errorText.includes("validation failed") && errorText.includes("did not match schema")) {
1826
+ const toolMatch = errorText.match(/parameters for tool (\w+)/);
1827
+ const toolName = toolMatch ? toolMatch[1] : "unknown";
1828
+ const schemaErrors = errorText.match(/errors: \[([^\]]+)\]/)?.[1] || "";
1829
+ return logAndFail(
1830
+ `Schema validation: ${toolName}`,
1831
+ `I called "${toolName}" with invalid parameters. Schema errors: ${schemaErrors}. Let me fix the parameters and try again.`
1832
+ );
1833
+ }
1782
1834
  if (errorText.includes("Parsing failed")) {
1783
1835
  return logAndFail(
1784
1836
  "Parsing failed",
1785
1837
  "My response format was invalid. Let me try again with a properly formatted response."
1786
1838
  );
1787
1839
  }
1788
- console.dir({ part }, { depth: null });
1789
- return logAndFail(
1790
- "Unknown error",
1791
- `An error occurred: ${errorText}. Let me try a different approach.`
1840
+ if (errorText.includes("Failed to call a function") || errorText.includes("failed_generation")) {
1841
+ if (context.availableTools.length > 0) {
1842
+ return logAndFail(
1843
+ "Failed function call",
1844
+ `My function call was malformed. Available tools: ${context.availableTools.join(", ")}. Let me format my tool call correctly.`
1845
+ );
1846
+ }
1847
+ return logAndFail(
1848
+ "Failed function call (no tools)",
1849
+ "My function call was malformed. Let me respond with plain text instead."
1850
+ );
1851
+ }
1852
+ console.log(
1853
+ `${prefix} ${chalk.yellow("Unknown error - stopping without retry")}`
1792
1854
  );
1855
+ return stop(part);
1793
1856
  }
1794
1857
  };
1795
- var STORE_DDL = `
1796
- -- Chats table
1797
- -- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
1798
- CREATE TABLE IF NOT EXISTS chats (
1799
- id TEXT PRIMARY KEY,
1800
- userId TEXT NOT NULL,
1801
- title TEXT,
1802
- metadata TEXT,
1803
- createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
1804
- updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
1805
- );
1806
-
1807
- CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
1808
- CREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);
1809
-
1810
- -- Messages table (nodes in the DAG)
1811
- CREATE TABLE IF NOT EXISTS messages (
1812
- id TEXT PRIMARY KEY,
1813
- chatId TEXT NOT NULL,
1814
- parentId TEXT,
1815
- name TEXT NOT NULL,
1816
- type TEXT,
1817
- data TEXT NOT NULL,
1818
- createdAt INTEGER NOT NULL,
1819
- FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1820
- FOREIGN KEY (parentId) REFERENCES messages(id)
1821
- );
1822
-
1823
- CREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);
1824
- CREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);
1825
-
1826
- -- Branches table (pointers to head messages)
1827
- CREATE TABLE IF NOT EXISTS branches (
1828
- id TEXT PRIMARY KEY,
1829
- chatId TEXT NOT NULL,
1830
- name TEXT NOT NULL,
1831
- headMessageId TEXT,
1832
- isActive INTEGER NOT NULL DEFAULT 0,
1833
- createdAt INTEGER NOT NULL,
1834
- FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1835
- FOREIGN KEY (headMessageId) REFERENCES messages(id),
1836
- UNIQUE(chatId, name)
1837
- );
1858
+ var SKILLS_INSTRUCTIONS = dedent`A skill is a set of local instructions to follow that is stored in a \`SKILL.md\` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.
1838
1859
 
1839
- CREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);
1840
-
1841
- -- Checkpoints table (pointers to message nodes)
1842
- CREATE TABLE IF NOT EXISTS checkpoints (
1843
- id TEXT PRIMARY KEY,
1844
- chatId TEXT NOT NULL,
1845
- name TEXT NOT NULL,
1846
- messageId TEXT NOT NULL,
1847
- createdAt INTEGER NOT NULL,
1848
- FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
1849
- FOREIGN KEY (messageId) REFERENCES messages(id),
1850
- UNIQUE(chatId, name)
1851
- );
1852
-
1853
- CREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);
1854
-
1855
- -- FTS5 virtual table for full-text search
1856
- -- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)
1857
- -- Only 'content' is indexed for full-text search
1858
- CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
1859
- messageId UNINDEXED,
1860
- chatId UNINDEXED,
1861
- name UNINDEXED,
1862
- content,
1863
- tokenize='porter unicode61'
1864
- );
1865
- `;
1860
+ ### How to use skills
1861
+ - Discovery: The list below shows the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
1862
+ - Trigger rules: If the user names a skill (with \`$SkillName\` or plain text) OR the task clearly matches a skill's description shown below, you must use that skill for that turn before doing anything else. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
1863
+ - Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
1864
+ - How to use a skill (progressive disclosure):
1865
+ 1) After deciding to use a skill, open its \`SKILL.md\`. Read only enough to follow the workflow.
1866
+ 2) If \`SKILL.md\` points to extra folders such as \`references/\`, load only the specific files needed for the request; don't bulk-load everything.
1867
+ 3) If \`scripts/\` exist, prefer running or patching them instead of retyping large code blocks.
1868
+ 4) If \`assets/\` or templates exist, reuse them instead of recreating from scratch.
1869
+ - Coordination and sequencing:
1870
+ - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
1871
+ - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
1872
+ - Context hygiene:
1873
+ - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
1874
+ - Avoid deep reference-chasing: prefer opening only files directly linked from \`SKILL.md\` unless you're blocked.
1875
+ - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
1876
+ - Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.`;
1877
+ var ddl_sqlite_default = "-- Context Store DDL for SQLite\n-- This schema implements a DAG-based message history with branching and checkpoints.\n\n-- Performance PRAGMAs (session-level, run on each connection)\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA cache_size = -64000;\nPRAGMA temp_store = MEMORY;\nPRAGMA mmap_size = 268435456;\n\n-- Integrity\nPRAGMA foreign_keys = ON;\n\n-- Chats table\n-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates\nCREATE TABLE IF NOT EXISTS chats (\n id TEXT PRIMARY KEY,\n userId TEXT NOT NULL,\n title TEXT,\n metadata TEXT,\n createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),\n updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)\n);\n\nCREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);\nCREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);\n-- Composite index for listChats(): WHERE userId = ? ORDER BY updatedAt DESC\nCREATE INDEX IF NOT EXISTS idx_chats_userId_updatedAt ON chats(userId, updatedAt DESC);\n\n-- Messages table (nodes in the DAG)\nCREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n parentId TEXT,\n name TEXT NOT NULL,\n type TEXT,\n data TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (parentId) REFERENCES messages(id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);\nCREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);\n-- Composite index for recursive CTE parent traversal in getMessageChain()\nCREATE INDEX IF NOT EXISTS idx_messages_chatId_parentId ON messages(chatId, parentId);\n\n-- Branches table (pointers to head messages)\nCREATE TABLE IF NOT EXISTS branches (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n headMessageId TEXT,\n isActive INTEGER NOT NULL DEFAULT 0,\n createdAt INTEGER NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (headMessageId) REFERENCES messages(id),\n UNIQUE(chatId, name)\n);\n\nCREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);\n-- Composite index for getActiveBranch(): WHERE chatId = ? AND isActive = 1\nCREATE INDEX IF NOT EXISTS idx_branches_chatId_isActive ON branches(chatId, isActive);\n\n-- Checkpoints table (pointers to message nodes)\nCREATE TABLE IF NOT EXISTS checkpoints (\n id TEXT PRIMARY KEY,\n chatId TEXT NOT NULL,\n name TEXT NOT NULL,\n messageId TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,\n FOREIGN KEY (messageId) REFERENCES messages(id),\n UNIQUE(chatId, name)\n);\n\nCREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);\n\n-- FTS5 virtual table for full-text search\n-- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)\n-- Only 'content' is indexed for full-text search\nCREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(\n messageId UNINDEXED,\n chatId UNINDEXED,\n name UNINDEXED,\n content,\n tokenize='porter unicode61'\n);\n";
1866
1878
  var SqliteContextStore = class extends ContextStore {
1867
1879
  #db;
1868
- constructor(path3) {
1880
+ #statements = /* @__PURE__ */ new Map();
1881
+ /**
1882
+ * Get or create a prepared statement.
1883
+ * Statements are cached for the lifetime of the store to avoid
1884
+ * repeated SQL parsing and compilation overhead.
1885
+ */
1886
+ #stmt(sql) {
1887
+ let stmt = this.#statements.get(sql);
1888
+ if (!stmt) {
1889
+ stmt = this.#db.prepare(sql);
1890
+ this.#statements.set(sql, stmt);
1891
+ }
1892
+ return stmt;
1893
+ }
1894
+ constructor(path32) {
1869
1895
  super();
1870
- this.#db = new DatabaseSync(path3);
1871
- this.#db.exec("PRAGMA foreign_keys = ON");
1872
- this.#db.exec(STORE_DDL);
1896
+ this.#db = new DatabaseSync(path32);
1897
+ this.#db.exec(ddl_sqlite_default);
1873
1898
  }
1874
1899
  /**
1875
1900
  * Execute a function within a transaction.
@@ -1890,11 +1915,12 @@ var SqliteContextStore = class extends ContextStore {
1890
1915
  // Chat Operations
1891
1916
  // ==========================================================================
1892
1917
  async createChat(chat) {
1893
- this.#useTransaction(() => {
1894
- this.#db.prepare(
1918
+ return this.#useTransaction(() => {
1919
+ const row = this.#db.prepare(
1895
1920
  `INSERT INTO chats (id, userId, title, metadata)
1896
- VALUES (?, ?, ?, ?)`
1897
- ).run(
1921
+ VALUES (?, ?, ?, ?)
1922
+ RETURNING *`
1923
+ ).get(
1898
1924
  chat.id,
1899
1925
  chat.userId,
1900
1926
  chat.title ?? null,
@@ -1904,6 +1930,14 @@ var SqliteContextStore = class extends ContextStore {
1904
1930
  `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
1905
1931
  VALUES (?, ?, 'main', NULL, 1, ?)`
1906
1932
  ).run(crypto.randomUUID(), chat.id, Date.now());
1933
+ return {
1934
+ id: row.id,
1935
+ userId: row.userId,
1936
+ title: row.title ?? void 0,
1937
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1938
+ createdAt: row.createdAt,
1939
+ updatedAt: row.updatedAt
1940
+ };
1907
1941
  });
1908
1942
  }
1909
1943
  async upsertChat(chat) {
@@ -2046,21 +2080,16 @@ var SqliteContextStore = class extends ContextStore {
2046
2080
  // Message Operations (Graph Nodes)
2047
2081
  // ==========================================================================
2048
2082
  async addMessage(message2) {
2049
- this.#db.prepare(
2083
+ if (message2.parentId === message2.id) {
2084
+ throw new Error(`Message ${message2.id} cannot be its own parent`);
2085
+ }
2086
+ this.#stmt(
2050
2087
  `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
2051
- VALUES (
2052
- ?1,
2053
- ?2,
2054
- CASE WHEN ?3 = ?1 THEN (SELECT parentId FROM messages WHERE id = ?1) ELSE ?3 END,
2055
- ?4,
2056
- ?5,
2057
- ?6,
2058
- ?7
2059
- )
2060
- ON CONFLICT(id) DO UPDATE SET
2061
- name = excluded.name,
2062
- type = excluded.type,
2063
- data = excluded.data`
2088
+ VALUES (?, ?, ?, ?, ?, ?, ?)
2089
+ ON CONFLICT(id) DO UPDATE SET
2090
+ name = excluded.name,
2091
+ type = excluded.type,
2092
+ data = excluded.data`
2064
2093
  ).run(
2065
2094
  message2.id,
2066
2095
  message2.chatId,
@@ -2071,14 +2100,16 @@ var SqliteContextStore = class extends ContextStore {
2071
2100
  message2.createdAt
2072
2101
  );
2073
2102
  const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
2074
- this.#db.prepare(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
2075
- this.#db.prepare(
2103
+ this.#stmt(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
2104
+ this.#stmt(
2076
2105
  `INSERT INTO messages_fts(messageId, chatId, name, content)
2077
- VALUES (?, ?, ?, ?)`
2106
+ VALUES (?, ?, ?, ?)`
2078
2107
  ).run(message2.id, message2.chatId, message2.name, content);
2079
2108
  }
2080
2109
  async getMessage(messageId) {
2081
- const row = this.#db.prepare("SELECT * FROM messages WHERE id = ?").get(messageId);
2110
+ const row = this.#stmt("SELECT * FROM messages WHERE id = ?").get(
2111
+ messageId
2112
+ );
2082
2113
  if (!row) {
2083
2114
  return void 0;
2084
2115
  }
@@ -2093,15 +2124,16 @@ var SqliteContextStore = class extends ContextStore {
2093
2124
  };
2094
2125
  }
2095
2126
  async getMessageChain(headId) {
2096
- const rows = this.#db.prepare(
2127
+ const rows = this.#stmt(
2097
2128
  `WITH RECURSIVE chain AS (
2098
- SELECT *, 0 as depth FROM messages WHERE id = ?
2099
- UNION ALL
2100
- SELECT m.*, c.depth + 1 FROM messages m
2101
- INNER JOIN chain c ON m.id = c.parentId
2102
- )
2103
- SELECT * FROM chain
2104
- ORDER BY depth DESC`
2129
+ SELECT *, 0 as depth FROM messages WHERE id = ?
2130
+ UNION ALL
2131
+ SELECT m.*, c.depth + 1 FROM messages m
2132
+ INNER JOIN chain c ON m.id = c.parentId
2133
+ WHERE c.depth < 100000
2134
+ )
2135
+ SELECT * FROM chain
2136
+ ORDER BY depth DESC`
2105
2137
  ).all(headId);
2106
2138
  return rows.map((row) => ({
2107
2139
  id: row.id,
@@ -2114,7 +2146,7 @@ var SqliteContextStore = class extends ContextStore {
2114
2146
  }));
2115
2147
  }
2116
2148
  async hasChildren(messageId) {
2117
- const row = this.#db.prepare(
2149
+ const row = this.#stmt(
2118
2150
  "SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ?) as hasChildren"
2119
2151
  ).get(messageId);
2120
2152
  return row.hasChildren === 1;
@@ -2161,7 +2193,9 @@ var SqliteContextStore = class extends ContextStore {
2161
2193
  };
2162
2194
  }
2163
2195
  async getActiveBranch(chatId) {
2164
- const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND isActive = 1").get(chatId);
2196
+ const row = this.#stmt(
2197
+ "SELECT * FROM branches WHERE chatId = ? AND isActive = 1"
2198
+ ).get(chatId);
2165
2199
  if (!row) {
2166
2200
  return void 0;
2167
2201
  }
@@ -2179,45 +2213,43 @@ var SqliteContextStore = class extends ContextStore {
2179
2213
  this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
2180
2214
  }
2181
2215
  async updateBranchHead(branchId, messageId) {
2182
- this.#db.prepare("UPDATE branches SET headMessageId = ? WHERE id = ?").run(messageId, branchId);
2216
+ this.#stmt("UPDATE branches SET headMessageId = ? WHERE id = ?").run(
2217
+ messageId,
2218
+ branchId
2219
+ );
2183
2220
  }
2184
2221
  async listBranches(chatId) {
2185
- const branches = this.#db.prepare(
2222
+ const rows = this.#db.prepare(
2186
2223
  `SELECT
2187
2224
  b.id,
2188
2225
  b.name,
2189
2226
  b.headMessageId,
2190
2227
  b.isActive,
2191
- b.createdAt
2228
+ b.createdAt,
2229
+ COALESCE(
2230
+ (
2231
+ WITH RECURSIVE chain AS (
2232
+ SELECT id, parentId FROM messages WHERE id = b.headMessageId
2233
+ UNION ALL
2234
+ SELECT m.id, m.parentId FROM messages m
2235
+ INNER JOIN chain c ON m.id = c.parentId
2236
+ )
2237
+ SELECT COUNT(*) FROM chain
2238
+ ),
2239
+ 0
2240
+ ) as messageCount
2192
2241
  FROM branches b
2193
2242
  WHERE b.chatId = ?
2194
2243
  ORDER BY b.createdAt ASC`
2195
2244
  ).all(chatId);
2196
- const result = [];
2197
- for (const branch of branches) {
2198
- let messageCount = 0;
2199
- if (branch.headMessageId) {
2200
- const countRow = this.#db.prepare(
2201
- `WITH RECURSIVE chain AS (
2202
- SELECT id, parentId FROM messages WHERE id = ?
2203
- UNION ALL
2204
- SELECT m.id, m.parentId FROM messages m
2205
- INNER JOIN chain c ON m.id = c.parentId
2206
- )
2207
- SELECT COUNT(*) as count FROM chain`
2208
- ).get(branch.headMessageId);
2209
- messageCount = countRow.count;
2210
- }
2211
- result.push({
2212
- id: branch.id,
2213
- name: branch.name,
2214
- headMessageId: branch.headMessageId,
2215
- isActive: branch.isActive === 1,
2216
- messageCount,
2217
- createdAt: branch.createdAt
2218
- });
2219
- }
2220
- return result;
2245
+ return rows.map((row) => ({
2246
+ id: row.id,
2247
+ name: row.name,
2248
+ headMessageId: row.headMessageId,
2249
+ isActive: row.isActive === 1,
2250
+ messageCount: row.messageCount,
2251
+ createdAt: row.createdAt
2252
+ }));
2221
2253
  }
2222
2254
  // ==========================================================================
2223
2255
  // Checkpoint Operations
@@ -2485,8 +2517,10 @@ var Agent = class _Agent {
2485
2517
  execute: async ({ writer }) => {
2486
2518
  let currentResult = result;
2487
2519
  let attempt = 0;
2520
+ const { mounts } = context.getSkillMounts();
2488
2521
  const guardrailContext = {
2489
- availableTools: Object.keys(this.tools)
2522
+ availableTools: Object.keys(this.tools),
2523
+ availableSkills: mounts
2490
2524
  };
2491
2525
  while (attempt < maxRetries) {
2492
2526
  if (config?.abortSignal?.aborted) {
@@ -2514,10 +2548,20 @@ var Agent = class _Agent {
2514
2548
  );
2515
2549
  break;
2516
2550
  }
2551
+ if (checkResult.type === "stop") {
2552
+ console.log(
2553
+ chalk2.red(
2554
+ `[${this.#options.name}] Guardrail stopped - unrecoverable error, no retry`
2555
+ )
2556
+ );
2557
+ writer.write(part);
2558
+ writer.write({ type: "finish" });
2559
+ return;
2560
+ }
2517
2561
  if (checkResult.part.type === "text-delta") {
2518
2562
  accumulatedText += checkResult.part.delta;
2519
2563
  }
2520
- writer.write(checkResult.part);
2564
+ writer.write(part);
2521
2565
  }
2522
2566
  if (!guardrailFailed) {
2523
2567
  writer.write({ type: "finish" });
@@ -2625,10 +2669,10 @@ var repairToolCall = async ({
2625
2669
  if (NoSuchToolError.isInstance(error)) {
2626
2670
  return null;
2627
2671
  }
2628
- const tool4 = tools3[toolCall.toolName];
2672
+ const tool3 = tools3[toolCall.toolName];
2629
2673
  const { output } = await generateText({
2630
2674
  model: groq("openai/gpt-oss-20b"),
2631
- output: Output.object({ schema: tool4.inputSchema }),
2675
+ output: Output.object({ schema: tool3.inputSchema }),
2632
2676
  prompt: [
2633
2677
  `The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
2634
2678
  JSON.stringify(toolCall.input),
@@ -2730,7 +2774,7 @@ var tools = {
2730
2774
  * Get plain-English explanation of a SQL query.
2731
2775
  */
2732
2776
  explain_sql: tool({
2733
- description: dedent`
2777
+ description: dedent2`
2734
2778
  Get a plain-English explanation of a SQL query.
2735
2779
  Use this to help the user understand what a query does.
2736
2780
 
@@ -2759,18 +2803,14 @@ var fragments = [
2759
2803
  var developer_agent_default = { tools, fragments };
2760
2804
 
2761
2805
  // packages/text2sql/src/lib/agents/result-tools.ts
2762
- import "ai";
2763
2806
  import { createBashTool as createBashTool2 } from "bash-tool";
2764
2807
  import chalk3 from "chalk";
2765
2808
  import {
2766
2809
  Bash,
2767
- InMemoryFs,
2768
2810
  MountableFs,
2769
2811
  OverlayFs,
2770
- ReadWriteFs,
2771
2812
  defineCommand as defineCommand2
2772
2813
  } from "just-bash";
2773
- import * as fs from "node:fs/promises";
2774
2814
  import * as path from "node:path";
2775
2815
  import { v7 } from "uuid";
2776
2816
  function createCommand(name, subcommands) {
@@ -2825,18 +2865,13 @@ function createSqlCommand(adapter) {
2825
2865
  const rowsArray = Array.isArray(rows) ? rows : [];
2826
2866
  const content = JSON.stringify(rowsArray, null, 2);
2827
2867
  const filename = `${v7()}.json`;
2828
- const isolatedPath = `/results/${filename}`;
2829
- const sharedPath = `/artifacts/${filename}`;
2830
- await Promise.all([
2831
- ctx.fs.writeFile(isolatedPath, content),
2832
- // Current turn's isolated copy
2833
- ctx.fs.writeFile(sharedPath, content)
2834
- // Shared copy for cross-turn access
2835
- ]);
2868
+ const sqlPath = `/sql/${filename}`;
2869
+ await ctx.fs.mkdir("/sql", { recursive: true });
2870
+ await ctx.fs.writeFile(sqlPath, content);
2836
2871
  const columns = rowsArray.length > 0 ? Object.keys(rowsArray[0]) : [];
2837
2872
  return {
2838
2873
  stdout: [
2839
- `results stored in ${sharedPath}`,
2874
+ `results stored in ${sqlPath}`,
2840
2875
  `columns: ${columns.join(", ") || "(none)"}`,
2841
2876
  `rows: ${rowsArray.length}`
2842
2877
  ].join("\n") + "\n",
@@ -2882,39 +2917,25 @@ function createSqlCommand(adapter) {
2882
2917
  });
2883
2918
  }
2884
2919
  async function createResultTools(options) {
2885
- const { adapter, chatId, messageId, skillMounts = [] } = options;
2920
+ const { adapter, skillMounts, filesystem: baseFs } = options;
2886
2921
  const sqlCommand = createSqlCommand(adapter);
2887
- const root = process.env.TEXT2SQL_FS_ROOT || process.cwd();
2888
- const chatDir = path.join(root, "artifacts", chatId);
2889
- const resultsDir = path.join(chatDir, messageId, "results");
2890
- await fs.mkdir(resultsDir, { recursive: true });
2891
2922
  const fsMounts = skillMounts.map(({ host, sandbox: sandbox2 }) => ({
2892
- mountPoint: sandbox2,
2923
+ mountPoint: path.dirname(sandbox2),
2893
2924
  filesystem: new OverlayFs({
2894
- root: host,
2925
+ root: path.dirname(host),
2895
2926
  mountPoint: "/",
2896
2927
  readOnly: true
2897
2928
  })
2898
2929
  }));
2899
2930
  const filesystem = new MountableFs({
2900
- base: new InMemoryFs(),
2901
- mounts: [
2902
- ...fsMounts,
2903
- {
2904
- mountPoint: "/results",
2905
- filesystem: new ReadWriteFs({ root: resultsDir })
2906
- },
2907
- {
2908
- mountPoint: "/artifacts",
2909
- filesystem: new ReadWriteFs({ root: chatDir })
2910
- }
2911
- ]
2931
+ base: baseFs,
2932
+ mounts: fsMounts
2912
2933
  });
2913
2934
  const bashInstance = new Bash({
2914
2935
  customCommands: [sqlCommand],
2915
2936
  fs: filesystem
2916
2937
  });
2917
- const { bash, sandbox } = await createBashTool2({
2938
+ const { sandbox, tools: tools3 } = await createBashTool2({
2918
2939
  sandbox: bashInstance,
2919
2940
  destination: "/",
2920
2941
  onBeforeBashCall: ({ command }) => {
@@ -2928,7 +2949,7 @@ async function createResultTools(options) {
2928
2949
  return { result };
2929
2950
  }
2930
2951
  });
2931
- return { bash, sandbox };
2952
+ return { sandbox, tools: tools3 };
2932
2953
  }
2933
2954
 
2934
2955
  // packages/text2sql/src/lib/agents/sql.agent.ts
@@ -3103,7 +3124,7 @@ async function withRetry(computation, options = { retries: 3 }) {
3103
3124
 
3104
3125
  // packages/text2sql/src/lib/agents/suggestions.agents.ts
3105
3126
  import { groq as groq4 } from "@ai-sdk/groq";
3106
- import dedent2 from "dedent";
3127
+ import dedent3 from "dedent";
3107
3128
  import z4 from "zod";
3108
3129
  import { agent as agent2, thirdPersonPrompt } from "@deepagents/agent";
3109
3130
  var suggestionsAgent = agent2({
@@ -3119,7 +3140,7 @@ var suggestionsAgent = agent2({
3119
3140
  ).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
3120
3141
  }),
3121
3142
  prompt: (state) => {
3122
- return dedent2`
3143
+ return dedent3`
3123
3144
  ${thirdPersonPrompt()}
3124
3145
 
3125
3146
  <identity>
@@ -3154,12 +3175,12 @@ var suggestionsAgent = agent2({
3154
3175
  });
3155
3176
 
3156
3177
  // packages/text2sql/src/lib/agents/text2sql.agent.ts
3157
- import { tool as tool3 } from "ai";
3178
+ import { tool as tool2 } from "ai";
3158
3179
  import z5 from "zod";
3159
3180
  import { toState as toState2 } from "@deepagents/agent";
3160
3181
  import { scratchpad_tool } from "@deepagents/toolbox";
3161
3182
  var tools2 = {
3162
- validate_query: tool3({
3183
+ validate_query: tool2({
3163
3184
  description: `Validate SQL query syntax before execution. Use this to check if your SQL is valid before running db_query. This helps catch errors early and allows you to correct the query if needed.`,
3164
3185
  inputSchema: z5.object({
3165
3186
  sql: z5.string().describe("The SQL query to validate.")
@@ -3173,7 +3194,7 @@ var tools2 = {
3173
3194
  return "Query is valid.";
3174
3195
  }
3175
3196
  }),
3176
- db_query: tool3({
3197
+ db_query: tool2({
3177
3198
  description: `Internal tool to fetch data from the store's database. Write a SQL query to retrieve the information needed to answer the user's question. The results will be returned as data that you can then present to the user in natural language.`,
3178
3199
  inputSchema: z5.object({
3179
3200
  reasoning: z5.string().describe(
@@ -3202,9 +3223,9 @@ var Checkpoint = class _Checkpoint {
3202
3223
  points;
3203
3224
  path;
3204
3225
  configHash;
3205
- constructor(path3, configHash, points) {
3226
+ constructor(path4, configHash, points) {
3206
3227
  this.points = points;
3207
- this.path = path3;
3228
+ this.path = path4;
3208
3229
  this.configHash = configHash;
3209
3230
  }
3210
3231
  /**
@@ -3212,14 +3233,14 @@ var Checkpoint = class _Checkpoint {
3212
3233
  * Handles corrupted files and config changes gracefully.
3213
3234
  */
3214
3235
  static async load(options) {
3215
- const { path: path3, configHash } = options;
3216
- if (existsSync(path3)) {
3236
+ const { path: path4, configHash } = options;
3237
+ if (existsSync(path4)) {
3217
3238
  try {
3218
- const content = readFileSync(path3, "utf-8");
3239
+ const content = readFileSync(path4, "utf-8");
3219
3240
  const file = JSON.parse(content);
3220
3241
  if (configHash && file.configHash && file.configHash !== configHash) {
3221
3242
  console.log("\u26A0 Config changed, starting fresh");
3222
- return new _Checkpoint(path3, configHash, {});
3243
+ return new _Checkpoint(path4, configHash, {});
3223
3244
  }
3224
3245
  const points = file.points ?? {};
3225
3246
  const totalEntries = Object.values(points).reduce(
@@ -3227,14 +3248,14 @@ var Checkpoint = class _Checkpoint {
3227
3248
  0
3228
3249
  );
3229
3250
  console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
3230
- return new _Checkpoint(path3, configHash, points);
3251
+ return new _Checkpoint(path4, configHash, points);
3231
3252
  } catch {
3232
3253
  console.log("\u26A0 Checkpoint corrupted, starting fresh");
3233
- return new _Checkpoint(path3, configHash, {});
3254
+ return new _Checkpoint(path4, configHash, {});
3234
3255
  }
3235
3256
  }
3236
3257
  console.log("Starting new checkpoint");
3237
- return new _Checkpoint(path3, configHash, {});
3258
+ return new _Checkpoint(path4, configHash, {});
3238
3259
  }
3239
3260
  /**
3240
3261
  * Run a single computation with checkpointing.
@@ -3401,6 +3422,815 @@ var JsonCache = class extends FileCache {
3401
3422
  }
3402
3423
  };
3403
3424
 
3425
+ // packages/text2sql/src/lib/fs/sqlite-fs.ts
3426
+ import * as path3 from "node:path";
3427
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
3428
+
3429
+ // packages/text2sql/src/lib/fs/ddl.sqlite-fs.sql
3430
+ var ddl_sqlite_fs_default = "-- SQLite-based filesystem schema for artifact storage\n-- Tables: fs_entries (file/directory metadata), fs_chunks (file content)\n\n-- Performance PRAGMAs (session-level, run on each connection)\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA cache_size = -64000;\nPRAGMA temp_store = MEMORY;\nPRAGMA mmap_size = 268435456;\n\n-- Integrity\nPRAGMA foreign_keys = ON;\n\n-- Filesystem entries table (files, directories, symlinks)\nCREATE TABLE IF NOT EXISTS fs_entries (\n path TEXT PRIMARY KEY, -- Normalized absolute path (e.g., '/results/uuid.json')\n type TEXT NOT NULL, -- 'file' | 'directory' | 'symlink'\n mode INTEGER NOT NULL, -- Unix permissions (e.g., 0o644 = 420)\n size INTEGER NOT NULL, -- File size in bytes (0 for directories)\n mtime INTEGER NOT NULL, -- Modified time (milliseconds since epoch)\n symlinkTarget TEXT -- Target path for symlinks (NULL for files/dirs)\n);\n\nCREATE INDEX IF NOT EXISTS idx_fs_entries_type ON fs_entries(type);\n\n-- File content chunks table (for handling large files)\n-- Files are split into 1MB chunks to avoid SQLite BLOB performance issues\nCREATE TABLE IF NOT EXISTS fs_chunks (\n path TEXT NOT NULL, -- Reference to fs_entries.path\n chunkIndex INTEGER NOT NULL, -- 0-based chunk sequence\n data BLOB NOT NULL, -- Chunk content (up to 1MB default)\n PRIMARY KEY (path, chunkIndex),\n FOREIGN KEY (path) REFERENCES fs_entries(path) ON DELETE CASCADE ON UPDATE CASCADE\n);\n";
3431
+
3432
+ // packages/text2sql/src/lib/fs/sqlite-fs.ts
3433
+ var SqliteFs = class {
3434
+ #db;
3435
+ #statements = /* @__PURE__ */ new Map();
3436
+ #chunkSize;
3437
+ #root;
3438
+ constructor(options) {
3439
+ this.#chunkSize = options.chunkSize ?? 1024 * 1024;
3440
+ const normalizedRoot = this.#normalizeRoot(options.root);
3441
+ this.#root = normalizedRoot === "/" ? "" : normalizedRoot;
3442
+ this.#db = new DatabaseSync2(options.dbPath);
3443
+ this.#db.exec(ddl_sqlite_fs_default);
3444
+ const rootSlashExists = this.#stmt(
3445
+ "SELECT 1 FROM fs_entries WHERE path = ?"
3446
+ ).get("/");
3447
+ if (!rootSlashExists) {
3448
+ this.#stmt(
3449
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3450
+ VALUES ('/', 'directory', 493, 0, ?)`
3451
+ ).run(Date.now());
3452
+ }
3453
+ if (this.#root) {
3454
+ this.#createParentDirs(this.#root);
3455
+ const rootExists = this.#stmt(
3456
+ "SELECT 1 FROM fs_entries WHERE path = ?"
3457
+ ).get(this.#root);
3458
+ if (!rootExists) {
3459
+ this.#stmt(
3460
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3461
+ VALUES (?, 'directory', 493, 0, ?)`
3462
+ ).run(this.#root, Date.now());
3463
+ }
3464
+ }
3465
+ }
3466
+ /**
3467
+ * Create parent directories for a path (used during initialization).
3468
+ * Creates all segments EXCEPT the last one (the path itself).
3469
+ */
3470
+ #createParentDirs(p) {
3471
+ const segments = p.split("/").filter(Boolean);
3472
+ let currentPath = "/";
3473
+ for (let i = 0; i < segments.length - 1; i++) {
3474
+ currentPath = path3.posix.join(currentPath, segments[i]);
3475
+ const exists = this.#stmt("SELECT 1 FROM fs_entries WHERE path = ?").get(
3476
+ currentPath
3477
+ );
3478
+ if (!exists) {
3479
+ this.#stmt(
3480
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3481
+ VALUES (?, 'directory', 493, 0, ?)`
3482
+ ).run(currentPath, Date.now());
3483
+ }
3484
+ }
3485
+ }
3486
+ #stmt(sql) {
3487
+ let stmt = this.#statements.get(sql);
3488
+ if (!stmt) {
3489
+ stmt = this.#db.prepare(sql);
3490
+ this.#statements.set(sql, stmt);
3491
+ }
3492
+ return stmt;
3493
+ }
3494
+ #normalizeRoot(root) {
3495
+ return path3.posix.resolve("/", root.trim());
3496
+ }
3497
+ #prefixPath(p) {
3498
+ if (!this.#root) {
3499
+ return p;
3500
+ }
3501
+ if (p === "/") {
3502
+ return this.#root;
3503
+ }
3504
+ return path3.posix.join(this.#root, p);
3505
+ }
3506
+ #useTransaction(fn) {
3507
+ this.#db.exec("BEGIN TRANSACTION");
3508
+ try {
3509
+ const result = fn();
3510
+ this.#db.exec("COMMIT");
3511
+ return result;
3512
+ } catch (error) {
3513
+ this.#db.exec("ROLLBACK");
3514
+ throw error;
3515
+ }
3516
+ }
3517
+ #normalizePath(p) {
3518
+ return path3.posix.resolve("/", p);
3519
+ }
3520
+ #dirname(p) {
3521
+ const dir = path3.posix.dirname(p);
3522
+ return dir === "" ? "/" : dir;
3523
+ }
3524
+ #ensureParentExists(filePath) {
3525
+ const parent = this.#dirname(filePath);
3526
+ const rootPath = this.#root || "/";
3527
+ if (parent === rootPath || parent === "/") return;
3528
+ const entry = this.#stmt("SELECT type FROM fs_entries WHERE path = ?").get(
3529
+ parent
3530
+ );
3531
+ if (!entry) {
3532
+ this.#ensureParentExists(parent);
3533
+ this.#stmt(
3534
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3535
+ VALUES (?, 'directory', 493, 0, ?)`
3536
+ ).run(parent, Date.now());
3537
+ } else if (entry.type !== "directory") {
3538
+ throw new Error(`mkdir: parent is not a directory: ${parent}`);
3539
+ }
3540
+ }
3541
+ #writeChunks(filePath, content) {
3542
+ this.#stmt("DELETE FROM fs_chunks WHERE path = ?").run(filePath);
3543
+ for (let i = 0; i < content.length; i += this.#chunkSize) {
3544
+ const chunk = content.slice(
3545
+ i,
3546
+ Math.min(i + this.#chunkSize, content.length)
3547
+ );
3548
+ this.#stmt(
3549
+ "INSERT INTO fs_chunks (path, chunkIndex, data) VALUES (?, ?, ?)"
3550
+ ).run(filePath, Math.floor(i / this.#chunkSize), chunk);
3551
+ }
3552
+ }
3553
+ #readChunks(filePath) {
3554
+ const rows = this.#stmt(
3555
+ "SELECT data FROM fs_chunks WHERE path = ? ORDER BY chunkIndex"
3556
+ ).all(filePath);
3557
+ if (rows.length === 0) {
3558
+ return new Uint8Array(0);
3559
+ }
3560
+ const totalSize = rows.reduce((sum, row) => sum + row.data.length, 0);
3561
+ const result = new Uint8Array(totalSize);
3562
+ let offset = 0;
3563
+ for (const row of rows) {
3564
+ result.set(row.data, offset);
3565
+ offset += row.data.length;
3566
+ }
3567
+ return result;
3568
+ }
3569
+ #resolveSymlink(p, seen = /* @__PURE__ */ new Set()) {
3570
+ if (seen.has(p)) {
3571
+ throw new Error(`readFile: circular symlink: ${p}`);
3572
+ }
3573
+ const entry = this.#stmt(
3574
+ "SELECT type, symlinkTarget FROM fs_entries WHERE path = ?"
3575
+ ).get(p);
3576
+ if (!entry) {
3577
+ throw new Error(`ENOENT: no such file or directory: ${p}`);
3578
+ }
3579
+ if (entry.type !== "symlink") {
3580
+ return p;
3581
+ }
3582
+ seen.add(p);
3583
+ const target = this.#normalizePath(
3584
+ path3.posix.resolve(this.#dirname(p), entry.symlinkTarget)
3585
+ );
3586
+ return this.#resolveSymlink(target, seen);
3587
+ }
3588
+ #toUint8Array(content, encoding) {
3589
+ if (content instanceof Uint8Array) {
3590
+ return content;
3591
+ }
3592
+ const enc = encoding ?? "utf8";
3593
+ return new Uint8Array(Buffer.from(content, enc));
3594
+ }
3595
+ // ============================================================================
3596
+ // IFileSystem Implementation
3597
+ // ============================================================================
3598
+ async readFile(filePath, options) {
3599
+ const normalized = this.#normalizePath(filePath);
3600
+ const prefixed = this.#prefixPath(normalized);
3601
+ const resolved = this.#resolveSymlink(prefixed);
3602
+ const entry = this.#stmt("SELECT type FROM fs_entries WHERE path = ?").get(
3603
+ resolved
3604
+ );
3605
+ if (!entry) {
3606
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3607
+ }
3608
+ if (entry.type === "directory") {
3609
+ throw new Error(`EISDIR: illegal operation on a directory: ${filePath}`);
3610
+ }
3611
+ const content = this.#readChunks(resolved);
3612
+ const encoding = typeof options === "string" ? options : options?.encoding ?? "utf8";
3613
+ return Buffer.from(content).toString(encoding);
3614
+ }
3615
+ async readFileBuffer(filePath) {
3616
+ const normalized = this.#normalizePath(filePath);
3617
+ const prefixed = this.#prefixPath(normalized);
3618
+ const resolved = this.#resolveSymlink(prefixed);
3619
+ const entry = this.#stmt("SELECT type FROM fs_entries WHERE path = ?").get(
3620
+ resolved
3621
+ );
3622
+ if (!entry) {
3623
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3624
+ }
3625
+ if (entry.type === "directory") {
3626
+ throw new Error(`EISDIR: illegal operation on a directory: ${filePath}`);
3627
+ }
3628
+ return this.#readChunks(resolved);
3629
+ }
3630
+ async writeFile(filePath, content, options) {
3631
+ const normalized = this.#normalizePath(filePath);
3632
+ const prefixed = this.#prefixPath(normalized);
3633
+ const encoding = typeof options === "string" ? options : options?.encoding;
3634
+ const data = this.#toUint8Array(content, encoding);
3635
+ this.#useTransaction(() => {
3636
+ this.#ensureParentExists(prefixed);
3637
+ this.#stmt(
3638
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3639
+ VALUES (?, 'file', 420, ?, ?)
3640
+ ON CONFLICT(path) DO UPDATE SET
3641
+ type = 'file',
3642
+ size = excluded.size,
3643
+ mtime = excluded.mtime`
3644
+ ).run(prefixed, data.length, Date.now());
3645
+ this.#writeChunks(prefixed, data);
3646
+ });
3647
+ }
3648
+ async appendFile(filePath, content, options) {
3649
+ const normalized = this.#normalizePath(filePath);
3650
+ const prefixed = this.#prefixPath(normalized);
3651
+ const encoding = typeof options === "string" ? options : options?.encoding;
3652
+ const newData = this.#toUint8Array(content, encoding);
3653
+ this.#useTransaction(() => {
3654
+ this.#ensureParentExists(prefixed);
3655
+ const entry = this.#stmt(
3656
+ "SELECT type FROM fs_entries WHERE path = ?"
3657
+ ).get(prefixed);
3658
+ if (entry && entry.type !== "file") {
3659
+ throw new Error(`appendFile: not a file: ${filePath}`);
3660
+ }
3661
+ const existing = entry ? this.#readChunks(prefixed) : new Uint8Array(0);
3662
+ const combined = new Uint8Array(existing.length + newData.length);
3663
+ combined.set(existing, 0);
3664
+ combined.set(newData, existing.length);
3665
+ this.#stmt(
3666
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3667
+ VALUES (?, 'file', 420, ?, ?)
3668
+ ON CONFLICT(path) DO UPDATE SET
3669
+ size = excluded.size,
3670
+ mtime = excluded.mtime`
3671
+ ).run(prefixed, combined.length, Date.now());
3672
+ this.#writeChunks(prefixed, combined);
3673
+ });
3674
+ }
3675
+ async exists(filePath) {
3676
+ const normalized = this.#normalizePath(filePath);
3677
+ const prefixed = this.#prefixPath(normalized);
3678
+ const row = this.#stmt("SELECT 1 FROM fs_entries WHERE path = ?").get(
3679
+ prefixed
3680
+ );
3681
+ return row !== void 0;
3682
+ }
3683
+ async stat(filePath) {
3684
+ const normalized = this.#normalizePath(filePath);
3685
+ const prefixed = this.#prefixPath(normalized);
3686
+ const resolved = this.#resolveSymlink(prefixed);
3687
+ const entry = this.#stmt("SELECT * FROM fs_entries WHERE path = ?").get(
3688
+ resolved
3689
+ );
3690
+ if (!entry) {
3691
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3692
+ }
3693
+ return {
3694
+ isFile: entry.type === "file",
3695
+ isDirectory: entry.type === "directory",
3696
+ isSymbolicLink: false,
3697
+ // stat follows symlinks
3698
+ mode: entry.mode,
3699
+ size: entry.size,
3700
+ mtime: new Date(entry.mtime)
3701
+ };
3702
+ }
3703
+ async lstat(filePath) {
3704
+ const normalized = this.#normalizePath(filePath);
3705
+ const prefixed = this.#prefixPath(normalized);
3706
+ const entry = this.#stmt("SELECT * FROM fs_entries WHERE path = ?").get(
3707
+ prefixed
3708
+ );
3709
+ if (!entry) {
3710
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3711
+ }
3712
+ return {
3713
+ isFile: entry.type === "file",
3714
+ isDirectory: entry.type === "directory",
3715
+ isSymbolicLink: entry.type === "symlink",
3716
+ mode: entry.mode,
3717
+ size: entry.size,
3718
+ mtime: new Date(entry.mtime)
3719
+ };
3720
+ }
3721
+ async mkdir(dirPath, options) {
3722
+ const normalized = this.#normalizePath(dirPath);
3723
+ const prefixed = this.#prefixPath(normalized);
3724
+ const existing = this.#stmt(
3725
+ "SELECT type FROM fs_entries WHERE path = ?"
3726
+ ).get(prefixed);
3727
+ if (existing) {
3728
+ if (options?.recursive) {
3729
+ return;
3730
+ }
3731
+ throw new Error(`EEXIST: file already exists: ${dirPath}`);
3732
+ }
3733
+ this.#useTransaction(() => {
3734
+ if (options?.recursive) {
3735
+ const rootPath = this.#root || "/";
3736
+ const relativePath = path3.posix.relative(rootPath, prefixed);
3737
+ const segments = relativePath.split("/").filter(Boolean);
3738
+ let currentPath = rootPath;
3739
+ for (const segment of segments) {
3740
+ currentPath = path3.posix.join(currentPath, segment);
3741
+ const exists = this.#stmt(
3742
+ "SELECT type FROM fs_entries WHERE path = ?"
3743
+ ).get(currentPath);
3744
+ if (!exists) {
3745
+ this.#stmt(
3746
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3747
+ VALUES (?, 'directory', 493, 0, ?)`
3748
+ ).run(currentPath, Date.now());
3749
+ } else if (exists.type !== "directory") {
3750
+ throw new Error(`mkdir: not a directory: ${currentPath}`);
3751
+ }
3752
+ }
3753
+ } else {
3754
+ const parent = this.#dirname(prefixed);
3755
+ const parentEntry = this.#stmt(
3756
+ "SELECT type FROM fs_entries WHERE path = ?"
3757
+ ).get(parent);
3758
+ if (!parentEntry) {
3759
+ throw new Error(`mkdir: parent does not exist: ${parent}`);
3760
+ }
3761
+ if (parentEntry.type !== "directory") {
3762
+ throw new Error(`mkdir: parent is not a directory: ${parent}`);
3763
+ }
3764
+ this.#stmt(
3765
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
3766
+ VALUES (?, 'directory', 493, 0, ?)`
3767
+ ).run(prefixed, Date.now());
3768
+ }
3769
+ });
3770
+ }
3771
+ async readdir(dirPath) {
3772
+ const normalized = this.#normalizePath(dirPath);
3773
+ const prefixed = this.#prefixPath(normalized);
3774
+ const resolved = this.#resolveSymlink(prefixed);
3775
+ const entry = this.#stmt("SELECT type FROM fs_entries WHERE path = ?").get(
3776
+ resolved
3777
+ );
3778
+ if (!entry) {
3779
+ throw new Error(`ENOENT: no such file or directory: ${dirPath}`);
3780
+ }
3781
+ if (entry.type !== "directory") {
3782
+ throw new Error(`ENOTDIR: not a directory: ${dirPath}`);
3783
+ }
3784
+ const prefix = resolved === "/" ? "/" : resolved + "/";
3785
+ const rows = this.#stmt(
3786
+ `SELECT path FROM fs_entries
3787
+ WHERE path LIKE ? || '%'
3788
+ AND path != ?
3789
+ AND path NOT LIKE ? || '%/%'`
3790
+ ).all(prefix, resolved, prefix);
3791
+ return rows.map((row) => path3.posix.basename(row.path));
3792
+ }
3793
+ async readdirWithFileTypes(dirPath) {
3794
+ const normalized = this.#normalizePath(dirPath);
3795
+ const prefixed = this.#prefixPath(normalized);
3796
+ const resolved = this.#resolveSymlink(prefixed);
3797
+ const entry = this.#stmt("SELECT type FROM fs_entries WHERE path = ?").get(
3798
+ resolved
3799
+ );
3800
+ if (!entry) {
3801
+ throw new Error(`ENOENT: no such file or directory: ${dirPath}`);
3802
+ }
3803
+ if (entry.type !== "directory") {
3804
+ throw new Error(`ENOTDIR: not a directory: ${dirPath}`);
3805
+ }
3806
+ const prefix = resolved === "/" ? "/" : resolved + "/";
3807
+ const rows = this.#stmt(
3808
+ `SELECT path, type FROM fs_entries
3809
+ WHERE path LIKE ? || '%'
3810
+ AND path != ?
3811
+ AND path NOT LIKE ? || '%/%'`
3812
+ ).all(prefix, resolved, prefix);
3813
+ return rows.map((row) => ({
3814
+ name: path3.posix.basename(row.path),
3815
+ isFile: row.type === "file",
3816
+ isDirectory: row.type === "directory",
3817
+ isSymbolicLink: row.type === "symlink"
3818
+ }));
3819
+ }
3820
+ async rm(filePath, options) {
3821
+ const normalized = this.#normalizePath(filePath);
3822
+ const prefixed = this.#prefixPath(normalized);
3823
+ const entry = this.#stmt("SELECT type FROM fs_entries WHERE path = ?").get(
3824
+ prefixed
3825
+ );
3826
+ if (!entry) {
3827
+ if (options?.force) {
3828
+ return;
3829
+ }
3830
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
3831
+ }
3832
+ this.#useTransaction(() => {
3833
+ if (entry.type === "directory") {
3834
+ const children = this.#stmt(
3835
+ `SELECT 1 FROM fs_entries WHERE path LIKE ? || '/%' LIMIT 1`
3836
+ ).get(prefixed);
3837
+ if (children && !options?.recursive) {
3838
+ throw new Error(`ENOTEMPTY: directory not empty: ${filePath}`);
3839
+ }
3840
+ this.#stmt(
3841
+ `DELETE FROM fs_entries WHERE path = ? OR path LIKE ? || '/%'`
3842
+ ).run(prefixed, prefixed);
3843
+ } else {
3844
+ this.#stmt("DELETE FROM fs_entries WHERE path = ?").run(prefixed);
3845
+ }
3846
+ });
3847
+ }
3848
+ async cp(src, dest, options) {
3849
+ const srcNormalized = this.#normalizePath(src);
3850
+ const destNormalized = this.#normalizePath(dest);
3851
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3852
+ const destPrefixed = this.#prefixPath(destNormalized);
3853
+ const srcEntry = this.#stmt("SELECT * FROM fs_entries WHERE path = ?").get(
3854
+ srcPrefixed
3855
+ );
3856
+ if (!srcEntry) {
3857
+ throw new Error(`ENOENT: no such file or directory: ${src}`);
3858
+ }
3859
+ if (srcEntry.type === "directory" && !options?.recursive) {
3860
+ throw new Error(`cp: -r not specified; omitting directory: ${src}`);
3861
+ }
3862
+ this.#useTransaction(() => {
3863
+ this.#ensureParentExists(destPrefixed);
3864
+ if (srcEntry.type === "directory") {
3865
+ const allEntries = this.#stmt(
3866
+ `SELECT * FROM fs_entries WHERE path = ? OR path LIKE ? || '/%'`
3867
+ ).all(srcPrefixed, srcPrefixed);
3868
+ for (const entry of allEntries) {
3869
+ const relativePath = path3.posix.relative(srcPrefixed, entry.path);
3870
+ const newPath = path3.posix.join(destPrefixed, relativePath);
3871
+ this.#stmt(
3872
+ `INSERT OR REPLACE INTO fs_entries (path, type, mode, size, mtime, symlinkTarget)
3873
+ VALUES (?, ?, ?, ?, ?, ?)`
3874
+ ).run(
3875
+ newPath,
3876
+ entry.type,
3877
+ entry.mode,
3878
+ entry.size,
3879
+ Date.now(),
3880
+ entry.symlinkTarget
3881
+ );
3882
+ if (entry.type === "file") {
3883
+ const chunks = this.#stmt(
3884
+ "SELECT chunkIndex, data FROM fs_chunks WHERE path = ?"
3885
+ ).all(entry.path);
3886
+ for (const chunk of chunks) {
3887
+ this.#stmt(
3888
+ "INSERT INTO fs_chunks (path, chunkIndex, data) VALUES (?, ?, ?)"
3889
+ ).run(newPath, chunk.chunkIndex, chunk.data);
3890
+ }
3891
+ }
3892
+ }
3893
+ } else {
3894
+ this.#stmt(
3895
+ `INSERT OR REPLACE INTO fs_entries (path, type, mode, size, mtime, symlinkTarget)
3896
+ VALUES (?, ?, ?, ?, ?, ?)`
3897
+ ).run(
3898
+ destPrefixed,
3899
+ srcEntry.type,
3900
+ srcEntry.mode,
3901
+ srcEntry.size,
3902
+ Date.now(),
3903
+ srcEntry.symlinkTarget
3904
+ );
3905
+ if (srcEntry.type === "file") {
3906
+ const chunks = this.#stmt(
3907
+ "SELECT chunkIndex, data FROM fs_chunks WHERE path = ?"
3908
+ ).all(srcPrefixed);
3909
+ this.#stmt("DELETE FROM fs_chunks WHERE path = ?").run(destPrefixed);
3910
+ for (const chunk of chunks) {
3911
+ this.#stmt(
3912
+ "INSERT INTO fs_chunks (path, chunkIndex, data) VALUES (?, ?, ?)"
3913
+ ).run(destPrefixed, chunk.chunkIndex, chunk.data);
3914
+ }
3915
+ }
3916
+ }
3917
+ });
3918
+ }
3919
+ async mv(src, dest) {
3920
+ const srcNormalized = this.#normalizePath(src);
3921
+ const destNormalized = this.#normalizePath(dest);
3922
+ const srcPrefixed = this.#prefixPath(srcNormalized);
3923
+ const destPrefixed = this.#prefixPath(destNormalized);
3924
+ const srcEntry = this.#stmt("SELECT * FROM fs_entries WHERE path = ?").get(
3925
+ srcPrefixed
3926
+ );
3927
+ if (!srcEntry) {
3928
+ throw new Error(`ENOENT: no such file or directory: ${src}`);
3929
+ }
3930
+ this.#useTransaction(() => {
3931
+ this.#ensureParentExists(destPrefixed);
3932
+ if (srcEntry.type === "directory") {
3933
+ const allEntries = this.#stmt(
3934
+ `SELECT * FROM fs_entries WHERE path = ? OR path LIKE ? || '/%' ORDER BY path DESC`
3935
+ ).all(srcPrefixed, srcPrefixed);
3936
+ for (const entry of [...allEntries].reverse()) {
3937
+ const relativePath = path3.posix.relative(srcPrefixed, entry.path);
3938
+ const newPath = path3.posix.join(destPrefixed, relativePath);
3939
+ this.#stmt(
3940
+ `INSERT INTO fs_entries (path, type, mode, size, mtime, symlinkTarget)
3941
+ VALUES (?, ?, ?, ?, ?, ?)`
3942
+ ).run(
3943
+ newPath,
3944
+ entry.type,
3945
+ entry.mode,
3946
+ entry.size,
3947
+ Date.now(),
3948
+ entry.symlinkTarget
3949
+ );
3950
+ if (entry.type === "file") {
3951
+ const chunks = this.#stmt(
3952
+ "SELECT chunkIndex, data FROM fs_chunks WHERE path = ?"
3953
+ ).all(entry.path);
3954
+ for (const chunk of chunks) {
3955
+ this.#stmt(
3956
+ "INSERT INTO fs_chunks (path, chunkIndex, data) VALUES (?, ?, ?)"
3957
+ ).run(newPath, chunk.chunkIndex, chunk.data);
3958
+ }
3959
+ }
3960
+ }
3961
+ this.#stmt(
3962
+ `DELETE FROM fs_entries WHERE path = ? OR path LIKE ? || '/%'`
3963
+ ).run(srcPrefixed, srcPrefixed);
3964
+ } else {
3965
+ this.#stmt(
3966
+ `INSERT INTO fs_entries (path, type, mode, size, mtime, symlinkTarget)
3967
+ VALUES (?, ?, ?, ?, ?, ?)`
3968
+ ).run(
3969
+ destPrefixed,
3970
+ srcEntry.type,
3971
+ srcEntry.mode,
3972
+ srcEntry.size,
3973
+ Date.now(),
3974
+ srcEntry.symlinkTarget
3975
+ );
3976
+ if (srcEntry.type === "file") {
3977
+ const chunks = this.#stmt(
3978
+ "SELECT chunkIndex, data FROM fs_chunks WHERE path = ?"
3979
+ ).all(srcPrefixed);
3980
+ for (const chunk of chunks) {
3981
+ this.#stmt(
3982
+ "INSERT INTO fs_chunks (path, chunkIndex, data) VALUES (?, ?, ?)"
3983
+ ).run(destPrefixed, chunk.chunkIndex, chunk.data);
3984
+ }
3985
+ }
3986
+ this.#stmt("DELETE FROM fs_entries WHERE path = ?").run(srcPrefixed);
3987
+ }
3988
+ });
3989
+ }
3990
+ resolvePath(base, relativePath) {
3991
+ return path3.posix.resolve(base, relativePath);
3992
+ }
3993
+ getAllPaths() {
3994
+ const rows = this.#stmt(
3995
+ "SELECT path FROM fs_entries ORDER BY path"
3996
+ ).all();
3997
+ return rows.map((row) => row.path);
3998
+ }
3999
+ async chmod(filePath, mode) {
4000
+ const normalized = this.#normalizePath(filePath);
4001
+ const prefixed = this.#prefixPath(normalized);
4002
+ const result = this.#stmt(
4003
+ "UPDATE fs_entries SET mode = ? WHERE path = ?"
4004
+ ).run(mode, prefixed);
4005
+ if (result.changes === 0) {
4006
+ throw new Error(`ENOENT: no such file or directory: ${filePath}`);
4007
+ }
4008
+ }
4009
+ async symlink(target, linkPath) {
4010
+ const normalized = this.#normalizePath(linkPath);
4011
+ const prefixed = this.#prefixPath(normalized);
4012
+ const existing = this.#stmt("SELECT 1 FROM fs_entries WHERE path = ?").get(
4013
+ prefixed
4014
+ );
4015
+ if (existing) {
4016
+ throw new Error(`EEXIST: file already exists: ${linkPath}`);
4017
+ }
4018
+ this.#useTransaction(() => {
4019
+ this.#ensureParentExists(prefixed);
4020
+ this.#stmt(
4021
+ `INSERT INTO fs_entries (path, type, mode, size, mtime, symlinkTarget)
4022
+ VALUES (?, 'symlink', 511, 0, ?, ?)`
4023
+ ).run(prefixed, Date.now(), target);
4024
+ });
4025
+ }
4026
+ async link(existingPath, newPath) {
4027
+ const srcNormalized = this.#normalizePath(existingPath);
4028
+ const destNormalized = this.#normalizePath(newPath);
4029
+ const srcPrefixed = this.#prefixPath(srcNormalized);
4030
+ const destPrefixed = this.#prefixPath(destNormalized);
4031
+ const srcEntry = this.#stmt("SELECT * FROM fs_entries WHERE path = ?").get(
4032
+ srcPrefixed
4033
+ );
4034
+ if (!srcEntry) {
4035
+ throw new Error(`ENOENT: no such file or directory: ${existingPath}`);
4036
+ }
4037
+ if (srcEntry.type !== "file") {
4038
+ throw new Error(`link: not supported for directories: ${existingPath}`);
4039
+ }
4040
+ const existing = this.#stmt("SELECT 1 FROM fs_entries WHERE path = ?").get(
4041
+ destPrefixed
4042
+ );
4043
+ if (existing) {
4044
+ throw new Error(`EEXIST: file already exists: ${newPath}`);
4045
+ }
4046
+ this.#useTransaction(() => {
4047
+ this.#ensureParentExists(destPrefixed);
4048
+ this.#stmt(
4049
+ `INSERT INTO fs_entries (path, type, mode, size, mtime)
4050
+ VALUES (?, 'file', ?, ?, ?)`
4051
+ ).run(destPrefixed, srcEntry.mode, srcEntry.size, Date.now());
4052
+ const chunks = this.#stmt(
4053
+ "SELECT chunkIndex, data FROM fs_chunks WHERE path = ?"
4054
+ ).all(srcPrefixed);
4055
+ for (const chunk of chunks) {
4056
+ this.#stmt(
4057
+ "INSERT INTO fs_chunks (path, chunkIndex, data) VALUES (?, ?, ?)"
4058
+ ).run(destPrefixed, chunk.chunkIndex, chunk.data);
4059
+ }
4060
+ });
4061
+ }
4062
+ async readlink(linkPath) {
4063
+ const normalized = this.#normalizePath(linkPath);
4064
+ const prefixed = this.#prefixPath(normalized);
4065
+ const entry = this.#stmt(
4066
+ "SELECT type, symlinkTarget FROM fs_entries WHERE path = ?"
4067
+ ).get(prefixed);
4068
+ if (!entry) {
4069
+ throw new Error(`ENOENT: no such file or directory: ${linkPath}`);
4070
+ }
4071
+ if (entry.type !== "symlink") {
4072
+ throw new Error(`readlink: not a symbolic link: ${linkPath}`);
4073
+ }
4074
+ return entry.symlinkTarget;
4075
+ }
4076
+ };
4077
+
4078
+ // packages/text2sql/src/lib/fs/scoped-fs.ts
4079
+ var ScopedFs = class {
4080
+ #base;
4081
+ #prefix;
4082
+ constructor(options) {
4083
+ this.#base = options.base;
4084
+ this.#prefix = options.prefix.replace(/\/$/, "");
4085
+ }
4086
+ #scope(path4) {
4087
+ return `${this.#prefix}${path4}`;
4088
+ }
4089
+ async writeFile(path4, content, options) {
4090
+ await this.#base.writeFile(this.#scope(path4), content, options);
4091
+ }
4092
+ async appendFile(path4, content, options) {
4093
+ await this.#base.appendFile(this.#scope(path4), content, options);
4094
+ }
4095
+ async mkdir(path4, options) {
4096
+ return this.#base.mkdir(this.#scope(path4), options);
4097
+ }
4098
+ async rm(path4, options) {
4099
+ await this.#base.rm(this.#scope(path4), options);
4100
+ }
4101
+ async cp(src, dest, options) {
4102
+ await this.#base.cp(this.#scope(src), this.#scope(dest), options);
4103
+ }
4104
+ async mv(src, dest) {
4105
+ await this.#base.mv(this.#scope(src), this.#scope(dest));
4106
+ }
4107
+ async chmod(path4, mode) {
4108
+ return this.#base.chmod(this.#scope(path4), mode);
4109
+ }
4110
+ async symlink(target, linkPath) {
4111
+ await this.#base.symlink(target, this.#scope(linkPath));
4112
+ }
4113
+ async link(existingPath, newPath) {
4114
+ await this.#base.link(this.#scope(existingPath), this.#scope(newPath));
4115
+ }
4116
+ readFile(path4, options) {
4117
+ return this.#base.readFile(this.#scope(path4), options);
4118
+ }
4119
+ readFileBuffer(path4) {
4120
+ return this.#base.readFileBuffer(this.#scope(path4));
4121
+ }
4122
+ stat(path4) {
4123
+ return this.#base.stat(this.#scope(path4));
4124
+ }
4125
+ lstat(path4) {
4126
+ return this.#base.lstat(this.#scope(path4));
4127
+ }
4128
+ readdir(path4) {
4129
+ return this.#base.readdir(this.#scope(path4));
4130
+ }
4131
+ readdirWithFileTypes(path4) {
4132
+ return this.#base.readdirWithFileTypes(this.#scope(path4));
4133
+ }
4134
+ exists(path4) {
4135
+ return this.#base.exists(this.#scope(path4));
4136
+ }
4137
+ readlink(path4) {
4138
+ return this.#base.readlink(this.#scope(path4));
4139
+ }
4140
+ resolvePath(base, relativePath) {
4141
+ return this.#base.resolvePath(base, relativePath);
4142
+ }
4143
+ getAllPaths() {
4144
+ const allPaths = this.#base.getAllPaths?.() ?? [];
4145
+ return allPaths.filter((p) => p.startsWith(this.#prefix)).map((p) => p.slice(this.#prefix.length) || "/");
4146
+ }
4147
+ };
4148
+
4149
+ // packages/text2sql/src/lib/fs/tracked-fs.ts
4150
+ var TrackedFs = class {
4151
+ #base;
4152
+ #createdFiles = /* @__PURE__ */ new Set();
4153
+ constructor(base) {
4154
+ this.#base = base;
4155
+ }
4156
+ getCreatedFiles() {
4157
+ return [...this.#createdFiles];
4158
+ }
4159
+ async writeFile(path4, content, options) {
4160
+ await this.#base.writeFile(path4, content, options);
4161
+ this.#createdFiles.add(path4);
4162
+ }
4163
+ async appendFile(path4, content, options) {
4164
+ await this.#base.appendFile(path4, content, options);
4165
+ this.#createdFiles.add(path4);
4166
+ }
4167
+ async mkdir(path4, options) {
4168
+ return this.#base.mkdir(path4, options);
4169
+ }
4170
+ async rm(path4, options) {
4171
+ await this.#base.rm(path4, options);
4172
+ this.#createdFiles.delete(path4);
4173
+ if (options?.recursive) {
4174
+ const prefix = path4.endsWith("/") ? path4 : path4 + "/";
4175
+ for (const file of this.#createdFiles) {
4176
+ if (file.startsWith(prefix)) {
4177
+ this.#createdFiles.delete(file);
4178
+ }
4179
+ }
4180
+ }
4181
+ }
4182
+ async cp(src, dest, options) {
4183
+ await this.#base.cp(src, dest, options);
4184
+ this.#createdFiles.add(dest);
4185
+ }
4186
+ async mv(src, dest) {
4187
+ await this.#base.mv(src, dest);
4188
+ this.#createdFiles.delete(src);
4189
+ this.#createdFiles.add(dest);
4190
+ }
4191
+ async chmod(path4, mode) {
4192
+ return this.#base.chmod(path4, mode);
4193
+ }
4194
+ async symlink(target, linkPath) {
4195
+ await this.#base.symlink(target, linkPath);
4196
+ this.#createdFiles.add(linkPath);
4197
+ }
4198
+ async link(existingPath, newPath) {
4199
+ await this.#base.link(existingPath, newPath);
4200
+ this.#createdFiles.add(newPath);
4201
+ }
4202
+ readFile(path4, options) {
4203
+ return this.#base.readFile(path4, options);
4204
+ }
4205
+ readFileBuffer(path4) {
4206
+ return this.#base.readFileBuffer(path4);
4207
+ }
4208
+ stat(path4) {
4209
+ return this.#base.stat(path4);
4210
+ }
4211
+ lstat(path4) {
4212
+ return this.#base.lstat(path4);
4213
+ }
4214
+ readdir(path4) {
4215
+ return this.#base.readdir(path4);
4216
+ }
4217
+ readdirWithFileTypes(path4) {
4218
+ return this.#base.readdirWithFileTypes(path4);
4219
+ }
4220
+ exists(path4) {
4221
+ return this.#base.exists(path4);
4222
+ }
4223
+ readlink(path4) {
4224
+ return this.#base.readlink(path4);
4225
+ }
4226
+ resolvePath(base, relativePath) {
4227
+ return this.#base.resolvePath(base, relativePath);
4228
+ }
4229
+ getAllPaths() {
4230
+ return this.#base.getAllPaths?.() ?? [];
4231
+ }
4232
+ };
4233
+
3404
4234
  // packages/text2sql/src/lib/instructions.ts
3405
4235
  function reasoningFramework() {
3406
4236
  return [
@@ -3779,6 +4609,7 @@ import {
3779
4609
  ToolCallRepairError,
3780
4610
  generateId as generateId3
3781
4611
  } from "ai";
4612
+ import "just-bash";
3782
4613
  import "@deepagents/agent";
3783
4614
  var Text2Sql = class {
3784
4615
  #config;
@@ -3790,6 +4621,7 @@ var Text2Sql = class {
3790
4621
  tools: config.tools ?? {},
3791
4622
  model: config.model,
3792
4623
  transform: config.transform,
4624
+ filesystem: config.filesystem,
3793
4625
  introspection: new JsonCache(
3794
4626
  "introspection-" + config.version
3795
4627
  )
@@ -3862,6 +4694,7 @@ var Text2Sql = class {
3862
4694
  ];
3863
4695
  }
3864
4696
  async chat(messages) {
4697
+ const trackedFs = new TrackedFs(this.#config.filesystem);
3865
4698
  const context = this.#config.context(
3866
4699
  ...guidelines(this.#config.teachingsOptions),
3867
4700
  ...await this.index(),
@@ -3872,20 +4705,18 @@ var Text2Sql = class {
3872
4705
  context.set(message(userMsg));
3873
4706
  await context.save();
3874
4707
  }
3875
- const messageId = userMsg?.id ?? generateId3();
3876
- const skillMounts = context.getSkillMounts();
3877
- const { bash } = await createResultTools({
4708
+ const { mounts: skillMounts } = context.getSkillMounts();
4709
+ const { tools: tools3 } = await createResultTools({
3878
4710
  adapter: this.#config.adapter,
3879
- chatId: context.chatId,
3880
- messageId,
3881
- skillMounts
4711
+ skillMounts,
4712
+ filesystem: trackedFs
3882
4713
  });
3883
4714
  const chatAgent = agent({
3884
4715
  name: "text2sql",
3885
4716
  model: this.#config.model,
3886
4717
  context,
3887
4718
  tools: {
3888
- bash,
4719
+ ...tools3,
3889
4720
  ...this.#config.tools
3890
4721
  },
3891
4722
  guardrails: [errorRecoveryGuardrail],
@@ -3904,7 +4735,15 @@ var Text2Sql = class {
3904
4735
  originalMessages: messages,
3905
4736
  generateMessageId: generateId3,
3906
4737
  onFinish: async ({ responseMessage }) => {
3907
- context.set(assistant(responseMessage));
4738
+ const createdFiles = trackedFs.getCreatedFiles();
4739
+ const messageWithMetadata = {
4740
+ ...responseMessage,
4741
+ metadata: {
4742
+ ...responseMessage.metadata ?? {},
4743
+ createdFiles
4744
+ }
4745
+ };
4746
+ context.set(assistant(messageWithMetadata));
3908
4747
  await context.save();
3909
4748
  await context.trackUsage(await result.totalUsage);
3910
4749
  }
@@ -3966,7 +4805,10 @@ export {
3966
4805
  JsonCache,
3967
4806
  Point,
3968
4807
  SQLValidationError,
4808
+ ScopedFs,
4809
+ SqliteFs,
3969
4810
  Text2Sql,
4811
+ TrackedFs,
3970
4812
  UnanswerableSQLError,
3971
4813
  applyTablesFilter,
3972
4814
  column,