open-agents-ai 0.16.4 → 0.17.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 (2) hide show
  1. package/dist/index.js +2104 -218
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1285,7 +1285,7 @@ ${stdinInput ?? ""}`);
1285
1285
  }
1286
1286
  runCommand(command, timeout, stdinInput) {
1287
1287
  const start = performance.now();
1288
- return new Promise((resolve16) => {
1288
+ return new Promise((resolve18) => {
1289
1289
  const child = spawn("bash", ["-c", command], {
1290
1290
  cwd: this.workingDir,
1291
1291
  env: {
@@ -1338,7 +1338,7 @@ ${stdinInput ?? ""}`);
1338
1338
  const combined = stdout + stderr;
1339
1339
  const looksInteractive = /\? .+[›>]|y\/n|yes\/no|\(Y\/n\)|\[y\/N\]/i.test(combined);
1340
1340
  const hint = looksInteractive ? " The command appears to be waiting for interactive input. Use non-interactive flags (e.g., --yes, --no-input) or provide input via the stdin parameter." : "";
1341
- resolve16({
1341
+ resolve18({
1342
1342
  success: false,
1343
1343
  output: stdout,
1344
1344
  error: `Command timed out after ${timeout}ms.${hint}`,
@@ -1347,7 +1347,7 @@ ${stdinInput ?? ""}`);
1347
1347
  return;
1348
1348
  }
1349
1349
  const success = code === 0;
1350
- resolve16({
1350
+ resolve18({
1351
1351
  success,
1352
1352
  output: stdout + (stderr && success ? `
1353
1353
  STDERR:
@@ -1358,7 +1358,7 @@ ${stderr}` : ""),
1358
1358
  });
1359
1359
  child.on("error", (err) => {
1360
1360
  clearTimeout(timer);
1361
- resolve16({
1361
+ resolve18({
1362
1362
  success: false,
1363
1363
  output: stdout,
1364
1364
  error: err.message,
@@ -1732,15 +1732,24 @@ var init_web_fetch = __esm({
1732
1732
  });
1733
1733
 
1734
1734
  // packages/execution/dist/tools/web-search.js
1735
- var DEFAULT_NUM_RESULTS, DUCKDUCKGO_HTML_URL, WebSearchTool;
1735
+ function detectSearchProvider() {
1736
+ if (process.env["TAVILY_API_KEY"])
1737
+ return "tavily";
1738
+ if (process.env["JINA_API_KEY"])
1739
+ return "jina";
1740
+ return "duckduckgo";
1741
+ }
1742
+ var DEFAULT_NUM_RESULTS, DUCKDUCKGO_HTML_URL, TAVILY_API_URL, JINA_SEARCH_URL, WebSearchTool;
1736
1743
  var init_web_search = __esm({
1737
1744
  "packages/execution/dist/tools/web-search.js"() {
1738
1745
  "use strict";
1739
1746
  DEFAULT_NUM_RESULTS = 5;
1740
1747
  DUCKDUCKGO_HTML_URL = "https://html.duckduckgo.com/html/";
1748
+ TAVILY_API_URL = "https://api.tavily.com/search";
1749
+ JINA_SEARCH_URL = "https://s.jina.ai/";
1741
1750
  WebSearchTool = class {
1742
1751
  name = "web_search";
1743
- description = "Search the web for information. Returns a list of search results with titles, URLs, and snippets.";
1752
+ description = "Search the web for information. Returns search results with titles, URLs, and snippets. Supports DuckDuckGo (free), Tavily (structured, set TAVILY_API_KEY), and Jina AI (markdown, set JINA_API_KEY).";
1744
1753
  parameters = {
1745
1754
  type: "object",
1746
1755
  properties: {
@@ -1751,6 +1760,11 @@ var init_web_search = __esm({
1751
1760
  num_results: {
1752
1761
  type: "number",
1753
1762
  description: `Number of results to return (default: ${DEFAULT_NUM_RESULTS})`
1763
+ },
1764
+ provider: {
1765
+ type: "string",
1766
+ enum: ["duckduckgo", "tavily", "jina"],
1767
+ description: "Search provider (auto-detected from API keys if omitted). tavily: structured JSON results with AI answer. jina: markdown-formatted results. duckduckgo: free HTML scraping fallback."
1754
1768
  }
1755
1769
  },
1756
1770
  required: ["query"]
@@ -1758,43 +1772,73 @@ var init_web_search = __esm({
1758
1772
  async execute(args) {
1759
1773
  const query = args["query"];
1760
1774
  const numResults = args["num_results"] ?? DEFAULT_NUM_RESULTS;
1775
+ const requestedProvider = args["provider"];
1761
1776
  const start = performance.now();
1777
+ const provider = requestedProvider ?? detectSearchProvider();
1762
1778
  try {
1763
- const searchUrl = `${DUCKDUCKGO_HTML_URL}?q=${encodeURIComponent(query)}`;
1764
- const response = await fetch(searchUrl, {
1765
- method: "GET",
1766
- headers: {
1767
- "User-Agent": "Mozilla/5.0 (compatible; open-coding-agent/1.0; +https://github.com/open-agents)",
1768
- Accept: "text/html"
1769
- },
1770
- signal: AbortSignal.timeout(15e3)
1771
- });
1772
- if (!response.ok) {
1773
- return {
1774
- success: false,
1775
- output: "",
1776
- error: `HTTP ${response.status} ${response.statusText} from DuckDuckGo`,
1777
- durationMs: performance.now() - start
1778
- };
1779
+ let response;
1780
+ switch (provider) {
1781
+ case "tavily":
1782
+ response = await this.#searchTavily(query, numResults);
1783
+ break;
1784
+ case "jina":
1785
+ response = await this.#searchJina(query, numResults);
1786
+ break;
1787
+ default:
1788
+ response = await this.#searchDuckDuckGo(query, numResults);
1789
+ break;
1779
1790
  }
1780
- const html = await response.text();
1781
- const results = this.#parseResults(html, numResults);
1782
- if (results.length === 0) {
1791
+ if (response.results.length === 0) {
1783
1792
  return {
1784
1793
  success: true,
1785
1794
  output: "No results found.",
1786
1795
  durationMs: performance.now() - start
1787
1796
  };
1788
1797
  }
1789
- const formatted = results.map((r, i) => `${i + 1}. ${r.title}
1790
- URL: ${r.url}
1791
- ${r.snippet}`).join("\n\n");
1798
+ const parts = [];
1799
+ if (response.aiAnswer) {
1800
+ parts.push(`AI Answer: ${response.aiAnswer}
1801
+ `);
1802
+ }
1803
+ for (let i = 0; i < response.results.length; i++) {
1804
+ const r = response.results[i];
1805
+ let line = `${i + 1}. ${r.title}
1806
+ URL: ${r.url}`;
1807
+ if (r.score !== void 0) {
1808
+ line += `
1809
+ Relevance: ${(r.score * 100).toFixed(0)}%`;
1810
+ }
1811
+ if (r.snippet) {
1812
+ line += `
1813
+ ${r.snippet}`;
1814
+ }
1815
+ parts.push(line);
1816
+ }
1817
+ const providerTag = provider !== "duckduckgo" ? ` [${provider}]` : "";
1792
1818
  return {
1793
1819
  success: true,
1794
- output: formatted,
1820
+ output: parts.join("\n\n") + providerTag,
1795
1821
  durationMs: performance.now() - start
1796
1822
  };
1797
1823
  } catch (error) {
1824
+ if (provider !== "duckduckgo") {
1825
+ try {
1826
+ const fallback = await this.#searchDuckDuckGo(query, numResults);
1827
+ if (fallback.results.length > 0) {
1828
+ const formatted = fallback.results.map((r, i) => `${i + 1}. ${r.title}
1829
+ URL: ${r.url}
1830
+ ${r.snippet}`).join("\n\n");
1831
+ return {
1832
+ success: true,
1833
+ output: `[${provider} failed, fell back to DuckDuckGo]
1834
+
1835
+ ${formatted}`,
1836
+ durationMs: performance.now() - start
1837
+ };
1838
+ }
1839
+ } catch {
1840
+ }
1841
+ }
1798
1842
  return {
1799
1843
  success: false,
1800
1844
  output: "",
@@ -1803,7 +1847,109 @@ var init_web_search = __esm({
1803
1847
  };
1804
1848
  }
1805
1849
  }
1806
- #parseResults(html, limit) {
1850
+ // -------------------------------------------------------------------------
1851
+ // Tavily provider — structured JSON search API
1852
+ // -------------------------------------------------------------------------
1853
+ async #searchTavily(query, numResults) {
1854
+ const apiKey = process.env["TAVILY_API_KEY"];
1855
+ if (!apiKey) {
1856
+ throw new Error("TAVILY_API_KEY not set. Set the env var or use provider=duckduckgo.");
1857
+ }
1858
+ const body = {
1859
+ query,
1860
+ max_results: numResults,
1861
+ include_answer: true,
1862
+ search_depth: "basic"
1863
+ };
1864
+ const resp = await fetch(TAVILY_API_URL, {
1865
+ method: "POST",
1866
+ headers: {
1867
+ "Content-Type": "application/json",
1868
+ Authorization: `Bearer ${apiKey}`
1869
+ },
1870
+ body: JSON.stringify(body),
1871
+ signal: AbortSignal.timeout(2e4)
1872
+ });
1873
+ if (!resp.ok) {
1874
+ throw new Error(`Tavily HTTP ${resp.status} ${resp.statusText}`);
1875
+ }
1876
+ const data = await resp.json();
1877
+ const results = (data.results ?? []).map((r) => ({
1878
+ title: r.title ?? "",
1879
+ url: r.url ?? "",
1880
+ snippet: r.content ?? "",
1881
+ score: r.score
1882
+ }));
1883
+ return {
1884
+ provider: "tavily",
1885
+ query,
1886
+ results,
1887
+ aiAnswer: data.answer
1888
+ };
1889
+ }
1890
+ // -------------------------------------------------------------------------
1891
+ // Jina AI provider — markdown-formatted search
1892
+ // -------------------------------------------------------------------------
1893
+ async #searchJina(query, numResults) {
1894
+ const apiKey = process.env["JINA_API_KEY"];
1895
+ if (!apiKey) {
1896
+ throw new Error("JINA_API_KEY not set. Set the env var or use provider=duckduckgo.");
1897
+ }
1898
+ const searchUrl = `${JINA_SEARCH_URL}${encodeURIComponent(query)}`;
1899
+ const headers = {
1900
+ Accept: "application/json",
1901
+ Authorization: `Bearer ${apiKey}`,
1902
+ "X-Retain-Images": "none"
1903
+ };
1904
+ const resp = await fetch(searchUrl, {
1905
+ method: "GET",
1906
+ headers,
1907
+ signal: AbortSignal.timeout(2e4)
1908
+ });
1909
+ if (!resp.ok) {
1910
+ throw new Error(`Jina HTTP ${resp.status} ${resp.statusText}`);
1911
+ }
1912
+ const data = await resp.json();
1913
+ const items = data.data ?? [];
1914
+ const results = items.slice(0, numResults).map((r) => ({
1915
+ title: r.title ?? "",
1916
+ url: r.url ?? "",
1917
+ snippet: r.description ?? r.content ?? ""
1918
+ }));
1919
+ return {
1920
+ provider: "jina",
1921
+ query,
1922
+ results
1923
+ };
1924
+ }
1925
+ // -------------------------------------------------------------------------
1926
+ // DuckDuckGo provider — free HTML scraping fallback
1927
+ // -------------------------------------------------------------------------
1928
+ async #searchDuckDuckGo(query, numResults) {
1929
+ const searchUrl = `${DUCKDUCKGO_HTML_URL}?q=${encodeURIComponent(query)}`;
1930
+ const response = await fetch(searchUrl, {
1931
+ method: "GET",
1932
+ headers: {
1933
+ "User-Agent": "Mozilla/5.0 (compatible; open-coding-agent/1.0; +https://github.com/open-agents)",
1934
+ Accept: "text/html"
1935
+ },
1936
+ signal: AbortSignal.timeout(15e3)
1937
+ });
1938
+ if (!response.ok) {
1939
+ throw new Error(`HTTP ${response.status} ${response.statusText} from DuckDuckGo`);
1940
+ }
1941
+ const html = await response.text();
1942
+ const results = this.#parseDDGResults(html, numResults);
1943
+ return {
1944
+ provider: "duckduckgo",
1945
+ query,
1946
+ results
1947
+ };
1948
+ }
1949
+ // -------------------------------------------------------------------------
1950
+ // DuckDuckGo HTML parsing (unchanged from original)
1951
+ // -------------------------------------------------------------------------
1952
+ #parseDDGResults(html, limit) {
1807
1953
  const results = [];
1808
1954
  const resultBlockRegex = /<div[^>]+class="[^"]*result[^"]*"[^>]*>([\s\S]*?)<\/div>\s*<\/div>/gi;
1809
1955
  let blockMatch;
@@ -3862,12 +4008,12 @@ var init_image = __esm({
3862
4008
  if (!existsSync7(fullPath)) {
3863
4009
  return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
3864
4010
  }
3865
- const stat3 = statSync4(fullPath);
3866
- if (stat3.size > maxSizeKb * 1024) {
4011
+ const stat5 = statSync4(fullPath);
4012
+ if (stat5.size > maxSizeKb * 1024) {
3867
4013
  return {
3868
4014
  success: false,
3869
4015
  output: "",
3870
- error: `File too large: ${(stat3.size / 1024).toFixed(0)}KB (max: ${maxSizeKb}KB)`,
4016
+ error: `File too large: ${(stat5.size / 1024).toFixed(0)}KB (max: ${maxSizeKb}KB)`,
3871
4017
  durationMs: Date.now() - start
3872
4018
  };
3873
4019
  }
@@ -3877,7 +4023,7 @@ var init_image = __esm({
3877
4023
  const base64 = imageToBase64(fullPath);
3878
4024
  const mime = getMimeType(fullPath);
3879
4025
  const dims = getImageDimensions(fullPath);
3880
- const sizeKb = (stat3.size / 1024).toFixed(1);
4026
+ const sizeKb = (stat5.size / 1024).toFixed(1);
3881
4027
  const parts = [
3882
4028
  `File: ${basename(fullPath)}`,
3883
4029
  `Size: ${sizeKb}KB`,
@@ -3955,12 +4101,12 @@ ${ocrText}`);
3955
4101
  if (!existsSync7(outputPath)) {
3956
4102
  return { success: false, output: "", error: "Screenshot file not created", durationMs: Date.now() - start };
3957
4103
  }
3958
- const stat3 = statSync4(outputPath);
4104
+ const stat5 = statSync4(outputPath);
3959
4105
  const base64 = imageToBase64(outputPath);
3960
4106
  const dims = getImageDimensions(outputPath);
3961
4107
  const parts = [
3962
4108
  `Screenshot saved: ${outputPath}`,
3963
- `Size: ${(stat3.size / 1024).toFixed(1)}KB`
4109
+ `Size: ${(stat5.size / 1024).toFixed(1)}KB`
3964
4110
  ];
3965
4111
  if (dims)
3966
4112
  parts.push(`Dimensions: ${dims.width}x${dims.height}`);
@@ -4265,7 +4411,7 @@ var init_custom_tool = __esm({
4265
4411
  }
4266
4412
  /** Execute a single shell command and return output */
4267
4413
  runCommand(command) {
4268
- return new Promise((resolve16) => {
4414
+ return new Promise((resolve18) => {
4269
4415
  const child = spawn3("bash", ["-c", command], {
4270
4416
  cwd: this.workingDir,
4271
4417
  env: { ...process.env, CI: "true", NO_COLOR: "1" },
@@ -4290,11 +4436,11 @@ var init_custom_tool = __esm({
4290
4436
  child.kill("SIGTERM");
4291
4437
  } catch {
4292
4438
  }
4293
- resolve16({ success: false, output: stdout, error: "Command timed out after 60s" });
4439
+ resolve18({ success: false, output: stdout, error: "Command timed out after 60s" });
4294
4440
  }, 6e4);
4295
4441
  child.on("close", (code) => {
4296
4442
  clearTimeout(timer);
4297
- resolve16({
4443
+ resolve18({
4298
4444
  success: code === 0,
4299
4445
  output: stdout + (stderr && code === 0 ? `
4300
4446
  STDERR:
@@ -4304,7 +4450,7 @@ ${stderr}` : ""),
4304
4450
  });
4305
4451
  child.on("error", (err) => {
4306
4452
  clearTimeout(timer);
4307
- resolve16({ success: false, output: stdout, error: err.message });
4453
+ resolve18({ success: false, output: stdout, error: err.message });
4308
4454
  });
4309
4455
  });
4310
4456
  }
@@ -5240,7 +5386,775 @@ ${result.output}`,
5240
5386
  return {
5241
5387
  success: false,
5242
5388
  output: "",
5243
- error: `Failed to download/transcribe: ${err instanceof Error ? err.message : String(err)}`,
5389
+ error: `Failed to download/transcribe: ${err instanceof Error ? err.message : String(err)}`,
5390
+ durationMs: performance.now() - start
5391
+ };
5392
+ }
5393
+ }
5394
+ };
5395
+ }
5396
+ });
5397
+
5398
+ // packages/execution/dist/tools/structured-file.js
5399
+ import { writeFile as writeFile6, mkdir as mkdir3 } from "node:fs/promises";
5400
+ import { resolve as resolve13, dirname as dirname3, extname as extname4 } from "node:path";
5401
+ function jsonToCSV(data, separator = ",") {
5402
+ if (!Array.isArray(data) || data.length === 0)
5403
+ return "";
5404
+ const headers = Object.keys(data[0]);
5405
+ const esc = (val) => {
5406
+ const str = val === null || val === void 0 ? "" : String(val);
5407
+ if (str.includes(separator) || str.includes('"') || str.includes("\n")) {
5408
+ return `"${str.replace(/"/g, '""')}"`;
5409
+ }
5410
+ return str;
5411
+ };
5412
+ const lines = [
5413
+ headers.map((h) => esc(h)).join(separator),
5414
+ ...data.map((row) => {
5415
+ const obj = row;
5416
+ return headers.map((h) => esc(obj[h])).join(separator);
5417
+ })
5418
+ ];
5419
+ return lines.join("\n") + "\n";
5420
+ }
5421
+ function jsonToMarkdownTable(data) {
5422
+ if (!Array.isArray(data) || data.length === 0)
5423
+ return "";
5424
+ const headers = Object.keys(data[0]);
5425
+ const headerRow = `| ${headers.join(" | ")} |`;
5426
+ const sepRow = `| ${headers.map(() => "---").join(" | ")} |`;
5427
+ const dataRows = data.map((row) => {
5428
+ const obj = row;
5429
+ return `| ${headers.map((h) => String(obj[h] ?? "")).join(" | ")} |`;
5430
+ });
5431
+ return [headerRow, sepRow, ...dataRows].join("\n") + "\n";
5432
+ }
5433
+ var StructuredFileTool;
5434
+ var init_structured_file = __esm({
5435
+ "packages/execution/dist/tools/structured-file.js"() {
5436
+ "use strict";
5437
+ StructuredFileTool = class {
5438
+ name = "create_structured_file";
5439
+ description = "Create a structured file (CSV, JSON, Markdown table, or Excel-compatible) from data. Provide data as a JSON array of objects. Each object becomes a row, keys become columns.";
5440
+ parameters = {
5441
+ type: "object",
5442
+ properties: {
5443
+ path: {
5444
+ type: "string",
5445
+ description: "Output file path (extension determines format: .csv, .json, .md, .tsv, .xlsx)"
5446
+ },
5447
+ data: {
5448
+ type: "array",
5449
+ description: "Array of objects \u2014 each object is a row, keys are column headers",
5450
+ items: { type: "object" }
5451
+ },
5452
+ format: {
5453
+ type: "string",
5454
+ enum: ["csv", "json", "markdown", "tsv", "xlsx"],
5455
+ description: "Output format (auto-detected from file extension if omitted)"
5456
+ },
5457
+ title: {
5458
+ type: "string",
5459
+ description: "Optional title/heading for markdown output"
5460
+ },
5461
+ sheets: {
5462
+ type: "array",
5463
+ description: "For multi-sheet xlsx: array of {name, data} objects",
5464
+ items: {
5465
+ type: "object",
5466
+ properties: {
5467
+ name: { type: "string" },
5468
+ data: { type: "array", items: { type: "object" } }
5469
+ }
5470
+ }
5471
+ }
5472
+ },
5473
+ required: ["path"]
5474
+ };
5475
+ workingDir;
5476
+ constructor(workingDir) {
5477
+ this.workingDir = workingDir;
5478
+ }
5479
+ async execute(args) {
5480
+ const filePath = args["path"];
5481
+ const data = args["data"] ?? [];
5482
+ const title = args["title"];
5483
+ const sheets = args["sheets"];
5484
+ const start = performance.now();
5485
+ if (!filePath) {
5486
+ return { success: false, output: "", error: "path is required", durationMs: 0 };
5487
+ }
5488
+ const ext = extname4(filePath).toLowerCase();
5489
+ let format = args["format"] ?? "";
5490
+ if (!format) {
5491
+ switch (ext) {
5492
+ case ".csv":
5493
+ format = "csv";
5494
+ break;
5495
+ case ".tsv":
5496
+ format = "tsv";
5497
+ break;
5498
+ case ".json":
5499
+ format = "json";
5500
+ break;
5501
+ case ".md":
5502
+ case ".markdown":
5503
+ format = "markdown";
5504
+ break;
5505
+ case ".xlsx":
5506
+ case ".xls":
5507
+ format = "xlsx";
5508
+ break;
5509
+ default:
5510
+ format = "csv";
5511
+ }
5512
+ }
5513
+ try {
5514
+ const fullPath = resolve13(this.workingDir, filePath);
5515
+ await mkdir3(dirname3(fullPath), { recursive: true });
5516
+ let content;
5517
+ let byteInfo = "";
5518
+ switch (format) {
5519
+ case "csv":
5520
+ content = jsonToCSV(data, ",");
5521
+ byteInfo = `${data.length} rows`;
5522
+ break;
5523
+ case "tsv":
5524
+ case "xlsx":
5525
+ content = jsonToCSV(data, " ");
5526
+ byteInfo = `${data.length} rows (tab-separated)`;
5527
+ break;
5528
+ case "json":
5529
+ content = JSON.stringify(data, null, 2) + "\n";
5530
+ byteInfo = `${data.length} items`;
5531
+ break;
5532
+ case "markdown": {
5533
+ const parts = [];
5534
+ if (title)
5535
+ parts.push(`# ${title}
5536
+ `);
5537
+ if (sheets && sheets.length > 0) {
5538
+ for (const sheet of sheets) {
5539
+ parts.push(`## ${sheet.name}
5540
+ `);
5541
+ parts.push(jsonToMarkdownTable(sheet.data));
5542
+ parts.push("");
5543
+ }
5544
+ } else if (data.length > 0) {
5545
+ parts.push(jsonToMarkdownTable(data));
5546
+ }
5547
+ content = parts.join("\n");
5548
+ byteInfo = sheets ? `${sheets.length} sections, ${sheets.reduce((s, sh) => s + sh.data.length, 0)} total rows` : `${data.length} rows`;
5549
+ break;
5550
+ }
5551
+ default:
5552
+ return {
5553
+ success: false,
5554
+ output: "",
5555
+ error: `Unknown format: ${format}. Use csv, json, markdown, tsv, or xlsx.`,
5556
+ durationMs: performance.now() - start
5557
+ };
5558
+ }
5559
+ await writeFile6(fullPath, content, "utf-8");
5560
+ return {
5561
+ success: true,
5562
+ output: `Created ${format.toUpperCase()} file: ${fullPath} (${byteInfo}, ${content.length} bytes)`,
5563
+ durationMs: performance.now() - start
5564
+ };
5565
+ } catch (error) {
5566
+ return {
5567
+ success: false,
5568
+ output: "",
5569
+ error: error instanceof Error ? error.message : String(error),
5570
+ durationMs: performance.now() - start
5571
+ };
5572
+ }
5573
+ }
5574
+ };
5575
+ }
5576
+ });
5577
+
5578
+ // packages/execution/dist/tools/code-sandbox.js
5579
+ import { spawn as spawn5 } from "node:child_process";
5580
+ import { writeFile as writeFile7, mkdtemp, rm, readdir, stat } from "node:fs/promises";
5581
+ import { join as join14 } from "node:path";
5582
+ import { tmpdir as tmpdir2 } from "node:os";
5583
+ function runProcess(cmd, args, options) {
5584
+ return new Promise((resolve18) => {
5585
+ const proc = spawn5(cmd, args, {
5586
+ cwd: options.cwd,
5587
+ timeout: options.timeout,
5588
+ env: {
5589
+ PATH: process.env["PATH"] ?? "/usr/bin:/bin",
5590
+ HOME: options.cwd,
5591
+ TMPDIR: options.cwd,
5592
+ NODE_ENV: "sandbox",
5593
+ ...options.env ?? {}
5594
+ },
5595
+ stdio: ["pipe", "pipe", "pipe"]
5596
+ });
5597
+ let stdout = "";
5598
+ let stderr = "";
5599
+ let timedOut = false;
5600
+ proc.stdout.on("data", (d) => {
5601
+ stdout += d.toString();
5602
+ if (stdout.length > 1e5) {
5603
+ stdout = stdout.slice(0, 1e5) + "\n[output truncated at 100KB]";
5604
+ proc.kill("SIGKILL");
5605
+ }
5606
+ });
5607
+ proc.stderr.on("data", (d) => {
5608
+ stderr += d.toString();
5609
+ if (stderr.length > 5e4) {
5610
+ stderr = stderr.slice(0, 5e4) + "\n[stderr truncated at 50KB]";
5611
+ }
5612
+ });
5613
+ proc.on("error", (err) => {
5614
+ resolve18({
5615
+ stdout,
5616
+ stderr: stderr || err.message,
5617
+ exitCode: 1,
5618
+ filesCreated: [],
5619
+ timedOut: false
5620
+ });
5621
+ });
5622
+ proc.on("close", (code, signal) => {
5623
+ if (signal === "SIGTERM" || signal === "SIGKILL") {
5624
+ timedOut = true;
5625
+ }
5626
+ resolve18({
5627
+ stdout,
5628
+ stderr,
5629
+ exitCode: code ?? (timedOut ? 124 : 1),
5630
+ filesCreated: [],
5631
+ timedOut
5632
+ });
5633
+ });
5634
+ if (options.stdin) {
5635
+ proc.stdin.write(options.stdin);
5636
+ }
5637
+ proc.stdin.end();
5638
+ });
5639
+ }
5640
+ async function listCreatedFiles(dir) {
5641
+ const files = [];
5642
+ try {
5643
+ const entries = await readdir(dir);
5644
+ for (const entry of entries) {
5645
+ if (entry.startsWith("_sandbox_script"))
5646
+ continue;
5647
+ const fullPath = join14(dir, entry);
5648
+ const s = await stat(fullPath);
5649
+ if (s.isFile()) {
5650
+ files.push(entry);
5651
+ }
5652
+ }
5653
+ } catch {
5654
+ }
5655
+ return files;
5656
+ }
5657
+ var LANGUAGE_CONFIG, CodeSandboxTool;
5658
+ var init_code_sandbox = __esm({
5659
+ "packages/execution/dist/tools/code-sandbox.js"() {
5660
+ "use strict";
5661
+ LANGUAGE_CONFIG = {
5662
+ javascript: { ext: ".js", cmd: "node", args: (f) => [f] },
5663
+ python: { ext: ".py", cmd: "python3", args: (f) => [f] },
5664
+ bash: { ext: ".sh", cmd: "bash", args: (f) => [f] },
5665
+ typescript: { ext: ".ts", cmd: "npx", args: (f) => ["tsx", f] }
5666
+ };
5667
+ CodeSandboxTool = class {
5668
+ name = "code_sandbox";
5669
+ description = "Execute code in an isolated sandbox environment. Safer than shell for running generated or untrusted code. Supports JavaScript, Python, Bash, and TypeScript. Returns stdout, stderr, exit code, and any files created.";
5670
+ parameters = {
5671
+ type: "object",
5672
+ properties: {
5673
+ code: {
5674
+ type: "string",
5675
+ description: "The code to execute"
5676
+ },
5677
+ language: {
5678
+ type: "string",
5679
+ enum: ["javascript", "python", "bash", "typescript"],
5680
+ description: "Programming language (default: javascript)"
5681
+ },
5682
+ timeout_ms: {
5683
+ type: "number",
5684
+ description: "Execution timeout in milliseconds (default: 30000, max: 120000)"
5685
+ },
5686
+ stdin: {
5687
+ type: "string",
5688
+ description: "Optional input to pass to stdin"
5689
+ },
5690
+ mode: {
5691
+ type: "string",
5692
+ enum: ["subprocess", "docker"],
5693
+ description: "Execution mode. subprocess (default): isolated process. docker: Docker container (requires Docker installed)."
5694
+ }
5695
+ },
5696
+ required: ["code"]
5697
+ };
5698
+ workingDir;
5699
+ constructor(workingDir) {
5700
+ this.workingDir = workingDir;
5701
+ }
5702
+ async execute(args) {
5703
+ const code = args["code"];
5704
+ const language = args["language"] ?? "javascript";
5705
+ const timeoutMs = Math.min(args["timeout_ms"] ?? 3e4, 12e4);
5706
+ const stdin = args["stdin"];
5707
+ const mode = args["mode"] ?? "subprocess";
5708
+ const start = performance.now();
5709
+ if (!code) {
5710
+ return { success: false, output: "", error: "code is required", durationMs: 0 };
5711
+ }
5712
+ const langConfig = LANGUAGE_CONFIG[language];
5713
+ if (!langConfig) {
5714
+ return {
5715
+ success: false,
5716
+ output: "",
5717
+ error: `Unsupported language: ${language}. Use javascript, python, bash, or typescript.`,
5718
+ durationMs: performance.now() - start
5719
+ };
5720
+ }
5721
+ try {
5722
+ let result;
5723
+ if (mode === "docker") {
5724
+ result = await this.#runDocker(code, language, langConfig, timeoutMs, stdin);
5725
+ } else {
5726
+ result = await this.#runSubprocess(code, langConfig, timeoutMs, stdin);
5727
+ }
5728
+ const parts = [];
5729
+ if (result.stdout) {
5730
+ parts.push(result.stdout.trimEnd());
5731
+ }
5732
+ if (result.stderr) {
5733
+ parts.push(`
5734
+ --- stderr ---
5735
+ ${result.stderr.trimEnd()}`);
5736
+ }
5737
+ if (result.timedOut) {
5738
+ parts.push(`
5739
+ [Execution timed out after ${timeoutMs}ms]`);
5740
+ }
5741
+ if (result.filesCreated.length > 0) {
5742
+ parts.push(`
5743
+ --- files created ---
5744
+ ${result.filesCreated.join("\n")}`);
5745
+ }
5746
+ parts.push(`
5747
+ [exit code: ${result.exitCode}]`);
5748
+ return {
5749
+ success: result.exitCode === 0,
5750
+ output: parts.join(""),
5751
+ error: result.exitCode !== 0 ? `Process exited with code ${result.exitCode}${result.timedOut ? " (timeout)" : ""}` : void 0,
5752
+ durationMs: performance.now() - start
5753
+ };
5754
+ } catch (error) {
5755
+ return {
5756
+ success: false,
5757
+ output: "",
5758
+ error: error instanceof Error ? error.message : String(error),
5759
+ durationMs: performance.now() - start
5760
+ };
5761
+ }
5762
+ }
5763
+ // -------------------------------------------------------------------------
5764
+ // Subprocess mode — temp directory + separate process
5765
+ // -------------------------------------------------------------------------
5766
+ async #runSubprocess(code, langConfig, timeoutMs, stdin) {
5767
+ const sandboxDir = await mkdtemp(join14(tmpdir2(), "oa-sandbox-"));
5768
+ try {
5769
+ const scriptFile = join14(sandboxDir, `_sandbox_script${langConfig.ext}`);
5770
+ await writeFile7(scriptFile, code, "utf-8");
5771
+ const result = await runProcess(langConfig.cmd, langConfig.args(scriptFile), {
5772
+ cwd: sandboxDir,
5773
+ timeout: timeoutMs,
5774
+ stdin
5775
+ });
5776
+ result.filesCreated = await listCreatedFiles(sandboxDir);
5777
+ return result;
5778
+ } finally {
5779
+ await rm(sandboxDir, { recursive: true, force: true }).catch(() => {
5780
+ });
5781
+ }
5782
+ }
5783
+ // -------------------------------------------------------------------------
5784
+ // Docker mode — run in container
5785
+ // -------------------------------------------------------------------------
5786
+ async #runDocker(code, language, langConfig, timeoutMs, stdin) {
5787
+ const images = {
5788
+ javascript: "node:22-slim",
5789
+ typescript: "node:22-slim",
5790
+ python: "python:3.12-slim",
5791
+ bash: "bash:5"
5792
+ };
5793
+ const image = images[language] ?? "node:22-slim";
5794
+ const sandboxDir = await mkdtemp(join14(tmpdir2(), "oa-docker-sandbox-"));
5795
+ try {
5796
+ const scriptFile = `_sandbox_script${langConfig.ext}`;
5797
+ await writeFile7(join14(sandboxDir, scriptFile), code, "utf-8");
5798
+ const dockerArgs = [
5799
+ "run",
5800
+ "--rm",
5801
+ "--network=none",
5802
+ // No network access
5803
+ "--memory=256m",
5804
+ // Memory limit
5805
+ "--cpus=1",
5806
+ // CPU limit
5807
+ "-v",
5808
+ `${sandboxDir}:/sandbox:rw`,
5809
+ "-w",
5810
+ "/sandbox",
5811
+ image,
5812
+ langConfig.cmd,
5813
+ ...langConfig.args(`/sandbox/${scriptFile}`)
5814
+ ];
5815
+ const result = await runProcess("docker", dockerArgs, {
5816
+ cwd: sandboxDir,
5817
+ timeout: timeoutMs,
5818
+ stdin
5819
+ });
5820
+ result.filesCreated = await listCreatedFiles(sandboxDir);
5821
+ return result;
5822
+ } finally {
5823
+ await rm(sandboxDir, { recursive: true, force: true }).catch(() => {
5824
+ });
5825
+ }
5826
+ }
5827
+ };
5828
+ }
5829
+ });
5830
+
5831
+ // packages/execution/dist/tools/structured-read.js
5832
+ import { readFile as readFile7, stat as stat2 } from "node:fs/promises";
5833
+ import { resolve as resolve14, extname as extname5 } from "node:path";
5834
+ function parseCSV(text, separator = ",") {
5835
+ const lines = text.split("\n").filter((l) => l.trim() !== "");
5836
+ if (lines.length < 2)
5837
+ return [];
5838
+ const parseLine = (line) => {
5839
+ const fields = [];
5840
+ let current = "";
5841
+ let inQuotes = false;
5842
+ for (let i = 0; i < line.length; i++) {
5843
+ const ch = line[i];
5844
+ if (inQuotes) {
5845
+ if (ch === '"' && line[i + 1] === '"') {
5846
+ current += '"';
5847
+ i++;
5848
+ } else if (ch === '"') {
5849
+ inQuotes = false;
5850
+ } else {
5851
+ current += ch;
5852
+ }
5853
+ } else {
5854
+ if (ch === '"') {
5855
+ inQuotes = true;
5856
+ } else if (ch === separator) {
5857
+ fields.push(current);
5858
+ current = "";
5859
+ } else {
5860
+ current += ch;
5861
+ }
5862
+ }
5863
+ }
5864
+ fields.push(current);
5865
+ return fields;
5866
+ };
5867
+ const headers = parseLine(lines[0]).map((h) => h.trim());
5868
+ const rows = [];
5869
+ for (let i = 1; i < lines.length; i++) {
5870
+ const values = parseLine(lines[i]);
5871
+ const row = {};
5872
+ for (let j = 0; j < headers.length; j++) {
5873
+ row[headers[j]] = (values[j] ?? "").trim();
5874
+ }
5875
+ rows.push(row);
5876
+ }
5877
+ return rows;
5878
+ }
5879
+ function parseMarkdownTables(text) {
5880
+ const tables = [];
5881
+ const lines = text.split("\n");
5882
+ let i = 0;
5883
+ while (i < lines.length) {
5884
+ const line = lines[i].trim();
5885
+ if (line.startsWith("|") && line.endsWith("|")) {
5886
+ const nextLine = (lines[i + 1] ?? "").trim();
5887
+ if (nextLine.startsWith("|") && /^\|[\s-:|]+\|$/.test(nextLine)) {
5888
+ let heading;
5889
+ for (let h = i - 1; h >= 0; h--) {
5890
+ const prev = lines[h].trim();
5891
+ if (prev === "")
5892
+ continue;
5893
+ if (prev.startsWith("#")) {
5894
+ heading = prev.replace(/^#+\s*/, "").trim();
5895
+ }
5896
+ break;
5897
+ }
5898
+ const headers = line.split("|").filter((c3) => c3.trim() !== "").map((c3) => c3.trim());
5899
+ const rows = [];
5900
+ i += 2;
5901
+ while (i < lines.length) {
5902
+ const dataLine = lines[i].trim();
5903
+ if (!dataLine.startsWith("|") || !dataLine.endsWith("|"))
5904
+ break;
5905
+ const values = dataLine.split("|").filter((c3) => c3.trim() !== "").map((c3) => c3.trim());
5906
+ const row = {};
5907
+ for (let j = 0; j < headers.length; j++) {
5908
+ row[headers[j]] = values[j] ?? "";
5909
+ }
5910
+ rows.push(row);
5911
+ i++;
5912
+ }
5913
+ if (rows.length > 0) {
5914
+ tables.push({ heading, rows });
5915
+ }
5916
+ continue;
5917
+ }
5918
+ }
5919
+ i++;
5920
+ }
5921
+ return tables;
5922
+ }
5923
+ function detectBinaryFormat(buffer) {
5924
+ if (buffer[0] === 80 && buffer[1] === 75) {
5925
+ const str = buffer.toString("utf-8", 0, Math.min(buffer.length, 2e3));
5926
+ if (str.includes("xl/") || str.includes("spreadsheetml"))
5927
+ return "xlsx";
5928
+ if (str.includes("word/") || str.includes("wordprocessingml"))
5929
+ return "docx";
5930
+ return "zip";
5931
+ }
5932
+ if (buffer[0] === 37 && buffer[1] === 80 && buffer[2] === 68 && buffer[3] === 70) {
5933
+ return "pdf";
5934
+ }
5935
+ return null;
5936
+ }
5937
+ var StructuredReadTool;
5938
+ var init_structured_read = __esm({
5939
+ "packages/execution/dist/tools/structured-read.js"() {
5940
+ "use strict";
5941
+ StructuredReadTool = class {
5942
+ name = "read_structured_file";
5943
+ description = "Read and parse a structured file (CSV, TSV, JSON, Markdown tables). Returns the file content as structured data the agent can work with. For binary formats (XLSX, PDF), detects the format and suggests tools.";
5944
+ parameters = {
5945
+ type: "object",
5946
+ properties: {
5947
+ path: {
5948
+ type: "string",
5949
+ description: "File path to read"
5950
+ },
5951
+ format: {
5952
+ type: "string",
5953
+ enum: ["csv", "tsv", "json", "markdown", "auto"],
5954
+ description: "File format (auto-detected from extension if omitted)"
5955
+ },
5956
+ max_rows: {
5957
+ type: "number",
5958
+ description: "Maximum rows to return (default: 100)"
5959
+ }
5960
+ },
5961
+ required: ["path"]
5962
+ };
5963
+ workingDir;
5964
+ constructor(workingDir) {
5965
+ this.workingDir = workingDir;
5966
+ }
5967
+ async execute(args) {
5968
+ const filePath = args["path"];
5969
+ const maxRows = args["max_rows"] ?? 100;
5970
+ const start = performance.now();
5971
+ if (!filePath) {
5972
+ return { success: false, output: "", error: "path is required", durationMs: 0 };
5973
+ }
5974
+ const fullPath = resolve14(this.workingDir, filePath);
5975
+ try {
5976
+ const fileStat = await stat2(fullPath);
5977
+ if (!fileStat.isFile()) {
5978
+ return {
5979
+ success: false,
5980
+ output: "",
5981
+ error: `Not a file: ${filePath}`,
5982
+ durationMs: performance.now() - start
5983
+ };
5984
+ }
5985
+ if (fileStat.size > 10 * 1024 * 1024) {
5986
+ return {
5987
+ success: false,
5988
+ output: "",
5989
+ error: `File too large (${(fileStat.size / 1024 / 1024).toFixed(1)}MB). Maximum is 10MB.`,
5990
+ durationMs: performance.now() - start
5991
+ };
5992
+ }
5993
+ const ext = extname5(filePath).toLowerCase();
5994
+ let format = args["format"] ?? "auto";
5995
+ if (format === "auto") {
5996
+ switch (ext) {
5997
+ case ".csv":
5998
+ format = "csv";
5999
+ break;
6000
+ case ".tsv":
6001
+ format = "tsv";
6002
+ break;
6003
+ case ".json":
6004
+ format = "json";
6005
+ break;
6006
+ case ".md":
6007
+ case ".markdown":
6008
+ format = "markdown";
6009
+ break;
6010
+ case ".xlsx":
6011
+ case ".xls":
6012
+ format = "xlsx";
6013
+ break;
6014
+ case ".pdf":
6015
+ format = "pdf";
6016
+ break;
6017
+ case ".docx":
6018
+ case ".doc":
6019
+ format = "docx";
6020
+ break;
6021
+ default:
6022
+ format = "auto";
6023
+ break;
6024
+ }
6025
+ }
6026
+ if (format === "xlsx" || format === "pdf" || format === "docx" || format === "auto") {
6027
+ const buffer = await readFile7(fullPath);
6028
+ const detected = detectBinaryFormat(buffer);
6029
+ if (detected === "xlsx") {
6030
+ return {
6031
+ success: true,
6032
+ output: `Detected Excel file (.xlsx), ${(fileStat.size / 1024).toFixed(1)}KB.
6033
+ To parse this file, use the shell tool to run:
6034
+ npx xlsx-cli "${filePath}" --json
6035
+ Or install exceljs for programmatic access.`,
6036
+ durationMs: performance.now() - start
6037
+ };
6038
+ }
6039
+ if (detected === "pdf") {
6040
+ return {
6041
+ success: true,
6042
+ output: `Detected PDF file, ${(fileStat.size / 1024).toFixed(1)}KB.
6043
+ To extract text, use the shell tool to run:
6044
+ pdftotext "${filePath}" -
6045
+ Or install pdf-parse for programmatic access.`,
6046
+ durationMs: performance.now() - start
6047
+ };
6048
+ }
6049
+ if (detected === "docx") {
6050
+ return {
6051
+ success: true,
6052
+ output: `Detected Word document (.docx), ${(fileStat.size / 1024).toFixed(1)}KB.
6053
+ To extract text, use the shell tool to run:
6054
+ pandoc "${filePath}" -t plain
6055
+ Or install mammoth for programmatic access.`,
6056
+ durationMs: performance.now() - start
6057
+ };
6058
+ }
6059
+ if (format === "auto") {
6060
+ const text2 = buffer.toString("utf-8");
6061
+ if (text2.trimStart().startsWith("{") || text2.trimStart().startsWith("[")) {
6062
+ format = "json";
6063
+ } else if (text2.includes(" ") && !text2.includes(",")) {
6064
+ format = "tsv";
6065
+ } else if (text2.includes("|") && /^\|[\s-:|]+\|$/m.test(text2)) {
6066
+ format = "markdown";
6067
+ } else {
6068
+ format = "csv";
6069
+ }
6070
+ }
6071
+ }
6072
+ const text = await readFile7(fullPath, "utf-8");
6073
+ switch (format) {
6074
+ case "csv": {
6075
+ const rows = parseCSV(text, ",");
6076
+ const limited = rows.slice(0, maxRows);
6077
+ const truncated = rows.length > maxRows ? `
6078
+ [showing ${maxRows} of ${rows.length} rows]` : "";
6079
+ return {
6080
+ success: true,
6081
+ output: `CSV: ${rows.length} rows, ${Object.keys(rows[0] ?? {}).length} columns
6082
+ ${JSON.stringify(limited, null, 2)}${truncated}`,
6083
+ durationMs: performance.now() - start
6084
+ };
6085
+ }
6086
+ case "tsv": {
6087
+ const rows = parseCSV(text, " ");
6088
+ const limited = rows.slice(0, maxRows);
6089
+ const truncated = rows.length > maxRows ? `
6090
+ [showing ${maxRows} of ${rows.length} rows]` : "";
6091
+ return {
6092
+ success: true,
6093
+ output: `TSV: ${rows.length} rows, ${Object.keys(rows[0] ?? {}).length} columns
6094
+ ${JSON.stringify(limited, null, 2)}${truncated}`,
6095
+ durationMs: performance.now() - start
6096
+ };
6097
+ }
6098
+ case "json": {
6099
+ const parsed = JSON.parse(text);
6100
+ const isArray = Array.isArray(parsed);
6101
+ const count = isArray ? parsed.length : Object.keys(parsed).length;
6102
+ let display;
6103
+ if (isArray && parsed.length > maxRows) {
6104
+ display = parsed.slice(0, maxRows);
6105
+ return {
6106
+ success: true,
6107
+ output: `JSON array: ${parsed.length} items
6108
+ ${JSON.stringify(display, null, 2)}
6109
+ [showing ${maxRows} of ${parsed.length} items]`,
6110
+ durationMs: performance.now() - start
6111
+ };
6112
+ }
6113
+ return {
6114
+ success: true,
6115
+ output: `JSON ${isArray ? "array" : "object"}: ${count} ${isArray ? "items" : "keys"}
6116
+ ${JSON.stringify(parsed, null, 2)}`,
6117
+ durationMs: performance.now() - start
6118
+ };
6119
+ }
6120
+ case "markdown": {
6121
+ const tables = parseMarkdownTables(text);
6122
+ if (tables.length === 0) {
6123
+ return {
6124
+ success: true,
6125
+ output: `Markdown file (no tables found). Content:
6126
+ ${text.slice(0, 5e3)}${text.length > 5e3 ? "\n[truncated]" : ""}`,
6127
+ durationMs: performance.now() - start
6128
+ };
6129
+ }
6130
+ const parts = [];
6131
+ for (const table of tables) {
6132
+ const heading = table.heading ? `Table: ${table.heading}` : "Table";
6133
+ const limited = table.rows.slice(0, maxRows);
6134
+ parts.push(`${heading} (${table.rows.length} rows)
6135
+ ${JSON.stringify(limited, null, 2)}`);
6136
+ }
6137
+ return {
6138
+ success: true,
6139
+ output: `Markdown: ${tables.length} table(s)
6140
+
6141
+ ${parts.join("\n\n")}`,
6142
+ durationMs: performance.now() - start
6143
+ };
6144
+ }
6145
+ default:
6146
+ return {
6147
+ success: false,
6148
+ output: "",
6149
+ error: `Unsupported format: ${format}`,
6150
+ durationMs: performance.now() - start
6151
+ };
6152
+ }
6153
+ } catch (error) {
6154
+ return {
6155
+ success: false,
6156
+ output: "",
6157
+ error: error instanceof Error ? error.message : String(error),
5244
6158
  durationMs: performance.now() - start
5245
6159
  };
5246
6160
  }
@@ -5353,6 +6267,9 @@ var init_dist2 = __esm({
5353
6267
  init_tool_creator();
5354
6268
  init_skill_tools();
5355
6269
  init_transcribe_tool();
6270
+ init_structured_file();
6271
+ init_code_sandbox();
6272
+ init_structured_read();
5356
6273
  init_shellRunner();
5357
6274
  init_gitWorktree();
5358
6275
  init_patchApplier();
@@ -6467,8 +7384,8 @@ var init_code_retriever = __esm({
6467
7384
  });
6468
7385
  }
6469
7386
  async getFileContent(filePath, startLine, endLine) {
6470
- const { readFile: readFile10 } = await import("node:fs/promises");
6471
- const content = await readFile10(filePath, "utf-8");
7387
+ const { readFile: readFile11 } = await import("node:fs/promises");
7388
+ const content = await readFile11(filePath, "utf-8");
6472
7389
  if (startLine === void 0)
6473
7390
  return content;
6474
7391
  const lines = content.split("\n");
@@ -6483,8 +7400,8 @@ var init_code_retriever = __esm({
6483
7400
  // packages/retrieval/dist/lexicalSearch.js
6484
7401
  import { execFile as execFile4 } from "node:child_process";
6485
7402
  import { promisify as promisify4 } from "node:util";
6486
- import { readFile as readFile7, readdir, stat } from "node:fs/promises";
6487
- import { join as join14, extname as extname4 } from "node:path";
7403
+ import { readFile as readFile8, readdir as readdir2, stat as stat3 } from "node:fs/promises";
7404
+ import { join as join15, extname as extname6 } from "node:path";
6488
7405
  async function searchByPath(pathPattern, options) {
6489
7406
  const allFiles = await collectFiles(options.rootDir, options.includeGlobs ?? DEFAULT_INCLUDE_GLOBS, options.excludeGlobs ?? DEFAULT_EXCLUDE_GLOBS);
6490
7407
  const pattern = options.caseInsensitive ? pathPattern.toLowerCase() : pathPattern;
@@ -6589,7 +7506,7 @@ async function searchWithNodeFallback(pattern, kind, options) {
6589
7506
  if (results.length >= maxMatches)
6590
7507
  break;
6591
7508
  try {
6592
- const content = await readFile7(filePath, "utf-8");
7509
+ const content = await readFile8(filePath, "utf-8");
6593
7510
  const contentLines = content.split("\n");
6594
7511
  for (let i = 0; i < contentLines.length; i++) {
6595
7512
  if (results.length >= maxMatches)
@@ -6617,7 +7534,7 @@ async function collectFiles(rootDir, includeGlobs, excludeGlobs) {
6617
7534
  async function walkForFiles(rootDir, dir, excludeGlobs, results) {
6618
7535
  let entries;
6619
7536
  try {
6620
- entries = await readdir(dir, { withFileTypes: true, encoding: "utf-8" });
7537
+ entries = await readdir2(dir, { withFileTypes: true, encoding: "utf-8" });
6621
7538
  } catch {
6622
7539
  return;
6623
7540
  }
@@ -6626,16 +7543,16 @@ async function walkForFiles(rootDir, dir, excludeGlobs, results) {
6626
7543
  continue;
6627
7544
  if (excludeGlobs.some((g) => entry.name === g || matchesGlob(entry.name, g)))
6628
7545
  continue;
6629
- const absPath = join14(dir, entry.name);
7546
+ const absPath = join15(dir, entry.name);
6630
7547
  if (entry.isDirectory()) {
6631
7548
  await walkForFiles(rootDir, absPath, excludeGlobs, results);
6632
7549
  } else if (entry.isFile()) {
6633
- const ext = extname4(entry.name);
7550
+ const ext = extname6(entry.name);
6634
7551
  if (!ALL_CODE_EXTS.has(ext))
6635
7552
  continue;
6636
7553
  const relativePath = absPath.startsWith(rootDir) ? absPath.slice(rootDir.length).replace(/^\//, "") : absPath;
6637
7554
  try {
6638
- const s = await stat(absPath);
7555
+ const s = await stat3(absPath);
6639
7556
  if (s.size > 2e6)
6640
7557
  continue;
6641
7558
  } catch {
@@ -6800,8 +7717,8 @@ var init_graphExpand = __esm({
6800
7717
  });
6801
7718
 
6802
7719
  // packages/retrieval/dist/snippetPacker.js
6803
- import { readFile as readFile8 } from "node:fs/promises";
6804
- import { join as join15 } from "node:path";
7720
+ import { readFile as readFile9 } from "node:fs/promises";
7721
+ import { join as join16 } from "node:path";
6805
7722
  async function packSnippets(requests, opts = {}) {
6806
7723
  const maxTokens = opts.maxTokens ?? DEFAULT_MAX_TOKENS;
6807
7724
  const contextLines = opts.contextLines ?? DEFAULT_CONTEXT_LINES;
@@ -6827,10 +7744,10 @@ async function packSnippets(requests, opts = {}) {
6827
7744
  return { packed, dropped, totalTokens };
6828
7745
  }
6829
7746
  async function extractSnippet(req, repoRoot, contextLines = DEFAULT_CONTEXT_LINES) {
6830
- const absPath = req.filePath.startsWith("/") ? req.filePath : join15(repoRoot, req.filePath);
7747
+ const absPath = req.filePath.startsWith("/") ? req.filePath : join16(repoRoot, req.filePath);
6831
7748
  let content;
6832
7749
  try {
6833
- content = await readFile8(absPath, "utf-8");
7750
+ content = await readFile9(absPath, "utf-8");
6834
7751
  } catch {
6835
7752
  return null;
6836
7753
  }
@@ -8697,14 +9614,14 @@ ${result.output}`;
8697
9614
  waitForSudoPassword(timeoutMs = 12e4) {
8698
9615
  if (this._sudoPassword)
8699
9616
  return Promise.resolve(this._sudoPassword);
8700
- return new Promise((resolve16) => {
9617
+ return new Promise((resolve18) => {
8701
9618
  const timer = setTimeout(() => {
8702
9619
  this._sudoResolve = null;
8703
- resolve16(null);
9620
+ resolve18(null);
8704
9621
  }, timeoutMs);
8705
9622
  this._sudoResolve = (pw) => {
8706
9623
  clearTimeout(timer);
8707
- resolve16(pw);
9624
+ resolve18(pw);
8708
9625
  };
8709
9626
  });
8710
9627
  }
@@ -9332,6 +10249,489 @@ ${newerSummary}` : newerSummary;
9332
10249
  }
9333
10250
  });
9334
10251
 
10252
+ // packages/orchestrator/dist/costTracker.js
10253
+ var DEFAULT_PRICING, CostTracker;
10254
+ var init_costTracker = __esm({
10255
+ "packages/orchestrator/dist/costTracker.js"() {
10256
+ "use strict";
10257
+ DEFAULT_PRICING = [
10258
+ // Cloud providers — typical pricing tiers
10259
+ { providerId: "openai", label: "OpenAI", inputPer1M: 2.5, outputPer1M: 10 },
10260
+ { providerId: "together", label: "Together AI", inputPer1M: 0.8, outputPer1M: 0.8 },
10261
+ { providerId: "groq", label: "Groq", inputPer1M: 0.05, outputPer1M: 0.1 },
10262
+ { providerId: "openrouter", label: "OpenRouter", inputPer1M: 1, outputPer1M: 2 },
10263
+ { providerId: "fireworks", label: "Fireworks AI", inputPer1M: 0.2, outputPer1M: 0.8 },
10264
+ { providerId: "deepinfra", label: "DeepInfra", inputPer1M: 0.1, outputPer1M: 0.4 },
10265
+ { providerId: "mistral", label: "Mistral AI", inputPer1M: 0.5, outputPer1M: 1.5 },
10266
+ { providerId: "chutes", label: "Chutes AI", inputPer1M: 0.2, outputPer1M: 0.8 },
10267
+ { providerId: "cerebras", label: "Cerebras", inputPer1M: 0.1, outputPer1M: 0.1 },
10268
+ { providerId: "sambanova", label: "SambaNova", inputPer1M: 0.1, outputPer1M: 0.4 },
10269
+ { providerId: "nvidia", label: "NVIDIA NIM", inputPer1M: 0.3, outputPer1M: 1.2 },
10270
+ { providerId: "hyperbolic", label: "Hyperbolic", inputPer1M: 0.1, outputPer1M: 0.3 },
10271
+ // Local providers — free (no token cost)
10272
+ { providerId: "ollama", label: "Ollama (local)", inputPer1M: 0, outputPer1M: 0 },
10273
+ { providerId: "lmstudio", label: "LM Studio (local)", inputPer1M: 0, outputPer1M: 0 },
10274
+ { providerId: "vllm", label: "vLLM (local)", inputPer1M: 0, outputPer1M: 0 }
10275
+ ];
10276
+ CostTracker = class {
10277
+ pricing;
10278
+ tasks = [];
10279
+ sessionStartedAt;
10280
+ // Running totals
10281
+ _totalInputTokens = 0;
10282
+ _totalOutputTokens = 0;
10283
+ _totalEstimatedCost = 0;
10284
+ // Current task accumulator
10285
+ _currentTask = null;
10286
+ _currentInputTokens = 0;
10287
+ _currentOutputTokens = 0;
10288
+ _currentStartedAt = "";
10289
+ _currentStartMs = 0;
10290
+ constructor(providerId, customPricing) {
10291
+ this.sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
10292
+ this.pricing = customPricing ?? DEFAULT_PRICING.find((p) => p.providerId === providerId) ?? { providerId: providerId ?? "unknown", label: "Unknown", inputPer1M: 0, outputPer1M: 0 };
10293
+ }
10294
+ /** Get the current provider pricing */
10295
+ get provider() {
10296
+ return this.pricing;
10297
+ }
10298
+ /** Update provider (e.g., when user switches endpoint) */
10299
+ setProvider(providerId, customPricing) {
10300
+ this.pricing = customPricing ?? DEFAULT_PRICING.find((p) => p.providerId === providerId) ?? { providerId, label: "Unknown", inputPer1M: 0, outputPer1M: 0 };
10301
+ }
10302
+ /** Start tracking a new task */
10303
+ startTask(taskDescription) {
10304
+ if (this._currentTask) {
10305
+ this.endTask();
10306
+ }
10307
+ this._currentTask = taskDescription;
10308
+ this._currentInputTokens = 0;
10309
+ this._currentOutputTokens = 0;
10310
+ this._currentStartedAt = (/* @__PURE__ */ new Date()).toISOString();
10311
+ this._currentStartMs = Date.now();
10312
+ }
10313
+ /** Record token usage from an API response */
10314
+ trackTokens(inputTokens, outputTokens) {
10315
+ this._currentInputTokens += inputTokens;
10316
+ this._currentOutputTokens += outputTokens;
10317
+ this._totalInputTokens += inputTokens;
10318
+ this._totalOutputTokens += outputTokens;
10319
+ const cost = this.calculateCost(inputTokens, outputTokens);
10320
+ this._totalEstimatedCost += cost;
10321
+ }
10322
+ /** End current task and record it */
10323
+ endTask() {
10324
+ if (!this._currentTask)
10325
+ return null;
10326
+ const cost = this.calculateCost(this._currentInputTokens, this._currentOutputTokens);
10327
+ const record = {
10328
+ task: this._currentTask,
10329
+ inputTokens: this._currentInputTokens,
10330
+ outputTokens: this._currentOutputTokens,
10331
+ estimatedCost: cost,
10332
+ durationMs: Date.now() - this._currentStartMs,
10333
+ startedAt: this._currentStartedAt
10334
+ };
10335
+ this.tasks.push(record);
10336
+ this._currentTask = null;
10337
+ return record;
10338
+ }
10339
+ /** Calculate cost for a given token usage */
10340
+ calculateCost(inputTokens, outputTokens) {
10341
+ return inputTokens / 1e6 * this.pricing.inputPer1M + outputTokens / 1e6 * this.pricing.outputPer1M;
10342
+ }
10343
+ /** Get current running cost estimate */
10344
+ get currentCost() {
10345
+ return this._totalEstimatedCost;
10346
+ }
10347
+ /** Get total input tokens */
10348
+ get totalInputTokens() {
10349
+ return this._totalInputTokens;
10350
+ }
10351
+ /** Get total output tokens */
10352
+ get totalOutputTokens() {
10353
+ return this._totalOutputTokens;
10354
+ }
10355
+ /** Whether this provider has non-zero pricing */
10356
+ get hasPricing() {
10357
+ return this.pricing.inputPer1M > 0 || this.pricing.outputPer1M > 0;
10358
+ }
10359
+ /** Format cost as a readable string */
10360
+ formatCost(cost) {
10361
+ const c3 = cost ?? this._totalEstimatedCost;
10362
+ if (c3 < 0.01)
10363
+ return `$${c3.toFixed(4)}`;
10364
+ if (c3 < 1)
10365
+ return `$${c3.toFixed(3)}`;
10366
+ return `$${c3.toFixed(2)}`;
10367
+ }
10368
+ /** Get full session summary */
10369
+ getSummary() {
10370
+ const pendingRecord = this._currentTask ? {
10371
+ task: this._currentTask,
10372
+ inputTokens: this._currentInputTokens,
10373
+ outputTokens: this._currentOutputTokens,
10374
+ estimatedCost: this.calculateCost(this._currentInputTokens, this._currentOutputTokens),
10375
+ durationMs: Date.now() - this._currentStartMs,
10376
+ startedAt: this._currentStartedAt
10377
+ } : null;
10378
+ const allTasks = pendingRecord ? [...this.tasks, pendingRecord] : [...this.tasks];
10379
+ return {
10380
+ totalInputTokens: this._totalInputTokens,
10381
+ totalOutputTokens: this._totalOutputTokens,
10382
+ totalEstimatedCost: this._totalEstimatedCost,
10383
+ taskCount: allTasks.length,
10384
+ tasks: allTasks,
10385
+ provider: this.pricing.label,
10386
+ sessionStartedAt: this.sessionStartedAt,
10387
+ sessionDurationMs: Date.now() - new Date(this.sessionStartedAt).getTime()
10388
+ };
10389
+ }
10390
+ /** Reset all tracking (e.g., new session) */
10391
+ reset() {
10392
+ this.tasks = [];
10393
+ this._totalInputTokens = 0;
10394
+ this._totalOutputTokens = 0;
10395
+ this._totalEstimatedCost = 0;
10396
+ this._currentTask = null;
10397
+ this.sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
10398
+ }
10399
+ };
10400
+ }
10401
+ });
10402
+
10403
+ // packages/orchestrator/dist/workEvaluator.js
10404
+ function detectTaskType(taskDescription) {
10405
+ let bestType = "general";
10406
+ let bestScore = 0;
10407
+ for (const { type, patterns } of TASK_TYPE_PATTERNS) {
10408
+ let score = 0;
10409
+ for (const p of patterns) {
10410
+ if (p.test(taskDescription))
10411
+ score++;
10412
+ }
10413
+ if (score > bestScore) {
10414
+ bestScore = score;
10415
+ bestType = type;
10416
+ }
10417
+ }
10418
+ return bestType;
10419
+ }
10420
+ function getRubric(taskType) {
10421
+ return RUBRICS[taskType];
10422
+ }
10423
+ function buildEvaluationPrompt(task, summary, rubric) {
10424
+ const dimensionList = rubric.dimensions.map((d, i) => `${i + 1}. **${d.name}** (${Math.round(d.weight * 100)}%): ${d.criteria}`).join("\n");
10425
+ return `You are a quality evaluator. Score the following completed work on these dimensions.
10426
+
10427
+ ## Task
10428
+ ${task}
10429
+
10430
+ ## Completed Work Summary
10431
+ ${summary}
10432
+
10433
+ ## Scoring Dimensions (${rubric.label} task)
10434
+ ${dimensionList}
10435
+
10436
+ ## Instructions
10437
+ For each dimension, provide:
10438
+ 1. A score from 0-10 (0 = completely missing, 5 = adequate, 10 = exceptional)
10439
+ 2. A brief explanation (1-2 sentences)
10440
+
10441
+ Then provide an overall summary of the work quality.
10442
+
10443
+ Respond in EXACTLY this JSON format:
10444
+ {
10445
+ "dimensions": [
10446
+ {"name": "${rubric.dimensions[0].name}", "score": <0-10>, "feedback": "<explanation>"},
10447
+ {"name": "${rubric.dimensions[1].name}", "score": <0-10>, "feedback": "<explanation>"},
10448
+ {"name": "${rubric.dimensions[2].name}", "score": <0-10>, "feedback": "<explanation>"},
10449
+ {"name": "${rubric.dimensions[3].name}", "score": <0-10>, "feedback": "<explanation>"}
10450
+ ],
10451
+ "summary": "<2-3 sentence overall assessment>"
10452
+ }
10453
+
10454
+ Respond with ONLY the JSON object, no other text.`;
10455
+ }
10456
+ var RUBRICS, TASK_TYPE_PATTERNS, WorkEvaluator;
10457
+ var init_workEvaluator = __esm({
10458
+ "packages/orchestrator/dist/workEvaluator.js"() {
10459
+ "use strict";
10460
+ RUBRICS = {
10461
+ code: {
10462
+ type: "code",
10463
+ label: "Code",
10464
+ dimensions: [
10465
+ { name: "Completeness", weight: 0.4, criteria: "All requested features implemented. Tests written if applicable. No missing edge cases." },
10466
+ { name: "Correctness", weight: 0.3, criteria: "Code compiles/runs without errors. Tests pass. Logic is sound. No bugs or regressions." },
10467
+ { name: "Quality", weight: 0.2, criteria: "Clean, readable code. Follows existing conventions. Appropriate naming. No unnecessary complexity." },
10468
+ { name: "Domain", weight: 0.1, criteria: "Follows language/framework best practices. Proper error handling. Security considerations." }
10469
+ ]
10470
+ },
10471
+ document: {
10472
+ type: "document",
10473
+ label: "Document",
10474
+ dimensions: [
10475
+ { name: "Completeness", weight: 0.4, criteria: "All required sections present. Covers the full scope. No gaps in coverage." },
10476
+ { name: "Correctness", weight: 0.3, criteria: "Factually accurate. No fabrications. Sources cited where applicable." },
10477
+ { name: "Quality", weight: 0.2, criteria: "Well-structured. Clear writing. Professional formatting. Good flow." },
10478
+ { name: "Domain", weight: 0.1, criteria: "Follows document type conventions. Appropriate tone and terminology." }
10479
+ ]
10480
+ },
10481
+ analysis: {
10482
+ type: "analysis",
10483
+ label: "Analysis",
10484
+ dimensions: [
10485
+ { name: "Completeness", weight: 0.4, criteria: "All data considered. Methodology explained. Conclusions cover key findings." },
10486
+ { name: "Correctness", weight: 0.3, criteria: "Calculations accurate. Logic sound. No errors in reasoning or data interpretation." },
10487
+ { name: "Quality", weight: 0.2, criteria: "Clear presentation. Data visualized where helpful. Executive summary included." },
10488
+ { name: "Domain", weight: 0.1, criteria: "Follows analytical best practices. Appropriate statistical methods. Caveats noted." }
10489
+ ]
10490
+ },
10491
+ plan: {
10492
+ type: "plan",
10493
+ label: "Plan",
10494
+ dimensions: [
10495
+ { name: "Completeness", weight: 0.4, criteria: "All phases covered. Timeline realistic. Resources identified. Risks addressed." },
10496
+ { name: "Correctness", weight: 0.3, criteria: "Dependencies accurate. Estimates reasonable. Constraints respected." },
10497
+ { name: "Quality", weight: 0.2, criteria: "Actionable steps. Clear ownership. Measurable milestones. Well-organized." },
10498
+ { name: "Domain", weight: 0.1, criteria: "Follows project management best practices. Appropriate methodology for scope." }
10499
+ ]
10500
+ },
10501
+ general: {
10502
+ type: "general",
10503
+ label: "General",
10504
+ dimensions: [
10505
+ { name: "Completeness", weight: 0.4, criteria: "All aspects of the request addressed. Nothing missing or incomplete." },
10506
+ { name: "Correctness", weight: 0.3, criteria: "Accurate and factual. No errors or inconsistencies." },
10507
+ { name: "Quality", weight: 0.2, criteria: "Well-structured. Clear communication. Professional output." },
10508
+ { name: "Domain", weight: 0.1, criteria: "Appropriate for the context. Follows relevant conventions." }
10509
+ ]
10510
+ }
10511
+ };
10512
+ TASK_TYPE_PATTERNS = [
10513
+ {
10514
+ type: "code",
10515
+ patterns: [
10516
+ /\b(fix|implement|refactor|debug|test|code|function|class|module|api|bug|feature|PR|commit|build|compile|lint)\b/i,
10517
+ /\.(ts|js|py|rs|go|java|cpp|rb|php|swift)\b/i
10518
+ ]
10519
+ },
10520
+ {
10521
+ type: "document",
10522
+ patterns: [
10523
+ /\b(write|draft|document|report|article|blog|readme|guide|manual|specification|proposal|email|letter)\b/i,
10524
+ /\.(md|doc|docx|pdf|txt)\b/i
10525
+ ]
10526
+ },
10527
+ {
10528
+ type: "analysis",
10529
+ patterns: [
10530
+ /\b(analyze|research|evaluate|compare|assess|review|audit|benchmark|metric|data|statistic|trend)\b/i
10531
+ ]
10532
+ },
10533
+ {
10534
+ type: "plan",
10535
+ patterns: [
10536
+ /\b(plan|schedule|roadmap|strategy|design|architect|timeline|milestone|sprint|backlog|estimate)\b/i
10537
+ ]
10538
+ }
10539
+ ];
10540
+ WorkEvaluator = class {
10541
+ backend;
10542
+ constructor(backend) {
10543
+ this.backend = backend ?? null;
10544
+ }
10545
+ /** Set or update the evaluation backend */
10546
+ setBackend(backend) {
10547
+ this.backend = backend;
10548
+ }
10549
+ /** Evaluate completed work */
10550
+ async evaluate(task, summary, taskType) {
10551
+ if (!this.backend) {
10552
+ return {
10553
+ overallScore: 0,
10554
+ dimensions: [],
10555
+ feedback: "No evaluation backend configured.",
10556
+ taskType: taskType ?? "general",
10557
+ success: false,
10558
+ error: "No evaluation backend configured"
10559
+ };
10560
+ }
10561
+ const detectedType = taskType ?? detectTaskType(task);
10562
+ const rubric = getRubric(detectedType);
10563
+ const prompt = buildEvaluationPrompt(task, summary, rubric);
10564
+ try {
10565
+ const response = await this.backend.evaluate(prompt);
10566
+ return this.parseResponse(response, rubric, detectedType);
10567
+ } catch (err) {
10568
+ return {
10569
+ overallScore: 0,
10570
+ dimensions: [],
10571
+ feedback: `Evaluation failed: ${err instanceof Error ? err.message : String(err)}`,
10572
+ taskType: detectedType,
10573
+ success: false,
10574
+ error: err instanceof Error ? err.message : String(err)
10575
+ };
10576
+ }
10577
+ }
10578
+ /** Parse the LLM's JSON response into an EvaluationResult */
10579
+ parseResponse(response, rubric, taskType) {
10580
+ try {
10581
+ let jsonStr = response.trim();
10582
+ const jsonMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
10583
+ if (jsonMatch) {
10584
+ jsonStr = jsonMatch[1].trim();
10585
+ }
10586
+ if (!jsonStr.startsWith("{")) {
10587
+ const braceMatch = jsonStr.match(/\{[\s\S]*\}/);
10588
+ if (braceMatch)
10589
+ jsonStr = braceMatch[0];
10590
+ }
10591
+ const parsed = JSON.parse(jsonStr);
10592
+ if (!parsed.dimensions || !Array.isArray(parsed.dimensions)) {
10593
+ throw new Error("Missing dimensions array in response");
10594
+ }
10595
+ const dimensions = rubric.dimensions.map((rd, i) => {
10596
+ const pd = parsed.dimensions[i];
10597
+ const score = Math.max(0, Math.min(10, pd?.score ?? 0));
10598
+ return {
10599
+ name: rd.name,
10600
+ weight: rd.weight,
10601
+ score,
10602
+ feedback: pd?.feedback ?? ""
10603
+ };
10604
+ });
10605
+ const overallScore = dimensions.reduce((sum, d) => sum + d.score / 10 * d.weight, 0);
10606
+ return {
10607
+ overallScore,
10608
+ dimensions,
10609
+ feedback: parsed.summary ?? "",
10610
+ taskType,
10611
+ success: true
10612
+ };
10613
+ } catch (err) {
10614
+ return {
10615
+ overallScore: 0,
10616
+ dimensions: [],
10617
+ feedback: `Failed to parse evaluation response: ${err instanceof Error ? err.message : String(err)}`,
10618
+ taskType,
10619
+ success: false,
10620
+ error: `Parse error: ${err instanceof Error ? err.message : String(err)}`
10621
+ };
10622
+ }
10623
+ }
10624
+ };
10625
+ }
10626
+ });
10627
+
10628
+ // packages/orchestrator/dist/sessionMetrics.js
10629
+ var SessionMetrics;
10630
+ var init_sessionMetrics = __esm({
10631
+ "packages/orchestrator/dist/sessionMetrics.js"() {
10632
+ "use strict";
10633
+ SessionMetrics = class {
10634
+ sessionStart;
10635
+ turns = 0;
10636
+ toolCalls = 0;
10637
+ toolCallsByName = {};
10638
+ inputTokens = 0;
10639
+ outputTokens = 0;
10640
+ filesModified = /* @__PURE__ */ new Set();
10641
+ tasks = [];
10642
+ currentTask = null;
10643
+ constructor() {
10644
+ this.sessionStart = Date.now();
10645
+ }
10646
+ /** Record a new turn (assistant response). */
10647
+ recordTurn() {
10648
+ this.turns++;
10649
+ }
10650
+ /** Record a tool call. */
10651
+ recordToolCall(toolName) {
10652
+ this.toolCalls++;
10653
+ this.toolCallsByName[toolName] = (this.toolCallsByName[toolName] ?? 0) + 1;
10654
+ if (this.currentTask) {
10655
+ this.currentTask.toolCalls++;
10656
+ }
10657
+ }
10658
+ /** Record token usage. */
10659
+ recordTokenUsage(inputTokens, outputTokens) {
10660
+ this.inputTokens += inputTokens;
10661
+ this.outputTokens += outputTokens;
10662
+ }
10663
+ /** Record a file modification. */
10664
+ recordFileModified(filePath) {
10665
+ this.filesModified.add(filePath);
10666
+ }
10667
+ /** Start tracking a new task. */
10668
+ startTask(description) {
10669
+ this.currentTask = {
10670
+ description,
10671
+ startTime: Date.now(),
10672
+ toolCalls: 0
10673
+ };
10674
+ }
10675
+ /** End the current task with optional evaluation score. */
10676
+ endTask(evaluationScore) {
10677
+ if (this.currentTask) {
10678
+ this.currentTask.endTime = Date.now();
10679
+ this.currentTask.durationMs = this.currentTask.endTime - this.currentTask.startTime;
10680
+ if (evaluationScore !== void 0) {
10681
+ this.currentTask.evaluationScore = evaluationScore;
10682
+ }
10683
+ this.tasks.push(this.currentTask);
10684
+ this.currentTask = null;
10685
+ }
10686
+ }
10687
+ /** Get current session summary. */
10688
+ getSummary() {
10689
+ const now = Date.now();
10690
+ const evalScores = this.tasks.filter((t) => t.evaluationScore !== void 0).map((t) => t.evaluationScore);
10691
+ return {
10692
+ sessionStartTime: this.sessionStart,
10693
+ sessionDurationMs: now - this.sessionStart,
10694
+ totalTurns: this.turns,
10695
+ totalToolCalls: this.toolCalls,
10696
+ toolCallsByName: { ...this.toolCallsByName },
10697
+ totalInputTokens: this.inputTokens,
10698
+ totalOutputTokens: this.outputTokens,
10699
+ filesModified: [...this.filesModified],
10700
+ tasksCompleted: this.tasks.length,
10701
+ tasks: [...this.tasks],
10702
+ averageEvalScore: evalScores.length > 0 ? evalScores.reduce((a, b) => a + b, 0) / evalScores.length : void 0
10703
+ };
10704
+ }
10705
+ /** Get the most-used tools (sorted by count). */
10706
+ getTopTools(limit = 5) {
10707
+ return Object.entries(this.toolCallsByName).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([name, count]) => ({ name, count }));
10708
+ }
10709
+ /** Format duration in human-readable form. */
10710
+ static formatDuration(ms) {
10711
+ if (ms < 1e3)
10712
+ return `${ms}ms`;
10713
+ const seconds = Math.floor(ms / 1e3);
10714
+ if (seconds < 60)
10715
+ return `${seconds}s`;
10716
+ const minutes = Math.floor(seconds / 60);
10717
+ const remainingSecs = seconds % 60;
10718
+ if (minutes < 60)
10719
+ return `${minutes}m ${remainingSecs}s`;
10720
+ const hours = Math.floor(minutes / 60);
10721
+ const remainingMins = minutes % 60;
10722
+ return `${hours}h ${remainingMins}m`;
10723
+ }
10724
+ };
10725
+ }
10726
+ });
10727
+
10728
+ // packages/orchestrator/dist/taskLearning.js
10729
+ var init_taskLearning = __esm({
10730
+ "packages/orchestrator/dist/taskLearning.js"() {
10731
+ "use strict";
10732
+ }
10733
+ });
10734
+
9335
10735
  // packages/orchestrator/dist/index.js
9336
10736
  var init_dist5 = __esm({
9337
10737
  "packages/orchestrator/dist/index.js"() {
@@ -9347,13 +10747,17 @@ var init_dist5 = __esm({
9347
10747
  init_mergeRunner();
9348
10748
  init_retryController();
9349
10749
  init_agenticRunner();
10750
+ init_costTracker();
10751
+ init_workEvaluator();
10752
+ init_sessionMetrics();
10753
+ init_taskLearning();
9350
10754
  }
9351
10755
  });
9352
10756
 
9353
10757
  // packages/cli/dist/tui/listen.js
9354
- import { spawn as spawn5, execSync as execSync9 } from "node:child_process";
10758
+ import { spawn as spawn6, execSync as execSync9 } from "node:child_process";
9355
10759
  import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
9356
- import { join as join16 } from "node:path";
10760
+ import { join as join17 } from "node:path";
9357
10761
  import { homedir as homedir6 } from "node:os";
9358
10762
  import { EventEmitter } from "node:events";
9359
10763
  function isAudioPath(path) {
@@ -9444,16 +10848,16 @@ function ensureTranscribeCliBackground() {
9444
10848
  timeout: 5e3,
9445
10849
  stdio: ["pipe", "pipe", "pipe"]
9446
10850
  }).trim();
9447
- if (existsSync11(join16(globalRoot, "transcribe-cli", "dist", "index.js"))) {
10851
+ if (existsSync11(join17(globalRoot, "transcribe-cli", "dist", "index.js"))) {
9448
10852
  return true;
9449
10853
  }
9450
10854
  } catch {
9451
10855
  }
9452
10856
  try {
9453
10857
  const { exec } = await import("node:child_process");
9454
- return new Promise((resolve16) => {
10858
+ return new Promise((resolve18) => {
9455
10859
  exec("npm i -g transcribe-cli", { timeout: 18e4 }, (err) => {
9456
- resolve16(!err);
10860
+ resolve18(!err);
9457
10861
  });
9458
10862
  });
9459
10863
  } catch {
@@ -9568,24 +10972,24 @@ var init_listen = __esm({
9568
10972
  timeout: 5e3,
9569
10973
  stdio: ["pipe", "pipe", "pipe"]
9570
10974
  }).trim();
9571
- const tcPath = join16(globalRoot, "transcribe-cli");
9572
- if (existsSync11(join16(tcPath, "dist", "index.js"))) {
10975
+ const tcPath = join17(globalRoot, "transcribe-cli");
10976
+ if (existsSync11(join17(tcPath, "dist", "index.js"))) {
9573
10977
  const { createRequire: createRequire4 } = await import("node:module");
9574
10978
  const req = createRequire4(import.meta.url);
9575
- return req(join16(tcPath, "dist", "index.js"));
10979
+ return req(join17(tcPath, "dist", "index.js"));
9576
10980
  }
9577
10981
  } catch {
9578
10982
  }
9579
- const nvmBase = join16(homedir6(), ".nvm", "versions", "node");
10983
+ const nvmBase = join17(homedir6(), ".nvm", "versions", "node");
9580
10984
  if (existsSync11(nvmBase)) {
9581
10985
  try {
9582
10986
  const { readdirSync: readdirSync9 } = await import("node:fs");
9583
10987
  for (const ver of readdirSync9(nvmBase)) {
9584
- const tcPath = join16(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
9585
- if (existsSync11(join16(tcPath, "dist", "index.js"))) {
10988
+ const tcPath = join17(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
10989
+ if (existsSync11(join17(tcPath, "dist", "index.js"))) {
9586
10990
  const { createRequire: createRequire4 } = await import("node:module");
9587
10991
  const req = createRequire4(import.meta.url);
9588
- return req(join16(tcPath, "dist", "index.js"));
10992
+ return req(join17(tcPath, "dist", "index.js"));
9589
10993
  }
9590
10994
  }
9591
10995
  } catch {
@@ -9646,18 +11050,18 @@ var init_listen = __esm({
9646
11050
  this.liveTranscriber.on("error", (err) => {
9647
11051
  this.emit("error", err);
9648
11052
  });
9649
- await new Promise((resolve16, reject) => {
11053
+ await new Promise((resolve18, reject) => {
9650
11054
  const timeout = setTimeout(() => reject(new Error("Model load timeout (60s)")), 6e4);
9651
11055
  this.liveTranscriber.on("ready", () => {
9652
11056
  clearTimeout(timeout);
9653
- resolve16();
11057
+ resolve18();
9654
11058
  });
9655
11059
  this.liveTranscriber.on("error", (err) => {
9656
11060
  clearTimeout(timeout);
9657
11061
  reject(err);
9658
11062
  });
9659
11063
  });
9660
- this.micProcess = spawn5(micCmd.cmd, micCmd.args, {
11064
+ this.micProcess = spawn6(micCmd.cmd, micCmd.args, {
9661
11065
  stdio: ["pipe", "pipe", "pipe"],
9662
11066
  env: { ...process.env }
9663
11067
  });
@@ -9768,9 +11172,9 @@ var init_listen = __esm({
9768
11172
  });
9769
11173
  if (outputDir) {
9770
11174
  const { basename: basename8 } = await import("node:path");
9771
- const transcriptDir = join16(outputDir, ".oa", "transcripts");
11175
+ const transcriptDir = join17(outputDir, ".oa", "transcripts");
9772
11176
  mkdirSync5(transcriptDir, { recursive: true });
9773
- const outFile = join16(transcriptDir, `${basename8(filePath)}.txt`);
11177
+ const outFile = join17(transcriptDir, `${basename8(filePath)}.txt`);
9774
11178
  writeFileSync5(outFile, result.text, "utf-8");
9775
11179
  }
9776
11180
  return {
@@ -9835,6 +11239,53 @@ async function fetchOllamaModels(baseUrl) {
9835
11239
  parameterSize: m.details?.parameter_size
9836
11240
  })).sort((a, b) => b.sizeBytes - a.sizeBytes);
9837
11241
  }
11242
+ async function fetchOpenAIModels(baseUrl, apiKey) {
11243
+ const normalized = normalizeBaseUrl(baseUrl);
11244
+ const url = `${normalized}/v1/models`;
11245
+ const headers = {};
11246
+ if (apiKey) {
11247
+ headers["Authorization"] = `Bearer ${apiKey}`;
11248
+ }
11249
+ const resp = await fetch(url, {
11250
+ headers,
11251
+ signal: AbortSignal.timeout(1e4)
11252
+ });
11253
+ if (!resp.ok) {
11254
+ throw new Error(`Failed to fetch models: HTTP ${resp.status}`);
11255
+ }
11256
+ const data = await resp.json();
11257
+ const models = data.data ?? [];
11258
+ return models.map((m) => ({
11259
+ name: m.id,
11260
+ size: m.context_length ? `${Math.round(m.context_length / 1024)}K ctx` : m.max_model_len ? `${Math.round(m.max_model_len / 1024)}K ctx` : "",
11261
+ sizeBytes: 0,
11262
+ modified: m.created ? formatRelativeTime(new Date(m.created * 1e3).toISOString()) : "",
11263
+ parameterSize: m.owned_by ?? void 0
11264
+ })).sort((a, b) => a.name.localeCompare(b.name));
11265
+ }
11266
+ async function fetchModels(baseUrl, apiKey) {
11267
+ const provider = detectProvider(baseUrl);
11268
+ if (provider.id === "ollama") {
11269
+ try {
11270
+ return await fetchOllamaModels(baseUrl);
11271
+ } catch {
11272
+ try {
11273
+ return await fetchOpenAIModels(baseUrl, apiKey);
11274
+ } catch {
11275
+ throw new Error("Cannot reach Ollama at " + baseUrl);
11276
+ }
11277
+ }
11278
+ }
11279
+ try {
11280
+ return await fetchOpenAIModels(baseUrl, apiKey);
11281
+ } catch {
11282
+ try {
11283
+ return await fetchOllamaModels(baseUrl);
11284
+ } catch {
11285
+ throw new Error(`Cannot fetch models from ${provider.label} at ${baseUrl}`);
11286
+ }
11287
+ }
11288
+ }
9838
11289
  function findModel(models, query) {
9839
11290
  const exact = models.find((m) => m.name === query);
9840
11291
  if (exact)
@@ -10330,6 +11781,10 @@ function renderSlashHelp() {
10330
11781
  ["/listen confirm", "Require Enter to submit transcription"],
10331
11782
  ["/listen auto", "Auto-submit after 3s silence (blinking \u25CF indicator)"],
10332
11783
  ["/listen stop", "Stop listening"],
11784
+ ["/cost", "Show session token cost breakdown"],
11785
+ ["/evaluate", "Evaluate last completed task (LLM quality scoring)"],
11786
+ ["/task-type", "Set task type (code, document, analysis, plan, general, auto)"],
11787
+ ["/stats", "Show session dashboard (metrics, tool usage, task history)"],
10333
11788
  ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
10334
11789
  ["/tools", "List agent-created custom tools"],
10335
11790
  ["/skills", "List available AIWG skills"],
@@ -10708,22 +12163,215 @@ var init_render = __esm({
10708
12163
  }
10709
12164
  });
10710
12165
 
12166
+ // packages/prompts/dist/loader.js
12167
+ var init_loader = __esm({
12168
+ "packages/prompts/dist/loader.js"() {
12169
+ "use strict";
12170
+ }
12171
+ });
12172
+
12173
+ // packages/prompts/dist/render.js
12174
+ var init_render2 = __esm({
12175
+ "packages/prompts/dist/render.js"() {
12176
+ "use strict";
12177
+ }
12178
+ });
12179
+
12180
+ // packages/prompts/dist/task-templates.js
12181
+ function getTaskTemplate(type) {
12182
+ return TEMPLATES[type];
12183
+ }
12184
+ function getTaskTypes() {
12185
+ return Object.keys(TEMPLATES);
12186
+ }
12187
+ function buildTaskContext(type) {
12188
+ const template = TEMPLATES[type];
12189
+ return `${template.systemPromptAddition}
12190
+
12191
+ ### Output Guidance
12192
+ ${template.outputGuidance}`;
12193
+ }
12194
+ var TEMPLATES;
12195
+ var init_task_templates = __esm({
12196
+ "packages/prompts/dist/task-templates.js"() {
12197
+ "use strict";
12198
+ TEMPLATES = {
12199
+ code: {
12200
+ type: "code",
12201
+ label: "Software Development",
12202
+ description: "Writing, debugging, refactoring, or reviewing code.",
12203
+ systemPromptAddition: `
12204
+ ## Task Context: Software Development
12205
+
12206
+ You are working on a software development task. Follow these principles:
12207
+
12208
+ - **Read before writing**: Always read the existing code and understand the context before making changes.
12209
+ - **Test-driven approach**: Run existing tests first, make changes, then verify tests still pass.
12210
+ - **Minimal changes**: Make the smallest change that solves the problem. Avoid refactoring unrelated code.
12211
+ - **Convention adherence**: Match the existing code style, naming conventions, and patterns in the project.
12212
+ - **Error handling**: Ensure proper error handling and edge case coverage.
12213
+ - **File organization**: Follow the existing project structure when adding new files.
12214
+
12215
+ When the task is complete, verify by running tests and/or type-checking where available.`,
12216
+ recommendedTools: [
12217
+ "file_read",
12218
+ "file_write",
12219
+ "file_edit",
12220
+ "shell",
12221
+ "grep_search",
12222
+ "glob_find",
12223
+ "git_info",
12224
+ "codebase_map"
12225
+ ],
12226
+ outputGuidance: "Deliver working code with tests passing. Summarize what was changed and why."
12227
+ },
12228
+ document: {
12229
+ type: "document",
12230
+ label: "Document Drafting",
12231
+ description: "Creating reports, specifications, guides, proposals, or other professional documents.",
12232
+ systemPromptAddition: `
12233
+ ## Task Context: Document Drafting
12234
+
12235
+ You are working on a professional document. Follow these principles:
12236
+
12237
+ - **Audience awareness**: Tailor language, depth, and terminology to the intended audience.
12238
+ - **Structure first**: Create a clear outline before filling in content. Use headings, sections, and lists.
12239
+ - **Completeness**: Cover all aspects the user requested. Flag any areas where you need clarification.
12240
+ - **Clarity**: Use clear, concise language. Avoid jargon unless appropriate for the audience.
12241
+ - **Formatting**: Use appropriate markdown formatting \u2014 tables for data, code blocks for technical content, lists for enumerations.
12242
+ - **Citations**: When referencing external information, note sources or indicate where citations are needed.
12243
+
12244
+ Use the create_structured_file tool for spreadsheets, CSV, or formatted output. Use file_write for documents.`,
12245
+ recommendedTools: [
12246
+ "file_read",
12247
+ "file_write",
12248
+ "web_search",
12249
+ "web_fetch",
12250
+ "create_structured_file",
12251
+ "memory_read"
12252
+ ],
12253
+ outputGuidance: "Deliver a complete, well-structured document. Specify the output format (Markdown, PDF, etc.)."
12254
+ },
12255
+ analysis: {
12256
+ type: "analysis",
12257
+ label: "Analysis & Research",
12258
+ description: "Analyzing data, evaluating options, conducting research, or producing insights.",
12259
+ systemPromptAddition: `
12260
+ ## Task Context: Analysis & Research
12261
+
12262
+ You are working on an analytical task. Follow these principles:
12263
+
12264
+ - **Evidence-based**: Ground conclusions in data, code, or verifiable sources. Avoid speculation.
12265
+ - **Methodology**: State your analytical approach clearly. When comparing options, define criteria upfront.
12266
+ - **Quantify**: Use numbers, metrics, and measurements wherever possible. Avoid vague qualifiers.
12267
+ - **Visualization**: Present data in tables or structured formats for clarity.
12268
+ - **Limitations**: Acknowledge limitations in your analysis \u2014 data gaps, assumptions, scope boundaries.
12269
+ - **Actionable conclusions**: End with clear recommendations or next steps.
12270
+
12271
+ Use web_search for external research. Use grep_search and codebase_map for codebase analysis.
12272
+ Use create_structured_file to output data as CSV, JSON, or markdown tables.`,
12273
+ recommendedTools: [
12274
+ "file_read",
12275
+ "grep_search",
12276
+ "glob_find",
12277
+ "web_search",
12278
+ "web_fetch",
12279
+ "codebase_map",
12280
+ "create_structured_file",
12281
+ "shell"
12282
+ ],
12283
+ outputGuidance: "Deliver a structured analysis with findings, data tables, and actionable recommendations."
12284
+ },
12285
+ plan: {
12286
+ type: "plan",
12287
+ label: "Planning & Design",
12288
+ description: "Creating project plans, system designs, architecture proposals, or roadmaps.",
12289
+ systemPromptAddition: `
12290
+ ## Task Context: Planning & Design
12291
+
12292
+ You are working on a planning or design task. Follow these principles:
12293
+
12294
+ - **Scope clarity**: Define what is in-scope and out-of-scope explicitly.
12295
+ - **Phased approach**: Break work into phases or milestones with clear dependencies.
12296
+ - **Risk identification**: Identify key risks, assumptions, and dependencies for each phase.
12297
+ - **Resource awareness**: Consider team size, skill requirements, and tool/infrastructure needs.
12298
+ - **Alternatives**: When making design decisions, briefly note alternatives considered and why the chosen approach is preferred.
12299
+ - **Actionable items**: Every section should include concrete next steps or action items.
12300
+ - **Traceability**: Link plan items to requirements or goals where applicable.
12301
+
12302
+ Use codebase_map and grep_search to understand existing architecture.
12303
+ Use create_structured_file for timeline/schedule output.`,
12304
+ recommendedTools: [
12305
+ "file_read",
12306
+ "grep_search",
12307
+ "glob_find",
12308
+ "codebase_map",
12309
+ "web_search",
12310
+ "create_structured_file",
12311
+ "file_write",
12312
+ "git_info"
12313
+ ],
12314
+ outputGuidance: "Deliver a structured plan with phases, milestones, risks, and action items."
12315
+ },
12316
+ general: {
12317
+ type: "general",
12318
+ label: "General Task",
12319
+ description: "Tasks that don't clearly fit another category.",
12320
+ systemPromptAddition: `
12321
+ ## Task Context: General
12322
+
12323
+ Approach this task thoughtfully:
12324
+
12325
+ - **Clarify intent**: If the task is ambiguous, use available tools to gather context before proceeding.
12326
+ - **Appropriate tools**: Select the right tools for the job \u2014 file tools for file work, search for research, shell for commands.
12327
+ - **Quality output**: Regardless of task type, produce clear, complete, and well-organized output.
12328
+ - **Verify results**: After completing work, verify the results are correct and complete.`,
12329
+ recommendedTools: [
12330
+ "file_read",
12331
+ "file_write",
12332
+ "shell",
12333
+ "grep_search",
12334
+ "web_search",
12335
+ "memory_read"
12336
+ ],
12337
+ outputGuidance: "Deliver complete results with a clear summary of what was done."
12338
+ }
12339
+ };
12340
+ }
12341
+ });
12342
+
12343
+ // packages/prompts/dist/index.js
12344
+ import { join as join18, dirname as dirname4 } from "node:path";
12345
+ import { fileURLToPath } from "node:url";
12346
+ var _dir, _packageRoot;
12347
+ var init_dist6 = __esm({
12348
+ "packages/prompts/dist/index.js"() {
12349
+ "use strict";
12350
+ init_loader();
12351
+ init_render2();
12352
+ init_task_templates();
12353
+ init_render2();
12354
+ _dir = dirname4(fileURLToPath(import.meta.url));
12355
+ _packageRoot = join18(_dir, "..");
12356
+ }
12357
+ });
12358
+
10711
12359
  // packages/cli/dist/tui/oa-directory.js
10712
12360
  import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6, readdirSync as readdirSync6, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
10713
- import { join as join17, relative as relative2, basename as basename4, extname as extname5 } from "node:path";
12361
+ import { join as join19, relative as relative2, basename as basename4, extname as extname7 } from "node:path";
10714
12362
  import { homedir as homedir7 } from "node:os";
10715
12363
  function initOaDirectory(repoRoot) {
10716
- const oaPath = join17(repoRoot, OA_DIR);
12364
+ const oaPath = join19(repoRoot, OA_DIR);
10717
12365
  for (const sub of SUBDIRS) {
10718
- mkdirSync6(join17(oaPath, sub), { recursive: true });
12366
+ mkdirSync6(join19(oaPath, sub), { recursive: true });
10719
12367
  }
10720
12368
  return oaPath;
10721
12369
  }
10722
12370
  function hasOaDirectory(repoRoot) {
10723
- return existsSync12(join17(repoRoot, OA_DIR, "index"));
12371
+ return existsSync12(join19(repoRoot, OA_DIR, "index"));
10724
12372
  }
10725
12373
  function loadProjectSettings(repoRoot) {
10726
- const settingsPath = join17(repoRoot, OA_DIR, "settings.json");
12374
+ const settingsPath = join19(repoRoot, OA_DIR, "settings.json");
10727
12375
  try {
10728
12376
  if (existsSync12(settingsPath)) {
10729
12377
  return JSON.parse(readFileSync10(settingsPath, "utf-8"));
@@ -10733,14 +12381,14 @@ function loadProjectSettings(repoRoot) {
10733
12381
  return {};
10734
12382
  }
10735
12383
  function saveProjectSettings(repoRoot, settings) {
10736
- const oaPath = join17(repoRoot, OA_DIR);
12384
+ const oaPath = join19(repoRoot, OA_DIR);
10737
12385
  mkdirSync6(oaPath, { recursive: true });
10738
12386
  const existing = loadProjectSettings(repoRoot);
10739
12387
  const merged = { ...existing, ...settings };
10740
- writeFileSync6(join17(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
12388
+ writeFileSync6(join19(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
10741
12389
  }
10742
12390
  function loadGlobalSettings() {
10743
- const settingsPath = join17(homedir7(), ".open-agents", "settings.json");
12391
+ const settingsPath = join19(homedir7(), ".open-agents", "settings.json");
10744
12392
  try {
10745
12393
  if (existsSync12(settingsPath)) {
10746
12394
  return JSON.parse(readFileSync10(settingsPath, "utf-8"));
@@ -10750,11 +12398,11 @@ function loadGlobalSettings() {
10750
12398
  return {};
10751
12399
  }
10752
12400
  function saveGlobalSettings(settings) {
10753
- const dir = join17(homedir7(), ".open-agents");
12401
+ const dir = join19(homedir7(), ".open-agents");
10754
12402
  mkdirSync6(dir, { recursive: true });
10755
12403
  const existing = loadGlobalSettings();
10756
12404
  const merged = { ...existing, ...settings };
10757
- writeFileSync6(join17(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
12405
+ writeFileSync6(join19(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", "utf-8");
10758
12406
  }
10759
12407
  function resolveSettings(repoRoot) {
10760
12408
  const global = loadGlobalSettings();
@@ -10769,7 +12417,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10769
12417
  while (dir && !visited.has(dir)) {
10770
12418
  visited.add(dir);
10771
12419
  for (const name of CONTEXT_FILES) {
10772
- const filePath = join17(dir, name);
12420
+ const filePath = join19(dir, name);
10773
12421
  const normalizedName = name.toLowerCase();
10774
12422
  if (existsSync12(filePath) && !seen.has(filePath)) {
10775
12423
  seen.add(filePath);
@@ -10788,7 +12436,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10788
12436
  }
10789
12437
  }
10790
12438
  }
10791
- const projectMap = join17(dir, OA_DIR, "context", "project-map.md");
12439
+ const projectMap = join19(dir, OA_DIR, "context", "project-map.md");
10792
12440
  if (existsSync12(projectMap) && !seen.has(projectMap)) {
10793
12441
  seen.add(projectMap);
10794
12442
  try {
@@ -10804,7 +12452,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10804
12452
  } catch {
10805
12453
  }
10806
12454
  }
10807
- const parent = join17(dir, "..");
12455
+ const parent = join19(dir, "..");
10808
12456
  if (parent === dir)
10809
12457
  break;
10810
12458
  dir = parent;
@@ -10822,7 +12470,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10822
12470
  return found;
10823
12471
  }
10824
12472
  function readIndexMeta(repoRoot) {
10825
- const metaPath = join17(repoRoot, OA_DIR, "index", "meta.json");
12473
+ const metaPath = join19(repoRoot, OA_DIR, "index", "meta.json");
10826
12474
  try {
10827
12475
  return JSON.parse(readFileSync10(metaPath, "utf-8"));
10828
12476
  } catch {
@@ -10875,28 +12523,28 @@ ${tree}\`\`\`
10875
12523
  sections.push("");
10876
12524
  }
10877
12525
  const content = sections.join("\n");
10878
- const contextDir = join17(repoRoot, OA_DIR, "context");
12526
+ const contextDir = join19(repoRoot, OA_DIR, "context");
10879
12527
  mkdirSync6(contextDir, { recursive: true });
10880
- writeFileSync6(join17(contextDir, "project-map.md"), content, "utf-8");
12528
+ writeFileSync6(join19(contextDir, "project-map.md"), content, "utf-8");
10881
12529
  return content;
10882
12530
  }
10883
12531
  function saveSession(repoRoot, session) {
10884
- const historyDir = join17(repoRoot, OA_DIR, "history");
12532
+ const historyDir = join19(repoRoot, OA_DIR, "history");
10885
12533
  mkdirSync6(historyDir, { recursive: true });
10886
- writeFileSync6(join17(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
12534
+ writeFileSync6(join19(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
10887
12535
  }
10888
12536
  function loadRecentSessions(repoRoot, limit = 5) {
10889
- const historyDir = join17(repoRoot, OA_DIR, "history");
12537
+ const historyDir = join19(repoRoot, OA_DIR, "history");
10890
12538
  if (!existsSync12(historyDir))
10891
12539
  return [];
10892
12540
  try {
10893
12541
  const files = readdirSync6(historyDir).filter((f) => f.endsWith(".json")).map((f) => {
10894
- const stat3 = statSync5(join17(historyDir, f));
10895
- return { file: f, mtime: stat3.mtimeMs };
12542
+ const stat5 = statSync5(join19(historyDir, f));
12543
+ return { file: f, mtime: stat5.mtimeMs };
10896
12544
  }).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
10897
12545
  return files.map((f) => {
10898
12546
  try {
10899
- return JSON.parse(readFileSync10(join17(historyDir, f.file), "utf-8"));
12547
+ return JSON.parse(readFileSync10(join19(historyDir, f.file), "utf-8"));
10900
12548
  } catch {
10901
12549
  return null;
10902
12550
  }
@@ -10906,12 +12554,12 @@ function loadRecentSessions(repoRoot, limit = 5) {
10906
12554
  }
10907
12555
  }
10908
12556
  function savePendingTask(repoRoot, task) {
10909
- const historyDir = join17(repoRoot, OA_DIR, "history");
12557
+ const historyDir = join19(repoRoot, OA_DIR, "history");
10910
12558
  mkdirSync6(historyDir, { recursive: true });
10911
- writeFileSync6(join17(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
12559
+ writeFileSync6(join19(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
10912
12560
  }
10913
12561
  function loadPendingTask(repoRoot) {
10914
- const filePath = join17(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
12562
+ const filePath = join19(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
10915
12563
  try {
10916
12564
  if (!existsSync12(filePath))
10917
12565
  return null;
@@ -10943,7 +12591,7 @@ function detectManifests(repoRoot) {
10943
12591
  { file: "docker-compose.yaml", type: "Docker Compose" }
10944
12592
  ];
10945
12593
  for (const check of checks) {
10946
- const filePath = join17(repoRoot, check.file);
12594
+ const filePath = join19(repoRoot, check.file);
10947
12595
  if (existsSync12(filePath)) {
10948
12596
  let name;
10949
12597
  if (check.nameField) {
@@ -10977,7 +12625,7 @@ function findKeyFiles(repoRoot) {
10977
12625
  { pattern: "CLAUDE.md", description: "Claude Code context" }
10978
12626
  ];
10979
12627
  for (const check of checks) {
10980
- if (existsSync12(join17(repoRoot, check.pattern))) {
12628
+ if (existsSync12(join19(repoRoot, check.pattern))) {
10981
12629
  keyFiles.push({ path: check.pattern, description: check.description });
10982
12630
  }
10983
12631
  }
@@ -11003,12 +12651,12 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
11003
12651
  if (entry.isDirectory()) {
11004
12652
  let fileCount = 0;
11005
12653
  try {
11006
- fileCount = readdirSync6(join17(root, entry.name)).filter((f) => !f.startsWith(".")).length;
12654
+ fileCount = readdirSync6(join19(root, entry.name)).filter((f) => !f.startsWith(".")).length;
11007
12655
  } catch {
11008
12656
  }
11009
12657
  result += `${prefix}${connector}${entry.name}/ (${fileCount})
11010
12658
  `;
11011
- result += buildDirTree(join17(root, entry.name), maxDepth, childPrefix, depth + 1);
12659
+ result += buildDirTree(join19(root, entry.name), maxDepth, childPrefix, depth + 1);
11012
12660
  } else if (depth < maxDepth) {
11013
12661
  result += `${prefix}${connector}${entry.name}
11014
12662
  `;
@@ -11061,7 +12709,7 @@ var init_oa_directory = __esm({
11061
12709
  import * as readline from "node:readline";
11062
12710
  import { execSync as execSync10 } from "node:child_process";
11063
12711
  import { existsSync as existsSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
11064
- import { join as join18 } from "node:path";
12712
+ import { join as join20 } from "node:path";
11065
12713
  import { homedir as homedir8 } from "node:os";
11066
12714
  function detectSystemSpecs() {
11067
12715
  let totalRamGB = 0;
@@ -11145,8 +12793,8 @@ function modelSupportsToolCalling(modelName) {
11145
12793
  return false;
11146
12794
  }
11147
12795
  function ask(rl, question) {
11148
- return new Promise((resolve16) => {
11149
- rl.question(question, (answer) => resolve16(answer.trim()));
12796
+ return new Promise((resolve18) => {
12797
+ rl.question(question, (answer) => resolve18(answer.trim()));
11150
12798
  });
11151
12799
  }
11152
12800
  function pullModelWithAutoUpdate(tag) {
@@ -11495,9 +13143,9 @@ async function doSetup(config, rl) {
11495
13143
  `PARAMETER num_predict 16384`,
11496
13144
  `PARAMETER stop "<|endoftext|>"`
11497
13145
  ].join("\n");
11498
- const modelDir2 = join18(homedir8(), ".open-agents", "models");
13146
+ const modelDir2 = join20(homedir8(), ".open-agents", "models");
11499
13147
  mkdirSync7(modelDir2, { recursive: true });
11500
- const modelfilePath = join18(modelDir2, `Modelfile.${customName}`);
13148
+ const modelfilePath = join20(modelDir2, `Modelfile.${customName}`);
11501
13149
  writeFileSync7(modelfilePath, modelfileContent + "\n", "utf8");
11502
13150
  process.stdout.write(` ${c2.dim("Creating model...")} `);
11503
13151
  execSync10(`ollama create ${customName} -f ${modelfilePath}`, {
@@ -11535,7 +13183,7 @@ async function doSetup(config, rl) {
11535
13183
  }
11536
13184
  async function isModelAvailable(config) {
11537
13185
  try {
11538
- const models = await fetchOllamaModels(config.backendUrl);
13186
+ const models = await fetchModels(config.backendUrl, config.apiKey);
11539
13187
  return !!findModel(models, config.model);
11540
13188
  } catch {
11541
13189
  return false;
@@ -11543,7 +13191,7 @@ async function isModelAvailable(config) {
11543
13191
  }
11544
13192
  function isFirstRun() {
11545
13193
  try {
11546
- return !existsSync13(join18(homedir8(), ".open-agents", "config.json"));
13194
+ return !existsSync13(join20(homedir8(), ".open-agents", "config.json"));
11547
13195
  } catch {
11548
13196
  return true;
11549
13197
  }
@@ -11581,9 +13229,9 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
11581
13229
  `PARAMETER num_predict 16384`,
11582
13230
  `PARAMETER stop "<|endoftext|>"`
11583
13231
  ].join("\n");
11584
- const modelDir2 = join18(homedir8(), ".open-agents", "models");
13232
+ const modelDir2 = join20(homedir8(), ".open-agents", "models");
11585
13233
  mkdirSync7(modelDir2, { recursive: true });
11586
- const modelfilePath = join18(modelDir2, `Modelfile.${customName}`);
13234
+ const modelfilePath = join20(modelDir2, `Modelfile.${customName}`);
11587
13235
  writeFileSync7(modelfilePath, modelfileContent + "\n", "utf8");
11588
13236
  execSync10(`ollama create ${customName} -f ${modelfilePath}`, {
11589
13237
  stdio: "pipe",
@@ -11712,6 +13360,174 @@ async function handleSlashCommand(input, ctx) {
11712
13360
  dryRun: String(ctx.config.dryRun)
11713
13361
  });
11714
13362
  return "handled";
13363
+ case "cost":
13364
+ case "costs":
13365
+ if (ctx.costTracker) {
13366
+ const summary = ctx.costTracker.getSummary();
13367
+ const lines = [];
13368
+ lines.push(c2.bold(` Session Cost Summary (${summary.provider})`));
13369
+ lines.push("");
13370
+ lines.push(` Input tokens: ${c2.bold(summary.totalInputTokens.toLocaleString())}`);
13371
+ lines.push(` Output tokens: ${c2.bold(summary.totalOutputTokens.toLocaleString())}`);
13372
+ if (ctx.costTracker.hasPricing) {
13373
+ lines.push(` Estimated cost: ${c2.bold(c2.yellow(ctx.costTracker.formatCost()))}`);
13374
+ lines.push(` Pricing: $${ctx.costTracker.provider.inputPer1M}/1M in, $${ctx.costTracker.provider.outputPer1M}/1M out`);
13375
+ } else {
13376
+ lines.push(` Cost: ${c2.dim("free (local provider)")}`);
13377
+ }
13378
+ if (summary.taskCount > 0) {
13379
+ lines.push("");
13380
+ lines.push(` Tasks: ${summary.taskCount}`);
13381
+ for (const t of summary.tasks.slice(-5)) {
13382
+ const costStr = ctx.costTracker.hasPricing ? ` ${ctx.costTracker.formatCost(t.estimatedCost)}` : "";
13383
+ const dur = (t.durationMs / 1e3).toFixed(1);
13384
+ lines.push(` ${c2.dim("\u2022")} ${t.task.slice(0, 50)}${t.task.length > 50 ? "..." : ""} \u2014 ${dur}s${costStr}`);
13385
+ }
13386
+ if (summary.taskCount > 5) {
13387
+ lines.push(` ${c2.dim(`... and ${summary.taskCount - 5} more`)}`);
13388
+ }
13389
+ }
13390
+ lines.push("");
13391
+ console.log(lines.join("\n"));
13392
+ } else {
13393
+ renderInfo("Cost tracking not available.");
13394
+ }
13395
+ return "handled";
13396
+ case "evaluate":
13397
+ case "eval":
13398
+ if (ctx.workEvaluator && ctx.lastTask && ctx.lastSummary) {
13399
+ renderInfo("Evaluating last completed task...");
13400
+ try {
13401
+ const evalResult = await ctx.workEvaluator.evaluate(ctx.lastTask, ctx.lastSummary);
13402
+ if (evalResult.success) {
13403
+ const lines = [];
13404
+ const pct = Math.round(evalResult.overallScore * 100);
13405
+ const scoreColor = pct >= 80 ? c2.green : pct >= 60 ? c2.yellow : c2.red;
13406
+ lines.push(`
13407
+ ${c2.bold("Task Evaluation")} (${evalResult.taskType})`);
13408
+ lines.push(` Overall: ${scoreColor(c2.bold(`${pct}%`))}`);
13409
+ lines.push("");
13410
+ for (const d of evalResult.dimensions) {
13411
+ const bar = "\u2588".repeat(d.score) + "\u2591".repeat(10 - d.score);
13412
+ const dPct = Math.round(d.score / 10 * 100);
13413
+ const dColor = dPct >= 80 ? c2.green : dPct >= 60 ? c2.yellow : c2.red;
13414
+ lines.push(` ${d.name.padEnd(14)} ${dColor(bar)} ${dColor(`${d.score}/10`)} ${c2.dim(`(${Math.round(d.weight * 100)}%)`)}`);
13415
+ if (d.feedback)
13416
+ lines.push(` ${c2.dim(" " + d.feedback)}`);
13417
+ }
13418
+ if (evalResult.feedback) {
13419
+ lines.push("");
13420
+ lines.push(` ${c2.dim(evalResult.feedback)}`);
13421
+ }
13422
+ lines.push("");
13423
+ console.log(lines.join("\n"));
13424
+ } else {
13425
+ renderError(`Evaluation failed: ${evalResult.error}`);
13426
+ }
13427
+ } catch (err) {
13428
+ renderError(`Evaluation error: ${err instanceof Error ? err.message : String(err)}`);
13429
+ }
13430
+ } else if (!ctx.lastTask) {
13431
+ renderWarning("No completed task to evaluate. Complete a task first.");
13432
+ } else {
13433
+ renderWarning("Evaluation backend not configured.");
13434
+ }
13435
+ return "handled";
13436
+ case "task-type":
13437
+ case "tasktype":
13438
+ case "tt": {
13439
+ const validTypes = getTaskTypes();
13440
+ if (arg === "auto" || arg === "clear" || arg === "reset") {
13441
+ ctx.setTaskType?.(void 0);
13442
+ renderInfo("Task type set to auto-detect.");
13443
+ } else if (arg && validTypes.includes(arg)) {
13444
+ const type = arg;
13445
+ const template = getTaskTemplate(type);
13446
+ ctx.setTaskType?.(type);
13447
+ renderInfo(`Task type: ${c2.bold(template.label)} (${type})`);
13448
+ console.log(` ${c2.dim(template.description)}`);
13449
+ console.log(` ${c2.dim("Recommended tools:")} ${template.recommendedTools.slice(0, 5).join(", ")}`);
13450
+ console.log("");
13451
+ } else {
13452
+ const lines = [];
13453
+ lines.push(`
13454
+ ${c2.bold("Available Task Types")}
13455
+ `);
13456
+ for (const t of validTypes) {
13457
+ const tmpl = getTaskTemplate(t);
13458
+ const active = ctx.taskType === t ? c2.green(" \u2190 active") : "";
13459
+ lines.push(` ${c2.bold(t.padEnd(10))} ${tmpl.label}${active}`);
13460
+ lines.push(` ${c2.dim(" " + tmpl.description)}`);
13461
+ }
13462
+ if (!ctx.taskType) {
13463
+ lines.push(`
13464
+ ${c2.dim("Current: auto-detect")}`);
13465
+ }
13466
+ lines.push(`
13467
+ ${c2.dim("Usage: /task-type <type> or /task-type auto")}`);
13468
+ lines.push("");
13469
+ console.log(lines.join("\n"));
13470
+ }
13471
+ return "handled";
13472
+ }
13473
+ case "stats":
13474
+ case "metrics":
13475
+ case "dashboard": {
13476
+ if (!ctx.sessionMetrics) {
13477
+ renderInfo("Session metrics not available.");
13478
+ return "handled";
13479
+ }
13480
+ const summary = ctx.sessionMetrics.getSummary();
13481
+ const dur = SessionMetrics.formatDuration(summary.sessionDurationMs);
13482
+ const lines = [];
13483
+ lines.push(`
13484
+ ${c2.bold("Session Dashboard")}
13485
+ `);
13486
+ lines.push(` Duration: ${c2.bold(dur)}`);
13487
+ lines.push(` Tasks completed: ${c2.bold(String(summary.tasksCompleted))}`);
13488
+ lines.push(` Turns: ${c2.bold(String(summary.totalTurns))}`);
13489
+ lines.push(` Tool calls: ${c2.bold(String(summary.totalToolCalls))}`);
13490
+ lines.push(` Input tokens: ${c2.bold(summary.totalInputTokens.toLocaleString())}`);
13491
+ lines.push(` Output tokens: ${c2.bold(summary.totalOutputTokens.toLocaleString())}`);
13492
+ if (ctx.costTracker?.hasPricing) {
13493
+ lines.push(` Estimated cost: ${c2.bold(c2.yellow(ctx.costTracker.formatCost()))}`);
13494
+ }
13495
+ const topTools = ctx.sessionMetrics.getTopTools(5);
13496
+ if (topTools.length > 0) {
13497
+ lines.push(`
13498
+ ${c2.bold("Top Tools")}`);
13499
+ for (const t of topTools) {
13500
+ const bar = "\u2588".repeat(Math.min(t.count, 30));
13501
+ lines.push(` ${t.name.padEnd(20)} ${c2.dim(bar)} ${t.count}`);
13502
+ }
13503
+ }
13504
+ if (summary.filesModified.length > 0) {
13505
+ lines.push(`
13506
+ ${c2.bold("Files Modified")} (${summary.filesModified.length})`);
13507
+ for (const f of summary.filesModified.slice(0, 10)) {
13508
+ lines.push(` ${c2.dim("\u2022")} ${f}`);
13509
+ }
13510
+ if (summary.filesModified.length > 10) {
13511
+ lines.push(` ${c2.dim(`... and ${summary.filesModified.length - 10} more`)}`);
13512
+ }
13513
+ }
13514
+ if (summary.tasks.length > 0) {
13515
+ lines.push(`
13516
+ ${c2.bold("Task History")}`);
13517
+ for (const task of summary.tasks.slice(-5)) {
13518
+ const taskDur = task.durationMs ? SessionMetrics.formatDuration(task.durationMs) : "?";
13519
+ const score = task.evaluationScore !== void 0 ? ` ${c2.dim(`(${Math.round(task.evaluationScore * 100)}%)`)}` : "";
13520
+ lines.push(` ${c2.dim("\u2022")} ${task.description.slice(0, 50)}${task.description.length > 50 ? "..." : ""} \u2014 ${taskDur}${score}`);
13521
+ }
13522
+ }
13523
+ if (summary.averageEvalScore !== void 0) {
13524
+ lines.push(`
13525
+ Avg eval score: ${c2.bold(`${Math.round(summary.averageEvalScore * 100)}%`)}`);
13526
+ }
13527
+ lines.push("");
13528
+ console.log(lines.join("\n"));
13529
+ return "handled";
13530
+ }
11715
13531
  case "model":
11716
13532
  if (arg) {
11717
13533
  await switchModel(arg, ctx, hasLocal);
@@ -11910,7 +13726,7 @@ async function handleSlashCommand(input, ctx) {
11910
13726
  }
11911
13727
  async function listModels(ctx) {
11912
13728
  try {
11913
- const models = await fetchOllamaModels(ctx.config.backendUrl);
13729
+ const models = await fetchModels(ctx.config.backendUrl, ctx.config.apiKey);
11914
13730
  renderModelList(models.map((m) => ({ name: m.name, size: m.size, modified: m.modified })), ctx.config.model);
11915
13731
  } catch (err) {
11916
13732
  renderError(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
@@ -11918,9 +13734,9 @@ async function listModels(ctx) {
11918
13734
  }
11919
13735
  async function showModelPicker(ctx) {
11920
13736
  try {
11921
- const models = await fetchOllamaModels(ctx.config.backendUrl);
13737
+ const models = await fetchModels(ctx.config.backendUrl, ctx.config.apiKey);
11922
13738
  if (models.length === 0) {
11923
- renderWarning("No models found. Pull a model with: ollama pull <model>");
13739
+ renderWarning("No models found.");
11924
13740
  return;
11925
13741
  }
11926
13742
  renderModelList(models.map((m) => ({ name: m.name, size: m.size, modified: m.modified })), ctx.config.model);
@@ -12051,15 +13867,15 @@ async function handleUpdate(subcommand, ctx) {
12051
13867
  let currentVersion = "0.0.0";
12052
13868
  try {
12053
13869
  const { createRequire: createRequire4 } = await import("node:module");
12054
- const { fileURLToPath: fileURLToPath3 } = await import("node:url");
12055
- const { dirname: dirname5, join: join28 } = await import("node:path");
13870
+ const { fileURLToPath: fileURLToPath4 } = await import("node:url");
13871
+ const { dirname: dirname7, join: join30 } = await import("node:path");
12056
13872
  const { existsSync: existsSync19 } = await import("node:fs");
12057
13873
  const req = createRequire4(import.meta.url);
12058
- const thisDir = dirname5(fileURLToPath3(import.meta.url));
13874
+ const thisDir = dirname7(fileURLToPath4(import.meta.url));
12059
13875
  const candidates = [
12060
- join28(thisDir, "..", "package.json"),
12061
- join28(thisDir, "..", "..", "package.json"),
12062
- join28(thisDir, "..", "..", "..", "package.json")
13876
+ join30(thisDir, "..", "package.json"),
13877
+ join30(thisDir, "..", "..", "package.json"),
13878
+ join30(thisDir, "..", "..", "..", "package.json")
12063
13879
  ];
12064
13880
  for (const pkgPath of candidates) {
12065
13881
  if (existsSync19(pkgPath)) {
@@ -12114,7 +13930,7 @@ async function handleUpdate(subcommand, ctx) {
12114
13930
  }
12115
13931
  async function switchModel(query, ctx, local = false) {
12116
13932
  try {
12117
- const models = await fetchOllamaModels(ctx.config.backendUrl);
13933
+ const models = await fetchModels(ctx.config.backendUrl, ctx.config.apiKey);
12118
13934
  const match = findModel(models, query);
12119
13935
  if (!match) {
12120
13936
  renderError(`Model not found: "${query}"`);
@@ -12157,6 +13973,8 @@ var init_commands = __esm({
12157
13973
  init_render();
12158
13974
  init_dist2();
12159
13975
  init_config();
13976
+ init_dist5();
13977
+ init_dist6();
12160
13978
  init_updater();
12161
13979
  init_oa_directory();
12162
13980
  init_setup();
@@ -12166,7 +13984,7 @@ var init_commands = __esm({
12166
13984
 
12167
13985
  // packages/cli/dist/tui/project-context.js
12168
13986
  import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "node:fs";
12169
- import { join as join19, basename as basename5 } from "node:path";
13987
+ import { join as join21, basename as basename5 } from "node:path";
12170
13988
  import { execSync as execSync11 } from "node:child_process";
12171
13989
  import { homedir as homedir9, platform, release } from "node:os";
12172
13990
  function loadProjectFiles(repoRoot) {
@@ -12186,7 +14004,7 @@ function loadProjectMap(repoRoot) {
12186
14004
  if (!hasOaDirectory(repoRoot)) {
12187
14005
  initOaDirectory(repoRoot);
12188
14006
  }
12189
- const mapPath = join19(repoRoot, OA_DIR, "context", "project-map.md");
14007
+ const mapPath = join21(repoRoot, OA_DIR, "context", "project-map.md");
12190
14008
  if (existsSync14(mapPath)) {
12191
14009
  try {
12192
14010
  const content = readFileSync11(mapPath, "utf-8");
@@ -12230,17 +14048,17 @@ ${log}`);
12230
14048
  }
12231
14049
  function loadMemoryContext(repoRoot) {
12232
14050
  const sections = [];
12233
- const oaMemDir = join19(repoRoot, OA_DIR, "memory");
14051
+ const oaMemDir = join21(repoRoot, OA_DIR, "memory");
12234
14052
  const oaEntries = loadMemoryDir(oaMemDir, "project");
12235
14053
  if (oaEntries)
12236
14054
  sections.push(oaEntries);
12237
- const legacyMemDir = join19(repoRoot, ".open-agents", "memory");
14055
+ const legacyMemDir = join21(repoRoot, ".open-agents", "memory");
12238
14056
  if (legacyMemDir !== oaMemDir && existsSync14(legacyMemDir)) {
12239
14057
  const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
12240
14058
  if (legacyEntries)
12241
14059
  sections.push(legacyEntries);
12242
14060
  }
12243
- const globalMemDir = join19(homedir9(), ".open-agents", "memory");
14061
+ const globalMemDir = join21(homedir9(), ".open-agents", "memory");
12244
14062
  const globalEntries = loadMemoryDir(globalMemDir, "global");
12245
14063
  if (globalEntries)
12246
14064
  sections.push(globalEntries);
@@ -12254,7 +14072,7 @@ function loadMemoryDir(memDir, scope) {
12254
14072
  const files = readdirSync7(memDir).filter((f) => f.endsWith(".json"));
12255
14073
  for (const file of files.slice(0, 10)) {
12256
14074
  try {
12257
- const raw = readFileSync11(join19(memDir, file), "utf-8");
14075
+ const raw = readFileSync11(join21(memDir, file), "utf-8");
12258
14076
  const entries = JSON.parse(raw);
12259
14077
  const topic = basename5(file, ".json");
12260
14078
  const keys = Object.keys(entries);
@@ -12967,7 +14785,7 @@ var init_toolPatternStore = __esm({
12967
14785
  });
12968
14786
 
12969
14787
  // packages/memory/dist/index.js
12970
- var init_dist6 = __esm({
14788
+ var init_dist7 = __esm({
12971
14789
  "packages/memory/dist/index.js"() {
12972
14790
  "use strict";
12973
14791
  init_db();
@@ -13266,24 +15084,24 @@ var init_carousel = __esm({
13266
15084
 
13267
15085
  // packages/cli/dist/tui/voice.js
13268
15086
  import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8, readFileSync as readFileSync12, unlinkSync as unlinkSync3 } from "node:fs";
13269
- import { join as join20 } from "node:path";
13270
- import { homedir as homedir10, tmpdir as tmpdir2, platform as platform2 } from "node:os";
15087
+ import { join as join22 } from "node:path";
15088
+ import { homedir as homedir10, tmpdir as tmpdir3, platform as platform2 } from "node:os";
13271
15089
  import { execSync as execSync12, spawn as nodeSpawn } from "node:child_process";
13272
15090
  import { createRequire } from "node:module";
13273
15091
  function voiceDir() {
13274
- return join20(homedir10(), ".open-agents", "voice");
15092
+ return join22(homedir10(), ".open-agents", "voice");
13275
15093
  }
13276
15094
  function modelsDir() {
13277
- return join20(voiceDir(), "models");
15095
+ return join22(voiceDir(), "models");
13278
15096
  }
13279
15097
  function modelDir(id) {
13280
- return join20(modelsDir(), id);
15098
+ return join22(modelsDir(), id);
13281
15099
  }
13282
15100
  function modelOnnxPath(id) {
13283
- return join20(modelDir(id), "model.onnx");
15101
+ return join22(modelDir(id), "model.onnx");
13284
15102
  }
13285
15103
  function modelConfigPath(id) {
13286
- return join20(modelDir(id), "config.json");
15104
+ return join22(modelDir(id), "config.json");
13287
15105
  }
13288
15106
  function describeToolCall(toolName, args) {
13289
15107
  const path = args["path"];
@@ -13556,7 +15374,7 @@ var init_voice = __esm({
13556
15374
  const audioData = result["output"].data;
13557
15375
  if (audioData.length === 0)
13558
15376
  return;
13559
- const wavPath = join20(tmpdir2(), `oa-voice-${Date.now()}.wav`);
15377
+ const wavPath = join22(tmpdir3(), `oa-voice-${Date.now()}.wav`);
13560
15378
  this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
13561
15379
  await this.playWav(wavPath);
13562
15380
  try {
@@ -13645,7 +15463,7 @@ var init_voice = __esm({
13645
15463
  const cmd = this.getPlayCommand(path);
13646
15464
  if (!cmd)
13647
15465
  return;
13648
- return new Promise((resolve16) => {
15466
+ return new Promise((resolve18) => {
13649
15467
  const child = nodeSpawn(cmd[0], cmd.slice(1), {
13650
15468
  stdio: "ignore",
13651
15469
  detached: false
@@ -13654,12 +15472,12 @@ var init_voice = __esm({
13654
15472
  child.on("close", () => {
13655
15473
  if (this.currentPlayback === child)
13656
15474
  this.currentPlayback = null;
13657
- resolve16();
15475
+ resolve18();
13658
15476
  });
13659
15477
  child.on("error", () => {
13660
15478
  if (this.currentPlayback === child)
13661
15479
  this.currentPlayback = null;
13662
- resolve16();
15480
+ resolve18();
13663
15481
  });
13664
15482
  setTimeout(() => {
13665
15483
  if (this.currentPlayback === child) {
@@ -13669,7 +15487,7 @@ var init_voice = __esm({
13669
15487
  }
13670
15488
  this.currentPlayback = null;
13671
15489
  }
13672
- resolve16();
15490
+ resolve18();
13673
15491
  }, 15e3);
13674
15492
  });
13675
15493
  }
@@ -13709,7 +15527,7 @@ var init_voice = __esm({
13709
15527
  if (this.ort)
13710
15528
  return;
13711
15529
  mkdirSync8(voiceDir(), { recursive: true });
13712
- const pkgPath = join20(voiceDir(), "package.json");
15530
+ const pkgPath = join22(voiceDir(), "package.json");
13713
15531
  const expectedDeps = {
13714
15532
  "onnxruntime-node": "^1.21.0",
13715
15533
  "phonemizer": "^1.2.1"
@@ -13731,7 +15549,7 @@ var init_voice = __esm({
13731
15549
  dependencies: expectedDeps
13732
15550
  }, null, 2));
13733
15551
  }
13734
- const voiceRequire = createRequire(join20(voiceDir(), "index.js"));
15552
+ const voiceRequire = createRequire(join22(voiceDir(), "index.js"));
13735
15553
  try {
13736
15554
  this.ort = voiceRequire("onnxruntime-node");
13737
15555
  } catch {
@@ -14328,10 +16146,10 @@ var init_stream_renderer = __esm({
14328
16146
 
14329
16147
  // packages/cli/dist/tui/edit-history.js
14330
16148
  import { appendFileSync, mkdirSync as mkdirSync9 } from "node:fs";
14331
- import { join as join21 } from "node:path";
16149
+ import { join as join23 } from "node:path";
14332
16150
  function createEditHistoryLogger(repoRoot, sessionId) {
14333
- const historyDir = join21(repoRoot, ".oa", "history");
14334
- const logPath = join21(historyDir, "edits.jsonl");
16151
+ const historyDir = join23(repoRoot, ".oa", "history");
16152
+ const logPath = join23(historyDir, "edits.jsonl");
14335
16153
  try {
14336
16154
  mkdirSync9(historyDir, { recursive: true });
14337
16155
  } catch {
@@ -14443,7 +16261,7 @@ var init_edit_history = __esm({
14443
16261
 
14444
16262
  // packages/cli/dist/tui/dream-engine.js
14445
16263
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync13, existsSync as existsSync16, cpSync, rmSync, readdirSync as readdirSync8 } from "node:fs";
14446
- import { join as join22, basename as basename6 } from "node:path";
16264
+ import { join as join24, basename as basename6 } from "node:path";
14447
16265
  import { execSync as execSync13 } from "node:child_process";
14448
16266
  function adaptTool(tool) {
14449
16267
  return {
@@ -14618,12 +16436,12 @@ var init_dream_engine = __esm({
14618
16436
  const content = String(args["content"] ?? "");
14619
16437
  if (!rawPath)
14620
16438
  return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
14621
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join22(this.dreamsDir, basename6(rawPath)) : join22(this.dreamsDir, rawPath);
16439
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join24(this.dreamsDir, basename6(rawPath)) : join24(this.dreamsDir, rawPath);
14622
16440
  if (!targetPath.startsWith(this.dreamsDir)) {
14623
16441
  return { success: false, output: "", error: "Dream mode: writes are confined to .oa/dreams/", durationMs: Date.now() - start };
14624
16442
  }
14625
16443
  try {
14626
- const dir = join22(targetPath, "..");
16444
+ const dir = join24(targetPath, "..");
14627
16445
  mkdirSync10(dir, { recursive: true });
14628
16446
  writeFileSync9(targetPath, content, "utf-8");
14629
16447
  return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
@@ -14653,7 +16471,7 @@ var init_dream_engine = __esm({
14653
16471
  const rawPath = String(args["path"] ?? "");
14654
16472
  const oldStr = String(args["old_string"] ?? "");
14655
16473
  const newStr = String(args["new_string"] ?? "");
14656
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join22(this.dreamsDir, basename6(rawPath)) : join22(this.dreamsDir, rawPath);
16474
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join24(this.dreamsDir, basename6(rawPath)) : join24(this.dreamsDir, rawPath);
14657
16475
  if (!targetPath.startsWith(this.dreamsDir)) {
14658
16476
  return { success: false, output: "", error: "Dream mode: edits are confined to .oa/dreams/", durationMs: Date.now() - start };
14659
16477
  }
@@ -14720,7 +16538,7 @@ var init_dream_engine = __esm({
14720
16538
  constructor(config, repoRoot) {
14721
16539
  this.config = config;
14722
16540
  this.repoRoot = repoRoot;
14723
- this.dreamsDir = join22(repoRoot, ".oa", "dreams");
16541
+ this.dreamsDir = join24(repoRoot, ".oa", "dreams");
14724
16542
  this.state = {
14725
16543
  mode: "default",
14726
16544
  active: false,
@@ -14792,7 +16610,7 @@ ${result.summary}`;
14792
16610
  if (mode !== "default" || cycle === totalCycles) {
14793
16611
  renderDreamContraction(cycle);
14794
16612
  const cycleSummary = this.buildCycleSummary(cycle, previousFindings);
14795
- const summaryPath = join22(this.dreamsDir, `cycle-${cycle}-summary.md`);
16613
+ const summaryPath = join24(this.dreamsDir, `cycle-${cycle}-summary.md`);
14796
16614
  writeFileSync9(summaryPath, cycleSummary, "utf-8");
14797
16615
  }
14798
16616
  if (mode === "lucid" && !this.abortController.signal.aborted) {
@@ -14913,7 +16731,7 @@ Dreams directory: ${this.dreamsDir}`);
14913
16731
  }
14914
16732
  /** Save workspace backup for lucid mode */
14915
16733
  saveVersionCheckpoint(cycle) {
14916
- const checkpointDir = join22(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
16734
+ const checkpointDir = join24(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
14917
16735
  try {
14918
16736
  mkdirSync10(checkpointDir, { recursive: true });
14919
16737
  try {
@@ -14932,10 +16750,10 @@ Dreams directory: ${this.dreamsDir}`);
14932
16750
  encoding: "utf-8",
14933
16751
  timeout: 5e3
14934
16752
  }).trim();
14935
- writeFileSync9(join22(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
14936
- writeFileSync9(join22(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
14937
- writeFileSync9(join22(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
14938
- writeFileSync9(join22(checkpointDir, "checkpoint.json"), JSON.stringify({
16753
+ writeFileSync9(join24(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
16754
+ writeFileSync9(join24(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
16755
+ writeFileSync9(join24(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
16756
+ writeFileSync9(join24(checkpointDir, "checkpoint.json"), JSON.stringify({
14939
16757
  cycle,
14940
16758
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14941
16759
  gitHash,
@@ -14943,7 +16761,7 @@ Dreams directory: ${this.dreamsDir}`);
14943
16761
  }, null, 2), "utf-8");
14944
16762
  renderInfo(`Checkpoint saved: cycle ${cycle} (${gitHash.slice(0, 8)})`);
14945
16763
  } catch {
14946
- writeFileSync9(join22(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
16764
+ writeFileSync9(join24(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
14947
16765
  renderInfo(`Checkpoint saved: cycle ${cycle} (no git)`);
14948
16766
  }
14949
16767
  } catch (err) {
@@ -15001,14 +16819,14 @@ ${files.map((f) => `- [\`${f}\`](./${f})`).join("\n")}
15001
16819
  ---
15002
16820
  *Auto-generated by open-agents dream engine*
15003
16821
  `;
15004
- writeFileSync9(join22(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
16822
+ writeFileSync9(join24(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
15005
16823
  } catch {
15006
16824
  }
15007
16825
  }
15008
16826
  /** Save dream state for resume/inspection */
15009
16827
  saveDreamState() {
15010
16828
  try {
15011
- writeFileSync9(join22(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
16829
+ writeFileSync9(join24(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
15012
16830
  } catch {
15013
16831
  }
15014
16832
  }
@@ -15244,13 +17062,18 @@ var init_status_bar = __esm({
15244
17062
  const ctxPct = ctxTotal > 0 ? Math.max(0, Math.min(100, Math.round((1 - ctxUsed / ctxTotal) * 100))) : 100;
15245
17063
  const ctxColor = ctxPct > 50 ? c2.green : ctxPct > 20 ? c2.yellow : c2.red;
15246
17064
  const ctxLabel = pastel2(153, "Ctx: ") + c2.bold(`${ctxUsed.toLocaleString()}/${ctxTotal.toLocaleString()}`) + ` ${ctxColor(`${ctxPct}%`)}`;
17065
+ let costLabel = "";
17066
+ if (m.hasPricing && m.estimatedCost !== void 0) {
17067
+ const costStr = m.estimatedCost < 0.01 ? `$${m.estimatedCost.toFixed(4)}` : m.estimatedCost < 1 ? `$${m.estimatedCost.toFixed(3)}` : `$${m.estimatedCost.toFixed(2)}`;
17068
+ costLabel = pipe + pastel2(222, "Cost: ") + c2.bold(costStr);
17069
+ }
15247
17070
  let recordingLabel = "";
15248
17071
  if (this._recording) {
15249
17072
  const dot = this._recBlink ? pastel2(210, "\u25CF") : " ";
15250
17073
  const countdown = this._countdown > 0 ? c2.dim(` ${this._countdown}s`) : "";
15251
17074
  recordingLabel = pipe + dot + pastel2(210, " REC") + countdown;
15252
17075
  }
15253
- return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${recordingLabel}`;
17076
+ return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${costLabel}${recordingLabel}`;
15254
17077
  }
15255
17078
  // -------------------------------------------------------------------------
15256
17079
  // Private
@@ -15377,19 +17200,19 @@ var init_status_bar = __esm({
15377
17200
  import * as readline2 from "node:readline";
15378
17201
  import { Writable } from "node:stream";
15379
17202
  import { cwd } from "node:process";
15380
- import { resolve as resolve13, join as join23, dirname as dirname3, extname as extname6 } from "node:path";
17203
+ import { resolve as resolve15, join as join25, dirname as dirname5, extname as extname8 } from "node:path";
15381
17204
  import { createRequire as createRequire2 } from "node:module";
15382
- import { fileURLToPath } from "node:url";
17205
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
15383
17206
  import { readFileSync as readFileSync14 } from "node:fs";
15384
17207
  import { existsSync as existsSync17 } from "node:fs";
15385
17208
  function getVersion() {
15386
17209
  try {
15387
17210
  const require2 = createRequire2(import.meta.url);
15388
- const thisDir = dirname3(fileURLToPath(import.meta.url));
17211
+ const thisDir = dirname5(fileURLToPath2(import.meta.url));
15389
17212
  const candidates = [
15390
- join23(thisDir, "..", "package.json"),
15391
- join23(thisDir, "..", "..", "package.json"),
15392
- join23(thisDir, "..", "..", "..", "package.json")
17213
+ join25(thisDir, "..", "package.json"),
17214
+ join25(thisDir, "..", "..", "package.json"),
17215
+ join25(thisDir, "..", "..", "..", "package.json")
15393
17216
  ];
15394
17217
  for (const pkgPath of candidates) {
15395
17218
  if (existsSync17(pkgPath)) {
@@ -15472,7 +17295,13 @@ function buildTools(repoRoot, config) {
15472
17295
  new SkillExecuteTool(repoRoot),
15473
17296
  // Transcription tools (transcribe-cli / faster-whisper)
15474
17297
  new TranscribeFileTool(repoRoot),
15475
- new TranscribeUrlTool(repoRoot)
17298
+ new TranscribeUrlTool(repoRoot),
17299
+ // Structured file generation (CSV, JSON, Markdown, Excel-compatible)
17300
+ new StructuredFileTool(repoRoot),
17301
+ // Code sandbox (isolated code execution)
17302
+ new CodeSandboxTool(repoRoot),
17303
+ // Structured file reading (CSV, JSON, Markdown, binary detection)
17304
+ new StructuredReadTool(repoRoot)
15476
17305
  ];
15477
17306
  return [
15478
17307
  ...executionTools.map(adaptTool2),
@@ -15552,9 +17381,12 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
15552
17381
  }
15553
17382
  };
15554
17383
  }
15555
- function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback) {
17384
+ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType) {
15556
17385
  const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
15557
- const dynamicContext = formatContextForPrompt(projectCtx);
17386
+ let dynamicContext = formatContextForPrompt(projectCtx);
17387
+ if (taskType) {
17388
+ dynamicContext += "\n\n" + buildTaskContext(taskType);
17389
+ }
15558
17390
  const backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
15559
17391
  const runner = new AgenticRunner(backend, {
15560
17392
  maxTurns: 60,
@@ -15658,7 +17490,14 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15658
17490
  break;
15659
17491
  case "token_usage":
15660
17492
  if (statusBar && event.tokenUsage) {
15661
- statusBar.updateMetrics(event.tokenUsage);
17493
+ if (costTracker && event.tokenUsage.promptTokens > 0) {
17494
+ costTracker.trackTokens(event.tokenUsage.promptTokens, event.tokenUsage.completionTokens);
17495
+ }
17496
+ statusBar.updateMetrics({
17497
+ ...event.tokenUsage,
17498
+ estimatedCost: costTracker?.currentCost,
17499
+ hasPricing: costTracker?.hasPricing
17500
+ });
15662
17501
  }
15663
17502
  break;
15664
17503
  case "sudo_request":
@@ -15677,6 +17516,8 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15677
17516
  contentWrite(() => {
15678
17517
  if (result.completed) {
15679
17518
  renderTaskComplete(result.summary, result.turns, result.toolCalls, result.durationMs, tokens);
17519
+ if (onComplete)
17520
+ onComplete(result.summary);
15680
17521
  if (voice?.enabled && result.summary) {
15681
17522
  const ttsText = result.summary.length > 300 ? result.summary.slice(0, 300) + "..." : result.summary;
15682
17523
  voice.speak(`Task complete. ${ttsText}`);
@@ -15734,7 +17575,7 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15734
17575
  } };
15735
17576
  }
15736
17577
  async function startInteractive(config, repoPath) {
15737
- const repoRoot = resolve13(repoPath ?? cwd());
17578
+ const repoRoot = resolve15(repoPath ?? cwd());
15738
17579
  const isResumed = !!process.env.__OA_RESUMED;
15739
17580
  if (isResumed) {
15740
17581
  delete process.env.__OA_RESUMED;
@@ -15803,8 +17644,8 @@ async function startInteractive(config, repoPath) {
15803
17644
  if (!isResumed) {
15804
17645
  try {
15805
17646
  const baseUrl = normalizeBaseUrl(config.backendUrl);
15806
- const provider = detectProvider(config.backendUrl);
15807
- const healthUrl = `${baseUrl}${provider.modelsPath}`;
17647
+ const provider2 = detectProvider(config.backendUrl);
17648
+ const healthUrl = `${baseUrl}${provider2.modelsPath}`;
15808
17649
  const headers = {};
15809
17650
  if (config.apiKey) {
15810
17651
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -15813,9 +17654,9 @@ async function startInteractive(config, repoPath) {
15813
17654
  if (!resp.ok)
15814
17655
  throw new Error(`HTTP ${resp.status}`);
15815
17656
  } catch {
15816
- const provider = detectProvider(config.backendUrl);
15817
- renderWarning(`Cannot reach ${provider.label} at ${config.backendUrl}`);
15818
- if (provider.id === "ollama") {
17657
+ const provider2 = detectProvider(config.backendUrl);
17658
+ renderWarning(`Cannot reach ${provider2.label} at ${config.backendUrl}`);
17659
+ if (provider2.id === "ollama") {
15819
17660
  renderInfo("Start Ollama with: ollama serve");
15820
17661
  }
15821
17662
  renderInfo("Use /endpoint to configure a different backend. Starting anyway...");
@@ -15846,6 +17687,10 @@ async function startInteractive(config, repoPath) {
15846
17687
  end: () => statusBar.endContentWrite()
15847
17688
  });
15848
17689
  }
17690
+ const provider = detectProvider(config.backendUrl);
17691
+ const costTracker = new CostTracker(provider.id);
17692
+ const sessionMetrics = new SessionMetrics();
17693
+ const workEvaluator = new WorkEvaluator();
15849
17694
  ensureTranscribeCliBackground();
15850
17695
  const voiceEngine = new VoiceEngine();
15851
17696
  const streamRenderer = new StreamRenderer();
@@ -15863,6 +17708,8 @@ async function startInteractive(config, repoPath) {
15863
17708
  let messageQueue = [];
15864
17709
  let carouselRetired = isResumed;
15865
17710
  let lastSubmittedPrompt = "";
17711
+ let lastCompletedSummary = "";
17712
+ let currentTaskType;
15866
17713
  let sessionFilesTouched = [];
15867
17714
  let sessionToolCallCount = 0;
15868
17715
  let sessionSudoPassword = null;
@@ -15930,11 +17777,43 @@ async function startInteractive(config, repoPath) {
15930
17777
  const sudoPromptStr = ` ${c2.bold(c2.yellow("\u{1F511} Password:"))} `;
15931
17778
  process.stdout.write(sudoPromptStr);
15932
17779
  }
17780
+ const evalBackend = {
17781
+ async evaluate(prompt) {
17782
+ const backend = new OllamaAgenticBackend(currentConfig.backendUrl, currentConfig.model, currentConfig.apiKey);
17783
+ const result = await backend.chatCompletion({
17784
+ messages: [
17785
+ { role: "system", content: "You are a quality evaluator. Respond with JSON only." },
17786
+ { role: "user", content: prompt }
17787
+ ],
17788
+ tools: [],
17789
+ temperature: 0,
17790
+ maxTokens: 2048,
17791
+ timeoutMs: currentConfig.timeoutMs
17792
+ });
17793
+ return result.choices[0]?.message?.content ?? "";
17794
+ }
17795
+ };
17796
+ workEvaluator.setBackend(evalBackend);
15933
17797
  const commandCtx = {
15934
17798
  get config() {
15935
17799
  return currentConfig;
15936
17800
  },
15937
17801
  repoRoot,
17802
+ costTracker,
17803
+ workEvaluator,
17804
+ get lastTask() {
17805
+ return lastSubmittedPrompt;
17806
+ },
17807
+ get lastSummary() {
17808
+ return lastCompletedSummary;
17809
+ },
17810
+ get taskType() {
17811
+ return currentTaskType;
17812
+ },
17813
+ setTaskType(type) {
17814
+ currentTaskType = type;
17815
+ },
17816
+ sessionMetrics,
15938
17817
  setModel(model) {
15939
17818
  currentConfig = { ...currentConfig, model };
15940
17819
  },
@@ -15948,6 +17827,8 @@ async function startInteractive(config, repoPath) {
15948
17827
  backendType,
15949
17828
  ...apiKey !== void 0 ? { apiKey } : {}
15950
17829
  };
17830
+ const newProvider = detectProvider(url);
17831
+ costTracker.setProvider(newProvider.id);
15951
17832
  },
15952
17833
  clearScreen() {
15953
17834
  process.stdout.write("\x1B[2J\x1B[H");
@@ -16223,7 +18104,9 @@ Execute this skill now. Follow the behavioral guidance above.`;
16223
18104
  taskMemoryStore: taskMemoryStore ?? void 0,
16224
18105
  failureStore: failureStore ?? void 0,
16225
18106
  toolPatternStore: toolPatternStore ?? void 0
16226
- }, bruteForceEnabled, statusBar, handleSudoRequest);
18107
+ }, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary) => {
18108
+ lastCompletedSummary = summary;
18109
+ }, currentTaskType);
16227
18110
  activeTask = task;
16228
18111
  showPrompt();
16229
18112
  await task.promise;
@@ -16237,15 +18120,15 @@ Execute this skill now. Follow the behavioral guidance above.`;
16237
18120
  }
16238
18121
  }
16239
18122
  const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
16240
- const isImage = isImagePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
16241
- const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
18123
+ const isImage = isImagePath(cleanPath) && existsSync17(resolve15(repoRoot, cleanPath));
18124
+ const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync17(resolve15(repoRoot, cleanPath));
16242
18125
  if (activeTask) {
16243
18126
  if (isImage) {
16244
18127
  try {
16245
- const imgPath = resolve13(repoRoot, cleanPath);
18128
+ const imgPath = resolve15(repoRoot, cleanPath);
16246
18129
  const imgBuffer = readFileSync14(imgPath);
16247
18130
  const base64 = imgBuffer.toString("base64");
16248
- const ext = extname6(cleanPath).toLowerCase();
18131
+ const ext = extname8(cleanPath).toLowerCase();
16249
18132
  const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
16250
18133
  activeTask.runner.injectImage(base64, mime, `User shared image: ${cleanPath}`);
16251
18134
  writeContent(() => renderUserInterrupt(`[Image: ${cleanPath}]`));
@@ -16256,7 +18139,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
16256
18139
  } else if (isMedia) {
16257
18140
  writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
16258
18141
  const engine = getListenEngine();
16259
- const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
18142
+ const result = await engine.transcribeFile(resolve15(repoRoot, cleanPath), repoRoot);
16260
18143
  if (result) {
16261
18144
  const transcript = `[Transcription of ${cleanPath}]
16262
18145
  ${result.text}`;
@@ -16289,7 +18172,7 @@ ${result.text}`;
16289
18172
  if (isMedia && fullInput === input) {
16290
18173
  writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
16291
18174
  const engine = getListenEngine();
16292
- const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
18175
+ const result = await engine.transcribeFile(resolve15(repoRoot, cleanPath), repoRoot);
16293
18176
  if (result) {
16294
18177
  fullInput = `The user has provided an audio/video file: ${cleanPath}.
16295
18178
 
@@ -16321,7 +18204,9 @@ Summarize or analyze this transcription as appropriate.`;
16321
18204
  taskMemoryStore: taskMemoryStore ?? void 0,
16322
18205
  failureStore: failureStore ?? void 0,
16323
18206
  toolPatternStore: toolPatternStore ?? void 0
16324
- }, bruteForceEnabled, statusBar, handleSudoRequest);
18207
+ }, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary) => {
18208
+ lastCompletedSummary = summary;
18209
+ }, currentTaskType);
16325
18210
  activeTask = task;
16326
18211
  showPrompt();
16327
18212
  await task.promise;
@@ -16405,7 +18290,7 @@ ${c2.dim("(Use /quit to exit)")}
16405
18290
  });
16406
18291
  }
16407
18292
  async function runWithTUI(task, config, repoPath) {
16408
- const repoRoot = resolve13(repoPath ?? cwd());
18293
+ const repoRoot = resolve15(repoPath ?? cwd());
16409
18294
  const needsSetup = isFirstRun() || !await isModelAvailable(config);
16410
18295
  if (needsSetup && config.backendType === "ollama") {
16411
18296
  const setupModel = await runSetupWizard(config);
@@ -16463,7 +18348,7 @@ var init_interactive = __esm({
16463
18348
  init_commands();
16464
18349
  init_setup();
16465
18350
  init_project_context();
16466
- init_dist6();
18351
+ init_dist7();
16467
18352
  init_oa_directory();
16468
18353
  init_render();
16469
18354
  init_carousel();
@@ -16472,6 +18357,7 @@ var init_interactive = __esm({
16472
18357
  init_edit_history();
16473
18358
  init_dream_engine();
16474
18359
  init_status_bar();
18360
+ init_dist6();
16475
18361
  taskManager = new BackgroundTaskManager();
16476
18362
  }
16477
18363
  });
@@ -16505,9 +18391,9 @@ var init_run = __esm({
16505
18391
  // packages/indexer/dist/codebase-indexer.js
16506
18392
  import { glob } from "glob";
16507
18393
  import ignore from "ignore";
16508
- import { readFile as readFile9, stat as stat2 } from "node:fs/promises";
18394
+ import { readFile as readFile10, stat as stat4 } from "node:fs/promises";
16509
18395
  import { createHash } from "node:crypto";
16510
- import { join as join24, relative as relative3, extname as extname7, basename as basename7 } from "node:path";
18396
+ import { join as join26, relative as relative3, extname as extname9, basename as basename7 } from "node:path";
16511
18397
  var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
16512
18398
  var init_codebase_indexer = __esm({
16513
18399
  "packages/indexer/dist/codebase-indexer.js"() {
@@ -16551,7 +18437,7 @@ var init_codebase_indexer = __esm({
16551
18437
  const ig = ignore.default();
16552
18438
  if (this.config.respectGitignore) {
16553
18439
  try {
16554
- const gitignoreContent = await readFile9(join24(this.config.rootDir, ".gitignore"), "utf-8");
18440
+ const gitignoreContent = await readFile10(join26(this.config.rootDir, ".gitignore"), "utf-8");
16555
18441
  ig.add(gitignoreContent);
16556
18442
  } catch {
16557
18443
  }
@@ -16566,14 +18452,14 @@ var init_codebase_indexer = __esm({
16566
18452
  for (const relativePath of files) {
16567
18453
  if (ig.ignores(relativePath))
16568
18454
  continue;
16569
- const fullPath = join24(this.config.rootDir, relativePath);
18455
+ const fullPath = join26(this.config.rootDir, relativePath);
16570
18456
  try {
16571
- const fileStat = await stat2(fullPath);
18457
+ const fileStat = await stat4(fullPath);
16572
18458
  if (fileStat.size > this.config.maxFileSize)
16573
18459
  continue;
16574
- const content = await readFile9(fullPath);
18460
+ const content = await readFile10(fullPath);
16575
18461
  const hash = createHash("sha256").update(content).digest("hex");
16576
- const ext = extname7(relativePath);
18462
+ const ext = extname9(relativePath);
16577
18463
  indexed.push({
16578
18464
  path: fullPath,
16579
18465
  relativePath,
@@ -16612,7 +18498,7 @@ var init_codebase_indexer = __esm({
16612
18498
  if (!child) {
16613
18499
  child = {
16614
18500
  name: part,
16615
- path: join24(current.path, part),
18501
+ path: join26(current.path, part),
16616
18502
  type: "directory",
16617
18503
  children: []
16618
18504
  };
@@ -16669,7 +18555,7 @@ var init_embeddings = __esm({
16669
18555
  });
16670
18556
 
16671
18557
  // packages/indexer/dist/index.js
16672
- var init_dist7 = __esm({
18558
+ var init_dist8 = __esm({
16673
18559
  "packages/indexer/dist/index.js"() {
16674
18560
  "use strict";
16675
18561
  init_codebase_indexer();
@@ -16686,19 +18572,19 @@ var index_repo_exports = {};
16686
18572
  __export(index_repo_exports, {
16687
18573
  indexRepoCommand: () => indexRepoCommand
16688
18574
  });
16689
- import { resolve as resolve14 } from "node:path";
18575
+ import { resolve as resolve16 } from "node:path";
16690
18576
  import { existsSync as existsSync18, statSync as statSync6 } from "node:fs";
16691
18577
  import { cwd as cwd2 } from "node:process";
16692
18578
  async function indexRepoCommand(opts, _config) {
16693
- const repoRoot = resolve14(opts.repoPath ?? cwd2());
18579
+ const repoRoot = resolve16(opts.repoPath ?? cwd2());
16694
18580
  printHeader("Index Repository");
16695
18581
  printInfo(`Indexing: ${repoRoot}`);
16696
18582
  if (!existsSync18(repoRoot)) {
16697
18583
  printError(`Path does not exist: ${repoRoot}`);
16698
18584
  process.exit(1);
16699
18585
  }
16700
- const stat3 = statSync6(repoRoot);
16701
- if (!stat3.isDirectory()) {
18586
+ const stat5 = statSync6(repoRoot);
18587
+ if (!stat5.isDirectory()) {
16702
18588
  printError(`Path is not a directory: ${repoRoot}`);
16703
18589
  process.exit(1);
16704
18590
  }
@@ -16776,7 +18662,7 @@ async function indexRepoCommand(opts, _config) {
16776
18662
  var init_index_repo = __esm({
16777
18663
  "packages/cli/dist/commands/index-repo.js"() {
16778
18664
  "use strict";
16779
- init_dist7();
18665
+ init_dist8();
16780
18666
  init_spinner();
16781
18667
  init_output();
16782
18668
  }
@@ -16939,7 +18825,7 @@ var config_exports = {};
16939
18825
  __export(config_exports, {
16940
18826
  configCommand: () => configCommand
16941
18827
  });
16942
- import { join as join25, resolve as resolve15 } from "node:path";
18828
+ import { join as join27, resolve as resolve17 } from "node:path";
16943
18829
  import { homedir as homedir11 } from "node:os";
16944
18830
  import { cwd as cwd3 } from "node:process";
16945
18831
  function coerceForSettings(key, value) {
@@ -16960,7 +18846,7 @@ async function configCommand(opts, config) {
16960
18846
  return handleShow(opts, config);
16961
18847
  }
16962
18848
  function handleShow(opts, config) {
16963
- const repoRoot = resolve15(opts.repoPath ?? cwd3());
18849
+ const repoRoot = resolve17(opts.repoPath ?? cwd3());
16964
18850
  printHeader("Configuration");
16965
18851
  printSection("Active Settings (merged)");
16966
18852
  printKeyValue("backendUrl", config.backendUrl, 2);
@@ -16992,7 +18878,7 @@ function handleShow(opts, config) {
16992
18878
  }
16993
18879
  }
16994
18880
  printSection("Config File");
16995
- printInfo(`~/.open-agents/config.json (${join25(homedir11(), ".open-agents", "config.json")})`);
18881
+ printInfo(`~/.open-agents/config.json (${join27(homedir11(), ".open-agents", "config.json")})`);
16996
18882
  printSection("Priority Chain");
16997
18883
  printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
16998
18884
  printInfo(" 2. Project .oa/settings.json (--local)");
@@ -17025,13 +18911,13 @@ function handleSet(opts, _config) {
17025
18911
  process.exit(1);
17026
18912
  }
17027
18913
  if (opts.local) {
17028
- const repoRoot = resolve15(opts.repoPath ?? cwd3());
18914
+ const repoRoot = resolve17(opts.repoPath ?? cwd3());
17029
18915
  try {
17030
18916
  initOaDirectory(repoRoot);
17031
18917
  const coerced = coerceForSettings(key, value);
17032
18918
  saveProjectSettings(repoRoot, { [key]: coerced });
17033
18919
  printSuccess(`Project override set: ${key} = ${value}`);
17034
- printInfo(`Saved to ${join25(repoRoot, ".oa", "settings.json")}`);
18920
+ printInfo(`Saved to ${join27(repoRoot, ".oa", "settings.json")}`);
17035
18921
  printInfo("This override applies only when running in this workspace.");
17036
18922
  } catch (err) {
17037
18923
  printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
@@ -17091,7 +18977,7 @@ var serve_exports = {};
17091
18977
  __export(serve_exports, {
17092
18978
  serveCommand: () => serveCommand
17093
18979
  });
17094
- import { spawn as spawn6 } from "node:child_process";
18980
+ import { spawn as spawn7 } from "node:child_process";
17095
18981
  async function serveCommand(opts, config) {
17096
18982
  const backendType = config.backendType;
17097
18983
  if (backendType === "ollama") {
@@ -17183,8 +19069,8 @@ async function serveVllm(opts, config) {
17183
19069
  await runVllmServer(args, opts.verbose ?? false);
17184
19070
  }
17185
19071
  async function runVllmServer(args, verbose) {
17186
- return new Promise((resolve16, reject) => {
17187
- const child = spawn6("python", args, {
19072
+ return new Promise((resolve18, reject) => {
19073
+ const child = spawn7("python", args, {
17188
19074
  stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
17189
19075
  env: { ...process.env }
17190
19076
  });
@@ -17218,10 +19104,10 @@ async function runVllmServer(args, verbose) {
17218
19104
  child.once("exit", (code, signal) => {
17219
19105
  if (signal) {
17220
19106
  printInfo(`vLLM server stopped by signal ${signal}`);
17221
- resolve16();
19107
+ resolve18();
17222
19108
  } else if (code === 0) {
17223
19109
  printSuccess("vLLM server exited cleanly");
17224
- resolve16();
19110
+ resolve18();
17225
19111
  } else {
17226
19112
  printError(`vLLM server exited with code ${code}`);
17227
19113
  reject(new Error(`vLLM exited with code ${code}`));
@@ -17248,9 +19134,9 @@ var eval_exports = {};
17248
19134
  __export(eval_exports, {
17249
19135
  evalCommand: () => evalCommand
17250
19136
  });
17251
- import { tmpdir as tmpdir3 } from "node:os";
19137
+ import { tmpdir as tmpdir4 } from "node:os";
17252
19138
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
17253
- import { join as join26 } from "node:path";
19139
+ import { join as join28 } from "node:path";
17254
19140
  async function evalCommand(opts, config) {
17255
19141
  const suiteName = opts.suite ?? "basic";
17256
19142
  const suite = SUITES[suiteName];
@@ -17371,9 +19257,9 @@ async function evalCommand(opts, config) {
17371
19257
  process.exit(failed > 0 ? 1 : 0);
17372
19258
  }
17373
19259
  function createTempEvalRepo() {
17374
- const dir = join26(tmpdir3(), `open-agents-eval-${Date.now()}`);
19260
+ const dir = join28(tmpdir4(), `open-agents-eval-${Date.now()}`);
17375
19261
  mkdirSync11(dir, { recursive: true });
17376
- writeFileSync10(join26(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
19262
+ writeFileSync10(join28(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
17377
19263
  return dir;
17378
19264
  }
17379
19265
  var BASIC_SUITE, FULL_SUITE, SUITES;
@@ -17432,8 +19318,8 @@ init_output();
17432
19318
  init_updater();
17433
19319
  import { parseArgs as nodeParseArgs2 } from "node:util";
17434
19320
  import { createRequire as createRequire3 } from "node:module";
17435
- import { fileURLToPath as fileURLToPath2 } from "node:url";
17436
- import { dirname as dirname4, join as join27 } from "node:path";
19321
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
19322
+ import { dirname as dirname6, join as join29 } from "node:path";
17437
19323
 
17438
19324
  // packages/cli/dist/cli.js
17439
19325
  import { createInterface } from "node:readline";
@@ -17540,7 +19426,7 @@ init_output();
17540
19426
  function getVersion2() {
17541
19427
  try {
17542
19428
  const require2 = createRequire3(import.meta.url);
17543
- const pkgPath = join27(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
19429
+ const pkgPath = join29(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
17544
19430
  const pkg = require2(pkgPath);
17545
19431
  return pkg.version;
17546
19432
  } catch {