@wichayutdew/pi-workflows 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1496,6 +1496,182 @@ ${artifact}`, [APPROVE, REQUEST_CHANGES, PAUSE], ...selectionOptions(signal));
1496
1496
  // src/integrations/subagents/client.ts
1497
1497
  import { spawn } from "node:child_process";
1498
1498
  import { StringDecoder } from "node:string_decoder";
1499
+
1500
+ // src/engine/usage.ts
1501
+ var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1502
+ var emptyUsage = () => ({
1503
+ inputTokens: 0,
1504
+ outputTokens: 0,
1505
+ cacheReadTokens: 0,
1506
+ cacheWriteTokens: 0,
1507
+ inputCostUsd: 0,
1508
+ outputCostUsd: 0,
1509
+ cacheReadCostUsd: 0,
1510
+ cacheWriteCostUsd: 0,
1511
+ otherCostUsd: 0,
1512
+ totalCostUsd: 0
1513
+ });
1514
+ var emptyUsageAggregate = () => ({
1515
+ usage: emptyUsage(),
1516
+ models: []
1517
+ });
1518
+ var fields = [
1519
+ "inputTokens",
1520
+ "outputTokens",
1521
+ "cacheReadTokens",
1522
+ "cacheWriteTokens",
1523
+ "inputCostUsd",
1524
+ "outputCostUsd",
1525
+ "cacheReadCostUsd",
1526
+ "cacheWriteCostUsd",
1527
+ "otherCostUsd",
1528
+ "totalCostUsd"
1529
+ ];
1530
+ function finiteNonNegative(value) {
1531
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
1532
+ }
1533
+ function numberAt(value, ...keys) {
1534
+ for (const key of keys) {
1535
+ const candidate = finiteNonNegative(value[key]);
1536
+ if (candidate !== undefined)
1537
+ return candidate;
1538
+ }
1539
+ return 0;
1540
+ }
1541
+ function costNumber(value, costObj, flatKeys, nestedKey) {
1542
+ for (const key of flatKeys) {
1543
+ const candidate = finiteNonNegative(value[key]);
1544
+ if (candidate !== undefined)
1545
+ return candidate;
1546
+ }
1547
+ if (costObj) {
1548
+ const candidate = finiteNonNegative(costObj[nestedKey]);
1549
+ if (candidate !== undefined)
1550
+ return candidate;
1551
+ }
1552
+ return 0;
1553
+ }
1554
+ function hasMalformedNumber(value) {
1555
+ for (const candidate of Object.values(value)) {
1556
+ if (typeof candidate === "number" && (!Number.isFinite(candidate) || candidate < 0)) {
1557
+ return true;
1558
+ }
1559
+ if (isRecord3(candidate)) {
1560
+ for (const nested of Object.values(candidate)) {
1561
+ if (typeof nested === "number" && (!Number.isFinite(nested) || nested < 0)) {
1562
+ return true;
1563
+ }
1564
+ }
1565
+ }
1566
+ }
1567
+ return false;
1568
+ }
1569
+ function normalizeUsage(value) {
1570
+ if (!isRecord3(value))
1571
+ return;
1572
+ if (hasMalformedNumber(value))
1573
+ return;
1574
+ const costObj = isRecord3(value.cost) ? value.cost : undefined;
1575
+ const flatTotal = finiteNonNegative(value.totalCostUsd) ?? (typeof value.cost === "number" ? finiteNonNegative(value.cost) : undefined) ?? finiteNonNegative(value.costUsd);
1576
+ const nestedTotal = costObj ? finiteNonNegative(costObj.total) : undefined;
1577
+ const hasTotal = flatTotal !== undefined || nestedTotal !== undefined;
1578
+ const totalCostUsd = flatTotal ?? nestedTotal ?? 0;
1579
+ let usage = {
1580
+ inputTokens: numberAt(value, "inputTokens", "input"),
1581
+ outputTokens: numberAt(value, "outputTokens", "output"),
1582
+ cacheReadTokens: numberAt(value, "cacheReadTokens", "cacheRead"),
1583
+ cacheWriteTokens: numberAt(value, "cacheWriteTokens", "cacheWrite"),
1584
+ inputCostUsd: costNumber(value, costObj, ["inputCostUsd", "inputCost"], "input"),
1585
+ outputCostUsd: costNumber(value, costObj, ["outputCostUsd", "outputCost"], "output"),
1586
+ cacheReadCostUsd: costNumber(value, costObj, ["cacheReadCostUsd", "cacheReadCost"], "cacheRead"),
1587
+ cacheWriteCostUsd: costNumber(value, costObj, ["cacheWriteCostUsd", "cacheWriteCost"], "cacheWrite"),
1588
+ otherCostUsd: 0,
1589
+ totalCostUsd
1590
+ };
1591
+ const recognized = [
1592
+ "inputTokens",
1593
+ "input",
1594
+ "outputTokens",
1595
+ "output",
1596
+ "cacheReadTokens",
1597
+ "cacheRead",
1598
+ "cacheWriteTokens",
1599
+ "cacheWrite",
1600
+ "inputCostUsd",
1601
+ "inputCost",
1602
+ "outputCostUsd",
1603
+ "outputCost",
1604
+ "cacheReadCostUsd",
1605
+ "cacheReadCost",
1606
+ "cacheWriteCostUsd",
1607
+ "cacheWriteCost",
1608
+ "totalCostUsd",
1609
+ "cost",
1610
+ "costUsd"
1611
+ ].some((key) => (key in value));
1612
+ if (!recognized)
1613
+ return;
1614
+ const componentCost = usage.inputCostUsd + usage.outputCostUsd + usage.cacheReadCostUsd + usage.cacheWriteCostUsd;
1615
+ if (hasTotal) {
1616
+ if (componentCost > usage.totalCostUsd + 0.000000001)
1617
+ return;
1618
+ usage = { ...usage, otherCostUsd: usage.totalCostUsd - componentCost };
1619
+ } else {
1620
+ usage = { ...usage, totalCostUsd: componentCost };
1621
+ }
1622
+ return usage;
1623
+ }
1624
+ function isUsageTotals(value) {
1625
+ if (!isRecord3(value) || !fields.every((field) => finiteNonNegative(value[field]) !== undefined))
1626
+ return false;
1627
+ return Math.abs(value.inputCostUsd + value.outputCostUsd + value.cacheReadCostUsd + value.cacheWriteCostUsd + value.otherCostUsd - value.totalCostUsd) <= 0.000000001;
1628
+ }
1629
+ function addUsage(left, right) {
1630
+ return Object.fromEntries(fields.map((field) => [field, left[field] + right[field]]));
1631
+ }
1632
+ function mergeUsage(aggregate, entries) {
1633
+ const byModel = new Map(aggregate.models.map((entry) => [
1634
+ `${entry.provider}\x00${entry.model}`,
1635
+ entry.usage
1636
+ ]));
1637
+ for (const entry of entries) {
1638
+ if (!entry.provider || !entry.model || !isUsageTotals(entry.usage))
1639
+ continue;
1640
+ const key = `${entry.provider}\x00${entry.model}`;
1641
+ byModel.set(key, addUsage(byModel.get(key) ?? emptyUsage(), entry.usage));
1642
+ }
1643
+ const models = [...byModel.entries()].map(([key, usage]) => {
1644
+ const [provider, model] = key.split("\x00");
1645
+ return { provider: provider ?? "", model: model ?? "", usage };
1646
+ }).sort((left, right) => `${left.provider}/${left.model}`.localeCompare(`${right.provider}/${right.model}`));
1647
+ return {
1648
+ usage: models.reduce((total, entry) => addUsage(total, entry.usage), emptyUsage()),
1649
+ models
1650
+ };
1651
+ }
1652
+ function isUsageAggregate(value) {
1653
+ const usage = value && typeof value === "object" ? value.usage : undefined;
1654
+ const models = value && typeof value === "object" ? value.models : undefined;
1655
+ if (!isRecord3(value) || !isUsageTotals(usage) || !Array.isArray(models))
1656
+ return false;
1657
+ if (!models.every((entry) => isRecord3(entry) && typeof entry.provider === "string" && entry.provider.length > 0 && typeof entry.model === "string" && entry.model.length > 0 && isUsageTotals(entry.usage)))
1658
+ return false;
1659
+ if (new Set(models.map((entry) => `${entry.provider}\x00${entry.model}`)).size !== models.length)
1660
+ return false;
1661
+ const typedModels = models;
1662
+ const total = typedModels.reduce((sum, entry) => addUsage(sum, entry.usage), emptyUsage());
1663
+ return fields.every((field) => Math.abs(total[field] - usage[field]) <= 0.000000001);
1664
+ }
1665
+ function modelUsageFromMessage(value, fallbackProvider, fallbackModel) {
1666
+ if (!isRecord3(value))
1667
+ return;
1668
+ const usage = normalizeUsage(value.usage);
1669
+ const provider = typeof value.provider === "string" ? value.provider : fallbackProvider;
1670
+ const model = typeof value.model === "string" ? value.model : fallbackModel;
1671
+ return usage && provider && model ? { provider, model, usage } : undefined;
1672
+ }
1673
+
1674
+ // src/integrations/subagents/client.ts
1499
1675
  var directWorkerCommand = (request) => [
1500
1676
  "--no-session",
1501
1677
  "--mode",
@@ -1505,7 +1681,7 @@ var directWorkerCommand = (request) => [
1505
1681
  "--print",
1506
1682
  request.task
1507
1683
  ];
1508
- function directWorkerResponse(request, code, signal, stderr, diagnostic) {
1684
+ function directWorkerResponse(request, code, signal, stderr, diagnostic, usage = []) {
1509
1685
  const status = code === 0 ? "completed" : signal ? "cancelled" : "failed";
1510
1686
  return {
1511
1687
  requestId: request.requestId,
@@ -1513,11 +1689,30 @@ function directWorkerResponse(request, code, signal, stderr, diagnostic) {
1513
1689
  status,
1514
1690
  ...code === null ? {} : { exitCode: code },
1515
1691
  ...status !== "completed" && stderr.trim() ? { error: stderr.trim().slice(-4000) } : {},
1516
- ...diagnostic ? { diagnostic } : {}
1692
+ ...diagnostic ? { diagnostic } : {},
1693
+ ...usage.length > 0 ? { usage } : {}
1517
1694
  };
1518
1695
  }
1519
1696
  var MAX_PROGRESS_DETAIL_CHARS = 480;
1520
1697
  var MAX_DIAGNOSTIC_CALLS = 64;
1698
+ function workerUsageFromJsonLine(line, fallbackProvider, fallbackModel) {
1699
+ let event;
1700
+ try {
1701
+ const parsed = JSON.parse(line);
1702
+ if (typeof parsed !== "object" || parsed === null)
1703
+ return [];
1704
+ event = parsed;
1705
+ } catch {
1706
+ return [];
1707
+ }
1708
+ if (event.type !== "message_end" || !event.message)
1709
+ return [];
1710
+ const role = event.message.role;
1711
+ if (role !== "assistant" && role !== "toolResult" && role !== "tool")
1712
+ return [];
1713
+ const usage = modelUsageFromMessage(event.message, fallbackProvider, fallbackModel);
1714
+ return usage ? [usage] : [];
1715
+ }
1521
1716
  var SECRET_KEY = /authorization|cookie|password|secret|token|api[-_]?key/i;
1522
1717
  function redactProgressValue(value, key = "") {
1523
1718
  if (SECRET_KEY.test(key))
@@ -1648,6 +1843,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1648
1843
  env: {
1649
1844
  ...process.env,
1650
1845
  PI_WORKFLOWS_CHILD: "1",
1846
+ PI_WORKFLOWS_CHILD_RUNTIME: "1",
1651
1847
  PI_WORKFLOWS_CHILD_AGENT: request.agent
1652
1848
  },
1653
1849
  stdio: ["ignore", "pipe", "pipe"]
@@ -1658,7 +1854,27 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1658
1854
  let toolCount = 0;
1659
1855
  let responseText = "";
1660
1856
  const diagnostic = createDiagnostic2();
1857
+ let lastProvider;
1858
+ let lastModel;
1859
+ let usage = emptyUsageAggregate();
1661
1860
  const stdoutDecoder = new StringDecoder("utf8");
1861
+ const updateLastModel = (line) => {
1862
+ let event;
1863
+ try {
1864
+ const parsed = JSON.parse(line);
1865
+ if (typeof parsed !== "object" || parsed === null)
1866
+ return;
1867
+ event = parsed;
1868
+ } catch {
1869
+ return;
1870
+ }
1871
+ if (event.type === "message_end" && event.message && event.message.role === "assistant") {
1872
+ if (typeof event.message.provider === "string")
1873
+ lastProvider = event.message.provider;
1874
+ if (typeof event.message.model === "string")
1875
+ lastModel = event.message.model;
1876
+ }
1877
+ };
1662
1878
  const consumeWorkerLines = () => {
1663
1879
  while (true) {
1664
1880
  const newline = stdoutBuffer.indexOf(`
@@ -1668,6 +1884,16 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1668
1884
  const line = stdoutBuffer.slice(0, newline);
1669
1885
  stdoutBuffer = stdoutBuffer.slice(newline + 1);
1670
1886
  recordWorkerDiagnostic(line, diagnostic);
1887
+ updateLastModel(line);
1888
+ const lineUsage = workerUsageFromJsonLine(line, lastProvider, lastModel);
1889
+ if (lineUsage.length > 0) {
1890
+ usage = mergeUsage(usage, lineUsage);
1891
+ const latest = lineUsage[lineUsage.length - 1];
1892
+ if (latest) {
1893
+ lastProvider = latest.provider;
1894
+ lastModel = latest.model;
1895
+ }
1896
+ }
1671
1897
  const progress = workerProgressFromJsonLine(line, request.requestId, toolCount, responseText);
1672
1898
  toolCount = progress.toolCount;
1673
1899
  responseText = progress.responseText;
@@ -1698,7 +1924,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1698
1924
  if (active?.process === child)
1699
1925
  active = undefined;
1700
1926
  options.signal?.removeEventListener("abort", abort);
1701
- resolve3(directWorkerResponse(request, code, signal, stderr, diagnosticSnapshot(diagnostic)));
1927
+ resolve3(directWorkerResponse(request, code, signal, stderr, diagnosticSnapshot(diagnostic), usage.models));
1702
1928
  });
1703
1929
  });
1704
1930
  };
@@ -1722,13 +1948,13 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1722
1948
  }
1723
1949
 
1724
1950
  // src/policy/completion-batch.ts
1725
- var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1951
+ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1726
1952
  var toolCalls = (message) => {
1727
- if (!isRecord3(message))
1953
+ if (!isRecord4(message))
1728
1954
  return [];
1729
1955
  if (message.role !== "assistant" || !Array.isArray(message.content))
1730
1956
  return [];
1731
- return message.content.filter((item) => isRecord3(item) && item.type === "toolCall" && typeof item.id === "string" && typeof item.name === "string");
1957
+ return message.content.filter((item) => isRecord4(item) && item.type === "toolCall" && typeof item.id === "string" && typeof item.name === "string");
1732
1958
  };
1733
1959
  function invalidCompletionCallIds(message, completionTool) {
1734
1960
  const calls = toolCalls(message);
@@ -2152,7 +2378,7 @@ var MAX_WORKFLOW_TRACE_CHARS = 2000000;
2152
2378
  var REDACTED = "[redacted]";
2153
2379
  var SECRET_KEY2 = /(?:^|[-_])(authorization|cookie|set-cookie|api[-_]?key|access[-_]?token|refresh[-_]?token|token|secret|password|passwd|credential|private[-_]?key|client[-_]?secret)(?:$|[-_])/i;
2154
2380
  var LABELED_VALUE = /(^|[^A-Za-z0-9_])([A-Za-z0-9_-]*(?:authorization|proxy[-_]?authorization|cookie|set[-_]?cookie|api[-_]?key|access[-_]?token|refresh[-_]?token|client[-_]?secret|private[-_]?key|password|passwd|credential|token|secret)[A-Za-z0-9_-]*)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;]+)/gim;
2155
- var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2381
+ var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2156
2382
  var sanitizeControls = (value) => {
2157
2383
  let safe = "";
2158
2384
  for (const character of value) {
@@ -2178,7 +2404,7 @@ var redactStructured = (value, depth = 0) => {
2178
2404
  if (Array.isArray(value)) {
2179
2405
  return value.map((item) => redactStructured(item, depth + 1));
2180
2406
  }
2181
- if (!isRecord4(value)) {
2407
+ if (!isRecord5(value)) {
2182
2408
  return typeof value === "string" ? sanitizeStepLogText(value) : value;
2183
2409
  }
2184
2410
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [
@@ -2208,7 +2434,7 @@ var contentText = (content) => {
2208
2434
  if (!Array.isArray(content))
2209
2435
  return "";
2210
2436
  return content.flatMap((item) => {
2211
- if (!isRecord4(item))
2437
+ if (!isRecord5(item))
2212
2438
  return [];
2213
2439
  if (item.type === "text" && typeof item.text === "string") {
2214
2440
  return [item.text];
@@ -2222,24 +2448,24 @@ var contentText = (content) => {
2222
2448
  `);
2223
2449
  };
2224
2450
  function textOnlyUserMessage(message) {
2225
- if (!isRecord4(message) || message.role !== "user")
2451
+ if (!isRecord5(message) || message.role !== "user")
2226
2452
  return;
2227
2453
  if (typeof message.content === "string")
2228
2454
  return message.content;
2229
- if (!Array.isArray(message.content) || message.content.some((item) => !isRecord4(item) || item.type !== "text" || typeof item.text !== "string")) {
2455
+ if (!Array.isArray(message.content) || message.content.some((item) => !isRecord5(item) || item.type !== "text" || typeof item.text !== "string")) {
2230
2456
  return;
2231
2457
  }
2232
2458
  return message.content.map((item) => item.text).join(`
2233
2459
  `);
2234
2460
  }
2235
2461
  function stepLogLinesFromMessage(message) {
2236
- if (!isRecord4(message))
2462
+ if (!isRecord5(message))
2237
2463
  return [];
2238
2464
  if (message.role === "assistant") {
2239
2465
  if (!Array.isArray(message.content))
2240
2466
  return [];
2241
2467
  const contentLines = message.content.flatMap((item) => {
2242
- if (!isRecord4(item))
2468
+ if (!isRecord5(item))
2243
2469
  return [];
2244
2470
  if (item.type === "text" && typeof item.text === "string") {
2245
2471
  const text = redactStepLogText(item.text);
@@ -2275,6 +2501,19 @@ function stepLogLinesFromTurn(message, toolResults) {
2275
2501
  }
2276
2502
 
2277
2503
  // src/runtime/main-step-trace.ts
2504
+ var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2505
+ function mainTurnUsage(event) {
2506
+ const messageUsage = modelUsageFromMessage(event.message);
2507
+ const fallbackProvider = messageUsage?.provider ?? (isRecord6(event.message) && typeof event.message.provider === "string" ? event.message.provider : undefined);
2508
+ const fallbackModel = messageUsage?.model ?? (isRecord6(event.message) && typeof event.message.model === "string" ? event.message.model : undefined);
2509
+ const entries = messageUsage ? [messageUsage] : [];
2510
+ for (const toolResult of event.toolResults ?? []) {
2511
+ const toolUsage = modelUsageFromMessage(toolResult, fallbackProvider, fallbackModel);
2512
+ if (toolUsage)
2513
+ entries.push(toolUsage);
2514
+ }
2515
+ return entries;
2516
+ }
2278
2517
  function armMainStepTrace(state, message) {
2279
2518
  if (!state.active || state.traceArmed || state.traceClosed)
2280
2519
  return;
@@ -2292,8 +2531,9 @@ function registerMainStepTrace({
2292
2531
  return;
2293
2532
  const lines = stepLogLinesFromTurn(event.message, event.toolResults);
2294
2533
  try {
2295
- if (lines.length > 0)
2296
- await active.onTrace(lines, context);
2534
+ const usage = mainTurnUsage(event);
2535
+ if (lines.length > 0 || usage.length > 0)
2536
+ await active.onTrace(lines, context, usage);
2297
2537
  } catch {} finally {
2298
2538
  if (state.active === active && state.pendingResult) {
2299
2539
  state.traceClosed = true;
@@ -2691,6 +2931,31 @@ function padAnsi(value, width) {
2691
2931
 
2692
2932
  // src/workflow-status/render-path.ts
2693
2933
  import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
2934
+
2935
+ // src/workflow-status/format-usage.ts
2936
+ function workflowUsage(run) {
2937
+ return [
2938
+ ...run.history.map((entry) => entry.usage),
2939
+ run.currentStepUsage
2940
+ ].reduce((total, usage) => usage ? mergeUsage(total, usage.models) : total, emptyUsageAggregate());
2941
+ }
2942
+ function formatUsd(value) {
2943
+ if (value === 0)
2944
+ return "$0.00";
2945
+ return value < 0.01 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}`;
2946
+ }
2947
+ function formatTokens(usage) {
2948
+ const parts = [`${usage.inputTokens} in`, `${usage.outputTokens} out`];
2949
+ const cache = usage.cacheReadTokens + usage.cacheWriteTokens;
2950
+ if (cache > 0)
2951
+ parts.push(`${cache} cache`);
2952
+ return parts.join(" · ");
2953
+ }
2954
+ function formatUsage(usage) {
2955
+ return `${formatUsd(usage.usage.totalCostUsd)} · ${formatTokens(usage.usage)}`;
2956
+ }
2957
+
2958
+ // src/workflow-status/render-path.ts
2694
2959
  var MAX_PATH_ROWS = 16;
2695
2960
  function historyPathEntry(workflow, entry, visit, index) {
2696
2961
  return {
@@ -2701,6 +2966,7 @@ function historyPathEntry(workflow, entry, visit, index) {
2701
2966
  status: "completed",
2702
2967
  visit,
2703
2968
  outcome: entry.outcome,
2969
+ ...entry.usage ? { usage: entry.usage } : {},
2704
2970
  isCurrent: false
2705
2971
  };
2706
2972
  }
@@ -2721,6 +2987,7 @@ function buildPathEntries(snapshot) {
2721
2987
  title: stepTitle(workflow, run.currentStepId),
2722
2988
  status: runDisplayStatus(run),
2723
2989
  visit: Math.max(visits.get(run.currentStepId) ?? 0, run.visits[run.currentStepId] ?? 1),
2990
+ ...run.currentStepUsage ? { usage: run.currentStepUsage } : {},
2724
2991
  isCurrent: true
2725
2992
  }
2726
2993
  ];
@@ -2734,6 +3001,7 @@ function buildPathEntries(snapshot) {
2734
3001
  title: stepTitle(workflow, run.currentStepId),
2735
3002
  status: "completed",
2736
3003
  visit: Math.max(1, run.visits[run.currentStepId] ?? 1),
3004
+ ...run.currentStepUsage ? { usage: run.currentStepUsage } : {},
2737
3005
  isCurrent: true
2738
3006
  }
2739
3007
  ];
@@ -2756,7 +3024,8 @@ function renderPathLines(theme, snapshot, width, selectedIndex) {
2756
3024
  const visibleRows = entries.slice(windowStart, windowStart + MAX_PATH_ROWS).map((entry) => {
2757
3025
  const visit = entry.visit > 1 ? theme.fg("dim", ` · visit ${entry.visit}`) : "";
2758
3026
  const left = `${statusGlyph(theme, entry.status, snapshot.now)} ${theme.fg(entry.isCurrent ? "text" : "muted", entry.title)}${visit}`;
2759
- const right = entry.outcome ? `${statusLabel(entry.status)} · ${inline(entry.outcome)}` : statusLabel(entry.status);
3027
+ const cost = entry.usage?.models.length ? ` · ${formatUsd(entry.usage.usage.totalCostUsd)}` : "";
3028
+ const right = entry.outcome ? `${statusLabel(entry.status)} · ${inline(entry.outcome)}${cost}` : `${statusLabel(entry.status)}${cost}`;
2760
3029
  const row = joinColumns(left, theme.fg(statusColor(entry.status), right), width, Math.max(12, Math.floor(width * 0.58)));
2761
3030
  return entry.index === selection ? theme.bg("selectedBg", padAnsi(row, width)) : truncateToWidth2(row, width);
2762
3031
  });
@@ -2808,6 +3077,11 @@ function renderSummaryLines(theme, snapshot, width) {
2808
3077
  ...keyValueLines(theme, "started", formatTimestamp(run.startedAt), width),
2809
3078
  ...keyValueLines(theme, "updated", `${formatTimestamp(run.updatedAt)} · ${formatElapsed(elapsedMs(snapshot))}`, width)
2810
3079
  ];
3080
+ const usage = workflowUsage(run);
3081
+ if (usage.models.length > 0) {
3082
+ lines.push(...keyValueLines(theme, "usage", formatUsage(usage), width));
3083
+ lines.push(...usage.models.flatMap((entry) => keyValueLines(theme, "model", `${entry.provider}/${entry.model} · ${formatUsage({ usage: entry.usage, models: [] })}`, width, "muted")));
3084
+ }
2811
3085
  if (run.cwd && run.startCwd && run.cwd !== run.startCwd) {
2812
3086
  lines.push(...keyValueLines(theme, "workspace", run.cwd, width, "accent"));
2813
3087
  }
@@ -2871,7 +3145,9 @@ function formatWorkflowProgressStatus(snapshot, statusShortcutLabel) {
2871
3145
  const currentStep = formatStepName(stepTitle(workflow, run.currentStepId), run.currentStepId);
2872
3146
  const activity = run.status === "awaiting-gate" ? "awaiting review" : "working";
2873
3147
  const workerProgress = snapshot.execution?.kind === "subagent" ? ` · ${snapshot.execution.progress}` : "";
2874
- return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity}${workerProgress} · ${statusShortcutLabel}`;
3148
+ const usage = workflowUsage(run);
3149
+ const usageText = usage.models.length > 0 ? ` · ${formatUsage(usage)}` : "";
3150
+ return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity}${workerProgress}${usageText} · ${statusShortcutLabel}`;
2875
3151
  }
2876
3152
  // src/workflow-status/view.ts
2877
3153
  import {
@@ -2998,6 +3274,10 @@ function renderAttempt(theme, snapshot, attempt, attemptNumber, cache, width) {
2998
3274
  return [
2999
3275
  theme.bold(theme.fg("accent", `Attempt ${attemptNumber} · ${actor}`)),
3000
3276
  ...keyValueLines(theme, "request", attempt.requestId, width, "muted"),
3277
+ ...attempt.usage ? [
3278
+ ...keyValueLines(theme, "usage", formatUsage(attempt.usage), width),
3279
+ ...attempt.usage.models.flatMap((entry) => keyValueLines(theme, "model", `${entry.provider}/${entry.model} · ${formatUsage({ usage: entry.usage, models: [] })}`, width, "muted"))
3280
+ ] : [],
3001
3281
  "",
3002
3282
  theme.bold("Requirement fed to the agent"),
3003
3283
  ...wrapPlain(`${attempt.task}${truncation}`, width, theme),
@@ -3076,6 +3356,10 @@ function renderStepDetail(theme, snapshot, selectedIndex, cache, width) {
3076
3356
  ...keyValueLines(theme, "step", entry.stepId, width),
3077
3357
  ...keyValueLines(theme, "visit", String(entry.visit), width),
3078
3358
  ...keyValueLines(theme, "status", entry.status, width),
3359
+ ...entry.usage ? [
3360
+ ...keyValueLines(theme, "usage", formatUsage(entry.usage), width),
3361
+ ...entry.usage.models.flatMap((model) => keyValueLines(theme, "model", `${model.provider}/${model.model} · ${formatUsage({ usage: model.usage, models: [] })}`, width, "muted"))
3362
+ ] : [],
3079
3363
  ...history ? [
3080
3364
  ...keyValueLines(theme, "outcome", history.outcome, width, "success"),
3081
3365
  ...keyValueLines(theme, "summary", history.summary, width)
@@ -3143,7 +3427,7 @@ function logChars(lines) {
3143
3427
  return lines?.reduce((total, line) => total + line.length, 0) ?? 0;
3144
3428
  }
3145
3429
  function attemptSize(attempt) {
3146
- return attempt.requestId.length + attempt.task.length + (attempt.kind === "subagent" ? attempt.agent.length : 0) + (attempt.kind === "main" ? logChars(attempt.log) : 0) + (attempt.result?.outcome.length ?? 0) + (attempt.result?.summary.length ?? 0) + (attempt.result?.artifact?.length ?? 0) + (attempt.result?.workspaceCwd?.length ?? 0) + (attempt.kind === "subagent" ? (attempt.transcript?.trustedRoot.length ?? 0) + (attempt.transcript?.sessionFile.length ?? 0) + (attempt.transcript?.runId.length ?? 0) : 0) + (attempt.gateDecision?.requestId.length ?? 0) + (attempt.gateDecision?.feedback.length ?? 0) + (attempt.gateDecision?.reviewId?.length ?? 0);
3430
+ return attempt.requestId.length + attempt.task.length + (attempt.kind === "subagent" ? attempt.agent.length : 0) + (attempt.kind === "main" ? logChars(attempt.log) : 0) + (attempt.result?.outcome.length ?? 0) + (attempt.result?.summary.length ?? 0) + (attempt.result?.artifact?.length ?? 0) + (attempt.result?.workspaceCwd?.length ?? 0) + (attempt.kind === "subagent" ? (attempt.transcript?.trustedRoot.length ?? 0) + (attempt.transcript?.sessionFile.length ?? 0) + (attempt.transcript?.runId.length ?? 0) : 0) + (attempt.gateDecision?.requestId.length ?? 0) + (attempt.gateDecision?.feedback.length ?? 0) + (attempt.gateDecision?.reviewId?.length ?? 0) + (attempt.usage ? JSON.stringify(attempt.usage).length : 0);
3147
3431
  }
3148
3432
  function compactMainLog(attempt) {
3149
3433
  if (attempt.kind !== "main" || !attempt.log)
@@ -3194,10 +3478,15 @@ function compactAttempt(attempt) {
3194
3478
  };
3195
3479
  }
3196
3480
  function workflowTraceChars(run) {
3197
- return [
3481
+ const attempts = [
3198
3482
  ...run.history.flatMap((entry) => entry.attempts ?? []),
3199
3483
  ...run.currentStepAttempts ?? []
3200
3484
  ].reduce((total, attempt) => total + attemptSize(attempt), 0);
3485
+ const aggregates = [
3486
+ ...run.history.map((entry) => entry.usage),
3487
+ run.currentStepUsage
3488
+ ].reduce((total, usage) => total + (usage ? JSON.stringify(usage).length : 0), 0);
3489
+ return attempts + aggregates;
3201
3490
  }
3202
3491
  function compactRunTraceBudget(run) {
3203
3492
  let remaining = workflowTraceChars(run);
@@ -3381,6 +3670,29 @@ function attemptResult(result, workspaceCwd) {
3381
3670
  ...workspaceCwd ? { workspaceCwd } : {}
3382
3671
  };
3383
3672
  }
3673
+ function recordCurrentStepUsage(run, requestId, usage, now) {
3674
+ const attempts = run.currentStepAttempts;
3675
+ const index = attempts?.findIndex((attempt2) => attempt2.requestId === requestId);
3676
+ if (index === undefined || index < 0 || !attempts)
3677
+ return run;
3678
+ const attempt = attempts[index];
3679
+ if (!attempt)
3680
+ return run;
3681
+ const currentStepAttempts = [...attempts];
3682
+ currentStepAttempts[index] = {
3683
+ ...attempt,
3684
+ usage: mergeUsage(attempt.usage ?? emptyUsageAggregate(), usage.models)
3685
+ };
3686
+ return compactRunTraceBudget({
3687
+ ...run,
3688
+ currentStepAttempts,
3689
+ currentStepUsage: mergeUsage(run.currentStepUsage ?? emptyUsageAggregate(), usage.models),
3690
+ updatedAt: now
3691
+ });
3692
+ }
3693
+ function usageAggregateFromModels(entries) {
3694
+ return mergeUsage(emptyUsageAggregate(), entries);
3695
+ }
3384
3696
  function recordCurrentStepResult(run, result, now, workspaceCwd) {
3385
3697
  const attempts = run.currentStepAttempts;
3386
3698
  if (!attempts || attempts.length === 0)
@@ -3426,11 +3738,11 @@ function recordCurrentGateDecision(run, decision, now) {
3426
3738
  }
3427
3739
 
3428
3740
  // src/engine/run-validation.ts
3429
- var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3741
+ var isRecord7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3430
3742
  var isAbsoluteCwd = (value) => typeof value === "string" && value.length > 0 && isAbsolute4(value) && !value.includes("\x00");
3431
- var isGateApproval = (value) => isRecord5(value) && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.artifact === "string" && value.artifact.trim().length > 0 && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.stepStructuralDigest === "string" && value.stepStructuralDigest.length > 0;
3432
- var isStepAttemptResult = (value) => isRecord5(value) && typeof value.outcome === "string" && typeof value.summary === "string" && value.summary.length <= MAX_STEP_TRACE_SUMMARY_CHARS && (value.summaryTruncated === undefined || value.summaryTruncated === true) && (value.artifact === undefined || typeof value.artifact === "string") && (typeof value.artifact !== "string" || value.artifact.length <= MAX_STEP_TRACE_ARTIFACT_CHARS) && (value.artifactTruncated === undefined || value.artifactTruncated === true) && !(value.artifactTruncated === true && value.artifact === undefined) && (value.workspaceCwd === undefined || isAbsoluteCwd(value.workspaceCwd));
3433
- var isStepGateDecision = (value) => isRecord5(value) && (value.provider === "prompt" || value.provider === "plannotator") && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_STEP_TRACE_SUMMARY_CHARS && (value.feedbackTruncated === undefined || value.feedbackTruncated === true) && typeof value.resolvedAt === "number" && (value.reviewId === undefined || typeof value.reviewId === "string");
3743
+ var isGateApproval = (value) => isRecord7(value) && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.artifact === "string" && value.artifact.trim().length > 0 && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.stepStructuralDigest === "string" && value.stepStructuralDigest.length > 0;
3744
+ var isStepAttemptResult = (value) => isRecord7(value) && typeof value.outcome === "string" && typeof value.summary === "string" && value.summary.length <= MAX_STEP_TRACE_SUMMARY_CHARS && (value.summaryTruncated === undefined || value.summaryTruncated === true) && (value.artifact === undefined || typeof value.artifact === "string") && (typeof value.artifact !== "string" || value.artifact.length <= MAX_STEP_TRACE_ARTIFACT_CHARS) && (value.artifactTruncated === undefined || value.artifactTruncated === true) && !(value.artifactTruncated === true && value.artifact === undefined) && (value.workspaceCwd === undefined || isAbsoluteCwd(value.workspaceCwd));
3745
+ var isStepGateDecision = (value) => isRecord7(value) && (value.provider === "prompt" || value.provider === "plannotator") && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_STEP_TRACE_SUMMARY_CHARS && (value.feedbackTruncated === undefined || value.feedbackTruncated === true) && typeof value.resolvedAt === "number" && (value.reviewId === undefined || typeof value.reviewId === "string");
3434
3746
  var isSafeTraceIdentityField = (value) => typeof value === "string" && value.length > 0 && !value.includes("\x00") && !value.includes("/") && !value.includes("\\") && value !== "." && value !== "..";
3435
3747
  var hasValidMainStepLog = (value) => {
3436
3748
  const log = value.log;
@@ -3446,7 +3758,7 @@ var hasValidMainStepLog = (value) => {
3446
3758
  return value.logTruncated === true === (typeof value.omittedLogEvents === "number") && !(log === undefined && (value.logTruncated !== undefined || value.omittedLogEvents !== undefined));
3447
3759
  };
3448
3760
  var isStepExecutionAttempt = (value) => {
3449
- if (!isRecord5(value) || value.kind !== "main" && value.kind !== "subagent" || typeof value.requestId !== "string" || value.requestId.length === 0 || value.requestId.includes("\x00") || value.ordinal !== undefined && (!Number.isSafeInteger(value.ordinal) || value.ordinal <= 0) || typeof value.task !== "string" || value.task.trim().length === 0 || value.task.length > MAX_STEP_TRACE_TASK_CHARS || value.taskTruncated !== undefined && value.taskTruncated !== true || value.omittedTaskChars !== undefined && (!Number.isSafeInteger(value.omittedTaskChars) || value.omittedTaskChars <= 0) || value.taskTruncated === true !== (typeof value.omittedTaskChars === "number") || typeof value.startedAt !== "number" || value.result !== undefined && !isStepAttemptResult(value.result) || value.gateDecision !== undefined && !isStepGateDecision(value.gateDecision)) {
3761
+ if (!isRecord7(value) || value.kind !== "main" && value.kind !== "subagent" || typeof value.requestId !== "string" || value.requestId.length === 0 || value.requestId.includes("\x00") || value.ordinal !== undefined && (!Number.isSafeInteger(value.ordinal) || value.ordinal <= 0) || typeof value.task !== "string" || value.task.trim().length === 0 || value.task.length > MAX_STEP_TRACE_TASK_CHARS || value.taskTruncated !== undefined && value.taskTruncated !== true || value.omittedTaskChars !== undefined && (!Number.isSafeInteger(value.omittedTaskChars) || value.omittedTaskChars <= 0) || value.taskTruncated === true !== (typeof value.omittedTaskChars === "number") || typeof value.startedAt !== "number" || value.usage !== undefined && !isUsageAggregate(value.usage) || value.result !== undefined && !isStepAttemptResult(value.result) || value.gateDecision !== undefined && !isStepGateDecision(value.gateDecision)) {
3450
3762
  return false;
3451
3763
  }
3452
3764
  if (value.kind === "main") {
@@ -3460,7 +3772,7 @@ var isStepExecutionAttempt = (value) => {
3460
3772
  }
3461
3773
  if (value.transcript === undefined)
3462
3774
  return true;
3463
- if (!isRecord5(value.transcript))
3775
+ if (!isRecord7(value.transcript))
3464
3776
  return false;
3465
3777
  const transcript = value.transcript;
3466
3778
  if (!isAbsoluteCwd(transcript.trustedRoot) || !isAbsoluteCwd(transcript.sessionFile) || !isSafeTraceIdentityField(transcript.runId) || !Number.isSafeInteger(transcript.childIndex) || transcript.childIndex < 0) {
@@ -3474,10 +3786,16 @@ var isStepExecutionAttempts = (value) => Array.isArray(value) && value.length <=
3474
3786
  const ordinal = attempt.ordinal;
3475
3787
  return value.slice(0, index).every((earlier) => earlier.ordinal === undefined || earlier.ordinal < ordinal);
3476
3788
  });
3477
- var isStepHistoryEntry = (value) => isRecord5(value) && typeof value.stepId === "string" && typeof value.stepDigest === "string" && typeof value.outcome === "string" && typeof value.summary === "string" && (value.workspaceCwd === undefined || isAbsoluteCwd(value.workspaceCwd)) && (value.artifact === undefined || typeof value.artifact === "string") && (value.approval === undefined || isGateApproval(value.approval) && value.artifact === value.approval.artifact) && (value.attempts === undefined || isStepExecutionAttempts(value.attempts)) && (value.omittedAttempts === undefined || Number.isSafeInteger(value.omittedAttempts) && value.omittedAttempts > 0) && typeof value.completedAt === "number";
3478
- var isGateResolution = (value) => isRecord5(value) && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.resolvedAt === "number";
3479
- var isPendingGate = (value) => isRecord5(value) && (value.provider === "prompt" || value.provider === "plannotator") && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.stepId === "string" && typeof value.artifact === "string" && (value.summary === undefined || typeof value.summary === "string") && typeof value.submittedOutcome === "string" && typeof value.requestedAt === "number" && (value.reviewId === undefined || typeof value.reviewId === "string") && (value.resolution === undefined || isGateResolution(value.resolution));
3480
- var isVisitCounts = (value) => isRecord5(value) && Object.values(value).every((count) => typeof count === "number" && Number.isInteger(count) && count >= 0);
3789
+ var usageMatchesAttempts = (attempts, aggregate, omittedAttempts) => {
3790
+ if (!aggregate || omittedAttempts !== undefined)
3791
+ return true;
3792
+ const fromAttempts = (attempts ?? []).reduce((total, attempt) => attempt.usage ? mergeUsage(total, attempt.usage.models) : total, emptyUsageAggregate());
3793
+ return JSON.stringify(fromAttempts) === JSON.stringify(aggregate);
3794
+ };
3795
+ var isStepHistoryEntry = (value) => isRecord7(value) && typeof value.stepId === "string" && typeof value.stepDigest === "string" && typeof value.outcome === "string" && typeof value.summary === "string" && (value.workspaceCwd === undefined || isAbsoluteCwd(value.workspaceCwd)) && (value.artifact === undefined || typeof value.artifact === "string") && (value.approval === undefined || isGateApproval(value.approval) && value.artifact === value.approval.artifact) && (value.attempts === undefined || isStepExecutionAttempts(value.attempts)) && (value.omittedAttempts === undefined || typeof value.omittedAttempts === "number" && Number.isSafeInteger(value.omittedAttempts) && value.omittedAttempts > 0) && (value.usage === undefined || isUsageAggregate(value.usage)) && usageMatchesAttempts(value.attempts, value.usage, value.omittedAttempts) && typeof value.completedAt === "number";
3796
+ var isGateResolution = (value) => isRecord7(value) && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.resolvedAt === "number";
3797
+ var isPendingGate = (value) => isRecord7(value) && (value.provider === "prompt" || value.provider === "plannotator") && typeof value.requestId === "string" && value.requestId.length > 0 && typeof value.stepId === "string" && typeof value.artifact === "string" && (value.summary === undefined || typeof value.summary === "string") && typeof value.submittedOutcome === "string" && typeof value.requestedAt === "number" && (value.reviewId === undefined || typeof value.reviewId === "string") && (value.resolution === undefined || isGateResolution(value.resolution));
3798
+ var isVisitCounts = (value) => isRecord7(value) && Object.values(value).every((count) => typeof count === "number" && Number.isInteger(count) && count >= 0);
3481
3799
  var isOptionalString = (value) => value === undefined || typeof value === "string";
3482
3800
  var isOptionalResumeInput = (value) => value === undefined || typeof value === "string" && value.length <= MAX_RESUME_INPUT_CHARS;
3483
3801
  var isOptionalIteration = (value) => value === undefined || Number.isSafeInteger(value) && value >= 1;
@@ -3502,14 +3820,16 @@ var hasValidWorkspaceState = (run, history) => {
3502
3820
  return startCwd === undefined || cwd === startCwd;
3503
3821
  };
3504
3822
  var isWorkflowRun = (value) => {
3505
- if (!isRecord5(value))
3823
+ if (!isRecord7(value))
3506
3824
  return false;
3507
- const hasValidRequiredFields = value.stateVersion === RUN_STATE_VERSION && typeof value.runId === "string" && typeof value.workflowId === "string" && typeof value.workflowDigest === "string" && typeof value.input === "string" && isWorkflowRunStatus(value.status) && typeof value.currentStepId === "string" && typeof value.currentStepDigest === "string" && Array.isArray(value.baselineTools) && value.baselineTools.every((tool) => typeof tool === "string") && Array.isArray(value.history) && value.history.every(isStepHistoryEntry) && (value.currentStepAttempts === undefined || isStepExecutionAttempts(value.currentStepAttempts)) && (value.currentStepOmittedAttempts === undefined || Number.isSafeInteger(value.currentStepOmittedAttempts) && value.currentStepOmittedAttempts > 0) && isVisitCounts(value.visits) && typeof value.startedAt === "number" && typeof value.updatedAt === "number" && typeof value.lastSummary === "string" && typeof value.gateFeedback === "string" && value.gateFeedback.length <= MAX_GATE_FEEDBACK_CHARS;
3825
+ const hasValidRequiredFields = value.stateVersion === RUN_STATE_VERSION && typeof value.runId === "string" && typeof value.workflowId === "string" && typeof value.workflowDigest === "string" && typeof value.input === "string" && isWorkflowRunStatus(value.status) && typeof value.currentStepId === "string" && typeof value.currentStepDigest === "string" && Array.isArray(value.baselineTools) && value.baselineTools.every((tool) => typeof tool === "string") && Array.isArray(value.history) && value.history.every(isStepHistoryEntry) && (value.currentStepAttempts === undefined || isStepExecutionAttempts(value.currentStepAttempts)) && (value.currentStepOmittedAttempts === undefined || Number.isSafeInteger(value.currentStepOmittedAttempts) && value.currentStepOmittedAttempts > 0) && (value.currentStepUsage === undefined || isUsageAggregate(value.currentStepUsage)) && isVisitCounts(value.visits) && typeof value.startedAt === "number" && typeof value.updatedAt === "number" && typeof value.lastSummary === "string" && typeof value.gateFeedback === "string" && value.gateFeedback.length <= MAX_GATE_FEEDBACK_CHARS;
3508
3826
  if (!hasValidRequiredFields)
3509
3827
  return false;
3510
3828
  const hasValidOptionalFields = isOptionalIteration(value.iteration) && isOptionalString(value.reviewedArtifact) && isOptionalString(value.reviewedFeedback) && (typeof value.reviewedFeedback !== "string" || value.reviewedFeedback.length <= MAX_GATE_FEEDBACK_CHARS) && isOptionalString(value.stepHandoff) && isOptionalString(value.gateArtifact) && (value.restartWorkspaceCwd === undefined || isAbsoluteCwd(value.restartWorkspaceCwd)) && isOptionalResumeInput(value.resumeInput) && isOptionalString(value.pauseReason) && isOptionalString(value.failedStepId) && (value.pausedFrom === undefined || value.pausedFrom === "running" || value.pausedFrom === "awaiting-gate");
3511
3829
  if (!hasValidOptionalFields)
3512
3830
  return false;
3831
+ if (!usageMatchesAttempts(value.currentStepAttempts, value.currentStepUsage, value.currentStepOmittedAttempts))
3832
+ return false;
3513
3833
  const pendingGate = value.pendingGate;
3514
3834
  if (pendingGate !== undefined && !isPendingGate(pendingGate))
3515
3835
  return false;
@@ -3520,7 +3840,7 @@ var isWorkflowRun = (value) => {
3520
3840
  };
3521
3841
  // src/workflow-status/transcript-reader.ts
3522
3842
  var MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
3523
- function isRecord6(value) {
3843
+ function isRecord8(value) {
3524
3844
  return value !== null && typeof value === "object" && !Array.isArray(value);
3525
3845
  }
3526
3846
  function isWithin(root, candidate) {
@@ -3531,7 +3851,7 @@ function hasSafeIdentity(reference) {
3531
3851
  return isAbsolute5(reference.trustedRoot) && isAbsolute5(reference.sessionFile) && !reference.trustedRoot.includes("\x00") && !reference.sessionFile.includes("\x00") && reference.runId.length > 0 && !reference.runId.includes("\x00") && !reference.runId.includes("/") && !reference.runId.includes("\\") && reference.runId !== "." && reference.runId !== ".." && Number.isSafeInteger(reference.childIndex) && reference.childIndex >= 0 && resolve4(reference.sessionFile) === resolve4(reference.trustedRoot, reference.runId, `run-${reference.childIndex}`, "session.jsonl");
3532
3852
  }
3533
3853
  function transcriptEntryLines(entry) {
3534
- if (!isRecord6(entry))
3854
+ if (!isRecord8(entry))
3535
3855
  return [];
3536
3856
  if (entry.type === "custom_message") {
3537
3857
  const customType = typeof entry.customType === "string" ? entry.customType : "custom";
@@ -3539,7 +3859,7 @@ function transcriptEntryLines(entry) {
3539
3859
  return content ? [`event ${sanitizeStepLogText(customType)}
3540
3860
  ${content}`] : [];
3541
3861
  }
3542
- if (entry.type !== "message" || !isRecord6(entry.message))
3862
+ if (entry.type !== "message" || !isRecord8(entry.message))
3543
3863
  return [];
3544
3864
  return stepLogLinesFromMessage(entry.message);
3545
3865
  }
@@ -4215,6 +4535,7 @@ var completedStep = (run, outcome, summary, now, effects) => ({
4215
4535
  ...effects.workspaceCwd ? { workspaceCwd: effects.workspaceCwd } : {},
4216
4536
  ...run.currentStepAttempts?.length ? { attempts: run.currentStepAttempts } : {},
4217
4537
  ...run.currentStepOmittedAttempts ? { omittedAttempts: run.currentStepOmittedAttempts } : {},
4538
+ ...run.currentStepUsage ? { usage: run.currentStepUsage } : {},
4218
4539
  completedAt: now
4219
4540
  });
4220
4541
  var advanceRun = (workflow, run, outcome, summary, now, effects = {}, options = {}) => {
@@ -4259,6 +4580,7 @@ var advanceRun = (workflow, run, outcome, summary, now, effects = {}, options =
4259
4580
  history: [...run.history, completed],
4260
4581
  currentStepAttempts: undefined,
4261
4582
  currentStepOmittedAttempts: undefined,
4583
+ currentStepUsage: undefined,
4262
4584
  ...cwd ? { cwd } : {},
4263
4585
  ...effects.workspaceCwd ? { restartWorkspaceCwd: undefined } : {},
4264
4586
  stepHandoff: summary,
@@ -4295,6 +4617,7 @@ var advanceRun = (workflow, run, outcome, summary, now, effects = {}, options =
4295
4617
  history: [...run.history, completed],
4296
4618
  currentStepAttempts: undefined,
4297
4619
  currentStepOmittedAttempts: undefined,
4620
+ currentStepUsage: undefined,
4298
4621
  ...cwd ? { cwd } : {},
4299
4622
  ...effects.workspaceCwd ? { restartWorkspaceCwd: undefined } : {},
4300
4623
  stepHandoff: preservesGateRevisionContext ? run.stepHandoff : summary,
@@ -4745,6 +5068,7 @@ var reconcileRun = (run, workflow, now) => {
4745
5068
  history: retainedHistory,
4746
5069
  currentStepAttempts: changedEntry.attempts,
4747
5070
  currentStepOmittedAttempts: changedEntry.omittedAttempts,
5071
+ currentStepUsage: changedEntry.usage,
4748
5072
  visits: rebuildVisits(retainedHistory, restartedStep),
4749
5073
  cwd: retainedWorkspaceCwd(reconciledRun, retainedHistory),
4750
5074
  reviewedArtifact: reviewedApproval?.artifact ?? "",
@@ -5522,7 +5846,7 @@ var THINKING_LEVELS = [
5522
5846
  "xhigh",
5523
5847
  "max"
5524
5848
  ];
5525
- var isRecord7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5849
+ var isRecord9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5526
5850
  function parseAgentProfile(source, name) {
5527
5851
  if (!source.startsWith(`---
5528
5852
  `))
@@ -5534,7 +5858,7 @@ function parseAgentProfile(source, name) {
5534
5858
  throw new Error(`workflow agent profile is invalid: ${name}`);
5535
5859
  }
5536
5860
  const metadata = parse(source.slice(4, end));
5537
- if (!isRecord7(metadata)) {
5861
+ if (!isRecord9(metadata)) {
5538
5862
  throw new Error(`workflow agent profile metadata must be an object: ${name}`);
5539
5863
  }
5540
5864
  const unknownKey = Object.keys(metadata).find((key) => key !== "model" && key !== "thinking");
@@ -5973,16 +6297,16 @@ import { dirname as dirname3, isAbsolute as isAbsolute8, resolve as resolve7 } f
5973
6297
 
5974
6298
  // src/integrations/subagents/child-policy-sections.ts
5975
6299
  import { isAbsolute as isAbsolute7, win32 as win323 } from "node:path";
5976
- var isRecord8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6300
+ var isRecord10 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5977
6301
  var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
5978
6302
  var hasOnlyKeys = (value, allowed) => Object.keys(value).every((key) => allowed.has(key));
5979
6303
  var isStepPermissions = (value) => {
5980
- if (!isRecord8(value) || !isRecord8(value.bash))
6304
+ if (!isRecord10(value) || !isRecord10(value.bash))
5981
6305
  return false;
5982
6306
  const bash = value.bash;
5983
6307
  const bashRules = Array.isArray(bash.allow) ? bash.allow : undefined;
5984
6308
  const isValidMode = bash.mode === "deny" || bash.mode === "allow-list" || bash.mode === "unrestricted";
5985
- const hasValidRules = bashRules !== undefined && bashRules.every((rule) => isRecord8(rule) && hasOnlyKeys(rule, new Set(["executable", "argsPrefix"])) && typeof rule.executable === "string" && isStringArray(rule.argsPrefix));
6309
+ const hasValidRules = bashRules !== undefined && bashRules.every((rule) => isRecord10(rule) && hasOnlyKeys(rule, new Set(["executable", "argsPrefix"])) && typeof rule.executable === "string" && isStringArray(rule.argsPrefix));
5986
6310
  return hasOnlyKeys(value, new Set(["tools", "mcp", "extensions", "skills", "bash"])) && hasOnlyKeys(bash, new Set(["mode", "allow"])) && isStringArray(value.tools) && isStringArray(value.mcp) && isStringArray(value.extensions) && isStringArray(value.skills) && isValidMode && hasValidRules && (bash.mode !== "allow-list" || bashRules.length > 0);
5987
6311
  };
5988
6312
  var parsePermissions2 = (value) => {
@@ -6018,7 +6342,7 @@ var parseOutcomes = (value) => {
6018
6342
  var parseWorkspace2 = (value, outcomes) => {
6019
6343
  if (value.workspace === undefined)
6020
6344
  return {};
6021
- if (!isRecord8(value.workspace) || !hasOnlyKeys(value.workspace, new Set(["bindOn", "allowedRoots"]))) {
6345
+ if (!isRecord10(value.workspace) || !hasOnlyKeys(value.workspace, new Set(["bindOn", "allowedRoots"]))) {
6022
6346
  throw new Error("child policy workspace is invalid");
6023
6347
  }
6024
6348
  const bindOn = value.workspace.bindOn;
@@ -6063,7 +6387,7 @@ var POLICY_KEYS = new Set([
6063
6387
  "gateSubmitOutcome",
6064
6388
  "workspace"
6065
6389
  ]);
6066
- var isRecord9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6390
+ var isRecord11 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6067
6391
  var requiredString = (value, field) => {
6068
6392
  const candidate = value[field];
6069
6393
  if (typeof candidate !== "string" || !candidate) {
@@ -6129,7 +6453,7 @@ var parseIdentityAndPaths = (value, environment) => {
6129
6453
  };
6130
6454
  };
6131
6455
  var parseChildPolicy = (value, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => {
6132
- if (!isRecord9(value))
6456
+ if (!isRecord11(value))
6133
6457
  throw new Error("child policy must be an object");
6134
6458
  rejectUnknownProperties(value);
6135
6459
  return {
@@ -6525,7 +6849,7 @@ function launchMainStep(workflow, run, step) {
6525
6849
  summaryMaxChars: workflow.definition.summaryMaxChars,
6526
6850
  ...step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {},
6527
6851
  ...step.workspace ? { workspace: structuredClone(step.workspace) } : {},
6528
- onTrace: (lines, context) => this.queueMainStepLog(identity, lines, context),
6852
+ onTrace: (lines, context, usage) => this.queueMainStepLog(identity, lines, context, usage),
6529
6853
  onSettled: (result, context) => this.queueMainStepResult(identity, result, context)
6530
6854
  };
6531
6855
  try {
@@ -6551,10 +6875,10 @@ function matchesLatestMainStepAttempt(run, identity) {
6551
6875
  const attempt = run.currentStepAttempts?.at(-1);
6552
6876
  return attempt === undefined || attempt.kind === "main" && attempt.requestId === identity.requestId;
6553
6877
  }
6554
- function queueMainStepLog(identity, lines, context) {
6555
- return this.mutationQueue.run(() => this.recordMainStepLog(identity, lines, context)).catch(() => {});
6878
+ function queueMainStepLog(identity, lines, context, usage) {
6879
+ return this.mutationQueue.run(() => this.recordMainStepLog(identity, lines, context, usage)).catch(() => {});
6556
6880
  }
6557
- async function recordMainStepLog(identity, lines, context) {
6881
+ async function recordMainStepLog(identity, lines, context, usage) {
6558
6882
  if (!hasCurrentMainStepIdentity(this, identity) || !matchesLatestMainStepAttempt(this.run, identity)) {
6559
6883
  return;
6560
6884
  }
@@ -6563,7 +6887,10 @@ async function recordMainStepLog(identity, lines, context) {
6563
6887
  return;
6564
6888
  }
6565
6889
  this.latestContext = context;
6566
- const traced = appendMainStepLog(this.run, identity.requestId, lines, this.dependencies.now());
6890
+ let traced = appendMainStepLog(this.run, identity.requestId, lines, this.dependencies.now());
6891
+ if (usage && usage.length > 0) {
6892
+ traced = recordCurrentStepUsage(traced, identity.requestId, usageAggregateFromModels(usage), this.dependencies.now());
6893
+ }
6567
6894
  if (traced === this.run)
6568
6895
  return;
6569
6896
  this.run = traced;
@@ -6690,7 +7017,15 @@ async function finishDelegation(active, response) {
6690
7017
  if (!this.isSessionActive || this.sessionEpoch !== active.sessionEpoch || !this.run || this.run.status !== "running" || this.run.runId !== active.runId || this.run.currentStepId !== active.stepId || this.run.currentStepDigest !== active.stepDigest) {
6691
7018
  return;
6692
7019
  }
7020
+ if (response.requestId !== active.requestId) {
7021
+ throw new Error("Workflow worker returned an uncorrelated terminal response");
7022
+ }
6693
7023
  const terminalAt = this.dependencies.now();
7024
+ if (response.usage && response.usage.length > 0) {
7025
+ this.run = recordCurrentStepUsage(this.run, active.requestId, usageAggregateFromModels(response.usage), terminalAt);
7026
+ this.persist();
7027
+ this.updateStatus();
7028
+ }
6694
7029
  const workflow = this.catalog.workflows.get(this.run.workflowId);
6695
7030
  const step = workflow?.definition.steps[this.run.currentStepId];
6696
7031
  if (!workflow || !step) {
@@ -7532,18 +7867,18 @@ var STRUCTURED_RESULT_KEYS = new Set([
7532
7867
  "artifact",
7533
7868
  "workspace"
7534
7869
  ]);
7535
- var isRecord10 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7870
+ var isRecord12 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7536
7871
  var parseChildStructuredResult = ({
7537
7872
  input,
7538
7873
  policy
7539
7874
  }) => {
7540
- if (!isRecord10(input)) {
7875
+ if (!isRecord12(input)) {
7541
7876
  throw new Error("structured_output input must be an object");
7542
7877
  }
7543
7878
  if (Object.keys(input).length !== 1 || !Object.hasOwn(input, "value")) {
7544
7879
  throw new Error("structured_output input must contain only value");
7545
7880
  }
7546
- if (!isRecord10(input.value)) {
7881
+ if (!isRecord12(input.value)) {
7547
7882
  throw new Error("structured_output value must be an object");
7548
7883
  }
7549
7884
  const unknownKey = Object.keys(input.value).find((key) => !STRUCTURED_RESULT_KEYS.has(key));
@@ -7907,7 +8242,7 @@ var DEFAULT_DEPENDENCIES4 = {
7907
8242
  loadSettings: loadSettings2,
7908
8243
  userWorkflowDirectory: defaultUserWorkflowDirectory2,
7909
8244
  runtimeEnvironment: () => ({
7910
- isSubagentChild: process.env.PI_WORKFLOWS_CHILD === "1",
8245
+ isSubagentChild: process.env.PI_WORKFLOWS_CHILD === "1" && process.env.PI_WORKFLOWS_CHILD_RUNTIME === "1",
7911
8246
  childAgent: process.env.PI_WORKFLOWS_CHILD_AGENT?.trim()
7912
8247
  }),
7913
8248
  registerChildRuntime: (pi, childAgent) => {