@mindstudio-ai/remy 0.1.174 → 0.1.176

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/headless.js CHANGED
@@ -83,6 +83,7 @@ function resolveConfig(flags) {
83
83
  const env = file.environments?.[activeEnv];
84
84
  const apiKey = flags?.apiKey || process.env.MINDSTUDIO_API_KEY || env?.apiKey || "";
85
85
  const baseUrl2 = flags?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
86
+ const appId = process.env.MINDSTUDIO_APP_ID || void 0;
86
87
  if (!apiKey) {
87
88
  log.error("No API key found");
88
89
  throw new Error(
@@ -93,9 +94,10 @@ function resolveConfig(flags) {
93
94
  log.info("Config resolved", {
94
95
  baseUrl: baseUrl2,
95
96
  keySource,
96
- environment: activeEnv
97
+ environment: activeEnv,
98
+ appId
97
99
  });
98
- return { apiKey, baseUrl: baseUrl2 };
100
+ return { apiKey, baseUrl: baseUrl2, appId };
99
101
  }
100
102
 
101
103
  // src/assets.ts
@@ -521,8 +523,10 @@ async function* streamChat(params) {
521
523
  ...subAgentId && { subAgentId },
522
524
  durationMs: elapsed,
523
525
  stopReason: event.stopReason,
526
+ modelId: event.modelId,
524
527
  inputTokens: event.usage.inputTokens,
525
- outputTokens: event.usage.outputTokens
528
+ outputTokens: event.usage.outputTokens,
529
+ cost: event.cost
526
530
  });
527
531
  } else if (event.type === "error") {
528
532
  log2.error("SSE error event", {
@@ -611,6 +615,7 @@ async function generateBackgroundAck(params) {
611
615
  Authorization: `Bearer ${params.apiConfig.apiKey}`
612
616
  },
613
617
  body: JSON.stringify({
618
+ appId: params.apiConfig.appId,
614
619
  agentName: params.agentName,
615
620
  task: params.task
616
621
  }),
@@ -626,6 +631,24 @@ async function generateBackgroundAck(params) {
626
631
  }
627
632
  }
628
633
 
634
+ // src/usageLedger.ts
635
+ import fs5 from "fs";
636
+ var LEDGER_FILE = ".logs/usage.ndjson";
637
+ var fd = null;
638
+ function nanoToDollars(nano) {
639
+ return typeof nano === "number" ? nano / 1e9 : void 0;
640
+ }
641
+ function recordUsage(entry) {
642
+ try {
643
+ if (fd === null) {
644
+ fs5.mkdirSync(".logs", { recursive: true });
645
+ fd = fs5.openSync(LEDGER_FILE, "a");
646
+ }
647
+ fs5.writeSync(fd, JSON.stringify(entry) + "\n");
648
+ } catch {
649
+ }
650
+ }
651
+
629
652
  // src/compaction/index.ts
630
653
  var log3 = createLogger("compaction");
631
654
  var CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
@@ -840,6 +863,7 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
840
863
  Conversation to summarize:
841
864
 
842
865
  ${serialized}` : serialized;
866
+ const iterStart = Date.now();
843
867
  for await (const event of streamChat({
844
868
  ...apiConfig,
845
869
  subAgentId: "conversationSummarizer",
@@ -849,6 +873,20 @@ ${serialized}` : serialized;
849
873
  })) {
850
874
  if (event.type === "text") {
851
875
  summaryText += event.text;
876
+ } else if (event.type === "done") {
877
+ recordUsage({
878
+ ts: Date.now(),
879
+ agentName: "conversationSummarizer",
880
+ modelId: event.modelId,
881
+ inputTokens: event.usage.inputTokens,
882
+ outputTokens: event.usage.outputTokens,
883
+ cacheCreationTokens: event.usage.cacheCreationTokens,
884
+ cacheReadTokens: event.usage.cacheReadTokens,
885
+ cost: nanoToDollars(event.cost),
886
+ billingEvents: event.billingEvents,
887
+ durationMs: Date.now() - iterStart,
888
+ toolNames: []
889
+ });
852
890
  } else if (event.type === "error") {
853
891
  log3.error("Summary generation failed", { name, error: event.error });
854
892
  return null;
@@ -863,7 +901,7 @@ ${serialized}` : serialized;
863
901
  }
864
902
 
865
903
  // src/tools/spec/readSpec.ts
866
- import fs5 from "fs/promises";
904
+ import fs6 from "fs/promises";
867
905
 
868
906
  // src/tools/spec/_helpers.ts
869
907
  var HEADING_RE = /^(#{1,6})\s+(.+)$/;
@@ -983,7 +1021,7 @@ var readSpecTool = {
983
1021
  return `Error: ${err.message}`;
984
1022
  }
985
1023
  try {
986
- const content = await fs5.readFile(input.path, "utf-8");
1024
+ const content = await fs6.readFile(input.path, "utf-8");
987
1025
  const allLines = content.split("\n");
988
1026
  const totalLines = allLines.length;
989
1027
  const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES;
@@ -1011,7 +1049,7 @@ var readSpecTool = {
1011
1049
  };
1012
1050
 
1013
1051
  // src/tools/spec/writeSpec.ts
1014
- import fs6 from "fs/promises";
1052
+ import fs7 from "fs/promises";
1015
1053
  import path4 from "path";
1016
1054
 
1017
1055
  // src/tools/_helpers/diff.ts
@@ -1088,7 +1126,7 @@ var writeSpecTool = {
1088
1126
  },
1089
1127
  streaming: {
1090
1128
  transform: async (partial) => {
1091
- const oldContent = await fs6.readFile(partial.path, "utf-8").catch(() => "");
1129
+ const oldContent = await fs7.readFile(partial.path, "utf-8").catch(() => "");
1092
1130
  const lineCount = partial.content.split("\n").length;
1093
1131
  return `Writing ${partial.path} (${lineCount} lines)
1094
1132
  ${unifiedDiff(partial.path, oldContent, partial.content)}`;
@@ -1102,13 +1140,13 @@ ${unifiedDiff(partial.path, oldContent, partial.content)}`;
1102
1140
  }
1103
1141
  const release = await acquireFileLock(input.path);
1104
1142
  try {
1105
- await fs6.mkdir(path4.dirname(input.path), { recursive: true });
1143
+ await fs7.mkdir(path4.dirname(input.path), { recursive: true });
1106
1144
  let oldContent = null;
1107
1145
  try {
1108
- oldContent = await fs6.readFile(input.path, "utf-8");
1146
+ oldContent = await fs7.readFile(input.path, "utf-8");
1109
1147
  } catch {
1110
1148
  }
1111
- await fs6.writeFile(input.path, input.content, "utf-8");
1149
+ await fs7.writeFile(input.path, input.content, "utf-8");
1112
1150
  const lineCount = input.content.split("\n").length;
1113
1151
  const label = oldContent !== null ? "Wrote" : "Created";
1114
1152
  return `${label} ${input.path} (${lineCount} lines)
@@ -1122,7 +1160,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1122
1160
  };
1123
1161
 
1124
1162
  // src/tools/spec/editSpec.ts
1125
- import fs7 from "fs/promises";
1163
+ import fs8 from "fs/promises";
1126
1164
  var editSpecTool = {
1127
1165
  clearable: true,
1128
1166
  definition: {
@@ -1172,7 +1210,7 @@ var editSpecTool = {
1172
1210
  try {
1173
1211
  let originalContent;
1174
1212
  try {
1175
- originalContent = await fs7.readFile(input.path, "utf-8");
1213
+ originalContent = await fs8.readFile(input.path, "utf-8");
1176
1214
  } catch (err) {
1177
1215
  return `Error reading file: ${err.message}`;
1178
1216
  }
@@ -1228,7 +1266,7 @@ ${tree}`;
1228
1266
  content = lines.join("\n");
1229
1267
  }
1230
1268
  try {
1231
- await fs7.writeFile(input.path, content, "utf-8");
1269
+ await fs8.writeFile(input.path, content, "utf-8");
1232
1270
  } catch (err) {
1233
1271
  return `Error writing file: ${err.message}`;
1234
1272
  }
@@ -1240,7 +1278,7 @@ ${tree}`;
1240
1278
  };
1241
1279
 
1242
1280
  // src/tools/spec/listSpecFiles.ts
1243
- import fs8 from "fs/promises";
1281
+ import fs9 from "fs/promises";
1244
1282
  import path5 from "path";
1245
1283
  var listSpecFilesTool = {
1246
1284
  clearable: false,
@@ -1270,7 +1308,7 @@ var listSpecFilesTool = {
1270
1308
  };
1271
1309
  async function listRecursive(dir) {
1272
1310
  const results = [];
1273
- const entries = await fs8.readdir(dir, { withFileTypes: true });
1311
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
1274
1312
  entries.sort((a, b) => {
1275
1313
  if (a.isDirectory() && !b.isDirectory()) {
1276
1314
  return -1;
@@ -1316,7 +1354,7 @@ var presentPublishPlanTool = {
1316
1354
  };
1317
1355
 
1318
1356
  // src/tools/spec/writePlan.ts
1319
- import fs9 from "fs/promises";
1357
+ import fs10 from "fs/promises";
1320
1358
  var PLAN_FILE = ".remy-plan.md";
1321
1359
  var writePlanTool = {
1322
1360
  clearable: false,
@@ -1341,13 +1379,13 @@ status: pending
1341
1379
  ---
1342
1380
 
1343
1381
  ${content}`;
1344
- await fs9.writeFile(PLAN_FILE, file, "utf-8");
1382
+ await fs10.writeFile(PLAN_FILE, file, "utf-8");
1345
1383
  return "Plan written to .remy-plan.md. Waiting for user approval.";
1346
1384
  }
1347
1385
  };
1348
1386
 
1349
1387
  // src/tools/spec/updatePlanStatus.ts
1350
- import fs10 from "fs/promises";
1388
+ import fs11 from "fs/promises";
1351
1389
  var PLAN_FILE2 = ".remy-plan.md";
1352
1390
  var updatePlanStatusTool = {
1353
1391
  clearable: false,
@@ -1370,15 +1408,15 @@ var updatePlanStatusTool = {
1370
1408
  const status = input.status;
1371
1409
  let content;
1372
1410
  try {
1373
- content = await fs10.readFile(PLAN_FILE2, "utf-8");
1411
+ content = await fs11.readFile(PLAN_FILE2, "utf-8");
1374
1412
  } catch {
1375
1413
  return "No plan file found.";
1376
1414
  }
1377
1415
  if (status === "rejected") {
1378
- await fs10.unlink(PLAN_FILE2);
1416
+ await fs11.unlink(PLAN_FILE2);
1379
1417
  return "Plan rejected and removed.";
1380
1418
  }
1381
- await fs10.writeFile(
1419
+ await fs11.writeFile(
1382
1420
  PLAN_FILE2,
1383
1421
  content.replace(/^status:\s*\w+/m, `status: ${status}`),
1384
1422
  "utf-8"
@@ -1684,6 +1722,90 @@ var askMindStudioSdkTool = {
1684
1722
  }
1685
1723
  };
1686
1724
 
1725
+ // src/subagents/common/runMindstudioCli.ts
1726
+ function stripFlags(args) {
1727
+ const out = [];
1728
+ for (let i = 0; i < args.length; i++) {
1729
+ const arg = args[i];
1730
+ if (arg === "--no-meta") {
1731
+ continue;
1732
+ }
1733
+ if (arg === "--output-key") {
1734
+ i++;
1735
+ continue;
1736
+ }
1737
+ out.push(arg);
1738
+ }
1739
+ return out;
1740
+ }
1741
+ async function runMindstudioCli(args, options) {
1742
+ const cleanArgs = stripFlags(args);
1743
+ const cliAction = args[0];
1744
+ const agentName = options?.caller ?? "mindstudio-cli";
1745
+ const start = Date.now();
1746
+ const raw = await runCli("mindstudio", cleanArgs, options);
1747
+ let envelope;
1748
+ try {
1749
+ envelope = JSON.parse(raw);
1750
+ } catch {
1751
+ return raw;
1752
+ }
1753
+ if (envelope && typeof envelope === "object" && Array.isArray(envelope.results)) {
1754
+ const durationMs = Date.now() - start;
1755
+ for (const step of envelope.results) {
1756
+ if (typeof step?.billingCost === "number") {
1757
+ recordUsage({
1758
+ ts: Date.now(),
1759
+ agentName,
1760
+ cliAction: `${cliAction}:${step.stepType ?? "step"}`,
1761
+ cost: nanoToDollars(step.billingCost),
1762
+ inputTokens: 0,
1763
+ outputTokens: 0,
1764
+ durationMs,
1765
+ toolNames: []
1766
+ });
1767
+ }
1768
+ }
1769
+ return JSON.stringify(envelope.results);
1770
+ }
1771
+ if (typeof envelope?.$billingCost === "number") {
1772
+ recordUsage({
1773
+ ts: Date.now(),
1774
+ agentName,
1775
+ cliAction,
1776
+ cost: nanoToDollars(envelope.$billingCost),
1777
+ billingEvents: envelope.$billingEvents,
1778
+ // CLI billing isn't expressed as input/output tokens for most actions
1779
+ // (image gen is per-image, scrape per-page, etc). `numUnits` inside each
1780
+ // billingEvent carries the per-event unit count.
1781
+ inputTokens: 0,
1782
+ outputTokens: 0,
1783
+ durationMs: Date.now() - start,
1784
+ toolNames: []
1785
+ });
1786
+ }
1787
+ if (options?.outputKey) {
1788
+ const v = envelope?.[options.outputKey];
1789
+ if (v === void 0 || v === null) {
1790
+ return JSON.stringify(stripDollarKeys(envelope));
1791
+ }
1792
+ return typeof v === "string" ? v : JSON.stringify(v);
1793
+ }
1794
+ return JSON.stringify(stripDollarKeys(envelope));
1795
+ }
1796
+ function stripDollarKeys(envelope) {
1797
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
1798
+ return envelope;
1799
+ }
1800
+ const out = {};
1801
+ for (const [k, v] of Object.entries(envelope)) {
1802
+ if (!k.startsWith("$")) {
1803
+ out[k] = v;
1804
+ }
1805
+ }
1806
+ return out;
1807
+ }
1808
+
1687
1809
  // src/tools/common/searchGoogle.ts
1688
1810
  var searchGoogleTool = {
1689
1811
  clearable: false,
@@ -1703,19 +1825,9 @@ var searchGoogleTool = {
1703
1825
  },
1704
1826
  async execute(input, context) {
1705
1827
  const query = input.query;
1706
- return runCli(
1707
- "mindstudio",
1708
- [
1709
- "search-google",
1710
- "--query",
1711
- query,
1712
- "--export-type",
1713
- "json",
1714
- "--output-key",
1715
- "results",
1716
- "--no-meta"
1717
- ],
1718
- { maxBuffer: 512 * 1024, onLog: context?.onLog }
1828
+ return runMindstudioCli(
1829
+ ["search-google", "--query", query, "--export-type", "json"],
1830
+ { outputKey: "results", maxBuffer: 512 * 1024, onLog: context?.onLog }
1719
1831
  );
1720
1832
  }
1721
1833
  };
@@ -1779,7 +1891,7 @@ var compactConversationTool = {
1779
1891
  };
1780
1892
 
1781
1893
  // src/tools/code/readFile.ts
1782
- import fs11 from "fs/promises";
1894
+ import fs12 from "fs/promises";
1783
1895
  var DEFAULT_MAX_LINES2 = 500;
1784
1896
  function isBinary(buffer) {
1785
1897
  const sample = buffer.subarray(0, 8192);
@@ -1816,7 +1928,7 @@ var readFileTool = {
1816
1928
  },
1817
1929
  async execute(input) {
1818
1930
  try {
1819
- const buffer = await fs11.readFile(input.path);
1931
+ const buffer = await fs12.readFile(input.path);
1820
1932
  if (isBinary(buffer)) {
1821
1933
  const size = buffer.length;
1822
1934
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -1850,7 +1962,7 @@ var readFileTool = {
1850
1962
  };
1851
1963
 
1852
1964
  // src/tools/code/writeFile.ts
1853
- import fs12 from "fs/promises";
1965
+ import fs13 from "fs/promises";
1854
1966
  import path6 from "path";
1855
1967
  var writeFileTool = {
1856
1968
  clearable: true,
@@ -1888,7 +2000,7 @@ var writeFileTool = {
1888
2000
  lastNewlineCount = newlineCount;
1889
2001
  const lastNewline = partial.content.lastIndexOf("\n");
1890
2002
  const completeContent = partial.content.substring(0, lastNewline + 1);
1891
- const oldContent = await fs12.readFile(partial.path, "utf-8").catch(() => "");
2003
+ const oldContent = await fs13.readFile(partial.path, "utf-8").catch(() => "");
1892
2004
  return `Writing ${partial.path} (${newlineCount} lines)
1893
2005
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1894
2006
  }
@@ -1897,13 +2009,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
1897
2009
  async execute(input) {
1898
2010
  const release = await acquireFileLock(input.path);
1899
2011
  try {
1900
- await fs12.mkdir(path6.dirname(input.path), { recursive: true });
2012
+ await fs13.mkdir(path6.dirname(input.path), { recursive: true });
1901
2013
  let oldContent = null;
1902
2014
  try {
1903
- oldContent = await fs12.readFile(input.path, "utf-8");
2015
+ oldContent = await fs13.readFile(input.path, "utf-8");
1904
2016
  } catch {
1905
2017
  }
1906
- await fs12.writeFile(input.path, input.content, "utf-8");
2018
+ await fs13.writeFile(input.path, input.content, "utf-8");
1907
2019
  const lineCount = input.content.split("\n").length;
1908
2020
  const label = oldContent !== null ? "Wrote" : "Created";
1909
2021
  return `${label} ${input.path} (${lineCount} lines)
@@ -1917,7 +2029,7 @@ ${unifiedDiff(input.path, oldContent ?? "", input.content)}`;
1917
2029
  };
1918
2030
 
1919
2031
  // src/tools/code/editFile/index.ts
1920
- import fs13 from "fs/promises";
2032
+ import fs14 from "fs/promises";
1921
2033
 
1922
2034
  // src/tools/code/editFile/_helpers.ts
1923
2035
  function buildLineOffsets(content) {
@@ -2030,7 +2142,7 @@ var editFileTool = {
2030
2142
  async execute(input) {
2031
2143
  const release = await acquireFileLock(input.path);
2032
2144
  try {
2033
- const content = await fs13.readFile(input.path, "utf-8");
2145
+ const content = await fs14.readFile(input.path, "utf-8");
2034
2146
  const { old_string, new_string, replace_all } = input;
2035
2147
  const occurrences = findOccurrences(content, old_string);
2036
2148
  if (replace_all) {
@@ -2046,7 +2158,7 @@ var editFileTool = {
2046
2158
  new_string
2047
2159
  );
2048
2160
  }
2049
- await fs13.writeFile(input.path, updated, "utf-8");
2161
+ await fs14.writeFile(input.path, updated, "utf-8");
2050
2162
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2051
2163
  ${unifiedDiff(input.path, content, updated)}`;
2052
2164
  }
@@ -2057,7 +2169,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2057
2169
  old_string.length,
2058
2170
  new_string
2059
2171
  );
2060
- await fs13.writeFile(input.path, updated, "utf-8");
2172
+ await fs14.writeFile(input.path, updated, "utf-8");
2061
2173
  return `Updated ${input.path}
2062
2174
  ${unifiedDiff(input.path, content, updated)}`;
2063
2175
  }
@@ -2073,7 +2185,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2073
2185
  flex.matchedText.length,
2074
2186
  new_string
2075
2187
  );
2076
- await fs13.writeFile(input.path, updated, "utf-8");
2188
+ await fs14.writeFile(input.path, updated, "utf-8");
2077
2189
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2078
2190
  ${unifiedDiff(input.path, content, updated)}`;
2079
2191
  }
@@ -2305,12 +2417,12 @@ var globTool = {
2305
2417
  };
2306
2418
 
2307
2419
  // src/tools/code/listDir.ts
2308
- import fs14 from "fs/promises";
2420
+ import fs15 from "fs/promises";
2309
2421
  import path7 from "path";
2310
2422
  var EXCLUDE = /* @__PURE__ */ new Set([".git", "node_modules"]);
2311
2423
  var MAX_CHILDREN = 15;
2312
2424
  async function readAndSort(dirPath) {
2313
- const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2425
+ const entries = await fs15.readdir(dirPath, { withFileTypes: true });
2314
2426
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2315
2427
  if (a.isDirectory() && !b.isDirectory()) {
2316
2428
  return -1;
@@ -2351,7 +2463,7 @@ function formatSize(bytes) {
2351
2463
  }
2352
2464
  async function formatFile(dirPath, name, indent) {
2353
2465
  try {
2354
- const stat = await fs14.stat(path7.join(dirPath, name));
2466
+ const stat = await fs15.stat(path7.join(dirPath, name));
2355
2467
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2356
2468
  } catch {
2357
2469
  return `${indent}${name}`;
@@ -2655,14 +2767,13 @@ var queryDatabaseTool = {
2655
2767
 
2656
2768
  // src/subagents/common/analyzeImage.ts
2657
2769
  var VISION_MODEL = "claude-4-6-sonnet";
2658
- var VISION_MODEL_OVERRIDE = JSON.stringify({
2770
+ var VISION_MODEL_OVERRIDE = {
2659
2771
  model: VISION_MODEL,
2660
2772
  config: { thinkingBudget: "off" }
2661
- });
2773
+ };
2662
2774
  async function analyzeImage(params) {
2663
2775
  const { prompt, imageUrl, timeout = 2e5, onLog } = params;
2664
- return runCli(
2665
- "mindstudio",
2776
+ return runMindstudioCli(
2666
2777
  [
2667
2778
  "analyze-image",
2668
2779
  "--prompt",
@@ -2670,12 +2781,9 @@ async function analyzeImage(params) {
2670
2781
  "--image-url",
2671
2782
  imageUrl,
2672
2783
  "--vision-model-override",
2673
- JSON.stringify(VISION_MODEL_OVERRIDE),
2674
- "--output-key",
2675
- "analysis",
2676
- "--no-meta"
2784
+ JSON.stringify(VISION_MODEL_OVERRIDE)
2677
2785
  ],
2678
- { timeout, onLog }
2786
+ { outputKey: "analysis", timeout, onLog }
2679
2787
  );
2680
2788
  }
2681
2789
 
@@ -2719,11 +2827,11 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
2719
2827
  let prompt;
2720
2828
  let existingUrl;
2721
2829
  let onLog;
2722
- let path11;
2830
+ let path12;
2723
2831
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
2724
2832
  prompt = promptOrOptions.prompt;
2725
2833
  existingUrl = promptOrOptions.imageUrl;
2726
- path11 = promptOrOptions.path;
2834
+ path12 = promptOrOptions.path;
2727
2835
  onLog = promptOrOptions.onLog;
2728
2836
  } else {
2729
2837
  prompt = promptOrOptions;
@@ -2735,7 +2843,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
2735
2843
  } else {
2736
2844
  const ssResult = await sidecarRequest(
2737
2845
  "/screenshot-full-page",
2738
- path11 ? { path: path11 } : void 0,
2846
+ path12 ? { path: path12 } : void 0,
2739
2847
  { timeout: 12e4 }
2740
2848
  );
2741
2849
  url = ssResult?.url || ssResult?.screenshotUrl;
@@ -2792,7 +2900,7 @@ function startStatusWatcher(config) {
2792
2900
  "Content-Type": "application/json",
2793
2901
  Authorization: `Bearer ${apiConfig.apiKey}`
2794
2902
  },
2795
- body: JSON.stringify({ context }),
2903
+ body: JSON.stringify({ appId: apiConfig.appId, context }),
2796
2904
  signal
2797
2905
  });
2798
2906
  if (!res.ok || stopped) {
@@ -2984,8 +3092,14 @@ ${content}` : attachmentHeader;
2984
3092
  const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
2985
3093
  const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
2986
3094
  const thinking = blocks.filter(
2987
- (b) => b.type === "thinking"
2988
- ).map((b) => ({ thinking: b.thinking, signature: b.signature }));
3095
+ (b) => b.type === "thinking" || b.type === "redacted_thinking"
3096
+ ).map(
3097
+ (b) => b.type === "thinking" ? {
3098
+ type: "thinking",
3099
+ thinking: b.thinking,
3100
+ signature: b.signature
3101
+ } : { type: "redacted_thinking", data: b.data }
3102
+ );
2989
3103
  const cleaned2 = {
2990
3104
  role: msg.role,
2991
3105
  content: text
@@ -3075,10 +3189,13 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3075
3189
  if (signal?.aborted) {
3076
3190
  return abortResult([]);
3077
3191
  }
3192
+ const iterStart = Date.now();
3078
3193
  const contentBlocks = [];
3079
3194
  let thinkingStartedAt = 0;
3195
+ let lastThinkingRelatedStartedAt;
3080
3196
  let stopReason = "end_turn";
3081
3197
  let currentToolNames = "";
3198
+ let lastUsage;
3082
3199
  const statusWatcher = startStatusWatcher({
3083
3200
  apiConfig,
3084
3201
  getContext: () => {
@@ -3153,8 +3270,20 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3153
3270
  startedAt: thinkingStartedAt,
3154
3271
  completedAt: event.ts
3155
3272
  });
3273
+ lastThinkingRelatedStartedAt = thinkingStartedAt;
3156
3274
  thinkingStartedAt = 0;
3157
3275
  break;
3276
+ case "redacted_thinking_complete": {
3277
+ const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
3278
+ contentBlocks.push({
3279
+ type: "redacted_thinking",
3280
+ data: event.data,
3281
+ startedAt,
3282
+ completedAt: event.ts
3283
+ });
3284
+ lastThinkingRelatedStartedAt = startedAt;
3285
+ break;
3286
+ }
3158
3287
  case "tool_use":
3159
3288
  contentBlocks.push({
3160
3289
  type: "tool",
@@ -3172,6 +3301,30 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3172
3301
  break;
3173
3302
  case "done":
3174
3303
  stopReason = event.stopReason;
3304
+ lastUsage = {
3305
+ inputTokens: event.usage.inputTokens,
3306
+ outputTokens: event.usage.outputTokens,
3307
+ cacheCreationTokens: event.usage.cacheCreationTokens,
3308
+ cacheReadTokens: event.usage.cacheReadTokens,
3309
+ llmCalls: 1
3310
+ };
3311
+ recordUsage({
3312
+ ts: Date.now(),
3313
+ requestId,
3314
+ agentName: subAgentId || "sub-agent",
3315
+ parentToolId,
3316
+ modelId: event.modelId,
3317
+ inputTokens: event.usage.inputTokens,
3318
+ outputTokens: event.usage.outputTokens,
3319
+ cacheCreationTokens: event.usage.cacheCreationTokens,
3320
+ cacheReadTokens: event.usage.cacheReadTokens,
3321
+ cost: nanoToDollars(event.cost),
3322
+ billingEvents: event.billingEvents,
3323
+ durationMs: Date.now() - iterStart,
3324
+ toolNames: contentBlocks.filter(
3325
+ (b) => b.type === "tool"
3326
+ ).map((b) => b.name)
3327
+ });
3175
3328
  break;
3176
3329
  case "error":
3177
3330
  return {
@@ -3191,7 +3344,8 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3191
3344
  }
3192
3345
  messages.push({
3193
3346
  role: "assistant",
3194
- content: contentBlocks
3347
+ content: contentBlocks,
3348
+ ...lastUsage ? { usage: lastUsage } : {}
3195
3349
  });
3196
3350
  const toolCalls = contentBlocks.filter(
3197
3351
  (b) => b.type === "tool"
@@ -3517,11 +3671,11 @@ var BROWSER_TOOLS = [
3517
3671
  var BROWSER_EXTERNAL_TOOLS = /* @__PURE__ */ new Set(["browserCommand"]);
3518
3672
 
3519
3673
  // src/subagents/browserAutomation/prompt.ts
3520
- import fs15 from "fs";
3674
+ import fs16 from "fs";
3521
3675
  var BASE_PROMPT = readAsset("subagents/browserAutomation", "prompt.md");
3522
3676
  function getBrowserAutomationPrompt() {
3523
3677
  try {
3524
- const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
3678
+ const appSpec = fs16.readFileSync("src/app.md", "utf-8").trim();
3525
3679
  return `${BASE_PROMPT}
3526
3680
 
3527
3681
  <!-- cache_breakpoint -->
@@ -3540,7 +3694,7 @@ var browserAutomationTool = {
3540
3694
  clearable: true,
3541
3695
  definition: {
3542
3696
  name: "runAutomatedBrowserTest",
3543
- description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes to frontend code, to reproduce user-reported issues, or to test end-to-end flows. Reports settled states between steps; it cannot reliably observe transient animation frames. For timing-sensitive bugs, read the source first. Never give it explicit values to use when filling out forms or creating accounts \u2014 it will use its own judgement (often it needs specific values to trigger dev-mode bypasses of things like login verification codes).",
3697
+ description: "Run an automated browser test against the live preview. Describe what to test \u2014 the agent figures out how. Use after meaningful changes to frontend code, to reproduce user-reported issues, or to test end-to-end flows. Never give it explicit values to use when filling out forms or creating accounts \u2014 it will use its own judgement (often it needs specific values to trigger dev-mode bypasses of things like login verification codes).",
3544
3698
  inputSchema: {
3545
3699
  type: "object",
3546
3700
  properties: {
@@ -3616,13 +3770,13 @@ var browserAutomationTool = {
3616
3770
  imageUrl: s.result.url,
3617
3771
  prompt: buildScreenshotAnalysisPrompt({
3618
3772
  styleMap: s.result.styleMap
3619
- })
3773
+ }),
3774
+ visionModelOverride: VISION_MODEL_OVERRIDE
3620
3775
  }
3621
3776
  }));
3622
- const batchResult = await runCli(
3623
- "mindstudio",
3624
- ["batch", "--no-meta", JSON.stringify(batchInput)],
3625
- { timeout: 2e5 }
3777
+ const batchResult = await runMindstudioCli(
3778
+ ["batch", JSON.stringify(batchInput)],
3779
+ { timeout: 2e5, caller: "browserAutomation" }
3626
3780
  );
3627
3781
  try {
3628
3782
  const analyses = JSON.parse(batchResult);
@@ -3667,7 +3821,7 @@ var screenshotTool = {
3667
3821
  clearable: true,
3668
3822
  definition: {
3669
3823
  name: "screenshot",
3670
- description: "Capture a full-height screenshot of the app preview and get a description of what's on screen. Captures the settled page state \u2014 it cannot reliably catch animations, transitions, or transient state. For timing-sensitive bugs, read the source instead. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal), use the instructions param to describe the steps.",
3824
+ description: "Capture a full-height screenshot of the app preview and get a description of what's on screen. Captures the settled page state \u2014 it cannot catch animations, transitions, or transient state. Optionally provide specific questions about what you're looking for. Use a bulleted list to ask many questions at once. To ask additional questions about a screenshot you have already captured, pass its URL as imageUrl to skip recapture. If the screenshot requires interaction first (logging in, clicking a tab, dismissing a modal), use the instructions param to describe the steps.",
3671
3825
  inputSchema: {
3672
3826
  type: "object",
3673
3827
  properties: {
@@ -3845,19 +3999,9 @@ var definition = {
3845
3999
  }
3846
4000
  };
3847
4001
  async function execute(input, onLog) {
3848
- return runCli(
3849
- "mindstudio",
3850
- [
3851
- "search-google",
3852
- "--query",
3853
- input.query,
3854
- "--export-type",
3855
- "json",
3856
- "--output-key",
3857
- "results",
3858
- "--no-meta"
3859
- ],
3860
- { onLog }
4002
+ return runMindstudioCli(
4003
+ ["search-google", "--query", input.query, "--export-type", "json"],
4004
+ { outputKey: "results", onLog, caller: "designExpert" }
3861
4005
  );
3862
4006
  }
3863
4007
 
@@ -3887,17 +4031,15 @@ async function execute2(input, onLog) {
3887
4031
  if (input.screenshot) {
3888
4032
  pageOptions.screenshot = true;
3889
4033
  }
3890
- return runCli(
3891
- "mindstudio",
4034
+ return runMindstudioCli(
3892
4035
  [
3893
4036
  "scrape-url",
3894
4037
  "--url",
3895
4038
  input.url,
3896
4039
  "--page-options",
3897
- JSON.stringify(pageOptions),
3898
- "--no-meta"
4040
+ JSON.stringify(pageOptions)
3899
4041
  ],
3900
- { onLog }
4042
+ { onLog, caller: "designExpert" }
3901
4043
  );
3902
4044
  }
3903
4045
 
@@ -3955,8 +4097,7 @@ async function execute3(input, onLog) {
3955
4097
  const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
3956
4098
  let imageUrl = url;
3957
4099
  if (!isImageUrl) {
3958
- const ssUrl = await runCli(
3959
- "mindstudio",
4100
+ const ssUrl = await runMindstudioCli(
3960
4101
  [
3961
4102
  "screenshot-url",
3962
4103
  "--url",
@@ -3966,12 +4107,14 @@ async function execute3(input, onLog) {
3966
4107
  "--width",
3967
4108
  "1440",
3968
4109
  "--delay",
3969
- "2000",
3970
- "--output-key",
3971
- "screenshotUrl",
3972
- "--no-meta"
4110
+ "2000"
3973
4111
  ],
3974
- { timeout: 12e4, onLog }
4112
+ {
4113
+ outputKey: "screenshotUrl",
4114
+ timeout: 12e4,
4115
+ onLog,
4116
+ caller: "designExpert"
4117
+ }
3975
4118
  );
3976
4119
  if (ssUrl.startsWith("Error")) {
3977
4120
  return `Could not screenshot ${url}: ${ssUrl}`;
@@ -4102,10 +4245,10 @@ __export(generateImages_exports, {
4102
4245
 
4103
4246
  // src/subagents/designExpert/tools/images/enhancePrompt.ts
4104
4247
  var ENHANCE_MODEL = "claude-4-6-sonnet";
4105
- var MODEL_OVERRIDE = JSON.stringify({
4248
+ var MODEL_OVERRIDE = {
4106
4249
  model: ENHANCE_MODEL,
4107
4250
  config: { reasoning: "false" }
4108
- });
4251
+ };
4109
4252
  var SYSTEM_PROMPT = readAsset(
4110
4253
  "subagents/designExpert/tools/images/enhance-image-prompt.md"
4111
4254
  );
@@ -4129,19 +4272,15 @@ ${context}
4129
4272
  <brief>
4130
4273
  ${brief}
4131
4274
  </brief>`;
4132
- const enhanced = await runCli(
4133
- "mindstudio",
4275
+ const enhanced = await runMindstudioCli(
4134
4276
  [
4135
4277
  "generate-text",
4136
4278
  "--message",
4137
4279
  message,
4138
4280
  "--model-override",
4139
- JSON.stringify(MODEL_OVERRIDE),
4140
- "--output-key",
4141
- "content",
4142
- "--no-meta"
4281
+ JSON.stringify(MODEL_OVERRIDE)
4143
4282
  ],
4144
- { timeout: 6e4, onLog }
4283
+ { outputKey: "content", timeout: 6e4, onLog, caller: "designExpert" }
4145
4284
  );
4146
4285
  return enhanced.trim();
4147
4286
  }
@@ -4177,11 +4316,14 @@ async function generateImageAssets(opts) {
4177
4316
  config
4178
4317
  }
4179
4318
  });
4180
- const url = await runCli(
4181
- "mindstudio",
4182
- ["generate-image", "--output-key", "imageUrl", "--no-meta"],
4183
- { jsonLogs: true, timeout: 2e5, onLog, stdin: step }
4184
- );
4319
+ const url = await runMindstudioCli(["generate-image"], {
4320
+ outputKey: "imageUrl",
4321
+ jsonLogs: true,
4322
+ timeout: 2e5,
4323
+ onLog,
4324
+ stdin: step,
4325
+ caller: "designExpert"
4326
+ });
4185
4327
  imageUrls = [url];
4186
4328
  } else {
4187
4329
  const steps = enhancedPrompts.map((prompt) => ({
@@ -4194,11 +4336,12 @@ async function generateImageAssets(opts) {
4194
4336
  }
4195
4337
  }
4196
4338
  }));
4197
- const batchResult = await runCli("mindstudio", ["batch", "--no-meta"], {
4339
+ const batchResult = await runMindstudioCli(["batch"], {
4198
4340
  jsonLogs: true,
4199
4341
  timeout: 2e5,
4200
4342
  onLog,
4201
- stdin: JSON.stringify(steps)
4343
+ stdin: JSON.stringify(steps),
4344
+ caller: "designExpert"
4202
4345
  });
4203
4346
  try {
4204
4347
  const parsed = JSON.parse(batchResult);
@@ -4215,17 +4358,14 @@ async function generateImageAssets(opts) {
4215
4358
  if (url.startsWith("Error")) {
4216
4359
  return url;
4217
4360
  }
4218
- const result = await runCli(
4219
- "mindstudio",
4220
- [
4221
- "remove-background-from-image",
4222
- "--image-url",
4223
- url,
4224
- "--output-key",
4225
- "imageUrl",
4226
- "--no-meta"
4227
- ],
4228
- { timeout: 2e5, onLog }
4361
+ const result = await runMindstudioCli(
4362
+ ["remove-background-from-image", "--image-url", url],
4363
+ {
4364
+ outputKey: "imageUrl",
4365
+ timeout: 2e5,
4366
+ onLog,
4367
+ caller: "designExpert"
4368
+ }
4229
4369
  );
4230
4370
  return result.startsWith("Error") ? url : result;
4231
4371
  })
@@ -4377,12 +4517,12 @@ async function executeDesignExpertTool(name, input, context, toolCallId, onLog)
4377
4517
  }
4378
4518
 
4379
4519
  // src/subagents/common/context.ts
4380
- import fs16 from "fs";
4520
+ import fs17 from "fs";
4381
4521
  import path8 from "path";
4382
4522
  function walkMdFiles2(dir, skip) {
4383
4523
  const files = [];
4384
4524
  try {
4385
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
4525
+ for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
4386
4526
  const full = path8.join(dir, entry.name);
4387
4527
  if (entry.isDirectory()) {
4388
4528
  if (!skip?.has(entry.name)) {
@@ -4398,7 +4538,7 @@ function walkMdFiles2(dir, skip) {
4398
4538
  }
4399
4539
  function parseFrontmatter2(filePath) {
4400
4540
  try {
4401
- const content = fs16.readFileSync(filePath, "utf-8");
4541
+ const content = fs17.readFileSync(filePath, "utf-8");
4402
4542
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4403
4543
  if (!match) {
4404
4544
  return {};
@@ -4444,7 +4584,7 @@ function loadRoadmapIndex() {
4444
4584
  const parts = [];
4445
4585
  try {
4446
4586
  const indexJson = JSON.parse(
4447
- fs16.readFileSync("src/roadmap/index.json", "utf-8")
4587
+ fs17.readFileSync("src/roadmap/index.json", "utf-8")
4448
4588
  );
4449
4589
  if (indexJson.lanes?.length > 0) {
4450
4590
  const laneLines = indexJson.lanes.map(
@@ -4547,7 +4687,7 @@ The first-party SDK (@mindstudio-ai/agent) provides access to 200+ AI models (Op
4547
4687
  }
4548
4688
 
4549
4689
  // src/subagents/designExpert/data/sampleCache.ts
4550
- import fs17 from "fs";
4690
+ import fs18 from "fs";
4551
4691
  var SAMPLE_FILE = ".remy-design-sample.json";
4552
4692
  var cached = null;
4553
4693
  function generateIndices(poolSize, sampleSize) {
@@ -4561,14 +4701,14 @@ function generateIndices(poolSize, sampleSize) {
4561
4701
  }
4562
4702
  function load() {
4563
4703
  try {
4564
- return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
4704
+ return JSON.parse(fs18.readFileSync(SAMPLE_FILE, "utf-8"));
4565
4705
  } catch {
4566
4706
  return null;
4567
4707
  }
4568
4708
  }
4569
4709
  function save(indices) {
4570
4710
  try {
4571
- fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4711
+ fs18.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
4572
4712
  } catch {
4573
4713
  }
4574
4714
  }
@@ -4897,7 +5037,7 @@ var VISION_TOOLS = [
4897
5037
  ];
4898
5038
 
4899
5039
  // src/subagents/productVision/executor.ts
4900
- import fs18 from "fs";
5040
+ import fs19 from "fs";
4901
5041
  import path9 from "path";
4902
5042
  var ROADMAP_DIR = "src/roadmap";
4903
5043
  var PITCH_DECK_SHELL = readAsset(
@@ -4912,13 +5052,13 @@ async function executeVisionTool(name, input, context) {
4912
5052
  case "writeFile": {
4913
5053
  const filePath = resolve(input.path);
4914
5054
  try {
4915
- fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5055
+ fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
4916
5056
  let oldContent = null;
4917
5057
  try {
4918
- oldContent = fs18.readFileSync(filePath, "utf-8");
5058
+ oldContent = fs19.readFileSync(filePath, "utf-8");
4919
5059
  } catch {
4920
5060
  }
4921
- fs18.writeFileSync(filePath, input.content, "utf-8");
5061
+ fs19.writeFileSync(filePath, input.content, "utf-8");
4922
5062
  const lineCount = input.content.split("\n").length;
4923
5063
  const label = oldContent !== null ? "Wrote" : "Created";
4924
5064
  return `${label} ${filePath} (${lineCount} lines)
@@ -4930,11 +5070,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
4930
5070
  case "deleteFile": {
4931
5071
  const filePath = resolve(input.path);
4932
5072
  try {
4933
- if (!fs18.existsSync(filePath)) {
5073
+ if (!fs19.existsSync(filePath)) {
4934
5074
  return `Error: ${filePath} does not exist`;
4935
5075
  }
4936
- const oldContent = fs18.readFileSync(filePath, "utf-8");
4937
- fs18.unlinkSync(filePath);
5076
+ const oldContent = fs19.readFileSync(filePath, "utf-8");
5077
+ fs19.unlinkSync(filePath);
4938
5078
  return `Deleted ${filePath}
4939
5079
  ${unifiedDiff(filePath, oldContent, "")}`;
4940
5080
  } catch (err) {
@@ -4947,8 +5087,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
4947
5087
  }
4948
5088
  const filePath = resolve("pitch.html");
4949
5089
  try {
4950
- fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
4951
- const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
5090
+ fs19.mkdirSync(ROADMAP_DIR, { recursive: true });
5091
+ const existing = fs19.existsSync(filePath) ? fs19.readFileSync(filePath, "utf-8").trim() : "";
4952
5092
  const currentDeck = existing || PITCH_DECK_SHELL;
4953
5093
  const task = `
4954
5094
  <pitch_content>${input.task}</pitch_content>
@@ -4973,7 +5113,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
4973
5113
  /```(?:html|wireframe)\n([\s\S]*?)```/
4974
5114
  );
4975
5115
  const html = htmlMatch ? htmlMatch[1].trim() : result;
4976
- fs18.writeFileSync(filePath, html, "utf-8");
5116
+ fs19.writeFileSync(filePath, html, "utf-8");
4977
5117
  return `Pitch deck written successfully.`;
4978
5118
  } catch (err) {
4979
5119
  return `Error generating pitch deck: ${err.message}`;
@@ -5190,15 +5330,13 @@ var scrapeWebUrlTool = {
5190
5330
  if (screenshot) {
5191
5331
  pageOptions.screenshot = true;
5192
5332
  }
5193
- return runCli(
5194
- "mindstudio",
5333
+ return runMindstudioCli(
5195
5334
  [
5196
5335
  "scrape-url",
5197
5336
  "--url",
5198
5337
  url,
5199
5338
  "--page-options",
5200
- JSON.stringify(pageOptions),
5201
- "--no-meta"
5339
+ JSON.stringify(pageOptions)
5202
5340
  ],
5203
5341
  { onLog: context?.onLog }
5204
5342
  );
@@ -5306,7 +5444,7 @@ function triggerCompaction(state, apiConfig, opts = {}) {
5306
5444
  }
5307
5445
 
5308
5446
  // src/brandExtraction/index.ts
5309
- import fs19 from "fs";
5447
+ import fs20 from "fs";
5310
5448
  import path10 from "path";
5311
5449
  import { createHash } from "crypto";
5312
5450
  var log8 = createLogger("brandExtraction");
@@ -5355,7 +5493,7 @@ function sha256(input) {
5355
5493
  }
5356
5494
  function readSafe(filePath) {
5357
5495
  try {
5358
- return fs19.readFileSync(filePath, "utf-8");
5496
+ return fs20.readFileSync(filePath, "utf-8");
5359
5497
  } catch {
5360
5498
  return "";
5361
5499
  }
@@ -5363,7 +5501,7 @@ function readSafe(filePath) {
5363
5501
  function walkMdFiles3(dir) {
5364
5502
  const results = [];
5365
5503
  try {
5366
- const entries = fs19.readdirSync(dir, { withFileTypes: true });
5504
+ const entries = fs20.readdirSync(dir, { withFileTypes: true });
5367
5505
  for (const entry of entries) {
5368
5506
  const full = path10.join(dir, entry.name);
5369
5507
  if (entry.isDirectory()) {
@@ -5378,7 +5516,7 @@ function walkMdFiles3(dir) {
5378
5516
  }
5379
5517
  function parseFrontmatter3(filePath) {
5380
5518
  try {
5381
- const content = fs19.readFileSync(filePath, "utf-8");
5519
+ const content = fs20.readFileSync(filePath, "utf-8");
5382
5520
  const match = content.match(/^---\n([\s\S]*?)\n---/);
5383
5521
  if (!match) {
5384
5522
  return { type: "" };
@@ -5397,6 +5535,7 @@ async function extractBrand(apiConfig) {
5397
5535
  return { version: 1 };
5398
5536
  }
5399
5537
  let responseText = "";
5538
+ const iterStart = Date.now();
5400
5539
  try {
5401
5540
  for await (const event of streamChat({
5402
5541
  ...apiConfig,
@@ -5407,6 +5546,20 @@ async function extractBrand(apiConfig) {
5407
5546
  })) {
5408
5547
  if (event.type === "text") {
5409
5548
  responseText += event.text;
5549
+ } else if (event.type === "done") {
5550
+ recordUsage({
5551
+ ts: Date.now(),
5552
+ agentName: "brandExtractor",
5553
+ modelId: event.modelId,
5554
+ inputTokens: event.usage.inputTokens,
5555
+ outputTokens: event.usage.outputTokens,
5556
+ cacheCreationTokens: event.usage.cacheCreationTokens,
5557
+ cacheReadTokens: event.usage.cacheReadTokens,
5558
+ cost: nanoToDollars(event.cost),
5559
+ billingEvents: event.billingEvents,
5560
+ durationMs: Date.now() - iterStart,
5561
+ toolNames: []
5562
+ });
5410
5563
  } else if (event.type === "error") {
5411
5564
  log8.error("Brand extraction stream error", { error: event.error });
5412
5565
  return null;
@@ -5541,14 +5694,14 @@ function pickFont(raw) {
5541
5694
  }
5542
5695
  function persistBrand(brand, inputHash) {
5543
5696
  const tmp = `${BRAND_FILE}.tmp`;
5544
- fs19.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
5545
- fs19.renameSync(tmp, BRAND_FILE);
5697
+ fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
5698
+ fs20.renameSync(tmp, BRAND_FILE);
5546
5699
  const cache = { inputHash, generatedAt: Date.now() };
5547
- fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
5700
+ fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
5548
5701
  }
5549
5702
  function readCache() {
5550
5703
  try {
5551
- const raw = fs19.readFileSync(CACHE_FILE, "utf-8");
5704
+ const raw = fs20.readFileSync(CACHE_FILE, "utf-8");
5552
5705
  const parsed = JSON.parse(raw);
5553
5706
  if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
5554
5707
  return parsed;
@@ -5581,12 +5734,14 @@ function triggerBrandExtraction(apiConfig) {
5581
5734
  }
5582
5735
 
5583
5736
  // src/session.ts
5584
- import fs20 from "fs";
5737
+ import fs21 from "fs";
5738
+ import path11 from "path";
5585
5739
  var log10 = createLogger("session");
5586
5740
  var SESSION_FILE = ".remy-session.json";
5741
+ var ARCHIVE_DIR = ".logs/sessions";
5587
5742
  function loadSession(state) {
5588
5743
  try {
5589
- const raw = fs20.readFileSync(SESSION_FILE, "utf-8");
5744
+ const raw = fs21.readFileSync(SESSION_FILE, "utf-8");
5590
5745
  const data = JSON.parse(raw);
5591
5746
  if (Array.isArray(data.messages) && data.messages.length > 0) {
5592
5747
  state.messages = sanitizeMessages(data.messages);
@@ -5635,7 +5790,7 @@ function sanitizeMessages(messages) {
5635
5790
  }
5636
5791
  function saveSession(state) {
5637
5792
  try {
5638
- fs20.writeFileSync(
5793
+ fs21.writeFileSync(
5639
5794
  SESSION_FILE,
5640
5795
  JSON.stringify({ messages: state.messages }, null, 2),
5641
5796
  "utf-8"
@@ -5648,8 +5803,21 @@ function saveSession(state) {
5648
5803
  function clearSession(state) {
5649
5804
  state.messages = [];
5650
5805
  try {
5651
- fs20.unlinkSync(SESSION_FILE);
5652
- } catch {
5806
+ if (fs21.existsSync(SESSION_FILE)) {
5807
+ fs21.mkdirSync(ARCHIVE_DIR, { recursive: true });
5808
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5809
+ const dest = path11.join(ARCHIVE_DIR, `cleared-${ts}.json`);
5810
+ fs21.renameSync(SESSION_FILE, dest);
5811
+ log10.info("Session archived on clear", { dest });
5812
+ }
5813
+ } catch (err) {
5814
+ log10.warn("Session archive on clear failed, deleting instead", {
5815
+ error: err.message
5816
+ });
5817
+ try {
5818
+ fs21.unlinkSync(SESSION_FILE);
5819
+ } catch {
5820
+ }
5653
5821
  }
5654
5822
  }
5655
5823
 
@@ -5959,9 +6127,11 @@ async function runTurn(params) {
5959
6127
  saveSession(state);
5960
6128
  return;
5961
6129
  }
6130
+ const iterStart = Date.now();
5962
6131
  const contentBlocks = [];
5963
6132
  const thinkingBlockStartTimes = [];
5964
6133
  let thinkingCompleteCount = 0;
6134
+ let lastThinkingRelatedStartedAt;
5965
6135
  let textBlockOpen = false;
5966
6136
  const toolInputAccumulators = /* @__PURE__ */ new Map();
5967
6137
  let stopReason = "end_turn";
@@ -6093,16 +6263,30 @@ async function runTurn(params) {
6093
6263
  }
6094
6264
  onEvent({ type: "thinking", text: event.text });
6095
6265
  break;
6096
- case "thinking_complete":
6266
+ case "thinking_complete": {
6267
+ const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
6097
6268
  contentBlocks.push({
6098
6269
  type: "thinking",
6099
6270
  thinking: event.thinking,
6100
6271
  signature: event.signature,
6101
- startedAt: thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts,
6272
+ startedAt,
6102
6273
  completedAt: event.ts
6103
6274
  });
6104
6275
  thinkingCompleteCount++;
6276
+ lastThinkingRelatedStartedAt = startedAt;
6277
+ break;
6278
+ }
6279
+ case "redacted_thinking_complete": {
6280
+ const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
6281
+ contentBlocks.push({
6282
+ type: "redacted_thinking",
6283
+ data: event.data,
6284
+ startedAt,
6285
+ completedAt: event.ts
6286
+ });
6287
+ lastThinkingRelatedStartedAt = startedAt;
6105
6288
  break;
6289
+ }
6106
6290
  case "tool_input_delta": {
6107
6291
  const acc = getOrCreateAccumulator2(event.id, event.name);
6108
6292
  acc.json += event.delta;
@@ -6156,6 +6340,22 @@ async function runTurn(params) {
6156
6340
  turnOutputTokens += event.usage.outputTokens;
6157
6341
  turnCacheCreation += lastCallCacheCreation;
6158
6342
  turnCacheRead += lastCallCacheRead;
6343
+ recordUsage({
6344
+ ts: Date.now(),
6345
+ requestId,
6346
+ agentName: "parent",
6347
+ modelId: event.modelId,
6348
+ inputTokens: event.usage.inputTokens,
6349
+ outputTokens: event.usage.outputTokens,
6350
+ cacheCreationTokens: event.usage.cacheCreationTokens,
6351
+ cacheReadTokens: event.usage.cacheReadTokens,
6352
+ cost: nanoToDollars(event.cost),
6353
+ billingEvents: event.billingEvents,
6354
+ durationMs: Date.now() - iterStart,
6355
+ toolNames: contentBlocks.filter(
6356
+ (b) => b.type === "tool"
6357
+ ).map((b) => b.name)
6358
+ });
6159
6359
  break;
6160
6360
  case "error":
6161
6361
  onEvent({ type: "error", error: friendlyError(event.error) });