@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 +392 -53
- 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-plan.ts +1 -0
- package/src/harness/delegation-response-actions.ts +21 -1
- package/src/harness/status-actions.ts +3 -0
- package/src/harness/step-execution-actions.ts +19 -4
- package/src/harness/types.ts +1 -0
- 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 +11 -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 +32 -0
- package/src/workflow-status/render-summary.ts +16 -0
- package/src/workflow-status/types.ts +3 -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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
3432
|
-
var isStepAttemptResult = (value) =>
|
|
3433
|
-
var isStepGateDecision = (value) =>
|
|
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 (!
|
|
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 (!
|
|
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
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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" || !
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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) =>
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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) => {
|