patchrelay 0.35.11 → 0.35.13
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 +41 -9
- package/dist/build-info.json +3 -3
- package/dist/cli/args.js +19 -1
- package/dist/cli/commands/issues.js +18 -56
- package/dist/cli/commands/watch.js +5 -0
- package/dist/cli/data.js +160 -47
- package/dist/cli/formatters/text.js +51 -90
- package/dist/cli/help.js +15 -8
- package/dist/cli/index.js +3 -58
- package/dist/cli/operator-client.js +0 -82
- package/dist/cli/watch/App.js +21 -12
- package/dist/cli/watch/HelpBar.js +3 -3
- package/dist/cli/watch/IssueDetailView.js +63 -130
- package/dist/cli/watch/IssueRow.js +82 -27
- package/dist/cli/watch/StatusBar.js +8 -4
- package/dist/cli/watch/detail-rows.js +589 -0
- package/dist/cli/watch/render-rich-text.js +226 -0
- package/dist/cli/watch/state-visualization.js +48 -23
- package/dist/cli/watch/timeline-builder.js +2 -1
- package/dist/cli/watch/use-detail-stream.js +10 -104
- package/dist/cli/watch/use-watch-stream.js +11 -102
- package/dist/cli/watch/watch-state.js +129 -56
- package/dist/codex-thread-utils.js +3 -0
- package/dist/db/migrations.js +239 -2
- package/dist/db.js +628 -39
- package/dist/github-app-token.js +7 -0
- package/dist/github-failure-context.js +44 -1
- package/dist/github-rollup.js +47 -0
- package/dist/github-webhook-handler.js +423 -52
- package/dist/github-webhooks.js +7 -0
- package/dist/http.js +12 -264
- package/dist/idle-reconciliation.js +268 -76
- package/dist/issue-query-service.js +221 -129
- package/dist/issue-session-events.js +151 -0
- package/dist/issue-session.js +99 -0
- package/dist/linear-client.js +39 -25
- package/dist/linear-session-reporting.js +12 -0
- package/dist/linear-session-sync.js +253 -24
- package/dist/linear-workflow.js +33 -0
- package/dist/merge-queue-protocol.js +0 -51
- package/dist/preflight.js +1 -4
- package/dist/queue-health-monitor.js +11 -7
- package/dist/run-orchestrator.js +1364 -147
- package/dist/run-reporting.js +5 -3
- package/dist/service.js +279 -102
- package/dist/status-note.js +56 -0
- package/dist/waiting-reason.js +65 -0
- package/dist/webhook-handler.js +270 -79
- package/package.json +3 -2
- package/dist/cli/commands/feed.js +0 -60
- package/dist/cli/watch/FeedView.js +0 -28
- package/dist/cli/watch/use-feed-stream.js +0 -92
|
@@ -1,138 +1,71 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useReducer } from "react";
|
|
3
|
-
import { Box, Text } from "ink";
|
|
4
|
-
import { Timeline } from "./Timeline.js";
|
|
5
|
-
import { StateHistoryView } from "./StateHistoryView.js";
|
|
6
|
-
import { buildStateHistory } from "./history-builder.js";
|
|
3
|
+
import { Box, Text, useStdout } from "ink";
|
|
7
4
|
import { HelpBar } from "./HelpBar.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
17
|
-
if (n >= 1_000)
|
|
18
|
-
return `${(n / 1_000).toFixed(1)}k`;
|
|
19
|
-
return String(n);
|
|
20
|
-
}
|
|
21
|
-
function formatReviewState(reviewState) {
|
|
22
|
-
switch (reviewState) {
|
|
23
|
-
case "approved":
|
|
24
|
-
return "approved";
|
|
25
|
-
case "changes_requested":
|
|
26
|
-
return "changes requested";
|
|
27
|
-
case "commented":
|
|
28
|
-
return "commented";
|
|
29
|
-
default:
|
|
30
|
-
return reviewState ? reviewState.replaceAll("_", " ") : null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function formatCheckState(checkState) {
|
|
34
|
-
switch (checkState) {
|
|
35
|
-
case "passed":
|
|
36
|
-
case "success":
|
|
37
|
-
return "checks passed";
|
|
38
|
-
case "failed":
|
|
39
|
-
case "failure":
|
|
40
|
-
return "checks failed";
|
|
41
|
-
case "pending":
|
|
42
|
-
case "in_progress":
|
|
43
|
-
case "queued":
|
|
44
|
-
return "checks pending";
|
|
45
|
-
default:
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const STATE_DISPLAY = {
|
|
50
|
-
blocked: { label: "blocked", color: "yellow" },
|
|
51
|
-
ready: { label: "ready", color: "blueBright" },
|
|
52
|
-
delegated: { label: "delegated", color: "cyan" },
|
|
53
|
-
implementing: { label: "implementing", color: "cyan" },
|
|
54
|
-
pr_open: { label: "PR open", color: "cyan" },
|
|
55
|
-
changes_requested: { label: "review changes", color: "yellow" },
|
|
56
|
-
repairing_ci: { label: "repairing CI", color: "yellow" },
|
|
57
|
-
awaiting_queue: { label: "queued for merge", color: "cyan" },
|
|
58
|
-
repairing_queue: { label: "repairing queue", color: "yellow" },
|
|
59
|
-
done: { label: "merged", color: "green" },
|
|
60
|
-
failed: { label: "failed", color: "red" },
|
|
61
|
-
escalated: { label: "escalated", color: "red" },
|
|
62
|
-
awaiting_input: { label: "awaiting input", color: "yellow" },
|
|
63
|
-
};
|
|
64
|
-
function effectiveState(issue) {
|
|
65
|
-
if (issue.blockedByCount > 0 && !issue.activeRunType)
|
|
66
|
-
return "blocked";
|
|
67
|
-
if (issue.readyForExecution && !issue.activeRunType)
|
|
68
|
-
return "ready";
|
|
69
|
-
return issue.factoryState;
|
|
70
|
-
}
|
|
71
|
-
function blockerText(issue, issueContext) {
|
|
72
|
-
if (issue.blockedByCount > 0)
|
|
73
|
-
return `Waiting on ${issue.blockedByKeys.join(", ")}`;
|
|
74
|
-
if (issue.factoryState === "repairing_queue")
|
|
75
|
-
return "Merge queue conflict, repairing branch";
|
|
76
|
-
if (issue.factoryState === "repairing_ci") {
|
|
77
|
-
const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "CI";
|
|
78
|
-
return `Repairing ${check}`;
|
|
79
|
-
}
|
|
80
|
-
if (issue.factoryState === "awaiting_queue")
|
|
81
|
-
return "Waiting for merge queue";
|
|
82
|
-
if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
|
|
83
|
-
const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "checks";
|
|
84
|
-
return `${check} failed`;
|
|
85
|
-
}
|
|
86
|
-
if (issue.prReviewState === "changes_requested")
|
|
87
|
-
return "Review changes requested";
|
|
88
|
-
if (issue.prNumber !== undefined && !issue.prReviewState && issue.factoryState !== "done")
|
|
89
|
-
return "Awaiting review";
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
function ElapsedTime({ startedAt }) {
|
|
93
|
-
const [, tick] = useReducer((c) => c + 1, 0);
|
|
5
|
+
import { buildDetailLines } from "./detail-rows.js";
|
|
6
|
+
export function IssueDetailView({ issue, timeline, follow, scrollOffset, unreadBelow, activeRunStartedAt, activeRunId, tokenUsage, diffSummary, plan, issueContext, detailTab, rawRuns, rawFeedEvents, connected, lastServerMessageAt, reservedRows = 0, onLayoutChange, }) {
|
|
7
|
+
const [, tick] = useReducer((value) => value + 1, 0);
|
|
8
|
+
const { stdout } = useStdout();
|
|
9
|
+
const width = Math.max(20, stdout?.columns ?? 80);
|
|
10
|
+
const totalRows = stdout?.rows ?? 24;
|
|
11
|
+
const footerRows = 1 + (unreadBelow > 0 ? 1 : 0);
|
|
12
|
+
const viewportRows = Math.max(4, totalRows - reservedRows - footerRows);
|
|
94
13
|
useEffect(() => {
|
|
95
|
-
const id = setInterval(tick,
|
|
14
|
+
const id = setInterval(tick, 1_000);
|
|
96
15
|
return () => clearInterval(id);
|
|
97
16
|
}, []);
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
17
|
+
const lines = useMemo(() => {
|
|
18
|
+
if (!issue) {
|
|
19
|
+
return [{ key: "loading", segments: [{ text: "Loading issue…", dimColor: true }] }];
|
|
20
|
+
}
|
|
21
|
+
return buildDetailLines({
|
|
22
|
+
issue,
|
|
23
|
+
timeline,
|
|
24
|
+
activeRunStartedAt,
|
|
25
|
+
activeRunId,
|
|
26
|
+
tokenUsage,
|
|
27
|
+
diffSummary,
|
|
28
|
+
plan,
|
|
29
|
+
issueContext,
|
|
30
|
+
detailTab,
|
|
31
|
+
rawRuns,
|
|
32
|
+
rawFeedEvents,
|
|
33
|
+
follow,
|
|
34
|
+
connected,
|
|
35
|
+
lastServerMessageAt,
|
|
36
|
+
width,
|
|
37
|
+
});
|
|
38
|
+
}, [
|
|
39
|
+
issue,
|
|
40
|
+
timeline,
|
|
41
|
+
activeRunStartedAt,
|
|
42
|
+
activeRunId,
|
|
43
|
+
tokenUsage,
|
|
44
|
+
diffSummary,
|
|
45
|
+
plan,
|
|
46
|
+
issueContext,
|
|
47
|
+
detailTab,
|
|
48
|
+
rawRuns,
|
|
49
|
+
rawFeedEvents,
|
|
50
|
+
follow,
|
|
51
|
+
connected,
|
|
52
|
+
lastServerMessageAt,
|
|
53
|
+
width,
|
|
54
|
+
]);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
onLayoutChange(viewportRows, lines.length);
|
|
57
|
+
}, [lines.length, onLayoutChange, viewportRows]);
|
|
58
|
+
const maxOffset = Math.max(0, lines.length - viewportRows);
|
|
59
|
+
const start = Math.min(scrollOffset, maxOffset);
|
|
60
|
+
const visibleLines = lines.slice(start, start + viewportRows);
|
|
61
|
+
const fillerCount = Math.max(0, viewportRows - visibleLines.length);
|
|
62
|
+
return (_jsxs(Box, { flexDirection: "column", children: [visibleLines.map((line) => (_jsx(RenderedLine, { line: line }, line.key))), Array.from({ length: fillerCount }, (_, index) => (_jsx(Text, { children: " " }, `detail-fill-${index}`))), unreadBelow > 0 && (_jsx(Text, { color: "yellow", children: `${unreadBelow} below · End jumps back to live` })), _jsx(HelpBar, { view: "detail", follow: follow, detailTab: detailTab })] }));
|
|
102
63
|
}
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
const key = issue.issueKey ?? issue.projectId;
|
|
108
|
-
const meta = [];
|
|
109
|
-
if (tokenUsage)
|
|
110
|
-
meta.push(`${formatTokens(tokenUsage.inputTokens)} in / ${formatTokens(tokenUsage.outputTokens)} out`);
|
|
111
|
-
if (diffSummary && diffSummary.filesChanged > 0)
|
|
112
|
-
meta.push(`${diffSummary.filesChanged}f +${diffSummary.linesAdded} -${diffSummary.linesRemoved}`);
|
|
113
|
-
if (issueContext?.runCount)
|
|
114
|
-
meta.push(`${issueContext.runCount} runs`);
|
|
115
|
-
const state = STATE_DISPLAY[effectiveState(issue)] ?? { label: issue.factoryState, color: "white" };
|
|
116
|
-
const blocker = blockerText(issue, issueContext);
|
|
117
|
-
const history = useMemo(() => buildStateHistory(rawRuns, rawFeedEvents, issue.factoryState, activeRunId), [rawRuns, rawFeedEvents, issue.factoryState, activeRunId]);
|
|
118
|
-
const graph = useMemo(() => buildPatchRelayStateGraph(history, issue.factoryState), [history, issue.factoryState]);
|
|
119
|
-
const queueObservations = useMemo(() => buildPatchRelayQueueObservations(issue, rawFeedEvents), [issue, rawFeedEvents]);
|
|
120
|
-
// Build compact facts for the header
|
|
121
|
-
const facts = [];
|
|
122
|
-
if (issue.prNumber !== undefined)
|
|
123
|
-
facts.push(`PR #${issue.prNumber}`);
|
|
124
|
-
if (issue.prReviewState === "approved")
|
|
125
|
-
facts.push("approved");
|
|
126
|
-
else if (issue.prReviewState === "changes_requested")
|
|
127
|
-
facts.push("changes requested");
|
|
128
|
-
if (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
|
|
129
|
-
facts.push("checks passed");
|
|
130
|
-
else if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
|
|
131
|
-
const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "checks";
|
|
132
|
-
facts.push(`${check} failed`);
|
|
133
|
-
}
|
|
134
|
-
else if (issue.prChecksSummary?.total) {
|
|
135
|
-
facts.push(`checks ${issue.prChecksSummary.completed}/${issue.prChecksSummary.total}`);
|
|
64
|
+
function RenderedLine({ line }) {
|
|
65
|
+
if (line.segments.length === 0) {
|
|
66
|
+
return _jsx(Text, { children: " " });
|
|
136
67
|
}
|
|
137
|
-
return (
|
|
68
|
+
return (_jsx(Text, { children: line.segments.map((segment, index) => (_jsx(Text
|
|
69
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
70
|
+
, { ...(segment.color ? { color: segment.color } : {}), ...(segment.dimColor ? { dimColor: true } : {}), ...(segment.bold ? { bold: true } : {}), children: segment.text }, `${line.key}-${index}`))) }));
|
|
138
71
|
}
|
|
@@ -3,48 +3,94 @@ import { Box, Text } from "ink";
|
|
|
3
3
|
import { summarizeIssueStatusNote } from "./issue-status-note.js";
|
|
4
4
|
import { relativeTime, truncate } from "./format-utils.js";
|
|
5
5
|
// ─── State display ──────────────────────────────────────────────
|
|
6
|
-
const TERMINAL_STATES = new Set(["done", "failed", "escalated"
|
|
6
|
+
const TERMINAL_STATES = new Set(["done", "failed", "escalated"]);
|
|
7
|
+
function needsOperatorIntervention(issue) {
|
|
8
|
+
return issue.sessionState === "failed" || issue.factoryState === "failed" || issue.factoryState === "escalated";
|
|
9
|
+
}
|
|
7
10
|
function effectiveState(issue) {
|
|
11
|
+
if (issue.sessionState === "done")
|
|
12
|
+
return "done";
|
|
13
|
+
if (issue.sessionState === "failed")
|
|
14
|
+
return "failed";
|
|
8
15
|
if (issue.blockedByCount > 0 && !issue.activeRunType)
|
|
9
16
|
return "blocked";
|
|
10
17
|
if (issue.readyForExecution && !issue.activeRunType)
|
|
11
18
|
return "ready";
|
|
19
|
+
if (issue.sessionState === "waiting_input")
|
|
20
|
+
return "awaiting_input";
|
|
12
21
|
return issue.factoryState;
|
|
13
22
|
}
|
|
14
|
-
function
|
|
23
|
+
function sessionDisplay(issue) {
|
|
24
|
+
if (needsOperatorIntervention(issue)) {
|
|
25
|
+
return { label: "needs help", color: "red" };
|
|
26
|
+
}
|
|
27
|
+
switch (issue.sessionState) {
|
|
28
|
+
case "running":
|
|
29
|
+
return { label: "running", color: "cyan" };
|
|
30
|
+
case "idle":
|
|
31
|
+
return { label: "idle", color: "blueBright" };
|
|
32
|
+
case "waiting_input":
|
|
33
|
+
return { label: "needs input", color: "yellow" };
|
|
34
|
+
case "done":
|
|
35
|
+
return { label: "done", color: "green" };
|
|
36
|
+
case "failed":
|
|
37
|
+
return { label: "failed", color: "red" };
|
|
38
|
+
default:
|
|
39
|
+
return { label: "unknown", color: "white" };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function stageLabel(issue) {
|
|
15
43
|
const state = effectiveState(issue);
|
|
16
44
|
switch (state) {
|
|
17
|
-
case "blocked": return
|
|
18
|
-
case "ready": return
|
|
19
|
-
case "delegated": return
|
|
20
|
-
case "implementing": return
|
|
21
|
-
case "pr_open": return
|
|
22
|
-
case "changes_requested": return
|
|
23
|
-
case "repairing_ci": return
|
|
24
|
-
case "awaiting_queue": return
|
|
25
|
-
case "repairing_queue": return
|
|
26
|
-
case "done": return
|
|
27
|
-
case "failed": return
|
|
28
|
-
case "escalated": return
|
|
29
|
-
case "awaiting_input": return
|
|
30
|
-
default: return
|
|
45
|
+
case "blocked": return "blocked";
|
|
46
|
+
case "ready": return "ready";
|
|
47
|
+
case "delegated": return "delegated";
|
|
48
|
+
case "implementing": return "implementing";
|
|
49
|
+
case "pr_open": return "PR open";
|
|
50
|
+
case "changes_requested": return "review changes";
|
|
51
|
+
case "repairing_ci": return "repairing CI";
|
|
52
|
+
case "awaiting_queue": return "waiting downstream";
|
|
53
|
+
case "repairing_queue": return "repairing queue";
|
|
54
|
+
case "done": return "merged";
|
|
55
|
+
case "failed": return "failed";
|
|
56
|
+
case "escalated": return "escalated";
|
|
57
|
+
case "awaiting_input": return "needs input";
|
|
58
|
+
default: return state;
|
|
31
59
|
}
|
|
32
60
|
}
|
|
33
61
|
// ─── Context facts (what matters right now) ─────────────────────
|
|
34
|
-
function buildFacts(issue) {
|
|
62
|
+
function buildFacts(issue, selected) {
|
|
35
63
|
const facts = [];
|
|
64
|
+
const rereviewNeeded = issue.prReviewState === "changes_requested"
|
|
65
|
+
&& (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
|
|
66
|
+
&& !issue.activeRunType;
|
|
36
67
|
// PR number
|
|
37
68
|
if (issue.prNumber !== undefined) {
|
|
38
69
|
facts.push({ text: `PR #${issue.prNumber}` });
|
|
39
70
|
}
|
|
71
|
+
if (!issue.sessionState) {
|
|
72
|
+
facts.push({ text: `stage ${stageLabel(issue)}` });
|
|
73
|
+
}
|
|
74
|
+
else if (selected) {
|
|
75
|
+
facts.push({ text: `internal stage ${stageLabel(issue)}` });
|
|
76
|
+
}
|
|
77
|
+
if (issue.waitingReason && issue.sessionState === "waiting_input") {
|
|
78
|
+
facts.push({ text: issue.waitingReason, color: "yellow" });
|
|
79
|
+
}
|
|
80
|
+
if (needsOperatorIntervention(issue)) {
|
|
81
|
+
facts.push({ text: "operator action needed", color: "red" });
|
|
82
|
+
}
|
|
40
83
|
// Review state — only show when it matters (not yet approved, or changes requested)
|
|
41
84
|
if (issue.prReviewState === "approved") {
|
|
42
85
|
facts.push({ text: "approved", color: "green" });
|
|
43
86
|
}
|
|
87
|
+
else if (rereviewNeeded) {
|
|
88
|
+
facts.push({ text: "re-review needed", color: "yellow" });
|
|
89
|
+
}
|
|
44
90
|
else if (issue.prReviewState === "changes_requested") {
|
|
45
91
|
facts.push({ text: "changes requested", color: "yellow" });
|
|
46
92
|
}
|
|
47
|
-
else if (issue.prNumber !== undefined && !issue.prReviewState && !TERMINAL_STATES.has(issue
|
|
93
|
+
else if (issue.prNumber !== undefined && !issue.prReviewState && !TERMINAL_STATES.has(effectiveState(issue))) {
|
|
48
94
|
facts.push({ text: "awaiting review", color: "yellow" });
|
|
49
95
|
}
|
|
50
96
|
// Check status — compact
|
|
@@ -74,20 +120,29 @@ function buildFacts(issue) {
|
|
|
74
120
|
}
|
|
75
121
|
// ─── What's blocking progress ───────────────────────────────────
|
|
76
122
|
function blockerText(issue) {
|
|
123
|
+
const rereviewNeeded = issue.prReviewState === "changes_requested"
|
|
124
|
+
&& (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
|
|
125
|
+
&& !issue.activeRunType;
|
|
126
|
+
if (issue.sessionState === "waiting_input")
|
|
127
|
+
return issue.waitingReason ?? "Waiting for input";
|
|
128
|
+
if (needsOperatorIntervention(issue))
|
|
129
|
+
return issue.statusNote ?? issue.waitingReason ?? "Needs operator intervention";
|
|
130
|
+
if (issue.waitingReason && !issue.activeRunType)
|
|
131
|
+
return issue.waitingReason;
|
|
77
132
|
if (issue.blockedByCount > 0)
|
|
78
133
|
return `Waiting on ${issue.blockedByKeys.join(", ")}`;
|
|
79
|
-
if (issue
|
|
134
|
+
if (effectiveState(issue) === "repairing_queue")
|
|
80
135
|
return "Merge queue conflict, repairing branch";
|
|
81
|
-
if (issue
|
|
136
|
+
if (effectiveState(issue) === "repairing_ci") {
|
|
82
137
|
const check = issue.latestFailureCheckName ?? "CI";
|
|
83
138
|
return `Repairing ${check}`;
|
|
84
139
|
}
|
|
85
|
-
if (issue.factoryState === "awaiting_queue")
|
|
86
|
-
return "Waiting for merge queue";
|
|
87
140
|
if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
|
|
88
141
|
const check = issue.latestFailureCheckName ?? "checks";
|
|
89
142
|
return `${check} failed`;
|
|
90
143
|
}
|
|
144
|
+
if (rereviewNeeded)
|
|
145
|
+
return "Awaiting re-review after requested changes";
|
|
91
146
|
if (issue.prReviewState === "changes_requested")
|
|
92
147
|
return "Review changes requested";
|
|
93
148
|
return null;
|
|
@@ -98,13 +153,13 @@ export function IssueRow({ issue, selected, titleWidth }) {
|
|
|
98
153
|
const tw = titleWidth ?? 60;
|
|
99
154
|
const title = issue.title ? truncate(issue.title, tw) : "";
|
|
100
155
|
const detail = selected ? summarizeIssueStatusNote(issue.statusNote) : undefined;
|
|
101
|
-
const
|
|
102
|
-
const facts = buildFacts(issue);
|
|
156
|
+
const session = sessionDisplay(issue);
|
|
157
|
+
const facts = buildFacts(issue, selected);
|
|
103
158
|
const blocker = selected ? blockerText(issue) : null;
|
|
104
|
-
const isTerminal = TERMINAL_STATES.has(issue
|
|
159
|
+
const isTerminal = TERMINAL_STATES.has(effectiveState(issue));
|
|
105
160
|
// Terminal issues: compact single line
|
|
106
161
|
if (isTerminal && !selected) {
|
|
107
|
-
return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { dimColor: true, children: ` ${key}` }), _jsx(Text, { dimColor: true, children: ` ${relativeTime(issue.updatedAt).padStart(4)}` }), _jsx(Text, { children: ` ` }), _jsx(Text, { color:
|
|
162
|
+
return (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { dimColor: true, children: ` ${key}` }), _jsx(Text, { dimColor: true, children: ` ${relativeTime(issue.updatedAt).padStart(4)}` }), _jsx(Text, { children: ` ` }), _jsx(Text, { color: session.color, children: session.label })] }));
|
|
108
163
|
}
|
|
109
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: detail ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "gray", children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key}` }), _jsx(Text, { dimColor: true, children: ` ${relativeTime(issue.updatedAt).padStart(4)}` }), _jsx(Text, { children: ` ` }), _jsx(Text, { color:
|
|
164
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: detail ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "gray", children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key}` }), _jsx(Text, { dimColor: true, children: ` ${relativeTime(issue.updatedAt).padStart(4)}` }), _jsx(Text, { children: ` ` }), _jsx(Text, { color: session.color, children: session.label }), facts.length > 0 && (_jsx(Text, { dimColor: true, children: ` \u00b7 ` })), facts.map((fact, i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { dimColor: true, children: ` \u00b7 ` }) : null, _jsx(Text, { color: fact.color ?? "white", dimColor: !fact.color, children: fact.text })] }, i)))] }), title ? (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: title }) })) : null, blocker ? (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: "yellow", children: blocker }) })) : null, detail ? (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: detail }) })) : null, selected && issue.factoryState && issue.sessionState ? (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, children: `Debug stage: ${stageLabel(issue)}` }) })) : null] }));
|
|
110
165
|
}
|
|
@@ -9,8 +9,12 @@ const FILTER_LABELS = {
|
|
|
9
9
|
};
|
|
10
10
|
export function StatusBar({ issues, totalCount, filter, connected, lastServerMessageAt, allIssues, frozen, }) {
|
|
11
11
|
const showing = filter === "all" ? `${totalCount} issues` : `${issues.length}/${totalCount} issues`;
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
12
|
+
const aggregateSource = filter === "all" ? allIssues : issues;
|
|
13
|
+
const agg = computeAggregates(aggregateSource);
|
|
14
|
+
const withPr = aggregateSource.filter((i) => i.prNumber !== undefined).length;
|
|
15
|
+
const waitingInput = aggregateSource.filter((i) => i.sessionState === "waiting_input" || i.factoryState === "awaiting_input").length;
|
|
16
|
+
const intervention = aggregateSource.filter((i) => i.sessionState === "failed" || i.factoryState === "failed" || i.factoryState === "escalated").length;
|
|
17
|
+
const running = aggregateSource.filter((i) => i.sessionState === "running").length;
|
|
18
|
+
const idle = aggregateSource.filter((i) => i.sessionState === "idle").length;
|
|
19
|
+
return (_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { bold: true, children: showing }), _jsxs(Text, { dimColor: true, children: ["[", FILTER_LABELS[filter], "]"] }), _jsx(Text, { dimColor: true, children: "|" }), running > 0 && _jsxs(Text, { color: "cyan", children: [running, " running"] }), idle > 0 && _jsxs(Text, { color: "blueBright", children: [idle, " idle"] }), agg.ready > 0 && _jsxs(Text, { color: "blueBright", children: [agg.ready, " ready"] }), agg.blocked > 0 && _jsxs(Text, { color: "yellow", children: [agg.blocked, " blocked"] }), withPr > 0 && _jsxs(Text, { dimColor: true, children: [withPr, " PRs"] }), waitingInput > 0 && _jsxs(Text, { color: "yellow", children: [waitingInput, " needs input"] }), intervention > 0 && _jsxs(Text, { color: "red", children: [intervention, " needs help"] }), agg.done > 0 && _jsxs(Text, { color: "green", children: [agg.done, " done"] }), agg.failed > 0 && _jsxs(Text, { color: "red", children: [agg.failed, " failed"] }), frozen && _jsx(Text, { color: "magenta", children: "frozen" })] }), _jsx(FreshnessBadge, { connected: connected, lastServerMessageAt: lastServerMessageAt })] }));
|
|
16
20
|
}
|