open-agents-ai 0.16.5 → 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 +2052 -213
  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 {
@@ -10377,6 +11781,10 @@ function renderSlashHelp() {
10377
11781
  ["/listen confirm", "Require Enter to submit transcription"],
10378
11782
  ["/listen auto", "Auto-submit after 3s silence (blinking \u25CF indicator)"],
10379
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)"],
10380
11788
  ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
10381
11789
  ["/tools", "List agent-created custom tools"],
10382
11790
  ["/skills", "List available AIWG skills"],
@@ -10755,22 +12163,215 @@ var init_render = __esm({
10755
12163
  }
10756
12164
  });
10757
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
+
10758
12359
  // packages/cli/dist/tui/oa-directory.js
10759
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";
10760
- 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";
10761
12362
  import { homedir as homedir7 } from "node:os";
10762
12363
  function initOaDirectory(repoRoot) {
10763
- const oaPath = join17(repoRoot, OA_DIR);
12364
+ const oaPath = join19(repoRoot, OA_DIR);
10764
12365
  for (const sub of SUBDIRS) {
10765
- mkdirSync6(join17(oaPath, sub), { recursive: true });
12366
+ mkdirSync6(join19(oaPath, sub), { recursive: true });
10766
12367
  }
10767
12368
  return oaPath;
10768
12369
  }
10769
12370
  function hasOaDirectory(repoRoot) {
10770
- return existsSync12(join17(repoRoot, OA_DIR, "index"));
12371
+ return existsSync12(join19(repoRoot, OA_DIR, "index"));
10771
12372
  }
10772
12373
  function loadProjectSettings(repoRoot) {
10773
- const settingsPath = join17(repoRoot, OA_DIR, "settings.json");
12374
+ const settingsPath = join19(repoRoot, OA_DIR, "settings.json");
10774
12375
  try {
10775
12376
  if (existsSync12(settingsPath)) {
10776
12377
  return JSON.parse(readFileSync10(settingsPath, "utf-8"));
@@ -10780,14 +12381,14 @@ function loadProjectSettings(repoRoot) {
10780
12381
  return {};
10781
12382
  }
10782
12383
  function saveProjectSettings(repoRoot, settings) {
10783
- const oaPath = join17(repoRoot, OA_DIR);
12384
+ const oaPath = join19(repoRoot, OA_DIR);
10784
12385
  mkdirSync6(oaPath, { recursive: true });
10785
12386
  const existing = loadProjectSettings(repoRoot);
10786
12387
  const merged = { ...existing, ...settings };
10787
- 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");
10788
12389
  }
10789
12390
  function loadGlobalSettings() {
10790
- const settingsPath = join17(homedir7(), ".open-agents", "settings.json");
12391
+ const settingsPath = join19(homedir7(), ".open-agents", "settings.json");
10791
12392
  try {
10792
12393
  if (existsSync12(settingsPath)) {
10793
12394
  return JSON.parse(readFileSync10(settingsPath, "utf-8"));
@@ -10797,11 +12398,11 @@ function loadGlobalSettings() {
10797
12398
  return {};
10798
12399
  }
10799
12400
  function saveGlobalSettings(settings) {
10800
- const dir = join17(homedir7(), ".open-agents");
12401
+ const dir = join19(homedir7(), ".open-agents");
10801
12402
  mkdirSync6(dir, { recursive: true });
10802
12403
  const existing = loadGlobalSettings();
10803
12404
  const merged = { ...existing, ...settings };
10804
- 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");
10805
12406
  }
10806
12407
  function resolveSettings(repoRoot) {
10807
12408
  const global = loadGlobalSettings();
@@ -10816,7 +12417,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10816
12417
  while (dir && !visited.has(dir)) {
10817
12418
  visited.add(dir);
10818
12419
  for (const name of CONTEXT_FILES) {
10819
- const filePath = join17(dir, name);
12420
+ const filePath = join19(dir, name);
10820
12421
  const normalizedName = name.toLowerCase();
10821
12422
  if (existsSync12(filePath) && !seen.has(filePath)) {
10822
12423
  seen.add(filePath);
@@ -10835,7 +12436,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10835
12436
  }
10836
12437
  }
10837
12438
  }
10838
- const projectMap = join17(dir, OA_DIR, "context", "project-map.md");
12439
+ const projectMap = join19(dir, OA_DIR, "context", "project-map.md");
10839
12440
  if (existsSync12(projectMap) && !seen.has(projectMap)) {
10840
12441
  seen.add(projectMap);
10841
12442
  try {
@@ -10851,7 +12452,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10851
12452
  } catch {
10852
12453
  }
10853
12454
  }
10854
- const parent = join17(dir, "..");
12455
+ const parent = join19(dir, "..");
10855
12456
  if (parent === dir)
10856
12457
  break;
10857
12458
  dir = parent;
@@ -10869,7 +12470,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
10869
12470
  return found;
10870
12471
  }
10871
12472
  function readIndexMeta(repoRoot) {
10872
- const metaPath = join17(repoRoot, OA_DIR, "index", "meta.json");
12473
+ const metaPath = join19(repoRoot, OA_DIR, "index", "meta.json");
10873
12474
  try {
10874
12475
  return JSON.parse(readFileSync10(metaPath, "utf-8"));
10875
12476
  } catch {
@@ -10922,28 +12523,28 @@ ${tree}\`\`\`
10922
12523
  sections.push("");
10923
12524
  }
10924
12525
  const content = sections.join("\n");
10925
- const contextDir = join17(repoRoot, OA_DIR, "context");
12526
+ const contextDir = join19(repoRoot, OA_DIR, "context");
10926
12527
  mkdirSync6(contextDir, { recursive: true });
10927
- writeFileSync6(join17(contextDir, "project-map.md"), content, "utf-8");
12528
+ writeFileSync6(join19(contextDir, "project-map.md"), content, "utf-8");
10928
12529
  return content;
10929
12530
  }
10930
12531
  function saveSession(repoRoot, session) {
10931
- const historyDir = join17(repoRoot, OA_DIR, "history");
12532
+ const historyDir = join19(repoRoot, OA_DIR, "history");
10932
12533
  mkdirSync6(historyDir, { recursive: true });
10933
- 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");
10934
12535
  }
10935
12536
  function loadRecentSessions(repoRoot, limit = 5) {
10936
- const historyDir = join17(repoRoot, OA_DIR, "history");
12537
+ const historyDir = join19(repoRoot, OA_DIR, "history");
10937
12538
  if (!existsSync12(historyDir))
10938
12539
  return [];
10939
12540
  try {
10940
12541
  const files = readdirSync6(historyDir).filter((f) => f.endsWith(".json")).map((f) => {
10941
- const stat3 = statSync5(join17(historyDir, f));
10942
- return { file: f, mtime: stat3.mtimeMs };
12542
+ const stat5 = statSync5(join19(historyDir, f));
12543
+ return { file: f, mtime: stat5.mtimeMs };
10943
12544
  }).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
10944
12545
  return files.map((f) => {
10945
12546
  try {
10946
- return JSON.parse(readFileSync10(join17(historyDir, f.file), "utf-8"));
12547
+ return JSON.parse(readFileSync10(join19(historyDir, f.file), "utf-8"));
10947
12548
  } catch {
10948
12549
  return null;
10949
12550
  }
@@ -10953,12 +12554,12 @@ function loadRecentSessions(repoRoot, limit = 5) {
10953
12554
  }
10954
12555
  }
10955
12556
  function savePendingTask(repoRoot, task) {
10956
- const historyDir = join17(repoRoot, OA_DIR, "history");
12557
+ const historyDir = join19(repoRoot, OA_DIR, "history");
10957
12558
  mkdirSync6(historyDir, { recursive: true });
10958
- 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");
10959
12560
  }
10960
12561
  function loadPendingTask(repoRoot) {
10961
- const filePath = join17(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
12562
+ const filePath = join19(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
10962
12563
  try {
10963
12564
  if (!existsSync12(filePath))
10964
12565
  return null;
@@ -10990,7 +12591,7 @@ function detectManifests(repoRoot) {
10990
12591
  { file: "docker-compose.yaml", type: "Docker Compose" }
10991
12592
  ];
10992
12593
  for (const check of checks) {
10993
- const filePath = join17(repoRoot, check.file);
12594
+ const filePath = join19(repoRoot, check.file);
10994
12595
  if (existsSync12(filePath)) {
10995
12596
  let name;
10996
12597
  if (check.nameField) {
@@ -11024,7 +12625,7 @@ function findKeyFiles(repoRoot) {
11024
12625
  { pattern: "CLAUDE.md", description: "Claude Code context" }
11025
12626
  ];
11026
12627
  for (const check of checks) {
11027
- if (existsSync12(join17(repoRoot, check.pattern))) {
12628
+ if (existsSync12(join19(repoRoot, check.pattern))) {
11028
12629
  keyFiles.push({ path: check.pattern, description: check.description });
11029
12630
  }
11030
12631
  }
@@ -11050,12 +12651,12 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
11050
12651
  if (entry.isDirectory()) {
11051
12652
  let fileCount = 0;
11052
12653
  try {
11053
- fileCount = readdirSync6(join17(root, entry.name)).filter((f) => !f.startsWith(".")).length;
12654
+ fileCount = readdirSync6(join19(root, entry.name)).filter((f) => !f.startsWith(".")).length;
11054
12655
  } catch {
11055
12656
  }
11056
12657
  result += `${prefix}${connector}${entry.name}/ (${fileCount})
11057
12658
  `;
11058
- result += buildDirTree(join17(root, entry.name), maxDepth, childPrefix, depth + 1);
12659
+ result += buildDirTree(join19(root, entry.name), maxDepth, childPrefix, depth + 1);
11059
12660
  } else if (depth < maxDepth) {
11060
12661
  result += `${prefix}${connector}${entry.name}
11061
12662
  `;
@@ -11108,7 +12709,7 @@ var init_oa_directory = __esm({
11108
12709
  import * as readline from "node:readline";
11109
12710
  import { execSync as execSync10 } from "node:child_process";
11110
12711
  import { existsSync as existsSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
11111
- import { join as join18 } from "node:path";
12712
+ import { join as join20 } from "node:path";
11112
12713
  import { homedir as homedir8 } from "node:os";
11113
12714
  function detectSystemSpecs() {
11114
12715
  let totalRamGB = 0;
@@ -11192,8 +12793,8 @@ function modelSupportsToolCalling(modelName) {
11192
12793
  return false;
11193
12794
  }
11194
12795
  function ask(rl, question) {
11195
- return new Promise((resolve16) => {
11196
- rl.question(question, (answer) => resolve16(answer.trim()));
12796
+ return new Promise((resolve18) => {
12797
+ rl.question(question, (answer) => resolve18(answer.trim()));
11197
12798
  });
11198
12799
  }
11199
12800
  function pullModelWithAutoUpdate(tag) {
@@ -11542,9 +13143,9 @@ async function doSetup(config, rl) {
11542
13143
  `PARAMETER num_predict 16384`,
11543
13144
  `PARAMETER stop "<|endoftext|>"`
11544
13145
  ].join("\n");
11545
- const modelDir2 = join18(homedir8(), ".open-agents", "models");
13146
+ const modelDir2 = join20(homedir8(), ".open-agents", "models");
11546
13147
  mkdirSync7(modelDir2, { recursive: true });
11547
- const modelfilePath = join18(modelDir2, `Modelfile.${customName}`);
13148
+ const modelfilePath = join20(modelDir2, `Modelfile.${customName}`);
11548
13149
  writeFileSync7(modelfilePath, modelfileContent + "\n", "utf8");
11549
13150
  process.stdout.write(` ${c2.dim("Creating model...")} `);
11550
13151
  execSync10(`ollama create ${customName} -f ${modelfilePath}`, {
@@ -11590,7 +13191,7 @@ async function isModelAvailable(config) {
11590
13191
  }
11591
13192
  function isFirstRun() {
11592
13193
  try {
11593
- return !existsSync13(join18(homedir8(), ".open-agents", "config.json"));
13194
+ return !existsSync13(join20(homedir8(), ".open-agents", "config.json"));
11594
13195
  } catch {
11595
13196
  return true;
11596
13197
  }
@@ -11628,9 +13229,9 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
11628
13229
  `PARAMETER num_predict 16384`,
11629
13230
  `PARAMETER stop "<|endoftext|>"`
11630
13231
  ].join("\n");
11631
- const modelDir2 = join18(homedir8(), ".open-agents", "models");
13232
+ const modelDir2 = join20(homedir8(), ".open-agents", "models");
11632
13233
  mkdirSync7(modelDir2, { recursive: true });
11633
- const modelfilePath = join18(modelDir2, `Modelfile.${customName}`);
13234
+ const modelfilePath = join20(modelDir2, `Modelfile.${customName}`);
11634
13235
  writeFileSync7(modelfilePath, modelfileContent + "\n", "utf8");
11635
13236
  execSync10(`ollama create ${customName} -f ${modelfilePath}`, {
11636
13237
  stdio: "pipe",
@@ -11759,6 +13360,174 @@ async function handleSlashCommand(input, ctx) {
11759
13360
  dryRun: String(ctx.config.dryRun)
11760
13361
  });
11761
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
+ }
11762
13531
  case "model":
11763
13532
  if (arg) {
11764
13533
  await switchModel(arg, ctx, hasLocal);
@@ -12098,15 +13867,15 @@ async function handleUpdate(subcommand, ctx) {
12098
13867
  let currentVersion = "0.0.0";
12099
13868
  try {
12100
13869
  const { createRequire: createRequire4 } = await import("node:module");
12101
- const { fileURLToPath: fileURLToPath3 } = await import("node:url");
12102
- 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");
12103
13872
  const { existsSync: existsSync19 } = await import("node:fs");
12104
13873
  const req = createRequire4(import.meta.url);
12105
- const thisDir = dirname5(fileURLToPath3(import.meta.url));
13874
+ const thisDir = dirname7(fileURLToPath4(import.meta.url));
12106
13875
  const candidates = [
12107
- join28(thisDir, "..", "package.json"),
12108
- join28(thisDir, "..", "..", "package.json"),
12109
- join28(thisDir, "..", "..", "..", "package.json")
13876
+ join30(thisDir, "..", "package.json"),
13877
+ join30(thisDir, "..", "..", "package.json"),
13878
+ join30(thisDir, "..", "..", "..", "package.json")
12110
13879
  ];
12111
13880
  for (const pkgPath of candidates) {
12112
13881
  if (existsSync19(pkgPath)) {
@@ -12204,6 +13973,8 @@ var init_commands = __esm({
12204
13973
  init_render();
12205
13974
  init_dist2();
12206
13975
  init_config();
13976
+ init_dist5();
13977
+ init_dist6();
12207
13978
  init_updater();
12208
13979
  init_oa_directory();
12209
13980
  init_setup();
@@ -12213,7 +13984,7 @@ var init_commands = __esm({
12213
13984
 
12214
13985
  // packages/cli/dist/tui/project-context.js
12215
13986
  import { existsSync as existsSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "node:fs";
12216
- import { join as join19, basename as basename5 } from "node:path";
13987
+ import { join as join21, basename as basename5 } from "node:path";
12217
13988
  import { execSync as execSync11 } from "node:child_process";
12218
13989
  import { homedir as homedir9, platform, release } from "node:os";
12219
13990
  function loadProjectFiles(repoRoot) {
@@ -12233,7 +14004,7 @@ function loadProjectMap(repoRoot) {
12233
14004
  if (!hasOaDirectory(repoRoot)) {
12234
14005
  initOaDirectory(repoRoot);
12235
14006
  }
12236
- const mapPath = join19(repoRoot, OA_DIR, "context", "project-map.md");
14007
+ const mapPath = join21(repoRoot, OA_DIR, "context", "project-map.md");
12237
14008
  if (existsSync14(mapPath)) {
12238
14009
  try {
12239
14010
  const content = readFileSync11(mapPath, "utf-8");
@@ -12277,17 +14048,17 @@ ${log}`);
12277
14048
  }
12278
14049
  function loadMemoryContext(repoRoot) {
12279
14050
  const sections = [];
12280
- const oaMemDir = join19(repoRoot, OA_DIR, "memory");
14051
+ const oaMemDir = join21(repoRoot, OA_DIR, "memory");
12281
14052
  const oaEntries = loadMemoryDir(oaMemDir, "project");
12282
14053
  if (oaEntries)
12283
14054
  sections.push(oaEntries);
12284
- const legacyMemDir = join19(repoRoot, ".open-agents", "memory");
14055
+ const legacyMemDir = join21(repoRoot, ".open-agents", "memory");
12285
14056
  if (legacyMemDir !== oaMemDir && existsSync14(legacyMemDir)) {
12286
14057
  const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
12287
14058
  if (legacyEntries)
12288
14059
  sections.push(legacyEntries);
12289
14060
  }
12290
- const globalMemDir = join19(homedir9(), ".open-agents", "memory");
14061
+ const globalMemDir = join21(homedir9(), ".open-agents", "memory");
12291
14062
  const globalEntries = loadMemoryDir(globalMemDir, "global");
12292
14063
  if (globalEntries)
12293
14064
  sections.push(globalEntries);
@@ -12301,7 +14072,7 @@ function loadMemoryDir(memDir, scope) {
12301
14072
  const files = readdirSync7(memDir).filter((f) => f.endsWith(".json"));
12302
14073
  for (const file of files.slice(0, 10)) {
12303
14074
  try {
12304
- const raw = readFileSync11(join19(memDir, file), "utf-8");
14075
+ const raw = readFileSync11(join21(memDir, file), "utf-8");
12305
14076
  const entries = JSON.parse(raw);
12306
14077
  const topic = basename5(file, ".json");
12307
14078
  const keys = Object.keys(entries);
@@ -13014,7 +14785,7 @@ var init_toolPatternStore = __esm({
13014
14785
  });
13015
14786
 
13016
14787
  // packages/memory/dist/index.js
13017
- var init_dist6 = __esm({
14788
+ var init_dist7 = __esm({
13018
14789
  "packages/memory/dist/index.js"() {
13019
14790
  "use strict";
13020
14791
  init_db();
@@ -13313,24 +15084,24 @@ var init_carousel = __esm({
13313
15084
 
13314
15085
  // packages/cli/dist/tui/voice.js
13315
15086
  import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8, readFileSync as readFileSync12, unlinkSync as unlinkSync3 } from "node:fs";
13316
- import { join as join20 } from "node:path";
13317
- 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";
13318
15089
  import { execSync as execSync12, spawn as nodeSpawn } from "node:child_process";
13319
15090
  import { createRequire } from "node:module";
13320
15091
  function voiceDir() {
13321
- return join20(homedir10(), ".open-agents", "voice");
15092
+ return join22(homedir10(), ".open-agents", "voice");
13322
15093
  }
13323
15094
  function modelsDir() {
13324
- return join20(voiceDir(), "models");
15095
+ return join22(voiceDir(), "models");
13325
15096
  }
13326
15097
  function modelDir(id) {
13327
- return join20(modelsDir(), id);
15098
+ return join22(modelsDir(), id);
13328
15099
  }
13329
15100
  function modelOnnxPath(id) {
13330
- return join20(modelDir(id), "model.onnx");
15101
+ return join22(modelDir(id), "model.onnx");
13331
15102
  }
13332
15103
  function modelConfigPath(id) {
13333
- return join20(modelDir(id), "config.json");
15104
+ return join22(modelDir(id), "config.json");
13334
15105
  }
13335
15106
  function describeToolCall(toolName, args) {
13336
15107
  const path = args["path"];
@@ -13603,7 +15374,7 @@ var init_voice = __esm({
13603
15374
  const audioData = result["output"].data;
13604
15375
  if (audioData.length === 0)
13605
15376
  return;
13606
- const wavPath = join20(tmpdir2(), `oa-voice-${Date.now()}.wav`);
15377
+ const wavPath = join22(tmpdir3(), `oa-voice-${Date.now()}.wav`);
13607
15378
  this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
13608
15379
  await this.playWav(wavPath);
13609
15380
  try {
@@ -13692,7 +15463,7 @@ var init_voice = __esm({
13692
15463
  const cmd = this.getPlayCommand(path);
13693
15464
  if (!cmd)
13694
15465
  return;
13695
- return new Promise((resolve16) => {
15466
+ return new Promise((resolve18) => {
13696
15467
  const child = nodeSpawn(cmd[0], cmd.slice(1), {
13697
15468
  stdio: "ignore",
13698
15469
  detached: false
@@ -13701,12 +15472,12 @@ var init_voice = __esm({
13701
15472
  child.on("close", () => {
13702
15473
  if (this.currentPlayback === child)
13703
15474
  this.currentPlayback = null;
13704
- resolve16();
15475
+ resolve18();
13705
15476
  });
13706
15477
  child.on("error", () => {
13707
15478
  if (this.currentPlayback === child)
13708
15479
  this.currentPlayback = null;
13709
- resolve16();
15480
+ resolve18();
13710
15481
  });
13711
15482
  setTimeout(() => {
13712
15483
  if (this.currentPlayback === child) {
@@ -13716,7 +15487,7 @@ var init_voice = __esm({
13716
15487
  }
13717
15488
  this.currentPlayback = null;
13718
15489
  }
13719
- resolve16();
15490
+ resolve18();
13720
15491
  }, 15e3);
13721
15492
  });
13722
15493
  }
@@ -13756,7 +15527,7 @@ var init_voice = __esm({
13756
15527
  if (this.ort)
13757
15528
  return;
13758
15529
  mkdirSync8(voiceDir(), { recursive: true });
13759
- const pkgPath = join20(voiceDir(), "package.json");
15530
+ const pkgPath = join22(voiceDir(), "package.json");
13760
15531
  const expectedDeps = {
13761
15532
  "onnxruntime-node": "^1.21.0",
13762
15533
  "phonemizer": "^1.2.1"
@@ -13778,7 +15549,7 @@ var init_voice = __esm({
13778
15549
  dependencies: expectedDeps
13779
15550
  }, null, 2));
13780
15551
  }
13781
- const voiceRequire = createRequire(join20(voiceDir(), "index.js"));
15552
+ const voiceRequire = createRequire(join22(voiceDir(), "index.js"));
13782
15553
  try {
13783
15554
  this.ort = voiceRequire("onnxruntime-node");
13784
15555
  } catch {
@@ -14375,10 +16146,10 @@ var init_stream_renderer = __esm({
14375
16146
 
14376
16147
  // packages/cli/dist/tui/edit-history.js
14377
16148
  import { appendFileSync, mkdirSync as mkdirSync9 } from "node:fs";
14378
- import { join as join21 } from "node:path";
16149
+ import { join as join23 } from "node:path";
14379
16150
  function createEditHistoryLogger(repoRoot, sessionId) {
14380
- const historyDir = join21(repoRoot, ".oa", "history");
14381
- const logPath = join21(historyDir, "edits.jsonl");
16151
+ const historyDir = join23(repoRoot, ".oa", "history");
16152
+ const logPath = join23(historyDir, "edits.jsonl");
14382
16153
  try {
14383
16154
  mkdirSync9(historyDir, { recursive: true });
14384
16155
  } catch {
@@ -14490,7 +16261,7 @@ var init_edit_history = __esm({
14490
16261
 
14491
16262
  // packages/cli/dist/tui/dream-engine.js
14492
16263
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync13, existsSync as existsSync16, cpSync, rmSync, readdirSync as readdirSync8 } from "node:fs";
14493
- import { join as join22, basename as basename6 } from "node:path";
16264
+ import { join as join24, basename as basename6 } from "node:path";
14494
16265
  import { execSync as execSync13 } from "node:child_process";
14495
16266
  function adaptTool(tool) {
14496
16267
  return {
@@ -14665,12 +16436,12 @@ var init_dream_engine = __esm({
14665
16436
  const content = String(args["content"] ?? "");
14666
16437
  if (!rawPath)
14667
16438
  return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
14668
- 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);
14669
16440
  if (!targetPath.startsWith(this.dreamsDir)) {
14670
16441
  return { success: false, output: "", error: "Dream mode: writes are confined to .oa/dreams/", durationMs: Date.now() - start };
14671
16442
  }
14672
16443
  try {
14673
- const dir = join22(targetPath, "..");
16444
+ const dir = join24(targetPath, "..");
14674
16445
  mkdirSync10(dir, { recursive: true });
14675
16446
  writeFileSync9(targetPath, content, "utf-8");
14676
16447
  return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
@@ -14700,7 +16471,7 @@ var init_dream_engine = __esm({
14700
16471
  const rawPath = String(args["path"] ?? "");
14701
16472
  const oldStr = String(args["old_string"] ?? "");
14702
16473
  const newStr = String(args["new_string"] ?? "");
14703
- 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);
14704
16475
  if (!targetPath.startsWith(this.dreamsDir)) {
14705
16476
  return { success: false, output: "", error: "Dream mode: edits are confined to .oa/dreams/", durationMs: Date.now() - start };
14706
16477
  }
@@ -14767,7 +16538,7 @@ var init_dream_engine = __esm({
14767
16538
  constructor(config, repoRoot) {
14768
16539
  this.config = config;
14769
16540
  this.repoRoot = repoRoot;
14770
- this.dreamsDir = join22(repoRoot, ".oa", "dreams");
16541
+ this.dreamsDir = join24(repoRoot, ".oa", "dreams");
14771
16542
  this.state = {
14772
16543
  mode: "default",
14773
16544
  active: false,
@@ -14839,7 +16610,7 @@ ${result.summary}`;
14839
16610
  if (mode !== "default" || cycle === totalCycles) {
14840
16611
  renderDreamContraction(cycle);
14841
16612
  const cycleSummary = this.buildCycleSummary(cycle, previousFindings);
14842
- const summaryPath = join22(this.dreamsDir, `cycle-${cycle}-summary.md`);
16613
+ const summaryPath = join24(this.dreamsDir, `cycle-${cycle}-summary.md`);
14843
16614
  writeFileSync9(summaryPath, cycleSummary, "utf-8");
14844
16615
  }
14845
16616
  if (mode === "lucid" && !this.abortController.signal.aborted) {
@@ -14960,7 +16731,7 @@ Dreams directory: ${this.dreamsDir}`);
14960
16731
  }
14961
16732
  /** Save workspace backup for lucid mode */
14962
16733
  saveVersionCheckpoint(cycle) {
14963
- const checkpointDir = join22(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
16734
+ const checkpointDir = join24(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
14964
16735
  try {
14965
16736
  mkdirSync10(checkpointDir, { recursive: true });
14966
16737
  try {
@@ -14979,10 +16750,10 @@ Dreams directory: ${this.dreamsDir}`);
14979
16750
  encoding: "utf-8",
14980
16751
  timeout: 5e3
14981
16752
  }).trim();
14982
- writeFileSync9(join22(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
14983
- writeFileSync9(join22(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
14984
- writeFileSync9(join22(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
14985
- 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({
14986
16757
  cycle,
14987
16758
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14988
16759
  gitHash,
@@ -14990,7 +16761,7 @@ Dreams directory: ${this.dreamsDir}`);
14990
16761
  }, null, 2), "utf-8");
14991
16762
  renderInfo(`Checkpoint saved: cycle ${cycle} (${gitHash.slice(0, 8)})`);
14992
16763
  } catch {
14993
- 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");
14994
16765
  renderInfo(`Checkpoint saved: cycle ${cycle} (no git)`);
14995
16766
  }
14996
16767
  } catch (err) {
@@ -15048,14 +16819,14 @@ ${files.map((f) => `- [\`${f}\`](./${f})`).join("\n")}
15048
16819
  ---
15049
16820
  *Auto-generated by open-agents dream engine*
15050
16821
  `;
15051
- writeFileSync9(join22(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
16822
+ writeFileSync9(join24(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
15052
16823
  } catch {
15053
16824
  }
15054
16825
  }
15055
16826
  /** Save dream state for resume/inspection */
15056
16827
  saveDreamState() {
15057
16828
  try {
15058
- 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");
15059
16830
  } catch {
15060
16831
  }
15061
16832
  }
@@ -15291,13 +17062,18 @@ var init_status_bar = __esm({
15291
17062
  const ctxPct = ctxTotal > 0 ? Math.max(0, Math.min(100, Math.round((1 - ctxUsed / ctxTotal) * 100))) : 100;
15292
17063
  const ctxColor = ctxPct > 50 ? c2.green : ctxPct > 20 ? c2.yellow : c2.red;
15293
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
+ }
15294
17070
  let recordingLabel = "";
15295
17071
  if (this._recording) {
15296
17072
  const dot = this._recBlink ? pastel2(210, "\u25CF") : " ";
15297
17073
  const countdown = this._countdown > 0 ? c2.dim(` ${this._countdown}s`) : "";
15298
17074
  recordingLabel = pipe + dot + pastel2(210, " REC") + countdown;
15299
17075
  }
15300
- return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${recordingLabel}`;
17076
+ return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${costLabel}${recordingLabel}`;
15301
17077
  }
15302
17078
  // -------------------------------------------------------------------------
15303
17079
  // Private
@@ -15424,19 +17200,19 @@ var init_status_bar = __esm({
15424
17200
  import * as readline2 from "node:readline";
15425
17201
  import { Writable } from "node:stream";
15426
17202
  import { cwd } from "node:process";
15427
- 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";
15428
17204
  import { createRequire as createRequire2 } from "node:module";
15429
- import { fileURLToPath } from "node:url";
17205
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
15430
17206
  import { readFileSync as readFileSync14 } from "node:fs";
15431
17207
  import { existsSync as existsSync17 } from "node:fs";
15432
17208
  function getVersion() {
15433
17209
  try {
15434
17210
  const require2 = createRequire2(import.meta.url);
15435
- const thisDir = dirname3(fileURLToPath(import.meta.url));
17211
+ const thisDir = dirname5(fileURLToPath2(import.meta.url));
15436
17212
  const candidates = [
15437
- join23(thisDir, "..", "package.json"),
15438
- join23(thisDir, "..", "..", "package.json"),
15439
- join23(thisDir, "..", "..", "..", "package.json")
17213
+ join25(thisDir, "..", "package.json"),
17214
+ join25(thisDir, "..", "..", "package.json"),
17215
+ join25(thisDir, "..", "..", "..", "package.json")
15440
17216
  ];
15441
17217
  for (const pkgPath of candidates) {
15442
17218
  if (existsSync17(pkgPath)) {
@@ -15519,7 +17295,13 @@ function buildTools(repoRoot, config) {
15519
17295
  new SkillExecuteTool(repoRoot),
15520
17296
  // Transcription tools (transcribe-cli / faster-whisper)
15521
17297
  new TranscribeFileTool(repoRoot),
15522
- 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)
15523
17305
  ];
15524
17306
  return [
15525
17307
  ...executionTools.map(adaptTool2),
@@ -15599,9 +17381,12 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
15599
17381
  }
15600
17382
  };
15601
17383
  }
15602
- 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) {
15603
17385
  const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
15604
- const dynamicContext = formatContextForPrompt(projectCtx);
17386
+ let dynamicContext = formatContextForPrompt(projectCtx);
17387
+ if (taskType) {
17388
+ dynamicContext += "\n\n" + buildTaskContext(taskType);
17389
+ }
15605
17390
  const backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
15606
17391
  const runner = new AgenticRunner(backend, {
15607
17392
  maxTurns: 60,
@@ -15705,7 +17490,14 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15705
17490
  break;
15706
17491
  case "token_usage":
15707
17492
  if (statusBar && event.tokenUsage) {
15708
- 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
+ });
15709
17501
  }
15710
17502
  break;
15711
17503
  case "sudo_request":
@@ -15724,6 +17516,8 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15724
17516
  contentWrite(() => {
15725
17517
  if (result.completed) {
15726
17518
  renderTaskComplete(result.summary, result.turns, result.toolCalls, result.durationMs, tokens);
17519
+ if (onComplete)
17520
+ onComplete(result.summary);
15727
17521
  if (voice?.enabled && result.summary) {
15728
17522
  const ttsText = result.summary.length > 300 ? result.summary.slice(0, 300) + "..." : result.summary;
15729
17523
  voice.speak(`Task complete. ${ttsText}`);
@@ -15781,7 +17575,7 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15781
17575
  } };
15782
17576
  }
15783
17577
  async function startInteractive(config, repoPath) {
15784
- const repoRoot = resolve13(repoPath ?? cwd());
17578
+ const repoRoot = resolve15(repoPath ?? cwd());
15785
17579
  const isResumed = !!process.env.__OA_RESUMED;
15786
17580
  if (isResumed) {
15787
17581
  delete process.env.__OA_RESUMED;
@@ -15850,8 +17644,8 @@ async function startInteractive(config, repoPath) {
15850
17644
  if (!isResumed) {
15851
17645
  try {
15852
17646
  const baseUrl = normalizeBaseUrl(config.backendUrl);
15853
- const provider = detectProvider(config.backendUrl);
15854
- const healthUrl = `${baseUrl}${provider.modelsPath}`;
17647
+ const provider2 = detectProvider(config.backendUrl);
17648
+ const healthUrl = `${baseUrl}${provider2.modelsPath}`;
15855
17649
  const headers = {};
15856
17650
  if (config.apiKey) {
15857
17651
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -15860,9 +17654,9 @@ async function startInteractive(config, repoPath) {
15860
17654
  if (!resp.ok)
15861
17655
  throw new Error(`HTTP ${resp.status}`);
15862
17656
  } catch {
15863
- const provider = detectProvider(config.backendUrl);
15864
- renderWarning(`Cannot reach ${provider.label} at ${config.backendUrl}`);
15865
- if (provider.id === "ollama") {
17657
+ const provider2 = detectProvider(config.backendUrl);
17658
+ renderWarning(`Cannot reach ${provider2.label} at ${config.backendUrl}`);
17659
+ if (provider2.id === "ollama") {
15866
17660
  renderInfo("Start Ollama with: ollama serve");
15867
17661
  }
15868
17662
  renderInfo("Use /endpoint to configure a different backend. Starting anyway...");
@@ -15893,6 +17687,10 @@ async function startInteractive(config, repoPath) {
15893
17687
  end: () => statusBar.endContentWrite()
15894
17688
  });
15895
17689
  }
17690
+ const provider = detectProvider(config.backendUrl);
17691
+ const costTracker = new CostTracker(provider.id);
17692
+ const sessionMetrics = new SessionMetrics();
17693
+ const workEvaluator = new WorkEvaluator();
15896
17694
  ensureTranscribeCliBackground();
15897
17695
  const voiceEngine = new VoiceEngine();
15898
17696
  const streamRenderer = new StreamRenderer();
@@ -15910,6 +17708,8 @@ async function startInteractive(config, repoPath) {
15910
17708
  let messageQueue = [];
15911
17709
  let carouselRetired = isResumed;
15912
17710
  let lastSubmittedPrompt = "";
17711
+ let lastCompletedSummary = "";
17712
+ let currentTaskType;
15913
17713
  let sessionFilesTouched = [];
15914
17714
  let sessionToolCallCount = 0;
15915
17715
  let sessionSudoPassword = null;
@@ -15977,11 +17777,43 @@ async function startInteractive(config, repoPath) {
15977
17777
  const sudoPromptStr = ` ${c2.bold(c2.yellow("\u{1F511} Password:"))} `;
15978
17778
  process.stdout.write(sudoPromptStr);
15979
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);
15980
17797
  const commandCtx = {
15981
17798
  get config() {
15982
17799
  return currentConfig;
15983
17800
  },
15984
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,
15985
17817
  setModel(model) {
15986
17818
  currentConfig = { ...currentConfig, model };
15987
17819
  },
@@ -15995,6 +17827,8 @@ async function startInteractive(config, repoPath) {
15995
17827
  backendType,
15996
17828
  ...apiKey !== void 0 ? { apiKey } : {}
15997
17829
  };
17830
+ const newProvider = detectProvider(url);
17831
+ costTracker.setProvider(newProvider.id);
15998
17832
  },
15999
17833
  clearScreen() {
16000
17834
  process.stdout.write("\x1B[2J\x1B[H");
@@ -16270,7 +18104,9 @@ Execute this skill now. Follow the behavioral guidance above.`;
16270
18104
  taskMemoryStore: taskMemoryStore ?? void 0,
16271
18105
  failureStore: failureStore ?? void 0,
16272
18106
  toolPatternStore: toolPatternStore ?? void 0
16273
- }, bruteForceEnabled, statusBar, handleSudoRequest);
18107
+ }, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary) => {
18108
+ lastCompletedSummary = summary;
18109
+ }, currentTaskType);
16274
18110
  activeTask = task;
16275
18111
  showPrompt();
16276
18112
  await task.promise;
@@ -16284,15 +18120,15 @@ Execute this skill now. Follow the behavioral guidance above.`;
16284
18120
  }
16285
18121
  }
16286
18122
  const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
16287
- const isImage = isImagePath(cleanPath) && existsSync17(resolve13(repoRoot, cleanPath));
16288
- 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));
16289
18125
  if (activeTask) {
16290
18126
  if (isImage) {
16291
18127
  try {
16292
- const imgPath = resolve13(repoRoot, cleanPath);
18128
+ const imgPath = resolve15(repoRoot, cleanPath);
16293
18129
  const imgBuffer = readFileSync14(imgPath);
16294
18130
  const base64 = imgBuffer.toString("base64");
16295
- const ext = extname6(cleanPath).toLowerCase();
18131
+ const ext = extname8(cleanPath).toLowerCase();
16296
18132
  const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
16297
18133
  activeTask.runner.injectImage(base64, mime, `User shared image: ${cleanPath}`);
16298
18134
  writeContent(() => renderUserInterrupt(`[Image: ${cleanPath}]`));
@@ -16303,7 +18139,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
16303
18139
  } else if (isMedia) {
16304
18140
  writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
16305
18141
  const engine = getListenEngine();
16306
- const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
18142
+ const result = await engine.transcribeFile(resolve15(repoRoot, cleanPath), repoRoot);
16307
18143
  if (result) {
16308
18144
  const transcript = `[Transcription of ${cleanPath}]
16309
18145
  ${result.text}`;
@@ -16336,7 +18172,7 @@ ${result.text}`;
16336
18172
  if (isMedia && fullInput === input) {
16337
18173
  writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
16338
18174
  const engine = getListenEngine();
16339
- const result = await engine.transcribeFile(resolve13(repoRoot, cleanPath), repoRoot);
18175
+ const result = await engine.transcribeFile(resolve15(repoRoot, cleanPath), repoRoot);
16340
18176
  if (result) {
16341
18177
  fullInput = `The user has provided an audio/video file: ${cleanPath}.
16342
18178
 
@@ -16368,7 +18204,9 @@ Summarize or analyze this transcription as appropriate.`;
16368
18204
  taskMemoryStore: taskMemoryStore ?? void 0,
16369
18205
  failureStore: failureStore ?? void 0,
16370
18206
  toolPatternStore: toolPatternStore ?? void 0
16371
- }, bruteForceEnabled, statusBar, handleSudoRequest);
18207
+ }, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary) => {
18208
+ lastCompletedSummary = summary;
18209
+ }, currentTaskType);
16372
18210
  activeTask = task;
16373
18211
  showPrompt();
16374
18212
  await task.promise;
@@ -16452,7 +18290,7 @@ ${c2.dim("(Use /quit to exit)")}
16452
18290
  });
16453
18291
  }
16454
18292
  async function runWithTUI(task, config, repoPath) {
16455
- const repoRoot = resolve13(repoPath ?? cwd());
18293
+ const repoRoot = resolve15(repoPath ?? cwd());
16456
18294
  const needsSetup = isFirstRun() || !await isModelAvailable(config);
16457
18295
  if (needsSetup && config.backendType === "ollama") {
16458
18296
  const setupModel = await runSetupWizard(config);
@@ -16510,7 +18348,7 @@ var init_interactive = __esm({
16510
18348
  init_commands();
16511
18349
  init_setup();
16512
18350
  init_project_context();
16513
- init_dist6();
18351
+ init_dist7();
16514
18352
  init_oa_directory();
16515
18353
  init_render();
16516
18354
  init_carousel();
@@ -16519,6 +18357,7 @@ var init_interactive = __esm({
16519
18357
  init_edit_history();
16520
18358
  init_dream_engine();
16521
18359
  init_status_bar();
18360
+ init_dist6();
16522
18361
  taskManager = new BackgroundTaskManager();
16523
18362
  }
16524
18363
  });
@@ -16552,9 +18391,9 @@ var init_run = __esm({
16552
18391
  // packages/indexer/dist/codebase-indexer.js
16553
18392
  import { glob } from "glob";
16554
18393
  import ignore from "ignore";
16555
- import { readFile as readFile9, stat as stat2 } from "node:fs/promises";
18394
+ import { readFile as readFile10, stat as stat4 } from "node:fs/promises";
16556
18395
  import { createHash } from "node:crypto";
16557
- 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";
16558
18397
  var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
16559
18398
  var init_codebase_indexer = __esm({
16560
18399
  "packages/indexer/dist/codebase-indexer.js"() {
@@ -16598,7 +18437,7 @@ var init_codebase_indexer = __esm({
16598
18437
  const ig = ignore.default();
16599
18438
  if (this.config.respectGitignore) {
16600
18439
  try {
16601
- const gitignoreContent = await readFile9(join24(this.config.rootDir, ".gitignore"), "utf-8");
18440
+ const gitignoreContent = await readFile10(join26(this.config.rootDir, ".gitignore"), "utf-8");
16602
18441
  ig.add(gitignoreContent);
16603
18442
  } catch {
16604
18443
  }
@@ -16613,14 +18452,14 @@ var init_codebase_indexer = __esm({
16613
18452
  for (const relativePath of files) {
16614
18453
  if (ig.ignores(relativePath))
16615
18454
  continue;
16616
- const fullPath = join24(this.config.rootDir, relativePath);
18455
+ const fullPath = join26(this.config.rootDir, relativePath);
16617
18456
  try {
16618
- const fileStat = await stat2(fullPath);
18457
+ const fileStat = await stat4(fullPath);
16619
18458
  if (fileStat.size > this.config.maxFileSize)
16620
18459
  continue;
16621
- const content = await readFile9(fullPath);
18460
+ const content = await readFile10(fullPath);
16622
18461
  const hash = createHash("sha256").update(content).digest("hex");
16623
- const ext = extname7(relativePath);
18462
+ const ext = extname9(relativePath);
16624
18463
  indexed.push({
16625
18464
  path: fullPath,
16626
18465
  relativePath,
@@ -16659,7 +18498,7 @@ var init_codebase_indexer = __esm({
16659
18498
  if (!child) {
16660
18499
  child = {
16661
18500
  name: part,
16662
- path: join24(current.path, part),
18501
+ path: join26(current.path, part),
16663
18502
  type: "directory",
16664
18503
  children: []
16665
18504
  };
@@ -16716,7 +18555,7 @@ var init_embeddings = __esm({
16716
18555
  });
16717
18556
 
16718
18557
  // packages/indexer/dist/index.js
16719
- var init_dist7 = __esm({
18558
+ var init_dist8 = __esm({
16720
18559
  "packages/indexer/dist/index.js"() {
16721
18560
  "use strict";
16722
18561
  init_codebase_indexer();
@@ -16733,19 +18572,19 @@ var index_repo_exports = {};
16733
18572
  __export(index_repo_exports, {
16734
18573
  indexRepoCommand: () => indexRepoCommand
16735
18574
  });
16736
- import { resolve as resolve14 } from "node:path";
18575
+ import { resolve as resolve16 } from "node:path";
16737
18576
  import { existsSync as existsSync18, statSync as statSync6 } from "node:fs";
16738
18577
  import { cwd as cwd2 } from "node:process";
16739
18578
  async function indexRepoCommand(opts, _config) {
16740
- const repoRoot = resolve14(opts.repoPath ?? cwd2());
18579
+ const repoRoot = resolve16(opts.repoPath ?? cwd2());
16741
18580
  printHeader("Index Repository");
16742
18581
  printInfo(`Indexing: ${repoRoot}`);
16743
18582
  if (!existsSync18(repoRoot)) {
16744
18583
  printError(`Path does not exist: ${repoRoot}`);
16745
18584
  process.exit(1);
16746
18585
  }
16747
- const stat3 = statSync6(repoRoot);
16748
- if (!stat3.isDirectory()) {
18586
+ const stat5 = statSync6(repoRoot);
18587
+ if (!stat5.isDirectory()) {
16749
18588
  printError(`Path is not a directory: ${repoRoot}`);
16750
18589
  process.exit(1);
16751
18590
  }
@@ -16823,7 +18662,7 @@ async function indexRepoCommand(opts, _config) {
16823
18662
  var init_index_repo = __esm({
16824
18663
  "packages/cli/dist/commands/index-repo.js"() {
16825
18664
  "use strict";
16826
- init_dist7();
18665
+ init_dist8();
16827
18666
  init_spinner();
16828
18667
  init_output();
16829
18668
  }
@@ -16986,7 +18825,7 @@ var config_exports = {};
16986
18825
  __export(config_exports, {
16987
18826
  configCommand: () => configCommand
16988
18827
  });
16989
- import { join as join25, resolve as resolve15 } from "node:path";
18828
+ import { join as join27, resolve as resolve17 } from "node:path";
16990
18829
  import { homedir as homedir11 } from "node:os";
16991
18830
  import { cwd as cwd3 } from "node:process";
16992
18831
  function coerceForSettings(key, value) {
@@ -17007,7 +18846,7 @@ async function configCommand(opts, config) {
17007
18846
  return handleShow(opts, config);
17008
18847
  }
17009
18848
  function handleShow(opts, config) {
17010
- const repoRoot = resolve15(opts.repoPath ?? cwd3());
18849
+ const repoRoot = resolve17(opts.repoPath ?? cwd3());
17011
18850
  printHeader("Configuration");
17012
18851
  printSection("Active Settings (merged)");
17013
18852
  printKeyValue("backendUrl", config.backendUrl, 2);
@@ -17039,7 +18878,7 @@ function handleShow(opts, config) {
17039
18878
  }
17040
18879
  }
17041
18880
  printSection("Config File");
17042
- printInfo(`~/.open-agents/config.json (${join25(homedir11(), ".open-agents", "config.json")})`);
18881
+ printInfo(`~/.open-agents/config.json (${join27(homedir11(), ".open-agents", "config.json")})`);
17043
18882
  printSection("Priority Chain");
17044
18883
  printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
17045
18884
  printInfo(" 2. Project .oa/settings.json (--local)");
@@ -17072,13 +18911,13 @@ function handleSet(opts, _config) {
17072
18911
  process.exit(1);
17073
18912
  }
17074
18913
  if (opts.local) {
17075
- const repoRoot = resolve15(opts.repoPath ?? cwd3());
18914
+ const repoRoot = resolve17(opts.repoPath ?? cwd3());
17076
18915
  try {
17077
18916
  initOaDirectory(repoRoot);
17078
18917
  const coerced = coerceForSettings(key, value);
17079
18918
  saveProjectSettings(repoRoot, { [key]: coerced });
17080
18919
  printSuccess(`Project override set: ${key} = ${value}`);
17081
- printInfo(`Saved to ${join25(repoRoot, ".oa", "settings.json")}`);
18920
+ printInfo(`Saved to ${join27(repoRoot, ".oa", "settings.json")}`);
17082
18921
  printInfo("This override applies only when running in this workspace.");
17083
18922
  } catch (err) {
17084
18923
  printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
@@ -17138,7 +18977,7 @@ var serve_exports = {};
17138
18977
  __export(serve_exports, {
17139
18978
  serveCommand: () => serveCommand
17140
18979
  });
17141
- import { spawn as spawn6 } from "node:child_process";
18980
+ import { spawn as spawn7 } from "node:child_process";
17142
18981
  async function serveCommand(opts, config) {
17143
18982
  const backendType = config.backendType;
17144
18983
  if (backendType === "ollama") {
@@ -17230,8 +19069,8 @@ async function serveVllm(opts, config) {
17230
19069
  await runVllmServer(args, opts.verbose ?? false);
17231
19070
  }
17232
19071
  async function runVllmServer(args, verbose) {
17233
- return new Promise((resolve16, reject) => {
17234
- const child = spawn6("python", args, {
19072
+ return new Promise((resolve18, reject) => {
19073
+ const child = spawn7("python", args, {
17235
19074
  stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
17236
19075
  env: { ...process.env }
17237
19076
  });
@@ -17265,10 +19104,10 @@ async function runVllmServer(args, verbose) {
17265
19104
  child.once("exit", (code, signal) => {
17266
19105
  if (signal) {
17267
19106
  printInfo(`vLLM server stopped by signal ${signal}`);
17268
- resolve16();
19107
+ resolve18();
17269
19108
  } else if (code === 0) {
17270
19109
  printSuccess("vLLM server exited cleanly");
17271
- resolve16();
19110
+ resolve18();
17272
19111
  } else {
17273
19112
  printError(`vLLM server exited with code ${code}`);
17274
19113
  reject(new Error(`vLLM exited with code ${code}`));
@@ -17295,9 +19134,9 @@ var eval_exports = {};
17295
19134
  __export(eval_exports, {
17296
19135
  evalCommand: () => evalCommand
17297
19136
  });
17298
- import { tmpdir as tmpdir3 } from "node:os";
19137
+ import { tmpdir as tmpdir4 } from "node:os";
17299
19138
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
17300
- import { join as join26 } from "node:path";
19139
+ import { join as join28 } from "node:path";
17301
19140
  async function evalCommand(opts, config) {
17302
19141
  const suiteName = opts.suite ?? "basic";
17303
19142
  const suite = SUITES[suiteName];
@@ -17418,9 +19257,9 @@ async function evalCommand(opts, config) {
17418
19257
  process.exit(failed > 0 ? 1 : 0);
17419
19258
  }
17420
19259
  function createTempEvalRepo() {
17421
- const dir = join26(tmpdir3(), `open-agents-eval-${Date.now()}`);
19260
+ const dir = join28(tmpdir4(), `open-agents-eval-${Date.now()}`);
17422
19261
  mkdirSync11(dir, { recursive: true });
17423
- 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");
17424
19263
  return dir;
17425
19264
  }
17426
19265
  var BASIC_SUITE, FULL_SUITE, SUITES;
@@ -17479,8 +19318,8 @@ init_output();
17479
19318
  init_updater();
17480
19319
  import { parseArgs as nodeParseArgs2 } from "node:util";
17481
19320
  import { createRequire as createRequire3 } from "node:module";
17482
- import { fileURLToPath as fileURLToPath2 } from "node:url";
17483
- 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";
17484
19323
 
17485
19324
  // packages/cli/dist/cli.js
17486
19325
  import { createInterface } from "node:readline";
@@ -17587,7 +19426,7 @@ init_output();
17587
19426
  function getVersion2() {
17588
19427
  try {
17589
19428
  const require2 = createRequire3(import.meta.url);
17590
- const pkgPath = join27(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json");
19429
+ const pkgPath = join29(dirname6(fileURLToPath3(import.meta.url)), "..", "package.json");
17591
19430
  const pkg = require2(pkgPath);
17592
19431
  return pkg.version;
17593
19432
  } catch {