@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/index.js CHANGED
@@ -55,14 +55,14 @@ function initLoggerHeadless(level = "info") {
55
55
  }
56
56
  function initLoggerInteractive(level = "error") {
57
57
  currentLevel = LEVELS[level];
58
- let fd = null;
58
+ let fd2 = null;
59
59
  writeFn = (line) => {
60
60
  try {
61
- if (fd === null) {
61
+ if (fd2 === null) {
62
62
  fs.mkdirSync(".logs", { recursive: true });
63
- fd = fs.openSync(".logs/agent.ndjson", "a");
63
+ fd2 = fs.openSync(".logs/agent.ndjson", "a");
64
64
  }
65
- fs.writeSync(fd, line + "\n");
65
+ fs.writeSync(fd2, line + "\n");
66
66
  } catch {
67
67
  }
68
68
  };
@@ -209,8 +209,10 @@ async function* streamChat(params) {
209
209
  ...subAgentId && { subAgentId },
210
210
  durationMs: elapsed,
211
211
  stopReason: event.stopReason,
212
+ modelId: event.modelId,
212
213
  inputTokens: event.usage.inputTokens,
213
- outputTokens: event.usage.outputTokens
214
+ outputTokens: event.usage.outputTokens,
215
+ cost: event.cost
214
216
  });
215
217
  } else if (event.type === "error") {
216
218
  log.error("SSE error event", {
@@ -296,6 +298,7 @@ async function generateBackgroundAck(params) {
296
298
  Authorization: `Bearer ${params.apiConfig.apiKey}`
297
299
  },
298
300
  body: JSON.stringify({
301
+ appId: params.apiConfig.appId,
299
302
  agentName: params.agentName,
300
303
  task: params.task
301
304
  }),
@@ -1237,12 +1240,127 @@ var init_sdkConsultant = __esm({
1237
1240
  }
1238
1241
  });
1239
1242
 
1243
+ // src/usageLedger.ts
1244
+ import fs8 from "fs";
1245
+ function nanoToDollars(nano) {
1246
+ return typeof nano === "number" ? nano / 1e9 : void 0;
1247
+ }
1248
+ function recordUsage(entry) {
1249
+ try {
1250
+ if (fd === null) {
1251
+ fs8.mkdirSync(".logs", { recursive: true });
1252
+ fd = fs8.openSync(LEDGER_FILE, "a");
1253
+ }
1254
+ fs8.writeSync(fd, JSON.stringify(entry) + "\n");
1255
+ } catch {
1256
+ }
1257
+ }
1258
+ var LEDGER_FILE, fd;
1259
+ var init_usageLedger = __esm({
1260
+ "src/usageLedger.ts"() {
1261
+ "use strict";
1262
+ LEDGER_FILE = ".logs/usage.ndjson";
1263
+ fd = null;
1264
+ }
1265
+ });
1266
+
1267
+ // src/subagents/common/runMindstudioCli.ts
1268
+ function stripFlags(args2) {
1269
+ const out = [];
1270
+ for (let i = 0; i < args2.length; i++) {
1271
+ const arg = args2[i];
1272
+ if (arg === "--no-meta") {
1273
+ continue;
1274
+ }
1275
+ if (arg === "--output-key") {
1276
+ i++;
1277
+ continue;
1278
+ }
1279
+ out.push(arg);
1280
+ }
1281
+ return out;
1282
+ }
1283
+ async function runMindstudioCli(args2, options) {
1284
+ const cleanArgs = stripFlags(args2);
1285
+ const cliAction = args2[0];
1286
+ const agentName = options?.caller ?? "mindstudio-cli";
1287
+ const start = Date.now();
1288
+ const raw = await runCli("mindstudio", cleanArgs, options);
1289
+ let envelope;
1290
+ try {
1291
+ envelope = JSON.parse(raw);
1292
+ } catch {
1293
+ return raw;
1294
+ }
1295
+ if (envelope && typeof envelope === "object" && Array.isArray(envelope.results)) {
1296
+ const durationMs = Date.now() - start;
1297
+ for (const step of envelope.results) {
1298
+ if (typeof step?.billingCost === "number") {
1299
+ recordUsage({
1300
+ ts: Date.now(),
1301
+ agentName,
1302
+ cliAction: `${cliAction}:${step.stepType ?? "step"}`,
1303
+ cost: nanoToDollars(step.billingCost),
1304
+ inputTokens: 0,
1305
+ outputTokens: 0,
1306
+ durationMs,
1307
+ toolNames: []
1308
+ });
1309
+ }
1310
+ }
1311
+ return JSON.stringify(envelope.results);
1312
+ }
1313
+ if (typeof envelope?.$billingCost === "number") {
1314
+ recordUsage({
1315
+ ts: Date.now(),
1316
+ agentName,
1317
+ cliAction,
1318
+ cost: nanoToDollars(envelope.$billingCost),
1319
+ billingEvents: envelope.$billingEvents,
1320
+ // CLI billing isn't expressed as input/output tokens for most actions
1321
+ // (image gen is per-image, scrape per-page, etc). `numUnits` inside each
1322
+ // billingEvent carries the per-event unit count.
1323
+ inputTokens: 0,
1324
+ outputTokens: 0,
1325
+ durationMs: Date.now() - start,
1326
+ toolNames: []
1327
+ });
1328
+ }
1329
+ if (options?.outputKey) {
1330
+ const v = envelope?.[options.outputKey];
1331
+ if (v === void 0 || v === null) {
1332
+ return JSON.stringify(stripDollarKeys(envelope));
1333
+ }
1334
+ return typeof v === "string" ? v : JSON.stringify(v);
1335
+ }
1336
+ return JSON.stringify(stripDollarKeys(envelope));
1337
+ }
1338
+ function stripDollarKeys(envelope) {
1339
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
1340
+ return envelope;
1341
+ }
1342
+ const out = {};
1343
+ for (const [k, v] of Object.entries(envelope)) {
1344
+ if (!k.startsWith("$")) {
1345
+ out[k] = v;
1346
+ }
1347
+ }
1348
+ return out;
1349
+ }
1350
+ var init_runMindstudioCli = __esm({
1351
+ "src/subagents/common/runMindstudioCli.ts"() {
1352
+ "use strict";
1353
+ init_runCli();
1354
+ init_usageLedger();
1355
+ }
1356
+ });
1357
+
1240
1358
  // src/tools/common/searchGoogle.ts
1241
1359
  var searchGoogleTool;
1242
1360
  var init_searchGoogle = __esm({
1243
1361
  "src/tools/common/searchGoogle.ts"() {
1244
1362
  "use strict";
1245
- init_runCli();
1363
+ init_runMindstudioCli();
1246
1364
  searchGoogleTool = {
1247
1365
  clearable: false,
1248
1366
  definition: {
@@ -1261,19 +1379,9 @@ var init_searchGoogle = __esm({
1261
1379
  },
1262
1380
  async execute(input, context) {
1263
1381
  const query = input.query;
1264
- return runCli(
1265
- "mindstudio",
1266
- [
1267
- "search-google",
1268
- "--query",
1269
- query,
1270
- "--export-type",
1271
- "json",
1272
- "--output-key",
1273
- "results",
1274
- "--no-meta"
1275
- ],
1276
- { maxBuffer: 512 * 1024, onLog: context?.onLog }
1382
+ return runMindstudioCli(
1383
+ ["search-google", "--query", query, "--export-type", "json"],
1384
+ { outputKey: "results", maxBuffer: 512 * 1024, onLog: context?.onLog }
1277
1385
  );
1278
1386
  }
1279
1387
  };
@@ -1320,12 +1428,12 @@ var init_setProjectMetadata = __esm({
1320
1428
  });
1321
1429
 
1322
1430
  // src/assets.ts
1323
- import fs8 from "fs";
1431
+ import fs9 from "fs";
1324
1432
  import path3 from "path";
1325
1433
  function findRoot(start) {
1326
1434
  let dir = start;
1327
1435
  while (dir !== path3.dirname(dir)) {
1328
- if (fs8.existsSync(path3.join(dir, "package.json"))) {
1436
+ if (fs9.existsSync(path3.join(dir, "package.json"))) {
1329
1437
  return dir;
1330
1438
  }
1331
1439
  dir = path3.dirname(dir);
@@ -1338,7 +1446,7 @@ function assetPath(...segments) {
1338
1446
  function readAsset(...segments) {
1339
1447
  const full = assetPath(...segments);
1340
1448
  try {
1341
- return fs8.readFileSync(full, "utf-8").trim();
1449
+ return fs9.readFileSync(full, "utf-8").trim();
1342
1450
  } catch {
1343
1451
  throw new Error(`Required asset missing: ${full}`);
1344
1452
  }
@@ -1346,7 +1454,7 @@ function readAsset(...segments) {
1346
1454
  function readJsonAsset(fallback, ...segments) {
1347
1455
  const full = assetPath(...segments);
1348
1456
  try {
1349
- return JSON.parse(fs8.readFileSync(full, "utf-8"));
1457
+ return JSON.parse(fs9.readFileSync(full, "utf-8"));
1350
1458
  } catch {
1351
1459
  return fallback;
1352
1460
  }
@@ -1358,7 +1466,7 @@ var init_assets = __esm({
1358
1466
  ROOT = findRoot(
1359
1467
  import.meta.dirname ?? path3.dirname(new URL(import.meta.url).pathname)
1360
1468
  );
1361
- ASSETS_BASE = fs8.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
1469
+ ASSETS_BASE = fs9.existsSync(path3.join(ROOT, "dist", "prompt")) ? path3.join(ROOT, "dist") : path3.join(ROOT, "src");
1362
1470
  }
1363
1471
  });
1364
1472
 
@@ -1571,6 +1679,7 @@ async function generateSummary(apiConfig, name, compactionPrompt, messagesToSumm
1571
1679
  Conversation to summarize:
1572
1680
 
1573
1681
  ${serialized}` : serialized;
1682
+ const iterStart = Date.now();
1574
1683
  for await (const event of streamChat({
1575
1684
  ...apiConfig,
1576
1685
  subAgentId: "conversationSummarizer",
@@ -1580,6 +1689,20 @@ ${serialized}` : serialized;
1580
1689
  })) {
1581
1690
  if (event.type === "text") {
1582
1691
  summaryText += event.text;
1692
+ } else if (event.type === "done") {
1693
+ recordUsage({
1694
+ ts: Date.now(),
1695
+ agentName: "conversationSummarizer",
1696
+ modelId: event.modelId,
1697
+ inputTokens: event.usage.inputTokens,
1698
+ outputTokens: event.usage.outputTokens,
1699
+ cacheCreationTokens: event.usage.cacheCreationTokens,
1700
+ cacheReadTokens: event.usage.cacheReadTokens,
1701
+ cost: nanoToDollars(event.cost),
1702
+ billingEvents: event.billingEvents,
1703
+ durationMs: Date.now() - iterStart,
1704
+ toolNames: []
1705
+ });
1583
1706
  } else if (event.type === "error") {
1584
1707
  log2.error("Summary generation failed", { name, error: event.error });
1585
1708
  return null;
@@ -1599,6 +1722,7 @@ var init_compaction = __esm({
1599
1722
  init_api();
1600
1723
  init_assets();
1601
1724
  init_logger();
1725
+ init_usageLedger();
1602
1726
  log2 = createLogger("compaction");
1603
1727
  CONVERSATION_SUMMARY_PROMPT = readAsset("compaction", "conversation.md");
1604
1728
  SUBAGENT_SUMMARY_PROMPT = readAsset("compaction", "subagent.md");
@@ -1608,11 +1732,11 @@ var init_compaction = __esm({
1608
1732
  });
1609
1733
 
1610
1734
  // src/prompt/static/projectContext.ts
1611
- import fs9 from "fs";
1735
+ import fs10 from "fs";
1612
1736
  import path4 from "path";
1613
1737
  function loadProjectManifest() {
1614
1738
  try {
1615
- const manifest = fs9.readFileSync("mindstudio.json", "utf-8");
1739
+ const manifest = fs10.readFileSync("mindstudio.json", "utf-8");
1616
1740
  return `
1617
1741
  ## Project Manifest (mindstudio.json)
1618
1742
  \`\`\`json
@@ -1653,7 +1777,7 @@ ${entries.join("\n")}`;
1653
1777
  function walkMdFiles(dir) {
1654
1778
  const results = [];
1655
1779
  try {
1656
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
1780
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
1657
1781
  for (const entry of entries) {
1658
1782
  const full = path4.join(dir, entry.name);
1659
1783
  if (entry.isDirectory()) {
@@ -1668,7 +1792,7 @@ function walkMdFiles(dir) {
1668
1792
  }
1669
1793
  function parseFrontmatter(filePath) {
1670
1794
  try {
1671
- const content = fs9.readFileSync(filePath, "utf-8");
1795
+ const content = fs10.readFileSync(filePath, "utf-8");
1672
1796
  const match = content.match(/^---\n([\s\S]*?)\n---/);
1673
1797
  if (!match) {
1674
1798
  return { name: "", description: "", type: "" };
@@ -1684,7 +1808,7 @@ function parseFrontmatter(filePath) {
1684
1808
  }
1685
1809
  function loadPlanStatus() {
1686
1810
  try {
1687
- const content = fs9.readFileSync(".remy-plan.md", "utf-8");
1811
+ const content = fs10.readFileSync(".remy-plan.md", "utf-8");
1688
1812
  const match = content.match(/^---\n([\s\S]*?)\n---/);
1689
1813
  const status = match?.[1]?.match(/^status:\s*(.+)$/m)?.[1]?.trim();
1690
1814
  if (status === "pending") {
@@ -1706,7 +1830,7 @@ The user has approved your implementation plan in .remy-plan.md. You may referen
1706
1830
  }
1707
1831
  function loadProjectFileListing() {
1708
1832
  try {
1709
- const entries = fs9.readdirSync(".", { withFileTypes: true });
1833
+ const entries = fs10.readdirSync(".", { withFileTypes: true });
1710
1834
  const listing = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules").sort((a, b) => {
1711
1835
  if (a.isDirectory() && !b.isDirectory()) {
1712
1836
  return -1;
@@ -1961,7 +2085,7 @@ var init_compactConversation = __esm({
1961
2085
  });
1962
2086
 
1963
2087
  // src/tools/code/readFile.ts
1964
- import fs10 from "fs/promises";
2088
+ import fs11 from "fs/promises";
1965
2089
  function isBinary(buffer) {
1966
2090
  const sample = buffer.subarray(0, 8192);
1967
2091
  for (let i = 0; i < sample.length; i++) {
@@ -2002,7 +2126,7 @@ var init_readFile = __esm({
2002
2126
  },
2003
2127
  async execute(input) {
2004
2128
  try {
2005
- const buffer = await fs10.readFile(input.path);
2129
+ const buffer = await fs11.readFile(input.path);
2006
2130
  if (isBinary(buffer)) {
2007
2131
  const size = buffer.length;
2008
2132
  const unit = size > 1024 * 1024 ? `${(size / (1024 * 1024)).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`;
@@ -2038,7 +2162,7 @@ var init_readFile = __esm({
2038
2162
  });
2039
2163
 
2040
2164
  // src/tools/code/writeFile.ts
2041
- import fs11 from "fs/promises";
2165
+ import fs12 from "fs/promises";
2042
2166
  import path5 from "path";
2043
2167
  var writeFileTool;
2044
2168
  var init_writeFile = __esm({
@@ -2082,7 +2206,7 @@ var init_writeFile = __esm({
2082
2206
  lastNewlineCount = newlineCount;
2083
2207
  const lastNewline = partial.content.lastIndexOf("\n");
2084
2208
  const completeContent = partial.content.substring(0, lastNewline + 1);
2085
- const oldContent = await fs11.readFile(partial.path, "utf-8").catch(() => "");
2209
+ const oldContent = await fs12.readFile(partial.path, "utf-8").catch(() => "");
2086
2210
  return `Writing ${partial.path} (${newlineCount} lines)
2087
2211
  ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2088
2212
  }
@@ -2091,13 +2215,13 @@ ${unifiedDiff(partial.path, oldContent, completeContent)}`;
2091
2215
  async execute(input) {
2092
2216
  const release = await acquireFileLock(input.path);
2093
2217
  try {
2094
- await fs11.mkdir(path5.dirname(input.path), { recursive: true });
2218
+ await fs12.mkdir(path5.dirname(input.path), { recursive: true });
2095
2219
  let oldContent = null;
2096
2220
  try {
2097
- oldContent = await fs11.readFile(input.path, "utf-8");
2221
+ oldContent = await fs12.readFile(input.path, "utf-8");
2098
2222
  } catch {
2099
2223
  }
2100
- await fs11.writeFile(input.path, input.content, "utf-8");
2224
+ await fs12.writeFile(input.path, input.content, "utf-8");
2101
2225
  const lineCount = input.content.split("\n").length;
2102
2226
  const label = oldContent !== null ? "Wrote" : "Created";
2103
2227
  return `${label} ${input.path} (${lineCount} lines)
@@ -2197,7 +2321,7 @@ var init_helpers2 = __esm({
2197
2321
  });
2198
2322
 
2199
2323
  // src/tools/code/editFile/index.ts
2200
- import fs12 from "fs/promises";
2324
+ import fs13 from "fs/promises";
2201
2325
  var editFileTool;
2202
2326
  var init_editFile = __esm({
2203
2327
  "src/tools/code/editFile/index.ts"() {
@@ -2236,7 +2360,7 @@ var init_editFile = __esm({
2236
2360
  async execute(input) {
2237
2361
  const release = await acquireFileLock(input.path);
2238
2362
  try {
2239
- const content = await fs12.readFile(input.path, "utf-8");
2363
+ const content = await fs13.readFile(input.path, "utf-8");
2240
2364
  const { old_string, new_string, replace_all } = input;
2241
2365
  const occurrences = findOccurrences(content, old_string);
2242
2366
  if (replace_all) {
@@ -2252,7 +2376,7 @@ var init_editFile = __esm({
2252
2376
  new_string
2253
2377
  );
2254
2378
  }
2255
- await fs12.writeFile(input.path, updated, "utf-8");
2379
+ await fs13.writeFile(input.path, updated, "utf-8");
2256
2380
  return `Replaced ${occurrences.length} occurrence${occurrences.length > 1 ? "s" : ""} in ${input.path}
2257
2381
  ${unifiedDiff(input.path, content, updated)}`;
2258
2382
  }
@@ -2263,7 +2387,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2263
2387
  old_string.length,
2264
2388
  new_string
2265
2389
  );
2266
- await fs12.writeFile(input.path, updated, "utf-8");
2390
+ await fs13.writeFile(input.path, updated, "utf-8");
2267
2391
  return `Updated ${input.path}
2268
2392
  ${unifiedDiff(input.path, content, updated)}`;
2269
2393
  }
@@ -2279,7 +2403,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2279
2403
  flex.matchedText.length,
2280
2404
  new_string
2281
2405
  );
2282
- await fs12.writeFile(input.path, updated, "utf-8");
2406
+ await fs13.writeFile(input.path, updated, "utf-8");
2283
2407
  return `Updated ${input.path} (matched with flexible whitespace at line ${flex.line})
2284
2408
  ${unifiedDiff(input.path, content, updated)}`;
2285
2409
  }
@@ -2531,10 +2655,10 @@ var init_glob = __esm({
2531
2655
  });
2532
2656
 
2533
2657
  // src/tools/code/listDir.ts
2534
- import fs13 from "fs/promises";
2658
+ import fs14 from "fs/promises";
2535
2659
  import path6 from "path";
2536
2660
  async function readAndSort(dirPath) {
2537
- const entries = await fs13.readdir(dirPath, { withFileTypes: true });
2661
+ const entries = await fs14.readdir(dirPath, { withFileTypes: true });
2538
2662
  return entries.filter((e) => !EXCLUDE.has(e.name)).sort((a, b) => {
2539
2663
  if (a.isDirectory() && !b.isDirectory()) {
2540
2664
  return -1;
@@ -2575,7 +2699,7 @@ function formatSize(bytes) {
2575
2699
  }
2576
2700
  async function formatFile(dirPath, name, indent) {
2577
2701
  try {
2578
- const stat = await fs13.stat(path6.join(dirPath, name));
2702
+ const stat = await fs14.stat(path6.join(dirPath, name));
2579
2703
  return `${indent}${name}${" ".repeat(Math.max(1, 30 - indent.length - name.length))}${formatSize(stat.size)}`;
2580
2704
  } catch {
2581
2705
  return `${indent}${name}`;
@@ -2940,8 +3064,7 @@ var init_queryDatabase = __esm({
2940
3064
  // src/subagents/common/analyzeImage.ts
2941
3065
  async function analyzeImage(params) {
2942
3066
  const { prompt, imageUrl, timeout = 2e5, onLog } = params;
2943
- return runCli(
2944
- "mindstudio",
3067
+ return runMindstudioCli(
2945
3068
  [
2946
3069
  "analyze-image",
2947
3070
  "--prompt",
@@ -2949,24 +3072,21 @@ async function analyzeImage(params) {
2949
3072
  "--image-url",
2950
3073
  imageUrl,
2951
3074
  "--vision-model-override",
2952
- JSON.stringify(VISION_MODEL_OVERRIDE),
2953
- "--output-key",
2954
- "analysis",
2955
- "--no-meta"
3075
+ JSON.stringify(VISION_MODEL_OVERRIDE)
2956
3076
  ],
2957
- { timeout, onLog }
3077
+ { outputKey: "analysis", timeout, onLog }
2958
3078
  );
2959
3079
  }
2960
3080
  var VISION_MODEL, VISION_MODEL_OVERRIDE;
2961
3081
  var init_analyzeImage = __esm({
2962
3082
  "src/subagents/common/analyzeImage.ts"() {
2963
3083
  "use strict";
2964
- init_runCli();
3084
+ init_runMindstudioCli();
2965
3085
  VISION_MODEL = "claude-4-6-sonnet";
2966
- VISION_MODEL_OVERRIDE = JSON.stringify({
3086
+ VISION_MODEL_OVERRIDE = {
2967
3087
  model: VISION_MODEL,
2968
3088
  config: { thinkingBudget: "off" }
2969
- });
3089
+ };
2970
3090
  }
2971
3091
  });
2972
3092
 
@@ -3006,11 +3126,11 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3006
3126
  let prompt;
3007
3127
  let existingUrl;
3008
3128
  let onLog;
3009
- let path12;
3129
+ let path13;
3010
3130
  if (typeof promptOrOptions === "object" && promptOrOptions !== null) {
3011
3131
  prompt = promptOrOptions.prompt;
3012
3132
  existingUrl = promptOrOptions.imageUrl;
3013
- path12 = promptOrOptions.path;
3133
+ path13 = promptOrOptions.path;
3014
3134
  onLog = promptOrOptions.onLog;
3015
3135
  } else {
3016
3136
  prompt = promptOrOptions;
@@ -3022,7 +3142,7 @@ async function captureAndAnalyzeScreenshot(promptOrOptions) {
3022
3142
  } else {
3023
3143
  const ssResult = await sidecarRequest(
3024
3144
  "/screenshot-full-page",
3025
- path12 ? { path: path12 } : void 0,
3145
+ path13 ? { path: path13 } : void 0,
3026
3146
  { timeout: 12e4 }
3027
3147
  );
3028
3148
  url = ssResult?.url || ssResult?.screenshotUrl;
@@ -3097,7 +3217,7 @@ function startStatusWatcher(config) {
3097
3217
  "Content-Type": "application/json",
3098
3218
  Authorization: `Bearer ${apiConfig.apiKey}`
3099
3219
  },
3100
- body: JSON.stringify({ context }),
3220
+ body: JSON.stringify({ appId: apiConfig.appId, context }),
3101
3221
  signal
3102
3222
  });
3103
3223
  if (!res.ok || stopped) {
@@ -3299,8 +3419,14 @@ ${content}` : attachmentHeader;
3299
3419
  const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
3300
3420
  const toolCalls = blocks.filter((b) => b.type === "tool").map((b) => ({ id: b.id, name: b.name, input: b.input }));
3301
3421
  const thinking = blocks.filter(
3302
- (b) => b.type === "thinking"
3303
- ).map((b) => ({ thinking: b.thinking, signature: b.signature }));
3422
+ (b) => b.type === "thinking" || b.type === "redacted_thinking"
3423
+ ).map(
3424
+ (b) => b.type === "thinking" ? {
3425
+ type: "thinking",
3426
+ thinking: b.thinking,
3427
+ signature: b.signature
3428
+ } : { type: "redacted_thinking", data: b.data }
3429
+ );
3304
3430
  const cleaned2 = {
3305
3431
  role: msg.role,
3306
3432
  content: text
@@ -3395,10 +3521,13 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3395
3521
  if (signal?.aborted) {
3396
3522
  return abortResult([]);
3397
3523
  }
3524
+ const iterStart = Date.now();
3398
3525
  const contentBlocks = [];
3399
3526
  let thinkingStartedAt = 0;
3527
+ let lastThinkingRelatedStartedAt;
3400
3528
  let stopReason = "end_turn";
3401
3529
  let currentToolNames = "";
3530
+ let lastUsage;
3402
3531
  const statusWatcher = startStatusWatcher({
3403
3532
  apiConfig,
3404
3533
  getContext: () => {
@@ -3473,8 +3602,20 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3473
3602
  startedAt: thinkingStartedAt,
3474
3603
  completedAt: event.ts
3475
3604
  });
3605
+ lastThinkingRelatedStartedAt = thinkingStartedAt;
3476
3606
  thinkingStartedAt = 0;
3477
3607
  break;
3608
+ case "redacted_thinking_complete": {
3609
+ const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
3610
+ contentBlocks.push({
3611
+ type: "redacted_thinking",
3612
+ data: event.data,
3613
+ startedAt,
3614
+ completedAt: event.ts
3615
+ });
3616
+ lastThinkingRelatedStartedAt = startedAt;
3617
+ break;
3618
+ }
3478
3619
  case "tool_use":
3479
3620
  contentBlocks.push({
3480
3621
  type: "tool",
@@ -3492,6 +3633,30 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3492
3633
  break;
3493
3634
  case "done":
3494
3635
  stopReason = event.stopReason;
3636
+ lastUsage = {
3637
+ inputTokens: event.usage.inputTokens,
3638
+ outputTokens: event.usage.outputTokens,
3639
+ cacheCreationTokens: event.usage.cacheCreationTokens,
3640
+ cacheReadTokens: event.usage.cacheReadTokens,
3641
+ llmCalls: 1
3642
+ };
3643
+ recordUsage({
3644
+ ts: Date.now(),
3645
+ requestId,
3646
+ agentName: subAgentId || "sub-agent",
3647
+ parentToolId,
3648
+ modelId: event.modelId,
3649
+ inputTokens: event.usage.inputTokens,
3650
+ outputTokens: event.usage.outputTokens,
3651
+ cacheCreationTokens: event.usage.cacheCreationTokens,
3652
+ cacheReadTokens: event.usage.cacheReadTokens,
3653
+ cost: nanoToDollars(event.cost),
3654
+ billingEvents: event.billingEvents,
3655
+ durationMs: Date.now() - iterStart,
3656
+ toolNames: contentBlocks.filter(
3657
+ (b) => b.type === "tool"
3658
+ ).map((b) => b.name)
3659
+ });
3495
3660
  break;
3496
3661
  case "error":
3497
3662
  return {
@@ -3511,7 +3676,8 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
3511
3676
  }
3512
3677
  messages.push({
3513
3678
  role: "assistant",
3514
- content: contentBlocks
3679
+ content: contentBlocks,
3680
+ ...lastUsage ? { usage: lastUsage } : {}
3515
3681
  });
3516
3682
  const toolCalls = contentBlocks.filter(
3517
3683
  (b) => b.type === "tool"
@@ -3708,6 +3874,7 @@ var init_runner = __esm({
3708
3874
  "use strict";
3709
3875
  init_api();
3710
3876
  init_logger();
3877
+ init_usageLedger();
3711
3878
  init_statusWatcher();
3712
3879
  init_cleanMessages();
3713
3880
  log5 = createLogger("sub-agent");
@@ -3854,10 +4021,10 @@ var init_tools = __esm({
3854
4021
  });
3855
4022
 
3856
4023
  // src/subagents/browserAutomation/prompt.ts
3857
- import fs14 from "fs";
4024
+ import fs15 from "fs";
3858
4025
  function getBrowserAutomationPrompt() {
3859
4026
  try {
3860
- const appSpec = fs14.readFileSync("src/app.md", "utf-8").trim();
4027
+ const appSpec = fs15.readFileSync("src/app.md", "utf-8").trim();
3861
4028
  return `${BASE_PROMPT}
3862
4029
 
3863
4030
  <!-- cache_breakpoint -->
@@ -3889,14 +4056,15 @@ var init_browserAutomation = __esm({
3889
4056
  init_sidecar();
3890
4057
  init_browserLock();
3891
4058
  init_screenshot();
3892
- init_runCli();
4059
+ init_runMindstudioCli();
4060
+ init_analyzeImage();
3893
4061
  init_logger();
3894
4062
  log6 = createLogger("browser-automation");
3895
4063
  browserAutomationTool = {
3896
4064
  clearable: true,
3897
4065
  definition: {
3898
4066
  name: "runAutomatedBrowserTest",
3899
- 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).",
4067
+ 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).",
3900
4068
  inputSchema: {
3901
4069
  type: "object",
3902
4070
  properties: {
@@ -3972,13 +4140,13 @@ var init_browserAutomation = __esm({
3972
4140
  imageUrl: s.result.url,
3973
4141
  prompt: buildScreenshotAnalysisPrompt({
3974
4142
  styleMap: s.result.styleMap
3975
- })
4143
+ }),
4144
+ visionModelOverride: VISION_MODEL_OVERRIDE
3976
4145
  }
3977
4146
  }));
3978
- const batchResult = await runCli(
3979
- "mindstudio",
3980
- ["batch", "--no-meta", JSON.stringify(batchInput)],
3981
- { timeout: 2e5 }
4147
+ const batchResult = await runMindstudioCli(
4148
+ ["batch", JSON.stringify(batchInput)],
4149
+ { timeout: 2e5, caller: "browserAutomation" }
3982
4150
  );
3983
4151
  try {
3984
4152
  const analyses = JSON.parse(batchResult);
@@ -4032,7 +4200,7 @@ var init_screenshot2 = __esm({
4032
4200
  clearable: true,
4033
4201
  definition: {
4034
4202
  name: "screenshot",
4035
- 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.",
4203
+ 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.",
4036
4204
  inputSchema: {
4037
4205
  type: "object",
4038
4206
  properties: {
@@ -4203,26 +4371,16 @@ __export(searchGoogle_exports, {
4203
4371
  execute: () => execute
4204
4372
  });
4205
4373
  async function execute(input, onLog) {
4206
- return runCli(
4207
- "mindstudio",
4208
- [
4209
- "search-google",
4210
- "--query",
4211
- input.query,
4212
- "--export-type",
4213
- "json",
4214
- "--output-key",
4215
- "results",
4216
- "--no-meta"
4217
- ],
4218
- { onLog }
4374
+ return runMindstudioCli(
4375
+ ["search-google", "--query", input.query, "--export-type", "json"],
4376
+ { outputKey: "results", onLog, caller: "designExpert" }
4219
4377
  );
4220
4378
  }
4221
4379
  var definition;
4222
4380
  var init_searchGoogle2 = __esm({
4223
4381
  "src/subagents/designExpert/tools/searchGoogle.ts"() {
4224
4382
  "use strict";
4225
- init_runCli();
4383
+ init_runMindstudioCli();
4226
4384
  definition = {
4227
4385
  clearable: false,
4228
4386
  name: "searchGoogle",
@@ -4252,24 +4410,22 @@ async function execute2(input, onLog) {
4252
4410
  if (input.screenshot) {
4253
4411
  pageOptions.screenshot = true;
4254
4412
  }
4255
- return runCli(
4256
- "mindstudio",
4413
+ return runMindstudioCli(
4257
4414
  [
4258
4415
  "scrape-url",
4259
4416
  "--url",
4260
4417
  input.url,
4261
4418
  "--page-options",
4262
- JSON.stringify(pageOptions),
4263
- "--no-meta"
4419
+ JSON.stringify(pageOptions)
4264
4420
  ],
4265
- { onLog }
4421
+ { onLog, caller: "designExpert" }
4266
4422
  );
4267
4423
  }
4268
4424
  var definition2;
4269
4425
  var init_scrapeWebUrl = __esm({
4270
4426
  "src/subagents/designExpert/tools/scrapeWebUrl.ts"() {
4271
4427
  "use strict";
4272
- init_runCli();
4428
+ init_runMindstudioCli();
4273
4429
  definition2 = {
4274
4430
  clearable: false,
4275
4431
  name: "scrapeWebUrl",
@@ -4300,8 +4456,7 @@ async function execute3(input, onLog) {
4300
4456
  const isImageUrl = /\.(png|jpe?g|webp|gif|svg|avif)(\?|$)/i.test(url);
4301
4457
  let imageUrl = url;
4302
4458
  if (!isImageUrl) {
4303
- const ssUrl = await runCli(
4304
- "mindstudio",
4459
+ const ssUrl = await runMindstudioCli(
4305
4460
  [
4306
4461
  "screenshot-url",
4307
4462
  "--url",
@@ -4311,12 +4466,14 @@ async function execute3(input, onLog) {
4311
4466
  "--width",
4312
4467
  "1440",
4313
4468
  "--delay",
4314
- "2000",
4315
- "--output-key",
4316
- "screenshotUrl",
4317
- "--no-meta"
4469
+ "2000"
4318
4470
  ],
4319
- { timeout: 12e4, onLog }
4471
+ {
4472
+ outputKey: "screenshotUrl",
4473
+ timeout: 12e4,
4474
+ onLog,
4475
+ caller: "designExpert"
4476
+ }
4320
4477
  );
4321
4478
  if (ssUrl.startsWith("Error")) {
4322
4479
  return `Could not screenshot ${url}: ${ssUrl}`;
@@ -4334,7 +4491,7 @@ var DESIGN_REFERENCE_PROMPT, definition3;
4334
4491
  var init_analyzeDesign = __esm({
4335
4492
  "src/subagents/designExpert/tools/analyzeDesign.ts"() {
4336
4493
  "use strict";
4337
- init_runCli();
4494
+ init_runMindstudioCli();
4338
4495
  init_analyzeImage();
4339
4496
  DESIGN_REFERENCE_PROMPT = `
4340
4497
  You are analyzing a screenshot of a real website or app for a designer's personal technique/inspiration reference notes.
@@ -4526,19 +4683,15 @@ ${context}
4526
4683
  <brief>
4527
4684
  ${brief}
4528
4685
  </brief>`;
4529
- const enhanced = await runCli(
4530
- "mindstudio",
4686
+ const enhanced = await runMindstudioCli(
4531
4687
  [
4532
4688
  "generate-text",
4533
4689
  "--message",
4534
4690
  message,
4535
4691
  "--model-override",
4536
- JSON.stringify(MODEL_OVERRIDE),
4537
- "--output-key",
4538
- "content",
4539
- "--no-meta"
4692
+ JSON.stringify(MODEL_OVERRIDE)
4540
4693
  ],
4541
- { timeout: 6e4, onLog }
4694
+ { outputKey: "content", timeout: 6e4, onLog, caller: "designExpert" }
4542
4695
  );
4543
4696
  return enhanced.trim();
4544
4697
  }
@@ -4546,13 +4699,13 @@ var ENHANCE_MODEL, MODEL_OVERRIDE, SYSTEM_PROMPT;
4546
4699
  var init_enhancePrompt = __esm({
4547
4700
  "src/subagents/designExpert/tools/images/enhancePrompt.ts"() {
4548
4701
  "use strict";
4549
- init_runCli();
4702
+ init_runMindstudioCli();
4550
4703
  init_assets();
4551
4704
  ENHANCE_MODEL = "claude-4-6-sonnet";
4552
- MODEL_OVERRIDE = JSON.stringify({
4705
+ MODEL_OVERRIDE = {
4553
4706
  model: ENHANCE_MODEL,
4554
4707
  config: { reasoning: "false" }
4555
- });
4708
+ };
4556
4709
  SYSTEM_PROMPT = readAsset(
4557
4710
  "subagents/designExpert/tools/images/enhance-image-prompt.md"
4558
4711
  );
@@ -4589,11 +4742,14 @@ async function generateImageAssets(opts) {
4589
4742
  config
4590
4743
  }
4591
4744
  });
4592
- const url = await runCli(
4593
- "mindstudio",
4594
- ["generate-image", "--output-key", "imageUrl", "--no-meta"],
4595
- { jsonLogs: true, timeout: 2e5, onLog, stdin: step }
4596
- );
4745
+ const url = await runMindstudioCli(["generate-image"], {
4746
+ outputKey: "imageUrl",
4747
+ jsonLogs: true,
4748
+ timeout: 2e5,
4749
+ onLog,
4750
+ stdin: step,
4751
+ caller: "designExpert"
4752
+ });
4597
4753
  imageUrls = [url];
4598
4754
  } else {
4599
4755
  const steps = enhancedPrompts.map((prompt) => ({
@@ -4606,11 +4762,12 @@ async function generateImageAssets(opts) {
4606
4762
  }
4607
4763
  }
4608
4764
  }));
4609
- const batchResult = await runCli("mindstudio", ["batch", "--no-meta"], {
4765
+ const batchResult = await runMindstudioCli(["batch"], {
4610
4766
  jsonLogs: true,
4611
4767
  timeout: 2e5,
4612
4768
  onLog,
4613
- stdin: JSON.stringify(steps)
4769
+ stdin: JSON.stringify(steps),
4770
+ caller: "designExpert"
4614
4771
  });
4615
4772
  try {
4616
4773
  const parsed = JSON.parse(batchResult);
@@ -4627,17 +4784,14 @@ async function generateImageAssets(opts) {
4627
4784
  if (url.startsWith("Error")) {
4628
4785
  return url;
4629
4786
  }
4630
- const result = await runCli(
4631
- "mindstudio",
4632
- [
4633
- "remove-background-from-image",
4634
- "--image-url",
4635
- url,
4636
- "--output-key",
4637
- "imageUrl",
4638
- "--no-meta"
4639
- ],
4640
- { timeout: 2e5, onLog }
4787
+ const result = await runMindstudioCli(
4788
+ ["remove-background-from-image", "--image-url", url],
4789
+ {
4790
+ outputKey: "imageUrl",
4791
+ timeout: 2e5,
4792
+ onLog,
4793
+ caller: "designExpert"
4794
+ }
4641
4795
  );
4642
4796
  return result.startsWith("Error") ? url : result;
4643
4797
  })
@@ -4673,7 +4827,7 @@ var ANALYZE_PROMPT;
4673
4827
  var init_imageGenerator = __esm({
4674
4828
  "src/subagents/designExpert/tools/images/imageGenerator.ts"() {
4675
4829
  "use strict";
4676
- init_runCli();
4830
+ init_runMindstudioCli();
4677
4831
  init_analyzeImage();
4678
4832
  init_enhancePrompt();
4679
4833
  ANALYZE_PROMPT = 'You are reviewing this image for a visual designer sourcing assets for a project. Describe: what the image depicts, the mood and color palette, how the lighting and composition work, any text present in the image, whether there are any issues (artifacts, distortions), and how it could be used in a layout for an app or website. Be concise and practical. Respond only with your analysis as Markdown (starting with the title "Asset Review") and absolutely no other text. Do not use emojis - use unicode if you need symbols.';
@@ -4833,12 +4987,12 @@ var init_tools3 = __esm({
4833
4987
  });
4834
4988
 
4835
4989
  // src/subagents/common/context.ts
4836
- import fs15 from "fs";
4990
+ import fs16 from "fs";
4837
4991
  import path7 from "path";
4838
4992
  function walkMdFiles2(dir, skip) {
4839
4993
  const files = [];
4840
4994
  try {
4841
- for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
4995
+ for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
4842
4996
  const full = path7.join(dir, entry.name);
4843
4997
  if (entry.isDirectory()) {
4844
4998
  if (!skip?.has(entry.name)) {
@@ -4854,7 +5008,7 @@ function walkMdFiles2(dir, skip) {
4854
5008
  }
4855
5009
  function parseFrontmatter2(filePath) {
4856
5010
  try {
4857
- const content = fs15.readFileSync(filePath, "utf-8");
5011
+ const content = fs16.readFileSync(filePath, "utf-8");
4858
5012
  const match = content.match(/^---\n([\s\S]*?)\n---/);
4859
5013
  if (!match) {
4860
5014
  return {};
@@ -4900,7 +5054,7 @@ function loadRoadmapIndex() {
4900
5054
  const parts = [];
4901
5055
  try {
4902
5056
  const indexJson = JSON.parse(
4903
- fs15.readFileSync("src/roadmap/index.json", "utf-8")
5057
+ fs16.readFileSync("src/roadmap/index.json", "utf-8")
4904
5058
  );
4905
5059
  if (indexJson.lanes?.length > 0) {
4906
5060
  const laneLines = indexJson.lanes.map(
@@ -5008,7 +5162,7 @@ var init_context = __esm({
5008
5162
  });
5009
5163
 
5010
5164
  // src/subagents/designExpert/data/sampleCache.ts
5011
- import fs16 from "fs";
5165
+ import fs17 from "fs";
5012
5166
  function generateIndices(poolSize, sampleSize) {
5013
5167
  const n = Math.min(sampleSize, poolSize);
5014
5168
  const indices = Array.from({ length: poolSize }, (_, i) => i);
@@ -5020,14 +5174,14 @@ function generateIndices(poolSize, sampleSize) {
5020
5174
  }
5021
5175
  function load() {
5022
5176
  try {
5023
- return JSON.parse(fs16.readFileSync(SAMPLE_FILE, "utf-8"));
5177
+ return JSON.parse(fs17.readFileSync(SAMPLE_FILE, "utf-8"));
5024
5178
  } catch {
5025
5179
  return null;
5026
5180
  }
5027
5181
  }
5028
5182
  function save(indices) {
5029
5183
  try {
5030
- fs16.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5184
+ fs17.writeFileSync(SAMPLE_FILE, JSON.stringify(indices));
5031
5185
  } catch {
5032
5186
  }
5033
5187
  }
@@ -5424,7 +5578,7 @@ var init_tools4 = __esm({
5424
5578
  });
5425
5579
 
5426
5580
  // src/subagents/productVision/executor.ts
5427
- import fs17 from "fs";
5581
+ import fs18 from "fs";
5428
5582
  import path8 from "path";
5429
5583
  function resolve(filePath) {
5430
5584
  return path8.join(ROADMAP_DIR, filePath);
@@ -5434,13 +5588,13 @@ async function executeVisionTool(name, input, context) {
5434
5588
  case "writeFile": {
5435
5589
  const filePath = resolve(input.path);
5436
5590
  try {
5437
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5591
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5438
5592
  let oldContent = null;
5439
5593
  try {
5440
- oldContent = fs17.readFileSync(filePath, "utf-8");
5594
+ oldContent = fs18.readFileSync(filePath, "utf-8");
5441
5595
  } catch {
5442
5596
  }
5443
- fs17.writeFileSync(filePath, input.content, "utf-8");
5597
+ fs18.writeFileSync(filePath, input.content, "utf-8");
5444
5598
  const lineCount = input.content.split("\n").length;
5445
5599
  const label = oldContent !== null ? "Wrote" : "Created";
5446
5600
  return `${label} ${filePath} (${lineCount} lines)
@@ -5452,11 +5606,11 @@ ${unifiedDiff(filePath, oldContent ?? "", input.content)}`;
5452
5606
  case "deleteFile": {
5453
5607
  const filePath = resolve(input.path);
5454
5608
  try {
5455
- if (!fs17.existsSync(filePath)) {
5609
+ if (!fs18.existsSync(filePath)) {
5456
5610
  return `Error: ${filePath} does not exist`;
5457
5611
  }
5458
- const oldContent = fs17.readFileSync(filePath, "utf-8");
5459
- fs17.unlinkSync(filePath);
5612
+ const oldContent = fs18.readFileSync(filePath, "utf-8");
5613
+ fs18.unlinkSync(filePath);
5460
5614
  return `Deleted ${filePath}
5461
5615
  ${unifiedDiff(filePath, oldContent, "")}`;
5462
5616
  } catch (err) {
@@ -5469,8 +5623,8 @@ ${unifiedDiff(filePath, oldContent, "")}`;
5469
5623
  }
5470
5624
  const filePath = resolve("pitch.html");
5471
5625
  try {
5472
- fs17.mkdirSync(ROADMAP_DIR, { recursive: true });
5473
- const existing = fs17.existsSync(filePath) ? fs17.readFileSync(filePath, "utf-8").trim() : "";
5626
+ fs18.mkdirSync(ROADMAP_DIR, { recursive: true });
5627
+ const existing = fs18.existsSync(filePath) ? fs18.readFileSync(filePath, "utf-8").trim() : "";
5474
5628
  const currentDeck = existing || PITCH_DECK_SHELL;
5475
5629
  const task = `
5476
5630
  <pitch_content>${input.task}</pitch_content>
@@ -5495,7 +5649,7 @@ Respond only with the complete HTML file and absolutely no other text. Your resp
5495
5649
  /```(?:html|wireframe)\n([\s\S]*?)```/
5496
5650
  );
5497
5651
  const html = htmlMatch ? htmlMatch[1].trim() : result;
5498
- fs17.writeFileSync(filePath, html, "utf-8");
5652
+ fs18.writeFileSync(filePath, html, "utf-8");
5499
5653
  return `Pitch deck written successfully.`;
5500
5654
  } catch (err) {
5501
5655
  return `Error generating pitch deck: ${err.message}`;
@@ -5744,7 +5898,7 @@ var scrapeWebUrlTool;
5744
5898
  var init_scrapeWebUrl2 = __esm({
5745
5899
  "src/tools/common/scrapeWebUrl.ts"() {
5746
5900
  "use strict";
5747
- init_runCli();
5901
+ init_runMindstudioCli();
5748
5902
  scrapeWebUrlTool = {
5749
5903
  clearable: false,
5750
5904
  definition: {
@@ -5772,15 +5926,13 @@ var init_scrapeWebUrl2 = __esm({
5772
5926
  if (screenshot) {
5773
5927
  pageOptions.screenshot = true;
5774
5928
  }
5775
- return runCli(
5776
- "mindstudio",
5929
+ return runMindstudioCli(
5777
5930
  [
5778
5931
  "scrape-url",
5779
5932
  "--url",
5780
5933
  url,
5781
5934
  "--page-options",
5782
- JSON.stringify(pageOptions),
5783
- "--no-meta"
5935
+ JSON.stringify(pageOptions)
5784
5936
  ],
5785
5937
  { onLog: context?.onLog }
5786
5938
  );
@@ -5890,10 +6042,11 @@ var init_tools6 = __esm({
5890
6042
  });
5891
6043
 
5892
6044
  // src/session.ts
5893
- import fs18 from "fs";
6045
+ import fs19 from "fs";
6046
+ import path9 from "path";
5894
6047
  function loadSession(state) {
5895
6048
  try {
5896
- const raw = fs18.readFileSync(SESSION_FILE, "utf-8");
6049
+ const raw = fs19.readFileSync(SESSION_FILE, "utf-8");
5897
6050
  const data = JSON.parse(raw);
5898
6051
  if (Array.isArray(data.messages) && data.messages.length > 0) {
5899
6052
  state.messages = sanitizeMessages(data.messages);
@@ -5942,7 +6095,7 @@ function sanitizeMessages(messages) {
5942
6095
  }
5943
6096
  function saveSession(state) {
5944
6097
  try {
5945
- fs18.writeFileSync(
6098
+ fs19.writeFileSync(
5946
6099
  SESSION_FILE,
5947
6100
  JSON.stringify({ messages: state.messages }, null, 2),
5948
6101
  "utf-8"
@@ -5955,17 +6108,31 @@ function saveSession(state) {
5955
6108
  function clearSession(state) {
5956
6109
  state.messages = [];
5957
6110
  try {
5958
- fs18.unlinkSync(SESSION_FILE);
5959
- } catch {
6111
+ if (fs19.existsSync(SESSION_FILE)) {
6112
+ fs19.mkdirSync(ARCHIVE_DIR, { recursive: true });
6113
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6114
+ const dest = path9.join(ARCHIVE_DIR, `cleared-${ts}.json`);
6115
+ fs19.renameSync(SESSION_FILE, dest);
6116
+ log7.info("Session archived on clear", { dest });
6117
+ }
6118
+ } catch (err) {
6119
+ log7.warn("Session archive on clear failed, deleting instead", {
6120
+ error: err.message
6121
+ });
6122
+ try {
6123
+ fs19.unlinkSync(SESSION_FILE);
6124
+ } catch {
6125
+ }
5960
6126
  }
5961
6127
  }
5962
- var log7, SESSION_FILE;
6128
+ var log7, SESSION_FILE, ARCHIVE_DIR;
5963
6129
  var init_session = __esm({
5964
6130
  "src/session.ts"() {
5965
6131
  "use strict";
5966
6132
  init_logger();
5967
6133
  log7 = createLogger("session");
5968
6134
  SESSION_FILE = ".remy-session.json";
6135
+ ARCHIVE_DIR = ".logs/sessions";
5969
6136
  }
5970
6137
  });
5971
6138
 
@@ -6175,8 +6342,8 @@ var init_errors = __esm({
6175
6342
  });
6176
6343
 
6177
6344
  // src/brandExtraction/index.ts
6178
- import fs19 from "fs";
6179
- import path9 from "path";
6345
+ import fs20 from "fs";
6346
+ import path10 from "path";
6180
6347
  import { createHash } from "crypto";
6181
6348
  async function runExtraction(apiConfig) {
6182
6349
  const inputHash = computeInputHash();
@@ -6198,7 +6365,7 @@ async function runExtraction(apiConfig) {
6198
6365
  function computeInputHash() {
6199
6366
  const entries = [];
6200
6367
  for (const filePath of walkMdFiles3("src")) {
6201
- if (filePath === path9.join("src", "app.md")) {
6368
+ if (filePath === path10.join("src", "app.md")) {
6202
6369
  entries.push({ path: filePath, content: readSafe(filePath) });
6203
6370
  continue;
6204
6371
  }
@@ -6220,7 +6387,7 @@ function sha256(input) {
6220
6387
  }
6221
6388
  function readSafe(filePath) {
6222
6389
  try {
6223
- return fs19.readFileSync(filePath, "utf-8");
6390
+ return fs20.readFileSync(filePath, "utf-8");
6224
6391
  } catch {
6225
6392
  return "";
6226
6393
  }
@@ -6228,9 +6395,9 @@ function readSafe(filePath) {
6228
6395
  function walkMdFiles3(dir) {
6229
6396
  const results = [];
6230
6397
  try {
6231
- const entries = fs19.readdirSync(dir, { withFileTypes: true });
6398
+ const entries = fs20.readdirSync(dir, { withFileTypes: true });
6232
6399
  for (const entry of entries) {
6233
- const full = path9.join(dir, entry.name);
6400
+ const full = path10.join(dir, entry.name);
6234
6401
  if (entry.isDirectory()) {
6235
6402
  results.push(...walkMdFiles3(full));
6236
6403
  } else if (entry.name.endsWith(".md")) {
@@ -6243,7 +6410,7 @@ function walkMdFiles3(dir) {
6243
6410
  }
6244
6411
  function parseFrontmatter3(filePath) {
6245
6412
  try {
6246
- const content = fs19.readFileSync(filePath, "utf-8");
6413
+ const content = fs20.readFileSync(filePath, "utf-8");
6247
6414
  const match = content.match(/^---\n([\s\S]*?)\n---/);
6248
6415
  if (!match) {
6249
6416
  return { type: "" };
@@ -6262,6 +6429,7 @@ async function extractBrand(apiConfig) {
6262
6429
  return { version: 1 };
6263
6430
  }
6264
6431
  let responseText = "";
6432
+ const iterStart = Date.now();
6265
6433
  try {
6266
6434
  for await (const event of streamChat({
6267
6435
  ...apiConfig,
@@ -6272,6 +6440,20 @@ async function extractBrand(apiConfig) {
6272
6440
  })) {
6273
6441
  if (event.type === "text") {
6274
6442
  responseText += event.text;
6443
+ } else if (event.type === "done") {
6444
+ recordUsage({
6445
+ ts: Date.now(),
6446
+ agentName: "brandExtractor",
6447
+ modelId: event.modelId,
6448
+ inputTokens: event.usage.inputTokens,
6449
+ outputTokens: event.usage.outputTokens,
6450
+ cacheCreationTokens: event.usage.cacheCreationTokens,
6451
+ cacheReadTokens: event.usage.cacheReadTokens,
6452
+ cost: nanoToDollars(event.cost),
6453
+ billingEvents: event.billingEvents,
6454
+ durationMs: Date.now() - iterStart,
6455
+ toolNames: []
6456
+ });
6275
6457
  } else if (event.type === "error") {
6276
6458
  log8.error("Brand extraction stream error", { error: event.error });
6277
6459
  return null;
@@ -6406,14 +6588,14 @@ function pickFont(raw) {
6406
6588
  }
6407
6589
  function persistBrand(brand, inputHash) {
6408
6590
  const tmp = `${BRAND_FILE}.tmp`;
6409
- fs19.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
6410
- fs19.renameSync(tmp, BRAND_FILE);
6591
+ fs20.writeFileSync(tmp, JSON.stringify(brand, null, 2), "utf-8");
6592
+ fs20.renameSync(tmp, BRAND_FILE);
6411
6593
  const cache = { inputHash, generatedAt: Date.now() };
6412
- fs19.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
6594
+ fs20.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
6413
6595
  }
6414
6596
  function readCache() {
6415
6597
  try {
6416
- const raw = fs19.readFileSync(CACHE_FILE, "utf-8");
6598
+ const raw = fs20.readFileSync(CACHE_FILE, "utf-8");
6417
6599
  const parsed = JSON.parse(raw);
6418
6600
  if (parsed && typeof parsed.inputHash === "string" && typeof parsed.generatedAt === "number") {
6419
6601
  return parsed;
@@ -6430,6 +6612,7 @@ var init_brandExtraction = __esm({
6430
6612
  init_api();
6431
6613
  init_assets();
6432
6614
  init_logger();
6615
+ init_usageLedger();
6433
6616
  log8 = createLogger("brandExtraction");
6434
6617
  EXTRACT_PROMPT = readAsset("brandExtraction", "extract.md");
6435
6618
  BRAND_FILE = ".remy-brand.json";
@@ -6561,9 +6744,11 @@ async function runTurn(params) {
6561
6744
  saveSession(state);
6562
6745
  return;
6563
6746
  }
6747
+ const iterStart = Date.now();
6564
6748
  const contentBlocks = [];
6565
6749
  const thinkingBlockStartTimes = [];
6566
6750
  let thinkingCompleteCount = 0;
6751
+ let lastThinkingRelatedStartedAt;
6567
6752
  let textBlockOpen = false;
6568
6753
  const toolInputAccumulators = /* @__PURE__ */ new Map();
6569
6754
  let stopReason = "end_turn";
@@ -6695,16 +6880,30 @@ async function runTurn(params) {
6695
6880
  }
6696
6881
  onEvent({ type: "thinking", text: event.text });
6697
6882
  break;
6698
- case "thinking_complete":
6883
+ case "thinking_complete": {
6884
+ const startedAt = thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts;
6699
6885
  contentBlocks.push({
6700
6886
  type: "thinking",
6701
6887
  thinking: event.thinking,
6702
6888
  signature: event.signature,
6703
- startedAt: thinkingBlockStartTimes[thinkingCompleteCount] ?? event.ts,
6889
+ startedAt,
6704
6890
  completedAt: event.ts
6705
6891
  });
6706
6892
  thinkingCompleteCount++;
6893
+ lastThinkingRelatedStartedAt = startedAt;
6894
+ break;
6895
+ }
6896
+ case "redacted_thinking_complete": {
6897
+ const startedAt = lastThinkingRelatedStartedAt !== void 0 ? lastThinkingRelatedStartedAt + 1 : event.ts;
6898
+ contentBlocks.push({
6899
+ type: "redacted_thinking",
6900
+ data: event.data,
6901
+ startedAt,
6902
+ completedAt: event.ts
6903
+ });
6904
+ lastThinkingRelatedStartedAt = startedAt;
6707
6905
  break;
6906
+ }
6708
6907
  case "tool_input_delta": {
6709
6908
  const acc = getOrCreateAccumulator2(event.id, event.name);
6710
6909
  acc.json += event.delta;
@@ -6758,6 +6957,22 @@ async function runTurn(params) {
6758
6957
  turnOutputTokens += event.usage.outputTokens;
6759
6958
  turnCacheCreation += lastCallCacheCreation;
6760
6959
  turnCacheRead += lastCallCacheRead;
6960
+ recordUsage({
6961
+ ts: Date.now(),
6962
+ requestId,
6963
+ agentName: "parent",
6964
+ modelId: event.modelId,
6965
+ inputTokens: event.usage.inputTokens,
6966
+ outputTokens: event.usage.outputTokens,
6967
+ cacheCreationTokens: event.usage.cacheCreationTokens,
6968
+ cacheReadTokens: event.usage.cacheReadTokens,
6969
+ cost: nanoToDollars(event.cost),
6970
+ billingEvents: event.billingEvents,
6971
+ durationMs: Date.now() - iterStart,
6972
+ toolNames: contentBlocks.filter(
6973
+ (b) => b.type === "tool"
6974
+ ).map((b) => b.name)
6975
+ });
6761
6976
  break;
6762
6977
  case "error":
6763
6978
  onEvent({ type: "error", error: friendlyError(event.error) });
@@ -7004,6 +7219,7 @@ var init_agent = __esm({
7004
7219
  init_tools6();
7005
7220
  init_session();
7006
7221
  init_logger();
7222
+ init_usageLedger();
7007
7223
  init_parsePartialJson();
7008
7224
  init_statusWatcher();
7009
7225
  init_errors();
@@ -7033,12 +7249,12 @@ var init_agent = __esm({
7033
7249
  });
7034
7250
 
7035
7251
  // src/config.ts
7036
- import fs20 from "fs";
7037
- import path10 from "path";
7252
+ import fs21 from "fs";
7253
+ import path11 from "path";
7038
7254
  import os from "os";
7039
7255
  function loadConfigFile() {
7040
7256
  try {
7041
- const raw = fs20.readFileSync(CONFIG_PATH, "utf-8");
7257
+ const raw = fs21.readFileSync(CONFIG_PATH, "utf-8");
7042
7258
  log11.debug("Loaded config file", { path: CONFIG_PATH });
7043
7259
  return JSON.parse(raw);
7044
7260
  } catch (err) {
@@ -7055,6 +7271,7 @@ function resolveConfig(flags2) {
7055
7271
  const env = file.environments?.[activeEnv];
7056
7272
  const apiKey = flags2?.apiKey || process.env.MINDSTUDIO_API_KEY || env?.apiKey || "";
7057
7273
  const baseUrl2 = flags2?.baseUrl || process.env.MINDSTUDIO_BASE_URL || env?.apiBaseUrl || DEFAULT_BASE_URL;
7274
+ const appId = process.env.MINDSTUDIO_APP_ID || void 0;
7058
7275
  if (!apiKey) {
7059
7276
  log11.error("No API key found");
7060
7277
  throw new Error(
@@ -7065,9 +7282,10 @@ function resolveConfig(flags2) {
7065
7282
  log11.info("Config resolved", {
7066
7283
  baseUrl: baseUrl2,
7067
7284
  keySource,
7068
- environment: activeEnv
7285
+ environment: activeEnv,
7286
+ appId
7069
7287
  });
7070
- return { apiKey, baseUrl: baseUrl2 };
7288
+ return { apiKey, baseUrl: baseUrl2, appId };
7071
7289
  }
7072
7290
  var log11, CONFIG_PATH, DEFAULT_BASE_URL;
7073
7291
  var init_config = __esm({
@@ -7075,7 +7293,7 @@ var init_config = __esm({
7075
7293
  "use strict";
7076
7294
  init_logger();
7077
7295
  log11 = createLogger("config");
7078
- CONFIG_PATH = path10.join(
7296
+ CONFIG_PATH = path11.join(
7079
7297
  os.homedir(),
7080
7298
  ".mindstudio-local-tunnel",
7081
7299
  "config.json"
@@ -8321,8 +8539,8 @@ var init_headless = __esm({
8321
8539
  // src/index.tsx
8322
8540
  import { render } from "ink";
8323
8541
  import os2 from "os";
8324
- import fs21 from "fs";
8325
- import path11 from "path";
8542
+ import fs22 from "fs";
8543
+ import path12 from "path";
8326
8544
 
8327
8545
  // src/tui/App.tsx
8328
8546
  import { useState as useState2, useCallback, useRef } from "react";
@@ -8640,8 +8858,8 @@ for (let i = 0; i < args.length; i++) {
8640
8858
  var startupLog = createLogger("startup");
8641
8859
  function printDebugInfo(config) {
8642
8860
  const pkg = JSON.parse(
8643
- fs21.readFileSync(
8644
- path11.join(import.meta.dirname, "..", "package.json"),
8861
+ fs22.readFileSync(
8862
+ path12.join(import.meta.dirname, "..", "package.json"),
8645
8863
  "utf-8"
8646
8864
  )
8647
8865
  );