@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 +386 -51
- package/package.json +1 -1
- package/src/engine/run-advance.ts +3 -0
- package/src/engine/run-reconciliation.ts +1 -0
- package/src/engine/run-validation.ts +38 -2
- package/src/engine/state-types.ts +10 -0
- package/src/engine/state.ts +10 -0
- package/src/engine/step-trace.ts +55 -2
- package/src/engine/usage.ts +312 -0
- package/src/harness/action-context.ts +3 -0
- package/src/harness/delegation-response-actions.ts +21 -1
- package/src/harness/step-execution-actions.ts +19 -4
- package/src/index.ts +3 -1
- package/src/integrations/subagents/client.ts +80 -1
- package/src/integrations/subagents/protocol-events.ts +9 -1
- package/src/runtime/main-step-runtime-types.ts +2 -0
- package/src/runtime/main-step-trace.ts +36 -1
- package/src/workflow-status/format-status.ts +7 -1
- package/src/workflow-status/format-usage.ts +33 -0
- package/src/workflow-status/render-path.ts +9 -2
- package/src/workflow-status/render-step-detail.ts +29 -0
- package/src/workflow-status/render-summary.ts +16 -0
- package/src/workflow-status/types.ts +2 -0
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
|
|
1951
|
+
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1726
1952
|
var toolCalls = (message) => {
|
|
1727
|
-
if (!
|
|
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) =>
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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) => !
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
2296
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
3432
|
-
var isStepAttemptResult = (value) =>
|
|
3433
|
-
var isStepGateDecision = (value) =>
|
|
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 (!
|
|
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 (!
|
|
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
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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" || !
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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) =>
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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) => {
|