acpus 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +6 -103
  2. package/dist/catalog.d.ts +27 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/catalog.js +186 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/follow.d.ts +23 -0
  7. package/dist/follow.d.ts.map +1 -0
  8. package/dist/follow.js +109 -0
  9. package/dist/follow.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +522 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/io.d.ts +4 -0
  15. package/dist/io.d.ts.map +1 -0
  16. package/dist/io.js +28 -0
  17. package/dist/io.js.map +1 -0
  18. package/dist/observations.d.ts +26 -0
  19. package/dist/observations.d.ts.map +1 -0
  20. package/dist/observations.js +60 -0
  21. package/dist/observations.js.map +1 -0
  22. package/dist/output.d.ts +9 -0
  23. package/dist/output.d.ts.map +1 -0
  24. package/dist/output.js +51 -0
  25. package/dist/output.js.map +1 -0
  26. package/dist/runs-show.d.ts +5 -0
  27. package/dist/runs-show.d.ts.map +1 -0
  28. package/dist/runs-show.js +72 -0
  29. package/dist/runs-show.js.map +1 -0
  30. package/dist/supervisor-client.d.ts +9 -0
  31. package/dist/supervisor-client.d.ts.map +1 -0
  32. package/dist/supervisor-client.js +8 -0
  33. package/dist/supervisor-client.js.map +1 -0
  34. package/dist/supervisor.d.ts +18 -0
  35. package/dist/supervisor.d.ts.map +1 -0
  36. package/dist/supervisor.js +24 -0
  37. package/dist/supervisor.js.map +1 -0
  38. package/package.json +30 -44
  39. package/dist/cli.d.mts +0 -1
  40. package/dist/cli.mjs +0 -4017
  41. package/dist/index.d.mts +0 -2194
  42. package/dist/index.mjs +0 -243
  43. package/dist/monitor-app-CPlEcyHR.mjs +0 -369
  44. package/dist/monitor-rendering-LGr9Ebd_.mjs +0 -78
  45. package/dist/run-picker-app-utJ2f5CU.mjs +0 -104
  46. package/dist/run-workflow-DdIAC8Zu.mjs +0 -12922
  47. package/schemas/workflow-spec.schema.json +0 -2649
package/dist/index.mjs DELETED
@@ -1,243 +0,0 @@
1
- import { $ as ConditionSchema, B as retryCountByReason, C as RUN_MONITOR_VIEW_VERSION, Ct as TaskStageSchema, Dt as WorkflowLimitsSchema, E as buildTaskDetailView, Et as WorkflowInputSchema, F as parseWorkflowOutput, Ft as runDir, G as compileSchemaDsl, H as retryableOutputFailure, I as AGENT_TASK_RETRY_BUDGET, J as outputSchemaFooter, K as defaultAgentOutputZod, L as AGENT_TASK_RETRY_DELAY_MS, N as syncRun, Ot as WorkflowSpecSchema, P as collectWorkflowOutputCandidates, Q as AgentFaninSchema, R as agentTaskRetryDelayMs, S as resultFromIssues, St as TaskProgramStageSchema, T as buildRunMonitorView, Tt as VariableSchema, U as setAgentTaskRetryDelayForTests, V as retryExhaustedEnvelope, W as DEFAULT_AGENT_OUTPUT_SCHEMA, X as ActorModeSchema, Y as zodForCompiledSchema, Z as ActorSchema, _t as RouteStageSchema, a as renderStagePrompt, at as GateAgentStageSchema, b as OrchestratorError, bt as StageSchema, c as EXECUTION_PLAN_VERSION, ct as LimitBindingSchema, d as previewRunView, dt as LoopStageSchema, et as FaninSchema, f as runViewFromIndex, ft as PositiveIntegerLimitSchema, g as lintWorkflowSpec, gt as RouteRuleSchema, h as stringifyWorkflowSpec, ht as RouteProgramStageSchema, i as renderPromptMap, it as FanoutStageSchema, kt as RuntimeErrorCodes, l as estimateAgentCalls, lt as LimitValueSchema, m as loadWorkflowSpec, mt as RouteAgentStageSchema, n as startPreparedRun, nt as FanoutPolicySchema, o as stageActorLabel, ot as GateProgramStageSchema, p as isWorkflowYamlPath, pt as ProgramFaninSchema, q as formatSchema, r as compileExecutionPlan, rt as FanoutStageLimitsSchema, s as topologicalOrder, st as GateStageSchema, t as prepareRun, tt as FanoutLaneSchema, u as estimateFanoutWork, ut as LoopBodyStageSchema, vt as SCHEMA_VERSION, w as TASK_DETAIL_VIEW_VERSION, wt as TransformSchema, x as issue, xt as TaskAgentStageSchema, yt as StageLimitsSchema, z as formatContinuationPrompt } from "./run-workflow-DdIAC8Zu.mjs";
2
- import path from "node:path";
3
- import fs from "node:fs/promises";
4
- //#region src/projections/run-diagnostics.ts
5
- const RUN_DIAGNOSTICS_VIEW_VERSION = "acpus.diagnostics/v1";
6
- const RUN_LEVEL_RUNTIME_CODES = new Set([
7
- RuntimeErrorCodes.EVENT_APPEND_LOCK_TIMEOUT,
8
- RuntimeErrorCodes.RUN_INDEX_LOCK_TIMEOUT,
9
- RuntimeErrorCodes.AGENT_TASK_RETRY_EXHAUSTED,
10
- RuntimeErrorCodes.FANOUT_ITEM_UNSTARTED_TIMEOUT,
11
- RuntimeErrorCodes.FANOUT_ITEM_BLOCKED,
12
- RuntimeErrorCodes.FANOUT_ITEM_CASCADE_BLOCKED,
13
- RuntimeErrorCodes.FANOUT_LANE_RESULT_MISMATCH,
14
- RuntimeErrorCodes.NO_SELECTED_LANES,
15
- RuntimeErrorCodes.MISSING_FANOUT_ITEM_OUTPUT,
16
- RuntimeErrorCodes.FANOUT_STAGE_STUCK_PENDING_BATCH,
17
- RuntimeErrorCodes.RUN_INDEX_OUTPUT_MISMATCH,
18
- RuntimeErrorCodes.LOOP_EXHAUSTED,
19
- RuntimeErrorCodes.LOOP_BODY_STAGE_BLOCKED,
20
- RuntimeErrorCodes.LOOP_BODY_STAGE_FAILED,
21
- RuntimeErrorCodes.LOOP_BODY_OUTPUT_MISSING,
22
- RuntimeErrorCodes.VARIABLE_RESOLUTION_FAILED,
23
- RuntimeErrorCodes.AGENT_RUNTIME_ERROR,
24
- RuntimeErrorCodes.AGENT_TURN_FAILED,
25
- RuntimeErrorCodes.AGENT_TURN_CANCELLED,
26
- RuntimeErrorCodes.AGENT_STAGE_STALE_RECOVERY,
27
- RuntimeErrorCodes.GATE_CONDITION_FAILED,
28
- RuntimeErrorCodes.GATE_VERDICT_BLOCKED,
29
- RuntimeErrorCodes.GATE_VERDICT_FAILED,
30
- RuntimeErrorCodes.GATE_VERDICT_UNKNOWN
31
- ]);
32
- const RunDiagnosticCodes = { SCHEDULER_RECOVERY_SUCCEEDED_WITH_BLOCKED_VERDICT: "SCHEDULER_RECOVERY_SUCCEEDED_WITH_BLOCKED_VERDICT" };
33
- const DEFAULT_EVENT_TAIL_LIMIT = 50;
34
- const DEFAULT_EVENT_TAIL_MAX_BYTES = 256 * 1024;
35
- async function buildRunDiagnosticsView(cwd, index, options = {}) {
36
- const dir = runDir(index.logicalRunId, cwd);
37
- const eventTail = await readEventTail(path.join(dir, "events.ndjson"), {
38
- limit: options.eventTailLimit ?? DEFAULT_EVENT_TAIL_LIMIT,
39
- maxBytes: options.eventTailMaxBytes ?? DEFAULT_EVENT_TAIL_MAX_BYTES
40
- });
41
- const diagnostics = [...await buildRuntimeDiagnostics(dir, index), ...buildEventTailDiagnostics(path.join(dir, "events.ndjson"), eventTail)];
42
- return {
43
- version: RUN_DIAGNOSTICS_VIEW_VERSION,
44
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
45
- run: {
46
- logicalRunId: index.logicalRunId,
47
- workflowName: index.workflowName,
48
- status: index.status,
49
- blockedReason: index.blockedReason,
50
- gateVerdict: index.gateVerdict,
51
- runDir: dir
52
- },
53
- diagnostics,
54
- eventTail
55
- };
56
- }
57
- async function readNdjsonTail(filePath, maxLines, maxBytes = DEFAULT_EVENT_TAIL_MAX_BYTES) {
58
- if (maxLines <= 0 || maxBytes <= 0) return [];
59
- let handle;
60
- try {
61
- const stat = await fs.stat(filePath);
62
- if (stat.size === 0) return [];
63
- const bytesToRead = Math.min(stat.size, maxBytes);
64
- const start = stat.size - bytesToRead;
65
- const buffer = Buffer.alloc(bytesToRead);
66
- handle = await fs.open(filePath, "r");
67
- await handle.read(buffer, 0, bytesToRead, start);
68
- let lines = buffer.toString("utf8").split("\n");
69
- if (start > 0) lines = lines.slice(1);
70
- return lines.filter((line) => line.trim().length > 0).slice(-maxLines);
71
- } catch {
72
- return [];
73
- } finally {
74
- await handle?.close().catch(() => void 0);
75
- }
76
- }
77
- async function readEventTail(filePath, options) {
78
- return (await readNdjsonTail(filePath, options.limit, options.maxBytes)).map(parseEventLine).filter((event) => event !== void 0);
79
- }
80
- async function buildRuntimeDiagnostics(dir, index) {
81
- const diagnostics = [];
82
- if (index.blockedReason && isRunLevelRuntimeCode(index.blockedReason)) diagnostics.push(runtimeDiagnostic({
83
- code: index.blockedReason,
84
- path: path.join(dir, "run.json"),
85
- summary: runLevelBlockedSummary(index),
86
- source: "run_index"
87
- }));
88
- for (const stage of Object.values(index.stages)) {
89
- if (!stage.fanout && (stage.blockedReason === RuntimeErrorCodes.AGENT_RUNTIME_ERROR || stage.blockedReason === RuntimeErrorCodes.AGENT_STAGE_STALE_RECOVERY || stage.blockedReason === RuntimeErrorCodes.AGENT_TASK_RETRY_EXHAUSTED)) {
90
- const outputPath = stage.outputPath ? path.join(dir, stage.outputPath) : path.join(dir, "outputs", `${stage.stageId}.json`);
91
- const code = stage.blockedReason;
92
- diagnostics.push(runtimeDiagnostic({
93
- code,
94
- stageId: stage.stageId,
95
- path: outputPath,
96
- summary: stageRetryDiagnosticSummary(code),
97
- source: "run_index"
98
- }));
99
- }
100
- if (!stage.fanout) continue;
101
- const hasRunningItems = stage.fanout.items.some((item) => item.status === "running");
102
- const queuedItems = stage.fanout.items.filter((item) => item.status === "pending" || item.status === "ready");
103
- if (stage.status === "running" && !hasRunningItems && queuedItems.length > 0) diagnostics.push(runtimeDiagnostic({
104
- code: RuntimeErrorCodes.FANOUT_STAGE_STUCK_PENDING_BATCH,
105
- stageId: stage.stageId,
106
- path: path.join(dir, "run.json"),
107
- summary: `Fanout stage ${stage.stageId} is running with no running items and ${queuedItems.length} queued item(s).`,
108
- source: "run_index"
109
- }));
110
- for (const item of stage.fanout.items) {
111
- const outputPath = item.outputPath ? path.join(dir, item.outputPath) : path.join(dir, "outputs", stage.stageId, `${safeFileName(item.id)}.json`);
112
- if (item.status === "running" && await fileExists(outputPath)) diagnostics.push(runtimeDiagnostic({
113
- code: RuntimeErrorCodes.RUN_INDEX_OUTPUT_MISMATCH,
114
- stageId: stage.stageId,
115
- itemId: item.id,
116
- path: outputPath,
117
- summary: `Fanout item ${stage.stageId}/${item.id} is running in run.json but has an output file.`,
118
- source: "stage_output"
119
- }));
120
- if (item.errorCode) diagnostics.push(runtimeDiagnostic({
121
- code: item.errorCode,
122
- stageId: stage.stageId,
123
- itemId: item.id,
124
- path: outputPath,
125
- summary: item.errorMessage ?? item.blockedReason ?? item.errorCode,
126
- source: "run_index"
127
- }));
128
- }
129
- }
130
- const recoveryVerdictDiagnostic = buildRecoverySucceededWithBlockedVerdictDiagnostic(dir, index);
131
- if (recoveryVerdictDiagnostic) diagnostics.push(recoveryVerdictDiagnostic);
132
- return diagnostics;
133
- }
134
- function stageRetryDiagnosticSummary(code) {
135
- if (code === RuntimeErrorCodes.AGENT_STAGE_STALE_RECOVERY) return "Agent task retry exhausted after scheduler stale recovery.";
136
- if (code === RuntimeErrorCodes.AGENT_TASK_RETRY_EXHAUSTED) return "Agent task retry budget exhausted.";
137
- return "Agent runtime failed after retry exhaustion.";
138
- }
139
- function buildEventTailDiagnostics(eventPath, eventTail) {
140
- const diagnostics = [];
141
- for (const event of eventTail) {
142
- if (!(event.summary?.includes("Lock file is already being held") || event.errorCode === RuntimeErrorCodes.RUN_INDEX_LOCK_TIMEOUT || event.errorCode === RuntimeErrorCodes.EVENT_APPEND_LOCK_TIMEOUT)) continue;
143
- diagnostics.push(runtimeDiagnostic({
144
- code: event.errorCode ?? "LOCK_CONTENTION",
145
- stageId: event.stageId,
146
- itemId: event.itemId,
147
- attemptId: event.attemptId,
148
- path: eventPath,
149
- summary: "Runtime lock contention was observed in recent events.",
150
- source: "event_tail",
151
- status: "blocked"
152
- }));
153
- }
154
- return diagnostics;
155
- }
156
- function buildRecoverySucceededWithBlockedVerdictDiagnostic(dir, index) {
157
- if (!isBlockedGateVerdictCode(index.blockedReason) || !index.gateVerdict) return void 0;
158
- const recoveredFanoutStages = Object.values(index.stages).filter((stage) => {
159
- if (!stage.fanout) return false;
160
- if (stage.fanout.items.some((item) => item.status === "running" || item.status === "pending" || item.status === "ready")) return false;
161
- return stage.fanout.items.some((item) => item.errorCode === RuntimeErrorCodes.AGENT_TASK_RETRY_EXHAUSTED || item.errorCode === RuntimeErrorCodes.FANOUT_ITEM_CASCADE_BLOCKED || item.errorCode === RuntimeErrorCodes.FANOUT_ITEM_UNSTARTED_TIMEOUT || item.errorCode === RuntimeErrorCodes.RUN_INDEX_OUTPUT_MISMATCH);
162
- });
163
- if (recoveredFanoutStages.length === 0) return void 0;
164
- const stageIds = recoveredFanoutStages.map((stage) => stage.stageId);
165
- return {
166
- id: `runtime-${RunDiagnosticCodes.SCHEDULER_RECOVERY_SUCCEEDED_WITH_BLOCKED_VERDICT}-run-all`,
167
- code: RunDiagnosticCodes.SCHEDULER_RECOVERY_SUCCEEDED_WITH_BLOCKED_VERDICT,
168
- status: "completed",
169
- summary: `Scheduler recovery completed for fanout stage(s) ${stageIds.join(", ")}, but workflow gate verdict remains ${index.gateVerdict}.`,
170
- path: path.join(dir, "run.json"),
171
- source: "run_index"
172
- };
173
- }
174
- function runtimeDiagnostic(input) {
175
- return {
176
- id: `runtime-${input.code}-${input.stageId ?? "run"}-${input.itemId ?? input.attemptId ?? "all"}`,
177
- code: input.code,
178
- stageId: input.stageId,
179
- itemId: input.itemId,
180
- attemptId: input.attemptId,
181
- status: input.status ?? "blocked",
182
- summary: input.summary,
183
- path: input.path,
184
- source: input.source
185
- };
186
- }
187
- function isRunLevelRuntimeCode(value) {
188
- return RUN_LEVEL_RUNTIME_CODES.has(value);
189
- }
190
- function isBlockedGateVerdictCode(value) {
191
- return value === RuntimeErrorCodes.GATE_VERDICT_BLOCKED || value === RuntimeErrorCodes.GATE_VERDICT_FAILED || value === RuntimeErrorCodes.GATE_VERDICT_UNKNOWN;
192
- }
193
- function runLevelBlockedSummary(index) {
194
- if (index.blockedReason === RuntimeErrorCodes.AGENT_TASK_RETRY_EXHAUSTED) return "Agent Task Retry budget exhausted.";
195
- if (index.blockedReason === RuntimeErrorCodes.AGENT_RUNTIME_ERROR) return "Agent runtime failed after Agent Task Retry budget exhaustion.";
196
- if (index.blockedReason === RuntimeErrorCodes.AGENT_STAGE_STALE_RECOVERY) return "Agent stage stale recovery exhausted Agent Task Retry budget.";
197
- if (index.blockedReason === RuntimeErrorCodes.FANOUT_ITEM_BLOCKED) return "Fanout stage blocked because one or more items did not complete.";
198
- if (index.blockedReason === RuntimeErrorCodes.GATE_CONDITION_FAILED) return "Program gate condition failed.";
199
- if (index.gateVerdict === "blocked") return "Gate returned verdict=blocked.";
200
- if (index.gateVerdict === "failed") return "Gate returned verdict=failed.";
201
- return "Gate returned verdict=unknown.";
202
- }
203
- function parseEventLine(line) {
204
- const record = objectRecord(safeJson(line));
205
- if (!record) return void 0;
206
- const errorCode = stringField(record, "errorCode") ?? stringField(record, "code");
207
- return {
208
- at: stringField(record, "at"),
209
- type: stringField(record, "type"),
210
- stageId: stringField(record, "stageId"),
211
- itemId: stringField(record, "itemId"),
212
- attemptId: stringField(record, "attemptId"),
213
- errorCode,
214
- summary: stringField(record, "summary") ?? stringField(record, "error") ?? stringField(record, "errorMessage")
215
- };
216
- }
217
- async function fileExists(filePath) {
218
- try {
219
- await fs.access(filePath);
220
- return true;
221
- } catch {
222
- return false;
223
- }
224
- }
225
- function safeJson(value) {
226
- try {
227
- return JSON.parse(value);
228
- } catch {
229
- return;
230
- }
231
- }
232
- function objectRecord(value) {
233
- return value && typeof value === "object" ? value : void 0;
234
- }
235
- function stringField(value, key) {
236
- const field = value?.[key];
237
- return typeof field === "string" ? field : void 0;
238
- }
239
- function safeFileName(value) {
240
- return String(value || "item").replace(/[^A-Za-z0-9_.-]/g, "_");
241
- }
242
- //#endregion
243
- export { AGENT_TASK_RETRY_BUDGET, AGENT_TASK_RETRY_DELAY_MS, ActorModeSchema, ActorSchema, AgentFaninSchema, ConditionSchema, DEFAULT_AGENT_OUTPUT_SCHEMA, EXECUTION_PLAN_VERSION, FaninSchema, FanoutLaneSchema, FanoutPolicySchema, FanoutStageLimitsSchema, FanoutStageSchema, GateAgentStageSchema, GateProgramStageSchema, GateStageSchema, LimitBindingSchema, LimitValueSchema, LoopBodyStageSchema, LoopStageSchema, OrchestratorError, PositiveIntegerLimitSchema, ProgramFaninSchema, RUN_DIAGNOSTICS_VIEW_VERSION, RUN_MONITOR_VIEW_VERSION, RouteAgentStageSchema, RouteProgramStageSchema, RouteRuleSchema, RouteStageSchema, RunDiagnosticCodes, SCHEMA_VERSION, StageLimitsSchema, StageSchema, TASK_DETAIL_VIEW_VERSION, TaskAgentStageSchema, TaskProgramStageSchema, TaskStageSchema, TransformSchema, VariableSchema, WorkflowInputSchema, WorkflowLimitsSchema, WorkflowSpecSchema, agentTaskRetryDelayMs, buildRunDiagnosticsView, buildRunMonitorView, buildTaskDetailView, collectWorkflowOutputCandidates, compileExecutionPlan, compileSchemaDsl, defaultAgentOutputZod, estimateAgentCalls, estimateFanoutWork, formatContinuationPrompt, formatSchema, isWorkflowYamlPath, issue, lintWorkflowSpec, loadWorkflowSpec, outputSchemaFooter, parseWorkflowOutput, prepareRun, previewRunView, readNdjsonTail, renderPromptMap, renderStagePrompt, resultFromIssues, retryCountByReason, retryExhaustedEnvelope, retryableOutputFailure, runViewFromIndex, setAgentTaskRetryDelayForTests, stageActorLabel, startPreparedRun, stringifyWorkflowSpec, syncRun, topologicalOrder, zodForCompiledSchema };
@@ -1,369 +0,0 @@
1
- import { E as buildTaskDetailView, Ft as runDir, N as syncRun, Ot as WorkflowSpecSchema, T as buildRunMonitorView } from "./run-workflow-DdIAC8Zu.mjs";
2
- import { n as resolveRunLocator } from "./cli.mjs";
3
- import { a as nextIndex, c as shorten, d as tasksForStage, i as formatDuration, l as stageProgressLabel, n as defaultStageIndex, o as runProgressLabel, r as detailSummary, s as runStatusLabel, t as clampIndex, u as statusMark } from "./monitor-rendering-LGr9Ebd_.mjs";
4
- import path from "node:path";
5
- import fs from "node:fs/promises";
6
- import YAML from "yaml";
7
- import { useEffect, useMemo, useRef, useState } from "react";
8
- import { Box, Text, useApp, useInput, useStdout } from "ink";
9
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
- //#region src/tui/monitor-data.ts
11
- async function loadMonitorSnapshot(runArg) {
12
- const locator = await resolveRunLocator(runArg);
13
- const index = await syncRun(locator.cwd, locator.runId, { startPending: false });
14
- const spec = WorkflowSpecSchema.parse(YAML.parse(await fs.readFile(path.join(runDir(locator.runId, locator.cwd), "workflow.spec.yaml"), "utf8")));
15
- return {
16
- locator,
17
- view: await buildRunMonitorView(locator.cwd, spec, index)
18
- };
19
- }
20
- async function loadTaskDetail(locator, taskId) {
21
- const index = await syncRun(locator.cwd, locator.runId, { startPending: false });
22
- const spec = WorkflowSpecSchema.parse(YAML.parse(await fs.readFile(path.join(runDir(locator.runId, locator.cwd), "workflow.spec.yaml"), "utf8")));
23
- return buildTaskDetailView(locator.cwd, spec, index, taskId);
24
- }
25
- //#endregion
26
- //#region src/tui/monitor-app.tsx
27
- function MonitorApp({ runArg, pollMs = 1e3, initialView, initialLocator, initialFocus = "stages", loadSnapshot = loadMonitorSnapshot, loadDetail = loadTaskDetail }) {
28
- const { exit } = useApp();
29
- const { stdout } = useStdout();
30
- const [locator, setLocator] = useState(initialLocator);
31
- const [view, setView] = useState(initialView);
32
- const [error, setError] = useState();
33
- const [focus, setFocus] = useState(initialFocus);
34
- const [stageIndex, setStageIndex] = useState(() => initialView ? defaultStageIndex(initialView.stages) : 0);
35
- const [taskIndex, setTaskIndex] = useState(void 0);
36
- const [detailTaskId, setDetailTaskId] = useState();
37
- const [detail, setDetail] = useState();
38
- const refreshRequest = useRef(0);
39
- const detailRequest = useRef(0);
40
- const hasLoadedView = useRef(Boolean(initialView));
41
- const userSelectedStage = useRef(false);
42
- async function refresh() {
43
- const requestId = ++refreshRequest.current;
44
- try {
45
- const snapshot = await loadSnapshot(runArg);
46
- if (requestId !== refreshRequest.current) return;
47
- const shouldSelectDefaultStage = !hasLoadedView.current && !userSelectedStage.current;
48
- setLocator(snapshot.locator);
49
- setView(snapshot.view);
50
- setError(void 0);
51
- setStageIndex((current) => {
52
- if (shouldSelectDefaultStage) return defaultStageIndex(snapshot.view.stages);
53
- return clampIndex(current, snapshot.view.stages.length);
54
- });
55
- hasLoadedView.current = true;
56
- } catch (loadError) {
57
- if (requestId !== refreshRequest.current) return;
58
- setError(loadError instanceof Error ? loadError.message : String(loadError));
59
- }
60
- }
61
- useEffect(() => {
62
- refresh();
63
- const timer = setInterval(() => void refresh(), pollMs);
64
- return () => clearInterval(timer);
65
- }, [
66
- runArg,
67
- pollMs,
68
- loadSnapshot
69
- ]);
70
- const selectedStage = view?.stages[stageIndex];
71
- const stageTasks = useMemo(() => tasksForStage(view, selectedStage?.id), [view, selectedStage?.id]);
72
- const selectedTask = taskIndex === void 0 ? void 0 : stageTasks[taskIndex];
73
- const panelWidths = useMemo(() => monitorPanelWidths(stdout.columns), [stdout.columns]);
74
- useEffect(() => {
75
- setTaskIndex(void 0);
76
- setDetailTaskId(void 0);
77
- setDetail(void 0);
78
- }, [selectedStage?.id]);
79
- useEffect(() => {
80
- setTaskIndex((current) => current === void 0 ? void 0 : clampIndex(current, stageTasks.length));
81
- }, [stageTasks.length]);
82
- useEffect(() => {
83
- setDetailTaskId(selectedTask?.id);
84
- }, [selectedTask?.id]);
85
- useEffect(() => {
86
- const requestId = ++detailRequest.current;
87
- if (!locator || !detailTaskId) {
88
- setDetail(void 0);
89
- return;
90
- }
91
- setDetail(void 0);
92
- loadDetail(locator, detailTaskId).then((nextDetail) => {
93
- if (requestId !== detailRequest.current) return;
94
- setDetail(nextDetail);
95
- setError(void 0);
96
- }).catch((detailError) => {
97
- if (requestId !== detailRequest.current) return;
98
- setDetail(void 0);
99
- setError(detailError instanceof Error ? detailError.message : String(detailError));
100
- });
101
- }, [
102
- locator?.runId,
103
- detailTaskId,
104
- loadDetail
105
- ]);
106
- useInput((input, key) => {
107
- if (input === "q" || key.ctrl && input === "c") exit();
108
- if (input === "r") refresh();
109
- if (key.escape && focus === "detail") {
110
- setFocus("tasks");
111
- return;
112
- }
113
- if (key.leftArrow) {
114
- if (focus === "detail") setFocus("tasks");
115
- else if (focus === "tasks") setFocus("stages");
116
- return;
117
- }
118
- if (key.rightArrow) {
119
- if (focus === "stages" && stageTasks.length > 0) {
120
- setTaskIndex((current) => current ?? 0);
121
- setFocus("tasks");
122
- } else if (focus === "tasks" && selectedTask) setFocus("detail");
123
- return;
124
- }
125
- if (key.upArrow) {
126
- if (focus === "tasks") setTaskIndex((current) => nextIndex(current ?? 0, -1, stageTasks.length));
127
- else if (focus === "stages") {
128
- userSelectedStage.current = true;
129
- setStageIndex((current) => nextIndex(current, -1, view?.stages.length ?? 0));
130
- }
131
- }
132
- if (key.downArrow) {
133
- if (focus === "tasks") setTaskIndex((current) => nextIndex(current ?? 0, 1, stageTasks.length));
134
- else if (focus === "stages") {
135
- userSelectedStage.current = true;
136
- setStageIndex((current) => nextIndex(current, 1, view?.stages.length ?? 0));
137
- }
138
- }
139
- if (key.return && focus === "tasks" && selectedTask) setFocus("detail");
140
- });
141
- if (!view) return /* @__PURE__ */ jsx(Text, { children: error ? `Error: ${error}` : "Loading monitor..." });
142
- return /* @__PURE__ */ jsxs(Box, {
143
- flexDirection: "column",
144
- children: [
145
- /* @__PURE__ */ jsx(Header, { view }),
146
- error ? /* @__PURE__ */ jsxs(Text, {
147
- color: "red",
148
- children: ["Error: ", error]
149
- }) : null,
150
- /* @__PURE__ */ jsxs(Box, {
151
- marginTop: 1,
152
- children: [
153
- /* @__PURE__ */ jsx(StageList, {
154
- view,
155
- selectedIndex: stageIndex,
156
- focused: focus === "stages",
157
- width: panelWidths.stages
158
- }),
159
- /* @__PURE__ */ jsx(StageTaskPanel, {
160
- view,
161
- stage: selectedStage,
162
- tasks: stageTasks,
163
- selectedIndex: taskIndex,
164
- focused: focus === "tasks",
165
- width: panelWidths.tasks
166
- }),
167
- /* @__PURE__ */ jsx(DetailPanel, {
168
- detail,
169
- focused: focus === "detail",
170
- width: panelWidths.detail
171
- })
172
- ]
173
- }),
174
- /* @__PURE__ */ jsx(Text, {
175
- dimColor: true,
176
- children: "up/down move - left/right panel - enter detail - esc back - r refresh - q quit"
177
- })
178
- ]
179
- });
180
- }
181
- function Header({ view }) {
182
- const title = `${view.run.workflowName}`;
183
- const worker = view.run.worker ? ` - worker ${view.run.worker.status}` : "";
184
- const runTime = formatDuration(view.run.durationMs ?? view.run.elapsedMs);
185
- const meta = `${runProgressLabel(view)} - ${runStatusLabel(view)}${runTime ? ` - ${runTime}` : ""}${worker}`;
186
- return /* @__PURE__ */ jsxs(Box, {
187
- flexDirection: "column",
188
- children: [/* @__PURE__ */ jsx(Text, {
189
- color: "blue",
190
- bold: true,
191
- children: title
192
- }), /* @__PURE__ */ jsxs(Text, {
193
- dimColor: true,
194
- children: [
195
- shorten(view.run.logicalRunId, 48),
196
- " - ",
197
- meta
198
- ]
199
- })]
200
- });
201
- }
202
- function StageList({ view, selectedIndex, focused, width }) {
203
- const currentStage = view.stages.find((stage) => stage.status === "running") ?? view.stages.find((stage) => stage.status === "blocked" || stage.status === "failed") ?? view.stages[selectedIndex];
204
- const finished = view.stages.filter((stage) => stage.status === "completed" || stage.status === "skipped").length;
205
- return /* @__PURE__ */ jsxs(Box, {
206
- flexDirection: "column",
207
- width,
208
- borderStyle: "single",
209
- paddingX: 1,
210
- children: [
211
- /* @__PURE__ */ jsxs(Text, {
212
- bold: true,
213
- children: [focused ? "🟢 " : " ", "Stage List"]
214
- }),
215
- /* @__PURE__ */ jsxs(Text, {
216
- dimColor: true,
217
- children: ["Current: ", shorten(currentStage?.id ?? "-", Math.max(6, width - 13))]
218
- }),
219
- /* @__PURE__ */ jsxs(Text, {
220
- dimColor: true,
221
- children: [
222
- "Finished: ",
223
- finished,
224
- "/",
225
- view.stages.length
226
- ]
227
- }),
228
- view.stages.map((stage, index) => /* @__PURE__ */ jsxs(Box, { children: [
229
- /* @__PURE__ */ jsxs(Text, { children: [focused && index === selectedIndex ? "▶" : " ", " "] }),
230
- /* @__PURE__ */ jsx(StatusMark, { status: stage.status }),
231
- /* @__PURE__ */ jsxs(Text, { children: [
232
- " ",
233
- shorten(stage.id, 22),
234
- " ",
235
- stageProgressLabel(stage)
236
- ] })
237
- ] }, stage.id))
238
- ]
239
- });
240
- }
241
- function StageTaskPanel({ view, stage, tasks, selectedIndex, focused, width }) {
242
- const counts = stage?.taskCounts;
243
- const stageTime = formatDuration(stage?.durationMs ?? stage?.elapsedMs);
244
- return /* @__PURE__ */ jsxs(Box, {
245
- flexDirection: "column",
246
- width,
247
- borderStyle: "single",
248
- paddingX: 1,
249
- children: [
250
- /* @__PURE__ */ jsxs(Text, {
251
- bold: true,
252
- children: [focused ? "🟢 " : " ", "Stage Info"]
253
- }),
254
- stage ? /* @__PURE__ */ jsxs(Fragment, { children: [
255
- /* @__PURE__ */ jsxs(Text, { children: [
256
- shorten(stage.id, 28),
257
- " ",
258
- /* @__PURE__ */ jsx(Text, {
259
- dimColor: true,
260
- children: stage.kind
261
- })
262
- ] }),
263
- /* @__PURE__ */ jsxs(Text, { children: [
264
- "Status: ",
265
- stage.status,
266
- counts ? ` - ${counts.completed}/${counts.total} tasks` : "",
267
- stageTime ? ` - ${stageTime}` : ""
268
- ] }),
269
- stage.dependsOn.length > 0 ? /* @__PURE__ */ jsxs(Text, {
270
- dimColor: true,
271
- children: ["Depends: ", shorten(stage.dependsOn.join(", "), 34)]
272
- }) : null,
273
- stage.blockedReason ? /* @__PURE__ */ jsxs(Text, {
274
- color: "red",
275
- children: ["Reason: ", shorten(stage.blockedReason, 34)]
276
- }) : null,
277
- stage.outputPath ? /* @__PURE__ */ jsxs(Text, {
278
- dimColor: true,
279
- children: ["Output: ", shorten(stage.outputPath, 34)]
280
- }) : null,
281
- stage.kind === "gate" && view.run.gateVerdict ? /* @__PURE__ */ jsxs(Text, {
282
- dimColor: true,
283
- children: ["Gate: ", view.run.gateVerdict]
284
- }) : null
285
- ] }) : /* @__PURE__ */ jsx(Text, {
286
- dimColor: true,
287
- children: "No stage selected"
288
- }),
289
- /* @__PURE__ */ jsx(Text, {
290
- bold: true,
291
- children: "Tasks"
292
- }),
293
- tasks.length === 0 ? /* @__PURE__ */ jsx(Text, {
294
- dimColor: true,
295
- children: "No known Stage Tasks"
296
- }) : null,
297
- tasks.map((task, index) => /* @__PURE__ */ jsxs(Box, { children: [
298
- /* @__PURE__ */ jsxs(Text, { children: [focused && index === selectedIndex ? "▶" : " ", " "] }),
299
- /* @__PURE__ */ jsx(StatusMark, { status: task.status }),
300
- /* @__PURE__ */ jsxs(Text, { children: [" ", shorten(task.label, Math.max(16, width - 24))] }),
301
- /* @__PURE__ */ jsxs(Text, {
302
- dimColor: true,
303
- children: [
304
- " ",
305
- shorten(task.agent ?? task.execution, 10),
306
- " ",
307
- shorten(formatDuration(task.durationMs ?? task.elapsedMs), 7),
308
- " ",
309
- shorten(task.blockedReason ?? task.errorCode ?? "", 10)
310
- ]
311
- })
312
- ] }, task.id))
313
- ]
314
- });
315
- }
316
- function DetailPanel({ detail, focused, width }) {
317
- return /* @__PURE__ */ jsxs(Box, {
318
- flexDirection: "column",
319
- width,
320
- borderStyle: "single",
321
- paddingX: 1,
322
- children: [
323
- /* @__PURE__ */ jsxs(Text, {
324
- bold: true,
325
- children: [focused ? "🟢 " : " ", "Task Detail"]
326
- }),
327
- detail ? /* @__PURE__ */ jsx(Text, { children: shorten(detail.task.label, 60) }) : null,
328
- detailSummary(detail).slice(0, 16).map((line, index) => /* @__PURE__ */ jsx(Text, {
329
- dimColor: index > 0,
330
- children: shorten(line, 100)
331
- }, `${index}-${line}`))
332
- ]
333
- });
334
- }
335
- function monitorPanelWidths(columns) {
336
- const total = Math.max(80, columns ?? 120);
337
- const stages = Math.max(16, Math.floor(total * .2));
338
- const tasks = Math.max(40, Math.floor(total * .5));
339
- return {
340
- stages,
341
- tasks,
342
- detail: Math.max(32, total - stages - tasks)
343
- };
344
- }
345
- function StatusMark({ status }) {
346
- const mark = statusMark(status);
347
- if (status === "completed") return /* @__PURE__ */ jsx(Text, {
348
- color: "green",
349
- children: mark
350
- });
351
- if (status === "running" || status === "raw_received" || status === "parsing") return /* @__PURE__ */ jsx(Text, {
352
- color: "yellow",
353
- children: mark
354
- });
355
- if (status === "blocked" || status === "failed" || status === "cancelled" || status === "timed_out") return /* @__PURE__ */ jsx(Text, {
356
- color: "red",
357
- children: mark
358
- });
359
- if (status === "skipped") return /* @__PURE__ */ jsx(Text, {
360
- dimColor: true,
361
- children: mark
362
- });
363
- return /* @__PURE__ */ jsx(Text, {
364
- dimColor: true,
365
- children: mark
366
- });
367
- }
368
- //#endregion
369
- export { MonitorApp };