@runcofactor/cli 0.10.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/README.md +125 -0
- package/dist/api-catalog-GFGCLVS4.js +401 -0
- package/dist/api-catalog-GFGCLVS4.js.map +1 -0
- package/dist/attachment-SJC7D7FY.js +276 -0
- package/dist/attachment-SJC7D7FY.js.map +1 -0
- package/dist/auth-EN6NQCJY.js +31 -0
- package/dist/auth-EN6NQCJY.js.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-3CZU7XCS.js +272 -0
- package/dist/chunk-3CZU7XCS.js.map +1 -0
- package/dist/chunk-4NRDONSC.js +1048 -0
- package/dist/chunk-4NRDONSC.js.map +1 -0
- package/dist/chunk-4PNOV7OV.js +51980 -0
- package/dist/chunk-4PNOV7OV.js.map +1 -0
- package/dist/chunk-52IO7ACJ.js +2941 -0
- package/dist/chunk-52IO7ACJ.js.map +1 -0
- package/dist/chunk-6LEZCKKV.js +663 -0
- package/dist/chunk-6LEZCKKV.js.map +1 -0
- package/dist/chunk-A4OFTUVV.js +170 -0
- package/dist/chunk-A4OFTUVV.js.map +1 -0
- package/dist/chunk-B2VISN2V.js +2459 -0
- package/dist/chunk-B2VISN2V.js.map +1 -0
- package/dist/chunk-CLAWT7BJ.js +271 -0
- package/dist/chunk-CLAWT7BJ.js.map +1 -0
- package/dist/chunk-GKY2KXO5.js +281 -0
- package/dist/chunk-GKY2KXO5.js.map +1 -0
- package/dist/chunk-I4QLI3JO.js +413 -0
- package/dist/chunk-I4QLI3JO.js.map +1 -0
- package/dist/chunk-IMPZAX6L.js +180 -0
- package/dist/chunk-IMPZAX6L.js.map +1 -0
- package/dist/chunk-J6SH7GQT.js +241 -0
- package/dist/chunk-J6SH7GQT.js.map +1 -0
- package/dist/chunk-JDBCZ3Z2.js +298 -0
- package/dist/chunk-JDBCZ3Z2.js.map +1 -0
- package/dist/chunk-JICSYFRJ.js +46 -0
- package/dist/chunk-JICSYFRJ.js.map +1 -0
- package/dist/chunk-JWHYXEA2.js +32 -0
- package/dist/chunk-JWHYXEA2.js.map +1 -0
- package/dist/chunk-N7HFALX3.js +88 -0
- package/dist/chunk-N7HFALX3.js.map +1 -0
- package/dist/chunk-Q4BO4Z5V.js +178 -0
- package/dist/chunk-Q4BO4Z5V.js.map +1 -0
- package/dist/chunk-QFDOWABX.js +195 -0
- package/dist/chunk-QFDOWABX.js.map +1 -0
- package/dist/chunk-SJJW77WN.js +605 -0
- package/dist/chunk-SJJW77WN.js.map +1 -0
- package/dist/chunk-VKVL7WBN.js +50 -0
- package/dist/chunk-VKVL7WBN.js.map +1 -0
- package/dist/chunk-WOHL4FTB.js +353 -0
- package/dist/chunk-WOHL4FTB.js.map +1 -0
- package/dist/chunk-XQXVG7RA.js +426 -0
- package/dist/chunk-XQXVG7RA.js.map +1 -0
- package/dist/chunk-ZD3KHESI.js +94 -0
- package/dist/chunk-ZD3KHESI.js.map +1 -0
- package/dist/credential-COLTBV6D.js +450 -0
- package/dist/credential-COLTBV6D.js.map +1 -0
- package/dist/credential-template-EEPYFG7R.js +291 -0
- package/dist/credential-template-EEPYFG7R.js.map +1 -0
- package/dist/debug-workflow-KOVH5GOP.js +259 -0
- package/dist/debug-workflow-KOVH5GOP.js.map +1 -0
- package/dist/dist-44PZR7BQ.js +423 -0
- package/dist/dist-44PZR7BQ.js.map +1 -0
- package/dist/docs-3SSORHB5.js +366 -0
- package/dist/docs-3SSORHB5.js.map +1 -0
- package/dist/doctor-OTSIWV6G.js +132 -0
- package/dist/doctor-OTSIWV6G.js.map +1 -0
- package/dist/feature-flag-T2LRZME3.js +283 -0
- package/dist/feature-flag-T2LRZME3.js.map +1 -0
- package/dist/file-type-S3UUBVCT.js +4205 -0
- package/dist/file-type-S3UUBVCT.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +982 -0
- package/dist/index.js.map +1 -0
- package/dist/init-TGWRECT2.js +208 -0
- package/dist/init-TGWRECT2.js.map +1 -0
- package/dist/install-DXSNWZKO.js +295 -0
- package/dist/install-DXSNWZKO.js.map +1 -0
- package/dist/issues-XIZOPX6T.js +564 -0
- package/dist/issues-XIZOPX6T.js.map +1 -0
- package/dist/knobs-NBINZIUB.js +295 -0
- package/dist/knobs-NBINZIUB.js.map +1 -0
- package/dist/list-3TDHK637.js +59 -0
- package/dist/list-3TDHK637.js.map +1 -0
- package/dist/local-HICVD6PU.js +100 -0
- package/dist/local-HICVD6PU.js.map +1 -0
- package/dist/login-UOIUXVUK.js +315 -0
- package/dist/login-UOIUXVUK.js.map +1 -0
- package/dist/mcp-54IJEOUA.js +1956 -0
- package/dist/mcp-54IJEOUA.js.map +1 -0
- package/dist/pdf-extract-ABWV3L5X.js +13 -0
- package/dist/pdf-extract-ABWV3L5X.js.map +1 -0
- package/dist/pdfjs-DQ2YA5S3.js +41561 -0
- package/dist/pdfjs-DQ2YA5S3.js.map +1 -0
- package/dist/property-UXLOLPTD.js +701 -0
- package/dist/property-UXLOLPTD.js.map +1 -0
- package/dist/property-skill-guide.md +189 -0
- package/dist/psql-BYUUNLGG.js +115 -0
- package/dist/psql-BYUUNLGG.js.map +1 -0
- package/dist/record-4TR2UIIS.js +233 -0
- package/dist/record-4TR2UIIS.js.map +1 -0
- package/dist/redis-35VBUYW7.js +116 -0
- package/dist/redis-35VBUYW7.js.map +1 -0
- package/dist/release-OOLTUBUT.js +354 -0
- package/dist/release-OOLTUBUT.js.map +1 -0
- package/dist/sandbox-XGSUU6G7.js +2046 -0
- package/dist/sandbox-XGSUU6G7.js.map +1 -0
- package/dist/scenarios-GWLJEGHA.js +280 -0
- package/dist/scenarios-GWLJEGHA.js.map +1 -0
- package/dist/session-MYFNZPRP.js +2414 -0
- package/dist/session-MYFNZPRP.js.map +1 -0
- package/dist/skill-KM34LDMO.js +1839 -0
- package/dist/skill-KM34LDMO.js.map +1 -0
- package/dist/skills/debug-run/SKILL.md +262 -0
- package/dist/skills/debug-run/history/1.0.0.md +244 -0
- package/dist/skills/debug-run/history/1.0.1.md +245 -0
- package/dist/skills/debug-run/history/1.0.2.md +254 -0
- package/dist/skills/debug-run/history/1.1.0.md +262 -0
- package/dist/skills/debug-run/version.json +3 -0
- package/dist/spec-RA3CFEKG.js +951 -0
- package/dist/spec-RA3CFEKG.js.map +1 -0
- package/dist/src-LB4HBTJQ.js +1516 -0
- package/dist/src-LB4HBTJQ.js.map +1 -0
- package/dist/src-N3TPKGTY.js +244 -0
- package/dist/src-N3TPKGTY.js.map +1 -0
- package/dist/src-UVDX5UP6.js +17 -0
- package/dist/src-UVDX5UP6.js.map +1 -0
- package/dist/start-PSEQSQA3.js +151 -0
- package/dist/start-PSEQSQA3.js.map +1 -0
- package/dist/tag-YY6YAABQ.js +22 -0
- package/dist/tag-YY6YAABQ.js.map +1 -0
- package/dist/task-IQC3YR5K.js +922 -0
- package/dist/task-IQC3YR5K.js.map +1 -0
- package/dist/test-suite-DZNJFXDW.js +394 -0
- package/dist/test-suite-DZNJFXDW.js.map +1 -0
- package/dist/trace-HXIW7U2D.js +14 -0
- package/dist/trace-HXIW7U2D.js.map +1 -0
- package/dist/triage-ORG45R5W.js +16 -0
- package/dist/triage-ORG45R5W.js.map +1 -0
- package/dist/upgrade-L2VYMP2V.js +61 -0
- package/dist/upgrade-L2VYMP2V.js.map +1 -0
- package/dist/workflow-XYFH5LTP.js +3708 -0
- package/dist/workflow-XYFH5LTP.js.map +1 -0
- package/dist/workflow-cache-NQJQFWMV.js +253 -0
- package/dist/workflow-cache-NQJQFWMV.js.map +1 -0
- package/dist/workflow-debug-BGD4UIXU.js +927 -0
- package/dist/workflow-debug-BGD4UIXU.js.map +1 -0
- package/dist/workflow-proposals-MTUDX46O.js +382 -0
- package/dist/workflow-proposals-MTUDX46O.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
runTrace
|
|
4
|
+
} from "./chunk-Q4BO4Z5V.js";
|
|
5
|
+
import {
|
|
6
|
+
apiRequest
|
|
7
|
+
} from "./chunk-WOHL4FTB.js";
|
|
8
|
+
import "./chunk-SJJW77WN.js";
|
|
9
|
+
import "./chunk-3CZU7XCS.js";
|
|
10
|
+
import {
|
|
11
|
+
getArgValue,
|
|
12
|
+
hasFlag
|
|
13
|
+
} from "./chunk-A4OFTUVV.js";
|
|
14
|
+
import "./chunk-4PNOV7OV.js";
|
|
15
|
+
import "./chunk-JWHYXEA2.js";
|
|
16
|
+
import "./chunk-6LEZCKKV.js";
|
|
17
|
+
import "./chunk-ZD3KHESI.js";
|
|
18
|
+
import "./chunk-VKVL7WBN.js";
|
|
19
|
+
|
|
20
|
+
// src/workflow-debug.ts
|
|
21
|
+
import process from "process";
|
|
22
|
+
import os from "os";
|
|
23
|
+
import path from "path";
|
|
24
|
+
import fs from "fs/promises";
|
|
25
|
+
function printDebugHelp() {
|
|
26
|
+
console.log(
|
|
27
|
+
[
|
|
28
|
+
"Usage: canary workflow debug <sub-command> [options]",
|
|
29
|
+
"",
|
|
30
|
+
"Triangulate three signals to debug a workflow run:",
|
|
31
|
+
" 1. Visual \u2014 what happened on screen? \u2192 debug playwright <runId>",
|
|
32
|
+
" 2. DOM \u2014 what did the agent see? \u2192 debug trace-snapshot <runId>",
|
|
33
|
+
" 3. Telemetry \u2014 what did we record? \u2192 debug steps | cache | thoughts | logs",
|
|
34
|
+
"",
|
|
35
|
+
"Start with: canary workflow debug <runId> # diagnosis + pointers",
|
|
36
|
+
"Then drill in with the subcommand matching your question:",
|
|
37
|
+
' "Where did we miss the cache?" \u2192 debug cache --misses-only',
|
|
38
|
+
' "What steps were slow?" \u2192 debug steps --slow',
|
|
39
|
+
' "Why did the agent click X?" \u2192 debug thoughts --step <stepId>',
|
|
40
|
+
' "What was on the page?" \u2192 debug trace-snapshot <runId>',
|
|
41
|
+
' "What errors did we log?" \u2192 debug logs --level error',
|
|
42
|
+
' "What variables did we have?" \u2192 debug variables <runId>',
|
|
43
|
+
' "What changed since it worked?" \u2192 debug compare <oldRun> <newRun>',
|
|
44
|
+
' "Give me everything for a run" \u2192 debug bundle <runId> --out <dir>',
|
|
45
|
+
"",
|
|
46
|
+
"Sub-commands:",
|
|
47
|
+
" overview <runId> Diagnosis summary (default when no sub given)",
|
|
48
|
+
" steps <runId> [--slow] [--json] Step timings + status",
|
|
49
|
+
" cache <runId> [--misses-only] [--json] Cache hit/miss breakdown",
|
|
50
|
+
" thoughts <runId> [--step <id>] [--grep <text>] [--json]",
|
|
51
|
+
" Agent reasoning for this run",
|
|
52
|
+
" navigation <runId> [--transitions-only] [--json]",
|
|
53
|
+
" Timestamped browser-URL history + cross-host transitions",
|
|
54
|
+
" logs <runId> [--step <id>] [--level info|warning|error] [--json]",
|
|
55
|
+
" Sentry logs for this run",
|
|
56
|
+
" trace <runId> [--step <id>] [--json] Sentry span tree",
|
|
57
|
+
" playwright <runId> [--open] [--json] Playwright trace viewer URL",
|
|
58
|
+
" trace-fetch <runId> [--out <path>] [--json]",
|
|
59
|
+
" Download trace.zip to disk",
|
|
60
|
+
" trace-snapshot <runId> [--action <index>] [--output-dir <path>] [--screenshots] [--html]",
|
|
61
|
+
" Download Playwright trace + reconstruct DOM snapshots",
|
|
62
|
+
" snapshot <runId> [--out <path>] [--json] Workflow definition that ran (published or draft)",
|
|
63
|
+
" cache-debug <runId> [--step <id>] [--json]",
|
|
64
|
+
" Per-step cache compilation + replay details",
|
|
65
|
+
" variables <runId> [--name <var>] [--json]",
|
|
66
|
+
" Run-input + captured variables with definition timestamps",
|
|
67
|
+
" compare <runIdA> <runIdB> [--out <dir>] [--json]",
|
|
68
|
+
" Diff two runs (steps, thoughts, cache, snapshot)",
|
|
69
|
+
" bundle <runId> --out <dir> Pull all debug artifacts into one directory",
|
|
70
|
+
" materialize <runId> [--out <dir>] [--json]",
|
|
71
|
+
" Materialize the canonical diagnostic workspace (same shape",
|
|
72
|
+
" the v2 failure-diagnostic agent uses). Start with `summary.md`.",
|
|
73
|
+
"",
|
|
74
|
+
"Options:",
|
|
75
|
+
" --workflow <id> Workflow ID (auto-resolved from runId if omitted)",
|
|
76
|
+
" --json Raw JSON output",
|
|
77
|
+
" --env <env> Target environment (prod, dev, local)"
|
|
78
|
+
].join("\n")
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
async function handleWorkflowDebug(argv, apiUrl, token) {
|
|
82
|
+
const [maybeSub, ...rest] = argv;
|
|
83
|
+
if (!maybeSub || maybeSub === "help" || hasFlag(argv, "--help", "-h")) {
|
|
84
|
+
printDebugHelp();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (looksLikeRunId(maybeSub)) {
|
|
88
|
+
await handleOverview([maybeSub, ...rest], apiUrl, token);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
switch (maybeSub) {
|
|
92
|
+
case "overview":
|
|
93
|
+
await handleOverview(rest, apiUrl, token);
|
|
94
|
+
return;
|
|
95
|
+
case "steps":
|
|
96
|
+
await handleSteps(rest, apiUrl, token);
|
|
97
|
+
return;
|
|
98
|
+
case "cache":
|
|
99
|
+
await handleCache(rest, apiUrl, token);
|
|
100
|
+
return;
|
|
101
|
+
case "thoughts":
|
|
102
|
+
await handleThoughts(rest, apiUrl, token);
|
|
103
|
+
return;
|
|
104
|
+
case "navigation":
|
|
105
|
+
await handleNavigation(rest, apiUrl, token);
|
|
106
|
+
return;
|
|
107
|
+
case "logs":
|
|
108
|
+
await handleLogs(rest, apiUrl, token);
|
|
109
|
+
return;
|
|
110
|
+
case "trace":
|
|
111
|
+
await handleSentryTrace(rest, apiUrl, token);
|
|
112
|
+
return;
|
|
113
|
+
case "playwright":
|
|
114
|
+
await handlePlaywright(rest, apiUrl, token);
|
|
115
|
+
return;
|
|
116
|
+
case "trace-snapshot":
|
|
117
|
+
await handleTraceSnapshot(rest, apiUrl, token);
|
|
118
|
+
return;
|
|
119
|
+
case "trace-fetch":
|
|
120
|
+
await handleTraceFetch(rest, apiUrl, token);
|
|
121
|
+
return;
|
|
122
|
+
case "snapshot":
|
|
123
|
+
await handleSnapshot(rest, apiUrl, token);
|
|
124
|
+
return;
|
|
125
|
+
case "cache-debug":
|
|
126
|
+
await handleCacheDebug(rest, apiUrl, token);
|
|
127
|
+
return;
|
|
128
|
+
case "variables":
|
|
129
|
+
await handleVariables(rest, apiUrl, token);
|
|
130
|
+
return;
|
|
131
|
+
case "compare":
|
|
132
|
+
await handleCompare(rest, apiUrl, token);
|
|
133
|
+
return;
|
|
134
|
+
case "bundle":
|
|
135
|
+
await handleBundle(rest, apiUrl, token);
|
|
136
|
+
return;
|
|
137
|
+
case "materialize":
|
|
138
|
+
await handleMaterialize(rest, apiUrl, token);
|
|
139
|
+
return;
|
|
140
|
+
default:
|
|
141
|
+
console.error(`Unknown debug sub-command: ${maybeSub}`);
|
|
142
|
+
printDebugHelp();
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function looksLikeRunId(s) {
|
|
147
|
+
return !!s && !s.startsWith("-") && s.length >= 8 && !/^[a-z-]+$/i.test(s.slice(0, 8));
|
|
148
|
+
}
|
|
149
|
+
function requireRunId(argv) {
|
|
150
|
+
const runId = argv.find((a) => !a.startsWith("-"));
|
|
151
|
+
if (!runId) {
|
|
152
|
+
console.error("Error: <runId> is required");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
return runId;
|
|
156
|
+
}
|
|
157
|
+
async function resolveWorkflowId(argv, runId, apiUrl, token) {
|
|
158
|
+
const explicit = getArgValue(argv, "--workflow");
|
|
159
|
+
if (explicit) return explicit;
|
|
160
|
+
const res = await apiRequest(apiUrl, token, "GET", `/workflows/runs/${encodeURIComponent(runId)}/lookup`);
|
|
161
|
+
if (res.ok && res.data?.workflowId) return res.data.workflowId;
|
|
162
|
+
console.error(
|
|
163
|
+
`Error: could not resolve workflowId from runId (${res.error ?? "unknown error"}).`
|
|
164
|
+
);
|
|
165
|
+
console.error(
|
|
166
|
+
` Pass --workflow <workflowId> explicitly, or run: canary workflow runs list <workflowId>`
|
|
167
|
+
);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
return "";
|
|
170
|
+
}
|
|
171
|
+
async function fetchDebug(apiUrl, token, workflowId, runId, subpath = "") {
|
|
172
|
+
return apiRequest(
|
|
173
|
+
apiUrl,
|
|
174
|
+
token,
|
|
175
|
+
"GET",
|
|
176
|
+
`/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/debug${subpath}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
function fmtMs(ms) {
|
|
180
|
+
if (ms === null) return "\u2014";
|
|
181
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
182
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
183
|
+
}
|
|
184
|
+
async function handleOverview(argv, apiUrl, token) {
|
|
185
|
+
const runId = requireRunId(argv);
|
|
186
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
187
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId);
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
if (hasFlag(argv, "--json")) {
|
|
193
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const d = res.data;
|
|
197
|
+
console.log(`Run ${d.run.id.slice(0, 8)} \xB7 ${d.run.status} \xB7 ${fmtMs(d.run.durationMs)}`);
|
|
198
|
+
if (d.run.errorMessage) console.log(` error: ${d.run.errorMessage}`);
|
|
199
|
+
console.log(
|
|
200
|
+
` cache: ${Math.round(d.cache.hitRate * 100)}% hit (${d.cache.hitCount}/${d.cache.hitCount + d.cache.missCount})`
|
|
201
|
+
);
|
|
202
|
+
console.log(` thoughts: ${d.thoughts.count} recorded`);
|
|
203
|
+
console.log(
|
|
204
|
+
` playwright trace: ${d.playwright.traceStorageKey ? "available" : "not captured"}`
|
|
205
|
+
);
|
|
206
|
+
if (d.diagnosis.failedSteps.length > 0) {
|
|
207
|
+
console.log("\nFailed steps:");
|
|
208
|
+
for (const s of d.diagnosis.failedSteps) {
|
|
209
|
+
console.log(` \u2717 ${s.name ?? s.stepId.slice(0, 8)} ${s.errorMessage ?? ""}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (d.diagnosis.slowestSteps.length > 0) {
|
|
213
|
+
console.log("\nSlowest steps:");
|
|
214
|
+
for (const s of d.diagnosis.slowestSteps) {
|
|
215
|
+
console.log(` \xB7 ${s.name ?? s.stepId.slice(0, 8)} ${fmtMs(s.durationMs)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (d.diagnosis.cacheMissHotspots.length > 0) {
|
|
219
|
+
console.log("\nCache miss hotspots:");
|
|
220
|
+
for (const s of d.diagnosis.cacheMissHotspots) {
|
|
221
|
+
console.log(` \xB7 ${s.stepId.slice(0, 8)} ${s.reason}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
console.log("\nNext:");
|
|
225
|
+
console.log(
|
|
226
|
+
` canary workflow debug steps ${runId} --slow # timings`
|
|
227
|
+
);
|
|
228
|
+
console.log(
|
|
229
|
+
` canary workflow debug cache ${runId} --misses-only # cache diagnosis`
|
|
230
|
+
);
|
|
231
|
+
console.log(
|
|
232
|
+
` canary workflow debug thoughts ${runId} --step <id> # agent reasoning`
|
|
233
|
+
);
|
|
234
|
+
console.log(
|
|
235
|
+
` canary workflow debug trace-snapshot ${runId} # DOM the agent saw`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
async function handleSteps(argv, apiUrl, token) {
|
|
239
|
+
const runId = requireRunId(argv);
|
|
240
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
241
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, "/steps");
|
|
242
|
+
if (!res.ok) {
|
|
243
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
const steps = [...res.data.steps];
|
|
247
|
+
if (hasFlag(argv, "--slow")) {
|
|
248
|
+
steps.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
249
|
+
}
|
|
250
|
+
if (hasFlag(argv, "--json")) {
|
|
251
|
+
console.log(JSON.stringify(steps, null, 2));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
console.log("STEP STATUS DURATION CACHE");
|
|
255
|
+
for (const s of steps) {
|
|
256
|
+
const name = (s.name ?? s.id.slice(0, 8)).padEnd(28).slice(0, 28);
|
|
257
|
+
const status = s.status.padEnd(10);
|
|
258
|
+
const dur = fmtMs(s.durationMs).padEnd(10);
|
|
259
|
+
const cache = s.cacheHitSource ?? (s.cacheOutcome ?? "\u2014");
|
|
260
|
+
console.log(`${name} ${status} ${dur} ${cache}`);
|
|
261
|
+
if (s.errorMessage) console.log(` \u21B3 ${s.errorMessage}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function handleNavigation(argv, apiUrl, token) {
|
|
265
|
+
const runId = requireRunId(argv);
|
|
266
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
267
|
+
const res = await fetchDebug(
|
|
268
|
+
apiUrl,
|
|
269
|
+
token,
|
|
270
|
+
workflowId,
|
|
271
|
+
runId,
|
|
272
|
+
"/navigation"
|
|
273
|
+
);
|
|
274
|
+
if (!res.ok) {
|
|
275
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
const transitionsOnly = hasFlag(argv, "--transitions-only");
|
|
279
|
+
const payload = res.data;
|
|
280
|
+
if (hasFlag(argv, "--json")) {
|
|
281
|
+
console.log(
|
|
282
|
+
JSON.stringify(
|
|
283
|
+
transitionsOnly ? { startTimestamp: payload.startTimestamp, transitions: payload.transitions } : payload,
|
|
284
|
+
null,
|
|
285
|
+
2
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (payload.entries.length === 0) {
|
|
291
|
+
console.log("No navigation events recorded for this run.");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (payload.transitions.length > 0) {
|
|
295
|
+
console.log(
|
|
296
|
+
`\u26A0 ${payload.transitions.length} cross-host transition${payload.transitions.length === 1 ? "" : "s"}:`
|
|
297
|
+
);
|
|
298
|
+
for (const t of payload.transitions) {
|
|
299
|
+
console.log(
|
|
300
|
+
` ${formatRelMs(t.timestampMs)} ${t.fromHostname} \u2192 ${t.toHostname}`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
console.log("");
|
|
304
|
+
}
|
|
305
|
+
if (transitionsOnly) return;
|
|
306
|
+
const transitionTo = new Set(payload.transitions.map((t) => t.toIndex));
|
|
307
|
+
console.log("TIME HOSTNAME URL");
|
|
308
|
+
payload.entries.forEach((e, idx) => {
|
|
309
|
+
const time = formatRelMs(e.timestampMs).padEnd(12);
|
|
310
|
+
const host = (e.hostname ?? "\u2014").padEnd(25).slice(0, 25);
|
|
311
|
+
const marker = transitionTo.has(idx) ? "\u2192" : " ";
|
|
312
|
+
console.log(`${time} ${marker} ${host} ${e.url}`);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function formatRelMs(ms) {
|
|
316
|
+
if (ms < 0) ms = 0;
|
|
317
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
318
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
319
|
+
const seconds = totalSeconds % 60;
|
|
320
|
+
const millis = ms % 1e3;
|
|
321
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}.${String(millis).padStart(3, "0")}`;
|
|
322
|
+
}
|
|
323
|
+
async function handleCache(argv, apiUrl, token) {
|
|
324
|
+
const runId = requireRunId(argv);
|
|
325
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
326
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId);
|
|
327
|
+
if (!res.ok) {
|
|
328
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
const { cache, steps } = res.data;
|
|
332
|
+
if (hasFlag(argv, "--json")) {
|
|
333
|
+
console.log(JSON.stringify(cache, null, 2));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
console.log(
|
|
337
|
+
`Cache hit rate: ${Math.round(cache.hitRate * 100)}% (${cache.hitCount} hit, ${cache.missCount} miss)`
|
|
338
|
+
);
|
|
339
|
+
if (Object.keys(cache.bySource).length > 0) {
|
|
340
|
+
console.log("By source:");
|
|
341
|
+
for (const [src, n] of Object.entries(cache.bySource)) {
|
|
342
|
+
console.log(` ${src.padEnd(16)} ${n}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const stepById = new Map(steps.map((s) => [s.id, s]));
|
|
346
|
+
if (!hasFlag(argv, "--misses-only")) {
|
|
347
|
+
console.log("\nHits:");
|
|
348
|
+
for (const s of steps.filter((x) => x.cacheHitSource)) {
|
|
349
|
+
console.log(` \u2713 ${(s.name ?? s.id.slice(0, 8)).padEnd(28)} ${s.cacheHitSource}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (cache.misses.length > 0) {
|
|
353
|
+
console.log("\nMisses:");
|
|
354
|
+
for (const m of cache.misses) {
|
|
355
|
+
const s = stepById.get(m.stepId);
|
|
356
|
+
console.log(` \u2717 ${(s?.name ?? m.stepId.slice(0, 8)).padEnd(28)} ${m.reason}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function handleThoughts(argv, apiUrl, token) {
|
|
361
|
+
const runId = requireRunId(argv);
|
|
362
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
363
|
+
const stepId = getArgValue(argv, "--step");
|
|
364
|
+
const grep = getArgValue(argv, "--grep");
|
|
365
|
+
const qs = stepId ? `?step=${encodeURIComponent(stepId)}` : "";
|
|
366
|
+
const res = await fetchDebug(
|
|
367
|
+
apiUrl,
|
|
368
|
+
token,
|
|
369
|
+
workflowId,
|
|
370
|
+
runId,
|
|
371
|
+
`/thoughts${qs}`
|
|
372
|
+
);
|
|
373
|
+
if (!res.ok) {
|
|
374
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
const totalThoughts = res.data.thoughts.length;
|
|
378
|
+
let thoughts = res.data.thoughts;
|
|
379
|
+
if (grep) {
|
|
380
|
+
const needle = grep.toLowerCase();
|
|
381
|
+
thoughts = thoughts.filter((t) => t.content.toLowerCase().includes(needle));
|
|
382
|
+
}
|
|
383
|
+
if (hasFlag(argv, "--json")) {
|
|
384
|
+
console.log(JSON.stringify(thoughts, null, 2));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
for (const t of thoughts) {
|
|
388
|
+
const tag = t.toolName ? `[${t.toolName}] ` : "";
|
|
389
|
+
const step = t.stepId ? `step ${t.stepId.slice(0, 8)} ` : "";
|
|
390
|
+
console.log(`${step}${tag}${t.content}`);
|
|
391
|
+
console.log("");
|
|
392
|
+
}
|
|
393
|
+
if (thoughts.length === 0) {
|
|
394
|
+
if (totalThoughts === 0) {
|
|
395
|
+
const scope = stepId ? ` for step ${stepId.slice(0, 8)}` : "";
|
|
396
|
+
console.log(`(no thoughts recorded${scope})`);
|
|
397
|
+
} else if (grep) {
|
|
398
|
+
console.log(
|
|
399
|
+
`(no thoughts matched --grep "${grep}"; ${totalThoughts} total thought${totalThoughts === 1 ? "" : "s"} in scope)`
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
console.log("(no thoughts to display)");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function handleLogs(argv, apiUrl, token) {
|
|
407
|
+
const runId = requireRunId(argv);
|
|
408
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
409
|
+
const level = getArgValue(argv, "--level");
|
|
410
|
+
const stepId = getArgValue(argv, "--step");
|
|
411
|
+
const params = new URLSearchParams();
|
|
412
|
+
if (level) params.set("level", level);
|
|
413
|
+
if (stepId) params.set("step", stepId);
|
|
414
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
415
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, `/logs${qs}`);
|
|
416
|
+
if (!res.ok) {
|
|
417
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
if (hasFlag(argv, "--json")) {
|
|
421
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (res.data.partial) {
|
|
425
|
+
console.log(`(partial result: ${res.data.reason ?? "unknown"})`);
|
|
426
|
+
}
|
|
427
|
+
for (const row of res.data.rows) {
|
|
428
|
+
console.log(`${row.timestamp} ${row.level.padEnd(8)} ${row.message}`);
|
|
429
|
+
}
|
|
430
|
+
if (res.data.rows.length === 0 && !res.data.partial) {
|
|
431
|
+
console.log("(no logs)");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function handleSentryTrace(argv, apiUrl, token) {
|
|
435
|
+
const runId = requireRunId(argv);
|
|
436
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
437
|
+
const stepId = getArgValue(argv, "--step");
|
|
438
|
+
const qs = stepId ? `?step=${encodeURIComponent(stepId)}` : "";
|
|
439
|
+
const res = await fetchDebug(
|
|
440
|
+
apiUrl,
|
|
441
|
+
token,
|
|
442
|
+
workflowId,
|
|
443
|
+
runId,
|
|
444
|
+
`/sentry-trace${qs}`
|
|
445
|
+
);
|
|
446
|
+
if (!res.ok) {
|
|
447
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
if (hasFlag(argv, "--json")) {
|
|
451
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (res.data.partial) {
|
|
455
|
+
console.log(`(partial result: ${res.data.reason ?? "unknown"})`);
|
|
456
|
+
}
|
|
457
|
+
if (res.data.traceId) console.log(`trace ${res.data.traceId}`);
|
|
458
|
+
for (const span of res.data.spans) {
|
|
459
|
+
const op = (span.op ?? "span").padEnd(22);
|
|
460
|
+
const desc = (span.description ?? "").padEnd(40);
|
|
461
|
+
const dur = span.durationMs !== null ? `${span.durationMs}ms` : "";
|
|
462
|
+
console.log(` ${op} ${desc} ${dur}`);
|
|
463
|
+
}
|
|
464
|
+
if (res.data.spans.length === 0 && !res.data.partial) {
|
|
465
|
+
console.log("(no spans)");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async function handlePlaywright(argv, apiUrl, token) {
|
|
469
|
+
const runId = requireRunId(argv);
|
|
470
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
471
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, "/trace");
|
|
472
|
+
if (!res.ok) {
|
|
473
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
if (hasFlag(argv, "--json")) {
|
|
477
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
console.log(res.data.viewerUrl);
|
|
481
|
+
if (hasFlag(argv, "--open")) {
|
|
482
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
483
|
+
const { spawn } = await import("child_process");
|
|
484
|
+
spawn(opener, [res.data.viewerUrl], { detached: true, stdio: "ignore" }).unref();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async function handleTraceSnapshot(argv, apiUrl, token) {
|
|
488
|
+
const runId = requireRunId(argv);
|
|
489
|
+
if (getArgValue(argv, "--step")) {
|
|
490
|
+
console.error(
|
|
491
|
+
"Error: trace-snapshot --step is not supported by the local trace reconstructor. Use --action <0-based trace action index> or omit filtering."
|
|
492
|
+
);
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
496
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, "/trace");
|
|
497
|
+
if (!res.ok) {
|
|
498
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), `canary-trace-${runId}-`));
|
|
502
|
+
const traceFile = path.join(tmp, "trace.zip");
|
|
503
|
+
const download = await fetch(res.data.signedUrl);
|
|
504
|
+
if (!download.ok) {
|
|
505
|
+
console.error(`Failed to download trace: HTTP ${download.status}`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
const buf = Buffer.from(await download.arrayBuffer());
|
|
509
|
+
await fs.writeFile(traceFile, buf);
|
|
510
|
+
const forwarded = ["snapshot", traceFile];
|
|
511
|
+
for (const flag of ["--output-dir", "--filter", "--action"]) {
|
|
512
|
+
const v = getArgValue(argv, flag);
|
|
513
|
+
if (v !== void 0) forwarded.push(flag, v);
|
|
514
|
+
}
|
|
515
|
+
if (hasFlag(argv, "--screenshots")) forwarded.push("--screenshots");
|
|
516
|
+
if (hasFlag(argv, "--html")) forwarded.push("--html");
|
|
517
|
+
await runTrace(forwarded);
|
|
518
|
+
}
|
|
519
|
+
async function handleTraceFetch(argv, apiUrl, token) {
|
|
520
|
+
const runId = requireRunId(argv);
|
|
521
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
522
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, "/trace");
|
|
523
|
+
if (!res.ok) {
|
|
524
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
const out = getArgValue(argv, "--out") ?? `./trace-${runId}.zip`;
|
|
528
|
+
const download = await fetch(res.data.signedUrl);
|
|
529
|
+
if (!download.ok) {
|
|
530
|
+
console.error(`Failed to download trace: HTTP ${download.status}`);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
const buf = Buffer.from(await download.arrayBuffer());
|
|
534
|
+
await fs.writeFile(out, buf);
|
|
535
|
+
if (hasFlag(argv, "--json")) {
|
|
536
|
+
console.log(JSON.stringify({ path: out, storageKey: res.data.storageKey }, null, 2));
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
console.log(out);
|
|
540
|
+
}
|
|
541
|
+
async function fetchRunDetail(apiUrl, token, workflowId, runId) {
|
|
542
|
+
const res = await apiRequest(
|
|
543
|
+
apiUrl,
|
|
544
|
+
token,
|
|
545
|
+
"GET",
|
|
546
|
+
`/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}`
|
|
547
|
+
);
|
|
548
|
+
if (!res.run) {
|
|
549
|
+
console.error(`Failed to load run: ${res.error ?? "unknown error"}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
return res.run;
|
|
553
|
+
}
|
|
554
|
+
async function handleSnapshot(argv, apiUrl, token) {
|
|
555
|
+
const runId = requireRunId(argv);
|
|
556
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
557
|
+
const run = await fetchRunDetail(apiUrl, token, workflowId, runId);
|
|
558
|
+
if (!run) return;
|
|
559
|
+
let snapshotPath;
|
|
560
|
+
let kind;
|
|
561
|
+
if (run.draftSnapshot) {
|
|
562
|
+
snapshotPath = `/workflows/${encodeURIComponent(workflowId)}/draft-snapshots/${encodeURIComponent(run.draftSnapshot.version)}`;
|
|
563
|
+
kind = "draft";
|
|
564
|
+
} else if (run.snapshotVersion !== null && run.snapshotVersion !== void 0) {
|
|
565
|
+
snapshotPath = `/workflows/${encodeURIComponent(workflowId)}/snapshots/${run.snapshotVersion}`;
|
|
566
|
+
kind = "published";
|
|
567
|
+
} else {
|
|
568
|
+
console.error("Run has no snapshotVersion or draftSnapshotId \u2014 cannot resolve definition.");
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
const snap = await apiRequest(
|
|
572
|
+
apiUrl,
|
|
573
|
+
token,
|
|
574
|
+
"GET",
|
|
575
|
+
snapshotPath
|
|
576
|
+
);
|
|
577
|
+
if (!snap.snapshot) {
|
|
578
|
+
console.error(`Failed to load snapshot: ${snap.error ?? "unknown error"}`);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
const out = getArgValue(argv, "--out");
|
|
582
|
+
const text = JSON.stringify(snap.snapshot.snapshotJson, null, 2);
|
|
583
|
+
if (out) {
|
|
584
|
+
await fs.writeFile(out, text);
|
|
585
|
+
if (hasFlag(argv, "--json")) {
|
|
586
|
+
console.log(JSON.stringify({ path: out, kind, version: snap.snapshot.version }, null, 2));
|
|
587
|
+
} else {
|
|
588
|
+
console.log(`${kind} snapshot v${snap.snapshot.version} \u2192 ${out}`);
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (hasFlag(argv, "--json")) {
|
|
593
|
+
console.log(JSON.stringify({ kind, version: snap.snapshot.version, snapshotJson: snap.snapshot.snapshotJson }, null, 2));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
console.log(text);
|
|
597
|
+
}
|
|
598
|
+
async function handleVariables(argv, apiUrl, token) {
|
|
599
|
+
const runId = requireRunId(argv);
|
|
600
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
601
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, "/variables");
|
|
602
|
+
if (!res.ok) {
|
|
603
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
const filter = getArgValue(argv, "--name");
|
|
607
|
+
const data = filter ? {
|
|
608
|
+
inputs: res.data.inputs.filter((v) => v.name === filter),
|
|
609
|
+
captures: res.data.captures.filter((v) => v.name === filter)
|
|
610
|
+
} : res.data;
|
|
611
|
+
if (hasFlag(argv, "--json")) {
|
|
612
|
+
console.log(JSON.stringify(data, null, 2));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const fmtValue = (v) => v.length > 80 ? `${v.slice(0, 77)}...` : v;
|
|
616
|
+
console.log(`Run inputs (${data.inputs.length})`);
|
|
617
|
+
if (data.inputs.length === 0) {
|
|
618
|
+
console.log(" (none)");
|
|
619
|
+
} else {
|
|
620
|
+
for (const v of data.inputs) {
|
|
621
|
+
console.log(` ${v.name} = ${fmtValue(v.value)} [defined ${v.definedAt}]`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
console.log("");
|
|
625
|
+
console.log(`Captures (${data.captures.length})`);
|
|
626
|
+
if (data.captures.length === 0) {
|
|
627
|
+
console.log(" (none)");
|
|
628
|
+
} else {
|
|
629
|
+
for (const v of data.captures) {
|
|
630
|
+
const where = v.nodeName ? `${v.nodeName} (${v.stepId})` : v.stepId;
|
|
631
|
+
console.log(` ${v.name} = ${fmtValue(v.value)} [${v.definedAt} \xB7 ${where}]`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async function handleCacheDebug(argv, apiUrl, token) {
|
|
636
|
+
const runId = requireRunId(argv);
|
|
637
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
638
|
+
const res = await apiRequest(
|
|
639
|
+
apiUrl,
|
|
640
|
+
token,
|
|
641
|
+
"GET",
|
|
642
|
+
`/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/cache-debug`
|
|
643
|
+
);
|
|
644
|
+
if (res.ok === false) {
|
|
645
|
+
console.error(`Failed: ${res.error ?? "unknown error"}`);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
const stepFilter = getArgValue(argv, "--step");
|
|
649
|
+
const payload = res.data ?? res;
|
|
650
|
+
const data = stepFilter ? {
|
|
651
|
+
...payload,
|
|
652
|
+
steps: (payload.steps ?? []).filter(
|
|
653
|
+
(s) => s["stepId"] === stepFilter || s["nodeId"] === stepFilter
|
|
654
|
+
)
|
|
655
|
+
} : payload;
|
|
656
|
+
if (hasFlag(argv, "--json")) {
|
|
657
|
+
console.log(JSON.stringify(data, null, 2));
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
console.log(JSON.stringify(data, null, 2));
|
|
661
|
+
}
|
|
662
|
+
async function loadRunBundle(apiUrl, token, workflowId, runId) {
|
|
663
|
+
const overview = await fetchDebug(apiUrl, token, workflowId, runId);
|
|
664
|
+
if (!overview.ok) {
|
|
665
|
+
console.error(`Failed to load run ${runId}: ${overview.error ?? "unknown error"}`);
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
const stepsRes = await fetchDebug(apiUrl, token, workflowId, runId, "/steps");
|
|
669
|
+
const thoughtsRes = await fetchDebug(apiUrl, token, workflowId, runId, "/thoughts");
|
|
670
|
+
return {
|
|
671
|
+
runId,
|
|
672
|
+
overview: overview.data,
|
|
673
|
+
steps: stepsRes.ok ? stepsRes.data.steps : [],
|
|
674
|
+
thoughts: thoughtsRes.ok ? thoughtsRes.data.thoughts : []
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
async function handleCompare(argv, apiUrl, token) {
|
|
678
|
+
const positional = argv.filter((a2) => !a2.startsWith("-"));
|
|
679
|
+
if (positional.length < 2) {
|
|
680
|
+
console.error("Usage: canary workflow debug compare <runIdA> <runIdB> [--workflow <id>] [--out <dir>] [--json]");
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
const [runA, runB] = positional;
|
|
684
|
+
const workflowId = await resolveWorkflowId(argv, runA, apiUrl, token);
|
|
685
|
+
const [a, b] = await Promise.all([
|
|
686
|
+
loadRunBundle(apiUrl, token, workflowId, runA),
|
|
687
|
+
loadRunBundle(apiUrl, token, workflowId, runB)
|
|
688
|
+
]);
|
|
689
|
+
const stepsByNode = (steps) => {
|
|
690
|
+
const m = /* @__PURE__ */ new Map();
|
|
691
|
+
for (const s of steps) m.set(s.nodeId, s);
|
|
692
|
+
return m;
|
|
693
|
+
};
|
|
694
|
+
const aMap = stepsByNode(a.steps);
|
|
695
|
+
const bMap = stepsByNode(b.steps);
|
|
696
|
+
const allNodes = /* @__PURE__ */ new Set([...aMap.keys(), ...bMap.keys()]);
|
|
697
|
+
const stepDiffs = [];
|
|
698
|
+
for (const nodeId of allNodes) {
|
|
699
|
+
const sa = aMap.get(nodeId) ?? null;
|
|
700
|
+
const sb = bMap.get(nodeId) ?? null;
|
|
701
|
+
const flipped = [];
|
|
702
|
+
if ((sa?.status ?? null) !== (sb?.status ?? null)) flipped.push("status");
|
|
703
|
+
if ((sa?.cacheHitSource ?? null) !== (sb?.cacheHitSource ?? null)) flipped.push("cache");
|
|
704
|
+
const durA = sa?.durationMs ?? null;
|
|
705
|
+
const durB = sb?.durationMs ?? null;
|
|
706
|
+
let durationDeltaPct = null;
|
|
707
|
+
if (durA !== null && durB !== null && durA > 0) {
|
|
708
|
+
durationDeltaPct = Math.round((durB - durA) / durA * 100);
|
|
709
|
+
if (Math.abs(durationDeltaPct) >= 100) flipped.push("duration");
|
|
710
|
+
}
|
|
711
|
+
if (flipped.length > 0 || !sa || !sb) {
|
|
712
|
+
stepDiffs.push({
|
|
713
|
+
nodeId,
|
|
714
|
+
name: sa?.name ?? sb?.name ?? null,
|
|
715
|
+
statusA: sa?.status ?? null,
|
|
716
|
+
statusB: sb?.status ?? null,
|
|
717
|
+
durationA: durA,
|
|
718
|
+
durationB: durB,
|
|
719
|
+
durationDeltaPct,
|
|
720
|
+
cacheA: sa?.cacheHitSource ?? null,
|
|
721
|
+
cacheB: sb?.cacheHitSource ?? null,
|
|
722
|
+
flipped: !sa ? ["only-in-B"] : !sb ? ["only-in-A"] : flipped
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
const thoughtsByStep = (bundle) => {
|
|
727
|
+
const m = /* @__PURE__ */ new Map();
|
|
728
|
+
for (const t of bundle.thoughts) {
|
|
729
|
+
const key = t.stepId ?? "";
|
|
730
|
+
const arr = m.get(key) ?? [];
|
|
731
|
+
arr.push(t);
|
|
732
|
+
m.set(key, arr);
|
|
733
|
+
}
|
|
734
|
+
return m;
|
|
735
|
+
};
|
|
736
|
+
const aThoughts = thoughtsByStep(a);
|
|
737
|
+
const bThoughts = thoughtsByStep(b);
|
|
738
|
+
const thoughtDeltas = stepDiffs.map((d) => {
|
|
739
|
+
const sa = aMap.get(d.nodeId);
|
|
740
|
+
const sb = bMap.get(d.nodeId);
|
|
741
|
+
return {
|
|
742
|
+
nodeId: d.nodeId,
|
|
743
|
+
thoughtsA: sa ? aThoughts.get(sa.id)?.map((t) => t.content) ?? [] : [],
|
|
744
|
+
thoughtsB: sb ? bThoughts.get(sb.id)?.map((t) => t.content) ?? [] : []
|
|
745
|
+
};
|
|
746
|
+
});
|
|
747
|
+
const result = {
|
|
748
|
+
runA,
|
|
749
|
+
runB,
|
|
750
|
+
workflowId,
|
|
751
|
+
cacheHitRateDelta: b.overview.cache.hitRate - a.overview.cache.hitRate,
|
|
752
|
+
statusA: a.overview.run.status,
|
|
753
|
+
statusB: b.overview.run.status,
|
|
754
|
+
durationA: a.overview.run.durationMs,
|
|
755
|
+
durationB: b.overview.run.durationMs,
|
|
756
|
+
stepDiffs,
|
|
757
|
+
thoughtDeltas
|
|
758
|
+
};
|
|
759
|
+
const outDir = getArgValue(argv, "--out");
|
|
760
|
+
if (outDir) {
|
|
761
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
762
|
+
await fs.writeFile(path.join(outDir, "compare.json"), JSON.stringify(result, null, 2));
|
|
763
|
+
for (const [label, runId] of [["runA", runA], ["runB", runB]]) {
|
|
764
|
+
const sub = path.join(outDir, label);
|
|
765
|
+
await fs.mkdir(sub, { recursive: true });
|
|
766
|
+
await tryWriteSnapshot(apiUrl, token, workflowId, runId, path.join(sub, "snapshot.json"));
|
|
767
|
+
await tryWriteTrace(apiUrl, token, workflowId, runId, path.join(sub, "trace.zip"));
|
|
768
|
+
}
|
|
769
|
+
if (!hasFlag(argv, "--json")) console.log(`Wrote compare bundle to ${outDir}`);
|
|
770
|
+
}
|
|
771
|
+
if (hasFlag(argv, "--json") || !outDir) {
|
|
772
|
+
console.log(JSON.stringify(result, null, 2));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async function tryWriteTrace(apiUrl, token, workflowId, runId, outPath) {
|
|
776
|
+
const res = await fetchDebug(apiUrl, token, workflowId, runId, "/trace");
|
|
777
|
+
if (!res.ok) return;
|
|
778
|
+
const dl = await fetch(res.data.signedUrl);
|
|
779
|
+
if (!dl.ok) return;
|
|
780
|
+
await fs.writeFile(outPath, Buffer.from(await dl.arrayBuffer()));
|
|
781
|
+
}
|
|
782
|
+
async function tryWriteSnapshot(apiUrl, token, workflowId, runId, outPath) {
|
|
783
|
+
const run = await fetchRunDetail(apiUrl, token, workflowId, runId);
|
|
784
|
+
if (!run) return;
|
|
785
|
+
const snapPath = run.draftSnapshot ? `/workflows/${encodeURIComponent(workflowId)}/draft-snapshots/${encodeURIComponent(run.draftSnapshot.version)}` : run.snapshotVersion !== null && run.snapshotVersion !== void 0 ? `/workflows/${encodeURIComponent(workflowId)}/snapshots/${run.snapshotVersion}` : null;
|
|
786
|
+
if (!snapPath) return;
|
|
787
|
+
const snap = await apiRequest(
|
|
788
|
+
apiUrl,
|
|
789
|
+
token,
|
|
790
|
+
"GET",
|
|
791
|
+
snapPath
|
|
792
|
+
);
|
|
793
|
+
if (!snap.snapshot) return;
|
|
794
|
+
await fs.writeFile(outPath, JSON.stringify(snap.snapshot.snapshotJson, null, 2));
|
|
795
|
+
}
|
|
796
|
+
async function handleBundle(argv, apiUrl, token) {
|
|
797
|
+
const runId = requireRunId(argv);
|
|
798
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
799
|
+
const outDir = getArgValue(argv, "--out");
|
|
800
|
+
if (!outDir) {
|
|
801
|
+
console.error("Error: --out <dir> is required");
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
805
|
+
const manifest = [];
|
|
806
|
+
const writeJson = async (file, source, fetcher) => {
|
|
807
|
+
try {
|
|
808
|
+
const data = await fetcher();
|
|
809
|
+
await fs.writeFile(path.join(outDir, file), JSON.stringify(data, null, 2));
|
|
810
|
+
manifest.push({ file, source, ok: true });
|
|
811
|
+
} catch (err) {
|
|
812
|
+
manifest.push({ file, source, ok: false, error: String(err) });
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
await writeJson("overview.json", `GET /workflows/${workflowId}/runs/${runId}/debug`, async () => {
|
|
816
|
+
const r = await fetchDebug(apiUrl, token, workflowId, runId);
|
|
817
|
+
if (!r.ok) throw new Error(r.error ?? "failed");
|
|
818
|
+
return r.data;
|
|
819
|
+
});
|
|
820
|
+
await writeJson("steps.json", `/debug/steps`, async () => {
|
|
821
|
+
const r = await fetchDebug(apiUrl, token, workflowId, runId, "/steps");
|
|
822
|
+
if (!r.ok) throw new Error(r.error ?? "failed");
|
|
823
|
+
return r.data;
|
|
824
|
+
});
|
|
825
|
+
await writeJson("thoughts.json", `/debug/thoughts`, async () => {
|
|
826
|
+
const r = await fetchDebug(apiUrl, token, workflowId, runId, "/thoughts");
|
|
827
|
+
if (!r.ok) throw new Error(r.error ?? "failed");
|
|
828
|
+
return r.data;
|
|
829
|
+
});
|
|
830
|
+
await writeJson("cache-debug.json", `/cache-debug`, async () => {
|
|
831
|
+
const r = await apiRequest(
|
|
832
|
+
apiUrl,
|
|
833
|
+
token,
|
|
834
|
+
"GET",
|
|
835
|
+
`/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/cache-debug`
|
|
836
|
+
);
|
|
837
|
+
if (r.ok === false) throw new Error(r.error ?? "failed");
|
|
838
|
+
return r;
|
|
839
|
+
});
|
|
840
|
+
await writeJson("logs.json", `/debug/logs`, async () => {
|
|
841
|
+
const r = await fetchDebug(apiUrl, token, workflowId, runId, "/logs");
|
|
842
|
+
if (!r.ok) throw new Error(r.error ?? "failed");
|
|
843
|
+
return r.data;
|
|
844
|
+
});
|
|
845
|
+
await writeJson("sentry-trace.json", `/debug/sentry-trace`, async () => {
|
|
846
|
+
const r = await fetchDebug(apiUrl, token, workflowId, runId, "/sentry-trace");
|
|
847
|
+
if (!r.ok) throw new Error(r.error ?? "failed");
|
|
848
|
+
return r.data;
|
|
849
|
+
});
|
|
850
|
+
await writeJson("variables.json", `/debug/variables`, async () => {
|
|
851
|
+
const r = await fetchDebug(apiUrl, token, workflowId, runId, "/variables");
|
|
852
|
+
if (!r.ok) throw new Error(r.error ?? "failed");
|
|
853
|
+
return r.data;
|
|
854
|
+
});
|
|
855
|
+
try {
|
|
856
|
+
await tryWriteSnapshot(apiUrl, token, workflowId, runId, path.join(outDir, "snapshot.json"));
|
|
857
|
+
manifest.push({ file: "snapshot.json", source: "snapshots/:version", ok: true });
|
|
858
|
+
} catch (err) {
|
|
859
|
+
manifest.push({ file: "snapshot.json", source: "snapshots/:version", ok: false, error: String(err) });
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
await tryWriteTrace(apiUrl, token, workflowId, runId, path.join(outDir, "trace.zip"));
|
|
863
|
+
manifest.push({ file: "trace.zip", source: "/debug/trace", ok: true });
|
|
864
|
+
} catch (err) {
|
|
865
|
+
manifest.push({ file: "trace.zip", source: "/debug/trace", ok: false, error: String(err) });
|
|
866
|
+
}
|
|
867
|
+
await fs.writeFile(path.join(outDir, "bundle.json"), JSON.stringify({ runId, workflowId, manifest }, null, 2));
|
|
868
|
+
console.log(`Wrote bundle to ${outDir}`);
|
|
869
|
+
for (const m of manifest) {
|
|
870
|
+
console.log(` ${m.ok ? "\u2713" : "\u2717"} ${m.file}${m.ok ? "" : ` (${m.error ?? "failed"})`}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
async function handleMaterialize(argv, apiUrl, token) {
|
|
874
|
+
const runId = requireRunId(argv);
|
|
875
|
+
const workflowId = await resolveWorkflowId(argv, runId, apiUrl, token);
|
|
876
|
+
const outDir = getArgValue(argv, "--out") ?? `./diag-${runId}`;
|
|
877
|
+
const { assembleWorkspace } = await import("./src-LB4HBTJQ.js");
|
|
878
|
+
const dataUrl = `${apiUrl}/workflows/${encodeURIComponent(workflowId)}/runs/${encodeURIComponent(runId)}/debug/materialize-data`;
|
|
879
|
+
const dataRes = await fetch(dataUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
880
|
+
if (!dataRes.ok) {
|
|
881
|
+
const errText = await dataRes.text().catch(() => "");
|
|
882
|
+
console.error(`Failed to fetch run data: HTTP ${dataRes.status} ${errText}`);
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
885
|
+
const envelope = await dataRes.json();
|
|
886
|
+
if (!envelope.ok) {
|
|
887
|
+
console.error(`Failed: ${envelope.error ?? "unknown error"}`);
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
const { runData, signedTraceUrl } = envelope.data;
|
|
891
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
892
|
+
let traceZipPath = null;
|
|
893
|
+
if (signedTraceUrl) {
|
|
894
|
+
traceZipPath = path.join(outDir, ".trace-source.zip");
|
|
895
|
+
const traceRes = await fetch(signedTraceUrl);
|
|
896
|
+
if (!traceRes.ok || !traceRes.body) {
|
|
897
|
+
console.warn(`Trace download failed (HTTP ${traceRes.status}); workspace will be marked partial.`);
|
|
898
|
+
traceZipPath = null;
|
|
899
|
+
} else {
|
|
900
|
+
const handle = await fs.open(traceZipPath, "w");
|
|
901
|
+
try {
|
|
902
|
+
const reader = traceRes.body.getReader();
|
|
903
|
+
while (true) {
|
|
904
|
+
const { value, done } = await reader.read();
|
|
905
|
+
if (done) break;
|
|
906
|
+
if (value) await handle.write(value);
|
|
907
|
+
}
|
|
908
|
+
} finally {
|
|
909
|
+
await handle.close();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
await assembleWorkspace({ runData, workspaceRoot: outDir, traceZipPath });
|
|
914
|
+
if (traceZipPath) {
|
|
915
|
+
await fs.rm(traceZipPath, { force: true }).catch(() => void 0);
|
|
916
|
+
}
|
|
917
|
+
if (hasFlag(argv, "--json")) {
|
|
918
|
+
console.log(JSON.stringify({ workspace: outDir, runId, workflowId }, null, 2));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
console.log(`Materialized workspace at ${outDir}`);
|
|
922
|
+
console.log(`Start with: cat ${path.join(outDir, "summary.md")}`);
|
|
923
|
+
}
|
|
924
|
+
export {
|
|
925
|
+
handleWorkflowDebug
|
|
926
|
+
};
|
|
927
|
+
//# sourceMappingURL=workflow-debug-BGD4UIXU.js.map
|