@wichayutdew/pi-workflows 3.1.0 → 3.3.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
  }
@@ -2870,8 +3144,11 @@ function formatWorkflowProgressStatus(snapshot, statusShortcutLabel) {
2870
3144
  const { run, workflow } = snapshot;
2871
3145
  const currentStep = formatStepName(stepTitle(workflow, run.currentStepId), run.currentStepId);
2872
3146
  const activity = run.status === "awaiting-gate" ? "awaiting review" : "working";
3147
+ const workerModel = snapshot.execution?.kind === "subagent" && snapshot.execution.model ? ` · model ${snapshot.execution.model}` : "";
2873
3148
  const workerProgress = snapshot.execution?.kind === "subagent" ? ` · ${snapshot.execution.progress}` : "";
2874
- return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity}${workerProgress} · ${statusShortcutLabel}`;
3149
+ const usage = workflowUsage(run);
3150
+ const usageText = usage.models.length > 0 ? ` · ${formatUsage(usage)}` : "";
3151
+ return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity}${workerModel}${workerProgress}${usageText} · ${statusShortcutLabel}`;
2875
3152
  }
2876
3153
  // src/workflow-status/view.ts
2877
3154
  import {
@@ -2998,6 +3275,10 @@ function renderAttempt(theme, snapshot, attempt, attemptNumber, cache, width) {
2998
3275
  return [
2999
3276
  theme.bold(theme.fg("accent", `Attempt ${attemptNumber} · ${actor}`)),
3000
3277
  ...keyValueLines(theme, "request", attempt.requestId, width, "muted"),
3278
+ ...attempt.usage ? [
3279
+ ...keyValueLines(theme, "usage", formatUsage(attempt.usage), width),
3280
+ ...attempt.usage.models.flatMap((entry) => keyValueLines(theme, "model", `${entry.provider}/${entry.model} · ${formatUsage({ usage: entry.usage, models: [] })}`, width, "muted"))
3281
+ ] : [],
3001
3282
  "",
3002
3283
  theme.bold("Requirement fed to the agent"),
3003
3284
  ...wrapPlain(`${attempt.task}${truncation}`, width, theme),
@@ -3035,6 +3316,7 @@ function renderLiveWorkerSession(theme, snapshot, detail, width) {
3035
3316
  "",
3036
3317
  theme.bold(theme.fg("accent", "Live Worker Session")),
3037
3318
  ...keyValueLines(theme, "worker", snapshot.execution.agent, width, "accent"),
3319
+ ...snapshot.execution.model ? keyValueLines(theme, "model", snapshot.execution.model, width, "muted") : [],
3038
3320
  ...keyValueLines(theme, "state", snapshot.execution.progress, width, "accent"),
3039
3321
  "",
3040
3322
  theme.bold(theme.fg("accent", "● Input prompt")),
@@ -3076,6 +3358,10 @@ function renderStepDetail(theme, snapshot, selectedIndex, cache, width) {
3076
3358
  ...keyValueLines(theme, "step", entry.stepId, width),
3077
3359
  ...keyValueLines(theme, "visit", String(entry.visit), width),
3078
3360
  ...keyValueLines(theme, "status", entry.status, width),
3361
+ ...entry.usage ? [
3362
+ ...keyValueLines(theme, "usage", formatUsage(entry.usage), width),
3363
+ ...entry.usage.models.flatMap((model) => keyValueLines(theme, "model", `${model.provider}/${model.model} · ${formatUsage({ usage: model.usage, models: [] })}`, width, "muted"))
3364
+ ] : [],
3079
3365
  ...history ? [
3080
3366
  ...keyValueLines(theme, "outcome", history.outcome, width, "success"),
3081
3367
  ...keyValueLines(theme, "summary", history.summary, width)
@@ -3143,7 +3429,7 @@ function logChars(lines) {
3143
3429
  return lines?.reduce((total, line) => total + line.length, 0) ?? 0;
3144
3430
  }
3145
3431
  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);
3432
+ 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
3433
  }
3148
3434
  function compactMainLog(attempt) {
3149
3435
  if (attempt.kind !== "main" || !attempt.log)
@@ -3194,10 +3480,15 @@ function compactAttempt(attempt) {
3194
3480
  };
3195
3481
  }
3196
3482
  function workflowTraceChars(run) {
3197
- return [
3483
+ const attempts = [
3198
3484
  ...run.history.flatMap((entry) => entry.attempts ?? []),
3199
3485
  ...run.currentStepAttempts ?? []
3200
3486
  ].reduce((total, attempt) => total + attemptSize(attempt), 0);
3487
+ const aggregates = [
3488
+ ...run.history.map((entry) => entry.usage),
3489
+ run.currentStepUsage
3490
+ ].reduce((total, usage) => total + (usage ? JSON.stringify(usage).length : 0), 0);
3491
+ return attempts + aggregates;
3201
3492
  }
3202
3493
  function compactRunTraceBudget(run) {
3203
3494
  let remaining = workflowTraceChars(run);
@@ -3381,6 +3672,29 @@ function attemptResult(result, workspaceCwd) {
3381
3672
  ...workspaceCwd ? { workspaceCwd } : {}
3382
3673
  };
3383
3674
  }
3675
+ function recordCurrentStepUsage(run, requestId, usage, now) {
3676
+ const attempts = run.currentStepAttempts;
3677
+ const index = attempts?.findIndex((attempt2) => attempt2.requestId === requestId);
3678
+ if (index === undefined || index < 0 || !attempts)
3679
+ return run;
3680
+ const attempt = attempts[index];
3681
+ if (!attempt)
3682
+ return run;
3683
+ const currentStepAttempts = [...attempts];
3684
+ currentStepAttempts[index] = {
3685
+ ...attempt,
3686
+ usage: mergeUsage(attempt.usage ?? emptyUsageAggregate(), usage.models)
3687
+ };
3688
+ return compactRunTraceBudget({
3689
+ ...run,
3690
+ currentStepAttempts,
3691
+ currentStepUsage: mergeUsage(run.currentStepUsage ?? emptyUsageAggregate(), usage.models),
3692
+ updatedAt: now
3693
+ });
3694
+ }
3695
+ function usageAggregateFromModels(entries) {
3696
+ return mergeUsage(emptyUsageAggregate(), entries);
3697
+ }
3384
3698
  function recordCurrentStepResult(run, result, now, workspaceCwd) {
3385
3699
  const attempts = run.currentStepAttempts;
3386
3700
  if (!attempts || attempts.length === 0)
@@ -3426,11 +3740,11 @@ function recordCurrentGateDecision(run, decision, now) {
3426
3740
  }
3427
3741
 
3428
3742
  // src/engine/run-validation.ts
3429
- var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3743
+ var isRecord7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
3430
3744
  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");
3745
+ 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;
3746
+ 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));
3747
+ 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
3748
  var isSafeTraceIdentityField = (value) => typeof value === "string" && value.length > 0 && !value.includes("\x00") && !value.includes("/") && !value.includes("\\") && value !== "." && value !== "..";
3435
3749
  var hasValidMainStepLog = (value) => {
3436
3750
  const log = value.log;
@@ -3446,7 +3760,7 @@ var hasValidMainStepLog = (value) => {
3446
3760
  return value.logTruncated === true === (typeof value.omittedLogEvents === "number") && !(log === undefined && (value.logTruncated !== undefined || value.omittedLogEvents !== undefined));
3447
3761
  };
3448
3762
  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)) {
3763
+ 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
3764
  return false;
3451
3765
  }
3452
3766
  if (value.kind === "main") {
@@ -3460,7 +3774,7 @@ var isStepExecutionAttempt = (value) => {
3460
3774
  }
3461
3775
  if (value.transcript === undefined)
3462
3776
  return true;
3463
- if (!isRecord5(value.transcript))
3777
+ if (!isRecord7(value.transcript))
3464
3778
  return false;
3465
3779
  const transcript = value.transcript;
3466
3780
  if (!isAbsoluteCwd(transcript.trustedRoot) || !isAbsoluteCwd(transcript.sessionFile) || !isSafeTraceIdentityField(transcript.runId) || !Number.isSafeInteger(transcript.childIndex) || transcript.childIndex < 0) {
@@ -3474,10 +3788,16 @@ var isStepExecutionAttempts = (value) => Array.isArray(value) && value.length <=
3474
3788
  const ordinal = attempt.ordinal;
3475
3789
  return value.slice(0, index).every((earlier) => earlier.ordinal === undefined || earlier.ordinal < ordinal);
3476
3790
  });
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);
3791
+ var usageMatchesAttempts = (attempts, aggregate, omittedAttempts) => {
3792
+ if (!aggregate || omittedAttempts !== undefined)
3793
+ return true;
3794
+ const fromAttempts = (attempts ?? []).reduce((total, attempt) => attempt.usage ? mergeUsage(total, attempt.usage.models) : total, emptyUsageAggregate());
3795
+ return JSON.stringify(fromAttempts) === JSON.stringify(aggregate);
3796
+ };
3797
+ 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";
3798
+ var isGateResolution = (value) => isRecord7(value) && typeof value.approved === "boolean" && typeof value.feedback === "string" && value.feedback.length <= MAX_GATE_FEEDBACK_CHARS && typeof value.resolvedAt === "number";
3799
+ 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));
3800
+ var isVisitCounts = (value) => isRecord7(value) && Object.values(value).every((count) => typeof count === "number" && Number.isInteger(count) && count >= 0);
3481
3801
  var isOptionalString = (value) => value === undefined || typeof value === "string";
3482
3802
  var isOptionalResumeInput = (value) => value === undefined || typeof value === "string" && value.length <= MAX_RESUME_INPUT_CHARS;
3483
3803
  var isOptionalIteration = (value) => value === undefined || Number.isSafeInteger(value) && value >= 1;
@@ -3502,14 +3822,16 @@ var hasValidWorkspaceState = (run, history) => {
3502
3822
  return startCwd === undefined || cwd === startCwd;
3503
3823
  };
3504
3824
  var isWorkflowRun = (value) => {
3505
- if (!isRecord5(value))
3825
+ if (!isRecord7(value))
3506
3826
  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;
3827
+ 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
3828
  if (!hasValidRequiredFields)
3509
3829
  return false;
3510
3830
  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
3831
  if (!hasValidOptionalFields)
3512
3832
  return false;
3833
+ if (!usageMatchesAttempts(value.currentStepAttempts, value.currentStepUsage, value.currentStepOmittedAttempts))
3834
+ return false;
3513
3835
  const pendingGate = value.pendingGate;
3514
3836
  if (pendingGate !== undefined && !isPendingGate(pendingGate))
3515
3837
  return false;
@@ -3520,7 +3842,7 @@ var isWorkflowRun = (value) => {
3520
3842
  };
3521
3843
  // src/workflow-status/transcript-reader.ts
3522
3844
  var MAX_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
3523
- function isRecord6(value) {
3845
+ function isRecord8(value) {
3524
3846
  return value !== null && typeof value === "object" && !Array.isArray(value);
3525
3847
  }
3526
3848
  function isWithin(root, candidate) {
@@ -3531,7 +3853,7 @@ function hasSafeIdentity(reference) {
3531
3853
  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
3854
  }
3533
3855
  function transcriptEntryLines(entry) {
3534
- if (!isRecord6(entry))
3856
+ if (!isRecord8(entry))
3535
3857
  return [];
3536
3858
  if (entry.type === "custom_message") {
3537
3859
  const customType = typeof entry.customType === "string" ? entry.customType : "custom";
@@ -3539,7 +3861,7 @@ function transcriptEntryLines(entry) {
3539
3861
  return content ? [`event ${sanitizeStepLogText(customType)}
3540
3862
  ${content}`] : [];
3541
3863
  }
3542
- if (entry.type !== "message" || !isRecord6(entry.message))
3864
+ if (entry.type !== "message" || !isRecord8(entry.message))
3543
3865
  return [];
3544
3866
  return stepLogLinesFromMessage(entry.message);
3545
3867
  }
@@ -4102,7 +4424,8 @@ function workflowStatusSnapshot() {
4102
4424
  agent: this.activeDelegation.agent,
4103
4425
  requestId: this.activeDelegation.requestId,
4104
4426
  progress: this.activeDelegation.progress ?? "starting",
4105
- activityLog: this.activeDelegation.activityLog ?? []
4427
+ activityLog: this.activeDelegation.activityLog ?? [],
4428
+ ...this.activeDelegation.model ? { model: this.activeDelegation.model } : {}
4106
4429
  };
4107
4430
  } else if (this.mainSteps.activeStepId) {
4108
4431
  execution = { kind: "main" };
@@ -4215,6 +4538,7 @@ var completedStep = (run, outcome, summary, now, effects) => ({
4215
4538
  ...effects.workspaceCwd ? { workspaceCwd: effects.workspaceCwd } : {},
4216
4539
  ...run.currentStepAttempts?.length ? { attempts: run.currentStepAttempts } : {},
4217
4540
  ...run.currentStepOmittedAttempts ? { omittedAttempts: run.currentStepOmittedAttempts } : {},
4541
+ ...run.currentStepUsage ? { usage: run.currentStepUsage } : {},
4218
4542
  completedAt: now
4219
4543
  });
4220
4544
  var advanceRun = (workflow, run, outcome, summary, now, effects = {}, options = {}) => {
@@ -4259,6 +4583,7 @@ var advanceRun = (workflow, run, outcome, summary, now, effects = {}, options =
4259
4583
  history: [...run.history, completed],
4260
4584
  currentStepAttempts: undefined,
4261
4585
  currentStepOmittedAttempts: undefined,
4586
+ currentStepUsage: undefined,
4262
4587
  ...cwd ? { cwd } : {},
4263
4588
  ...effects.workspaceCwd ? { restartWorkspaceCwd: undefined } : {},
4264
4589
  stepHandoff: summary,
@@ -4295,6 +4620,7 @@ var advanceRun = (workflow, run, outcome, summary, now, effects = {}, options =
4295
4620
  history: [...run.history, completed],
4296
4621
  currentStepAttempts: undefined,
4297
4622
  currentStepOmittedAttempts: undefined,
4623
+ currentStepUsage: undefined,
4298
4624
  ...cwd ? { cwd } : {},
4299
4625
  ...effects.workspaceCwd ? { restartWorkspaceCwd: undefined } : {},
4300
4626
  stepHandoff: preservesGateRevisionContext ? run.stepHandoff : summary,
@@ -4745,6 +5071,7 @@ var reconcileRun = (run, workflow, now) => {
4745
5071
  history: retainedHistory,
4746
5072
  currentStepAttempts: changedEntry.attempts,
4747
5073
  currentStepOmittedAttempts: changedEntry.omittedAttempts,
5074
+ currentStepUsage: changedEntry.usage,
4748
5075
  visits: rebuildVisits(retainedHistory, restartedStep),
4749
5076
  cwd: retainedWorkspaceCwd(reconciledRun, retainedHistory),
4750
5077
  reviewedArtifact: reviewedApproval?.artifact ?? "",
@@ -5522,7 +5849,7 @@ var THINKING_LEVELS = [
5522
5849
  "xhigh",
5523
5850
  "max"
5524
5851
  ];
5525
- var isRecord7 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5852
+ var isRecord9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5526
5853
  function parseAgentProfile(source, name) {
5527
5854
  if (!source.startsWith(`---
5528
5855
  `))
@@ -5534,7 +5861,7 @@ function parseAgentProfile(source, name) {
5534
5861
  throw new Error(`workflow agent profile is invalid: ${name}`);
5535
5862
  }
5536
5863
  const metadata = parse(source.slice(4, end));
5537
- if (!isRecord7(metadata)) {
5864
+ if (!isRecord9(metadata)) {
5538
5865
  throw new Error(`workflow agent profile metadata must be an object: ${name}`);
5539
5866
  }
5540
5867
  const unknownKey = Object.keys(metadata).find((key) => key !== "model" && key !== "thinking");
@@ -5973,16 +6300,16 @@ import { dirname as dirname3, isAbsolute as isAbsolute8, resolve as resolve7 } f
5973
6300
 
5974
6301
  // src/integrations/subagents/child-policy-sections.ts
5975
6302
  import { isAbsolute as isAbsolute7, win32 as win323 } from "node:path";
5976
- var isRecord8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6303
+ var isRecord10 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5977
6304
  var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
5978
6305
  var hasOnlyKeys = (value, allowed) => Object.keys(value).every((key) => allowed.has(key));
5979
6306
  var isStepPermissions = (value) => {
5980
- if (!isRecord8(value) || !isRecord8(value.bash))
6307
+ if (!isRecord10(value) || !isRecord10(value.bash))
5981
6308
  return false;
5982
6309
  const bash = value.bash;
5983
6310
  const bashRules = Array.isArray(bash.allow) ? bash.allow : undefined;
5984
6311
  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));
6312
+ const hasValidRules = bashRules !== undefined && bashRules.every((rule) => isRecord10(rule) && hasOnlyKeys(rule, new Set(["executable", "argsPrefix"])) && typeof rule.executable === "string" && isStringArray(rule.argsPrefix));
5986
6313
  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
6314
  };
5988
6315
  var parsePermissions2 = (value) => {
@@ -6018,7 +6345,7 @@ var parseOutcomes = (value) => {
6018
6345
  var parseWorkspace2 = (value, outcomes) => {
6019
6346
  if (value.workspace === undefined)
6020
6347
  return {};
6021
- if (!isRecord8(value.workspace) || !hasOnlyKeys(value.workspace, new Set(["bindOn", "allowedRoots"]))) {
6348
+ if (!isRecord10(value.workspace) || !hasOnlyKeys(value.workspace, new Set(["bindOn", "allowedRoots"]))) {
6022
6349
  throw new Error("child policy workspace is invalid");
6023
6350
  }
6024
6351
  const bindOn = value.workspace.bindOn;
@@ -6063,7 +6390,7 @@ var POLICY_KEYS = new Set([
6063
6390
  "gateSubmitOutcome",
6064
6391
  "workspace"
6065
6392
  ]);
6066
- var isRecord9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6393
+ var isRecord11 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
6067
6394
  var requiredString = (value, field) => {
6068
6395
  const candidate = value[field];
6069
6396
  if (typeof candidate !== "string" || !candidate) {
@@ -6129,7 +6456,7 @@ var parseIdentityAndPaths = (value, environment) => {
6129
6456
  };
6130
6457
  };
6131
6458
  var parseChildPolicy = (value, environment = DEFAULT_CHILD_POLICY_ENVIRONMENT) => {
6132
- if (!isRecord9(value))
6459
+ if (!isRecord11(value))
6133
6460
  throw new Error("child policy must be an object");
6134
6461
  rejectUnknownProperties(value);
6135
6462
  return {
@@ -6369,7 +6696,8 @@ function createDelegationPlan(input, dependencies) {
6369
6696
  resultDirectory: workspace.resultDirectory,
6370
6697
  policy,
6371
6698
  transcriptTask: task,
6372
- agent
6699
+ agent,
6700
+ ...agentProfile.model ? { model: agentProfile.model } : {}
6373
6701
  };
6374
6702
  const request = {
6375
6703
  version: 1,
@@ -6525,7 +6853,7 @@ function launchMainStep(workflow, run, step) {
6525
6853
  summaryMaxChars: workflow.definition.summaryMaxChars,
6526
6854
  ...step.gate ? { gateSubmitOutcome: step.gate.submitOutcome } : {},
6527
6855
  ...step.workspace ? { workspace: structuredClone(step.workspace) } : {},
6528
- onTrace: (lines, context) => this.queueMainStepLog(identity, lines, context),
6856
+ onTrace: (lines, context, usage) => this.queueMainStepLog(identity, lines, context, usage),
6529
6857
  onSettled: (result, context) => this.queueMainStepResult(identity, result, context)
6530
6858
  };
6531
6859
  try {
@@ -6551,10 +6879,10 @@ function matchesLatestMainStepAttempt(run, identity) {
6551
6879
  const attempt = run.currentStepAttempts?.at(-1);
6552
6880
  return attempt === undefined || attempt.kind === "main" && attempt.requestId === identity.requestId;
6553
6881
  }
6554
- function queueMainStepLog(identity, lines, context) {
6555
- return this.mutationQueue.run(() => this.recordMainStepLog(identity, lines, context)).catch(() => {});
6882
+ function queueMainStepLog(identity, lines, context, usage) {
6883
+ return this.mutationQueue.run(() => this.recordMainStepLog(identity, lines, context, usage)).catch(() => {});
6556
6884
  }
6557
- async function recordMainStepLog(identity, lines, context) {
6885
+ async function recordMainStepLog(identity, lines, context, usage) {
6558
6886
  if (!hasCurrentMainStepIdentity(this, identity) || !matchesLatestMainStepAttempt(this.run, identity)) {
6559
6887
  return;
6560
6888
  }
@@ -6563,7 +6891,10 @@ async function recordMainStepLog(identity, lines, context) {
6563
6891
  return;
6564
6892
  }
6565
6893
  this.latestContext = context;
6566
- const traced = appendMainStepLog(this.run, identity.requestId, lines, this.dependencies.now());
6894
+ let traced = appendMainStepLog(this.run, identity.requestId, lines, this.dependencies.now());
6895
+ if (usage && usage.length > 0) {
6896
+ traced = recordCurrentStepUsage(traced, identity.requestId, usageAggregateFromModels(usage), this.dependencies.now());
6897
+ }
6567
6898
  if (traced === this.run)
6568
6899
  return;
6569
6900
  this.run = traced;
@@ -6690,7 +7021,15 @@ async function finishDelegation(active, response) {
6690
7021
  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
7022
  return;
6692
7023
  }
7024
+ if (response.requestId !== active.requestId) {
7025
+ throw new Error("Workflow worker returned an uncorrelated terminal response");
7026
+ }
6693
7027
  const terminalAt = this.dependencies.now();
7028
+ if (response.usage && response.usage.length > 0) {
7029
+ this.run = recordCurrentStepUsage(this.run, active.requestId, usageAggregateFromModels(response.usage), terminalAt);
7030
+ this.persist();
7031
+ this.updateStatus();
7032
+ }
6694
7033
  const workflow = this.catalog.workflows.get(this.run.workflowId);
6695
7034
  const step = workflow?.definition.steps[this.run.currentStepId];
6696
7035
  if (!workflow || !step) {
@@ -7532,18 +7871,18 @@ var STRUCTURED_RESULT_KEYS = new Set([
7532
7871
  "artifact",
7533
7872
  "workspace"
7534
7873
  ]);
7535
- var isRecord10 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7874
+ var isRecord12 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7536
7875
  var parseChildStructuredResult = ({
7537
7876
  input,
7538
7877
  policy
7539
7878
  }) => {
7540
- if (!isRecord10(input)) {
7879
+ if (!isRecord12(input)) {
7541
7880
  throw new Error("structured_output input must be an object");
7542
7881
  }
7543
7882
  if (Object.keys(input).length !== 1 || !Object.hasOwn(input, "value")) {
7544
7883
  throw new Error("structured_output input must contain only value");
7545
7884
  }
7546
- if (!isRecord10(input.value)) {
7885
+ if (!isRecord12(input.value)) {
7547
7886
  throw new Error("structured_output value must be an object");
7548
7887
  }
7549
7888
  const unknownKey = Object.keys(input.value).find((key) => !STRUCTURED_RESULT_KEYS.has(key));
@@ -7907,7 +8246,7 @@ var DEFAULT_DEPENDENCIES4 = {
7907
8246
  loadSettings: loadSettings2,
7908
8247
  userWorkflowDirectory: defaultUserWorkflowDirectory2,
7909
8248
  runtimeEnvironment: () => ({
7910
- isSubagentChild: process.env.PI_WORKFLOWS_CHILD === "1",
8249
+ isSubagentChild: process.env.PI_WORKFLOWS_CHILD === "1" && process.env.PI_WORKFLOWS_CHILD_RUNTIME === "1",
7911
8250
  childAgent: process.env.PI_WORKFLOWS_CHILD_AGENT?.trim()
7912
8251
  }),
7913
8252
  registerChildRuntime: (pi, childAgent) => {