patchrelay 0.34.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/cli/watch/App.js +1 -4
- package/dist/cli/watch/HelpBar.js +6 -9
- package/dist/cli/watch/IssueDetailView.js +60 -78
- package/dist/cli/watch/IssueRow.js +86 -236
- package/dist/cli/watch/ItemLine.js +22 -4
- package/dist/cli/watch/Timeline.js +4 -6
- package/dist/cli/watch/TimelineRow.js +16 -45
- package/dist/cli/watch/timeline-builder.js +2 -2
- package/dist/cli/watch/timeline-presentation.js +3 -134
- package/dist/cli/watch/watch-state.js +2 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/cli/watch/App.js
CHANGED
|
@@ -186,9 +186,6 @@ export function App({ baseUrl, bearerToken, initialIssueKey }) {
|
|
|
186
186
|
else if (input === "t") {
|
|
187
187
|
dispatch({ type: "switch-detail-tab", tab: "timeline" });
|
|
188
188
|
}
|
|
189
|
-
else if (input === "v") {
|
|
190
|
-
dispatch({ type: "toggle-timeline-mode" });
|
|
191
|
-
}
|
|
192
189
|
else if (input === "j" || key.downArrow) {
|
|
193
190
|
dispatch({ type: "detail-navigate", direction: "next", filtered });
|
|
194
191
|
}
|
|
@@ -202,5 +199,5 @@ export function App({ baseUrl, bearerToken, initialIssueKey }) {
|
|
|
202
199
|
}
|
|
203
200
|
}
|
|
204
201
|
});
|
|
205
|
-
return (_jsx(Box, { flexDirection: "column", children: state.view === "list" ? (_jsx(IssueListView, { issues: filtered, allIssues: state.issues, selectedIndex: state.selectedIndex, connected: state.connected, lastServerMessageAt: state.lastServerMessageAt, filter: state.filter, totalCount: state.issues.length, frozen: frozen })) : state.view === "detail" ? (_jsxs(Box, { flexDirection: "column", children: [state.activeDetailKey && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Issues" }), _jsx(Text, { dimColor: true, children: " \u203A " }), _jsx(Text, { bold: true, children: state.activeDetailKey }), _jsx(Text, { dimColor: true, children: " \u203A " }), _jsx(Text, { dimColor: true, children: state.detailTab === "timeline" ? "Timeline" : "History" })] })), _jsx(IssueDetailView, { issue: state.issues.find((i) => i.issueKey === state.activeDetailKey), timeline: state.timeline, follow: state.follow, activeRunStartedAt: state.activeRunStartedAt, activeRunId: state.activeRunId, tokenUsage: state.tokenUsage, diffSummary: state.diffSummary, plan: state.plan, issueContext: state.issueContext, detailTab: state.detailTab,
|
|
202
|
+
return (_jsx(Box, { flexDirection: "column", children: state.view === "list" ? (_jsx(IssueListView, { issues: filtered, allIssues: state.issues, selectedIndex: state.selectedIndex, connected: state.connected, lastServerMessageAt: state.lastServerMessageAt, filter: state.filter, totalCount: state.issues.length, frozen: frozen })) : state.view === "detail" ? (_jsxs(Box, { flexDirection: "column", children: [state.activeDetailKey && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Issues" }), _jsx(Text, { dimColor: true, children: " \u203A " }), _jsx(Text, { bold: true, children: state.activeDetailKey }), _jsx(Text, { dimColor: true, children: " \u203A " }), _jsx(Text, { dimColor: true, children: state.detailTab === "timeline" ? "Timeline" : "History" })] })), _jsx(IssueDetailView, { issue: state.issues.find((i) => i.issueKey === state.activeDetailKey), timeline: state.timeline, follow: state.follow, activeRunStartedAt: state.activeRunStartedAt, activeRunId: state.activeRunId, tokenUsage: state.tokenUsage, diffSummary: state.diffSummary, plan: state.plan, issueContext: state.issueContext, detailTab: state.detailTab, rawRuns: state.rawRuns, rawFeedEvents: state.rawFeedEvents, connected: state.connected, lastServerMessageAt: state.lastServerMessageAt }), promptMode && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "prompt> " }), _jsx(Text, { children: promptBuffer }), _jsx(Text, { dimColor: true, children: "_" })] })), promptStatus && !promptMode && (_jsx(Text, { dimColor: true, children: promptStatus }))] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Issues" }), _jsx(Text, { dimColor: true, children: " \u203A " }), _jsx(Text, { bold: true, children: "Operator Feed" })] }), _jsx(FeedView, { events: state.feedEvents, connected: state.connected, lastServerMessageAt: state.lastServerMessageAt })] })) }));
|
|
206
203
|
}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
list: "j/k: navigate Enter: detail F: feed Tab: filter x: freeze q: quit",
|
|
5
|
-
detail: "",
|
|
6
|
-
feed: "Esc: list q: quit",
|
|
7
|
-
};
|
|
8
|
-
export function HelpBar({ view, follow, detailTab, timelineMode }) {
|
|
3
|
+
export function HelpBar({ view, follow, detailTab }) {
|
|
9
4
|
let text;
|
|
10
5
|
if (view === "detail") {
|
|
11
6
|
const tabHint = detailTab === "history" ? "t: timeline" : "h: history";
|
|
12
|
-
|
|
13
|
-
text = [tabHint, timelineHint, "j/k: prev/next", "Esc: list", `f: follow ${follow ? "on" : "off"}`, "x: freeze", "p: prompt", "s: stop", "r: retry", "q: quit"]
|
|
7
|
+
text = [tabHint, `f: follow ${follow ? "on" : "off"}`, "p: prompt", "s: stop", "r: retry"]
|
|
14
8
|
.filter(Boolean)
|
|
15
9
|
.join(" ");
|
|
16
10
|
}
|
|
11
|
+
else if (view === "feed") {
|
|
12
|
+
text = "";
|
|
13
|
+
}
|
|
17
14
|
else {
|
|
18
|
-
text =
|
|
15
|
+
text = "F: feed Tab: filter";
|
|
19
16
|
}
|
|
20
17
|
return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: text }) }));
|
|
21
18
|
}
|
|
@@ -46,82 +46,47 @@ function formatCheckState(checkState) {
|
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
summary.push(`${issue.prChecksSummary.passed}/${issue.prChecksSummary.total} checks passed`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (reviewState) {
|
|
74
|
-
summary.push(`review ${reviewState}`);
|
|
75
|
-
}
|
|
76
|
-
else if (issue.factoryState === "pr_open" || issue.factoryState === "repairing_ci" || issue.factoryState === "awaiting_queue") {
|
|
77
|
-
summary.push("review pending");
|
|
78
|
-
}
|
|
79
|
-
if (issue.factoryState === "awaiting_queue") {
|
|
80
|
-
summary.push("queued for merge");
|
|
81
|
-
}
|
|
82
|
-
else if (issue.factoryState === "repairing_queue") {
|
|
83
|
-
summary.push("merge queue repair needed");
|
|
84
|
-
}
|
|
85
|
-
else if (issue.factoryState === "done") {
|
|
86
|
-
summary.push("merged");
|
|
87
|
-
}
|
|
88
|
-
else if (issue.prCheckStatus === "failed" || issue.prReviewState === undefined || issue.prReviewState === "changes_requested") {
|
|
89
|
-
summary.push("not mergeable");
|
|
90
|
-
}
|
|
91
|
-
return summary;
|
|
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;
|
|
92
70
|
}
|
|
93
|
-
function
|
|
94
|
-
if (issue.blockedByCount > 0)
|
|
95
|
-
return {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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";
|
|
100
82
|
if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
?? issue.latestFailureCheckName
|
|
104
|
-
?? (failedChecks.length > 0 ? failedChecks.slice(0, 2).join(", ") : undefined);
|
|
105
|
-
return {
|
|
106
|
-
text: failedCheck ? `Blocked by failed check: ${failedCheck}` : "Blocked by failed PR checks",
|
|
107
|
-
color: "red",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
if (issue.prCheckStatus === "pending" || issue.prCheckStatus === "in_progress" || issue.prCheckStatus === "queued") {
|
|
111
|
-
return { text: "Waiting for PR checks to finish", color: "yellow" };
|
|
112
|
-
}
|
|
113
|
-
if (issue.prReviewState === "changes_requested") {
|
|
114
|
-
return { text: "Blocked by requested review changes", color: "yellow" };
|
|
115
|
-
}
|
|
116
|
-
if (issue.factoryState === "repairing_queue") {
|
|
117
|
-
return { text: "Blocked by merge queue refresh failure", color: "yellow" };
|
|
118
|
-
}
|
|
119
|
-
if (issue.factoryState === "awaiting_queue") {
|
|
120
|
-
return { text: "Waiting in merge queue", color: "yellow" };
|
|
121
|
-
}
|
|
122
|
-
if (issue.prNumber !== undefined && !issue.prReviewState && issue.factoryState !== "done") {
|
|
123
|
-
return { text: "Blocked pending review approval", color: "yellow" };
|
|
83
|
+
const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "checks";
|
|
84
|
+
return `${check} failed`;
|
|
124
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";
|
|
125
90
|
return null;
|
|
126
91
|
}
|
|
127
92
|
function ElapsedTime({ startedAt }) {
|
|
@@ -135,9 +100,9 @@ function ElapsedTime({ startedAt }) {
|
|
|
135
100
|
const seconds = elapsed % 60;
|
|
136
101
|
return _jsxs(Text, { dimColor: true, children: [minutes, "m ", String(seconds).padStart(2, "0"), "s"] });
|
|
137
102
|
}
|
|
138
|
-
export function IssueDetailView({ issue, timeline, follow, activeRunStartedAt, activeRunId, tokenUsage, diffSummary, plan, issueContext, detailTab,
|
|
103
|
+
export function IssueDetailView({ issue, timeline, follow, activeRunStartedAt, activeRunId, tokenUsage, diffSummary, plan, issueContext, detailTab, rawRuns, rawFeedEvents, connected, lastServerMessageAt, }) {
|
|
139
104
|
if (!issue) {
|
|
140
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Issue not found." }), _jsx(HelpBar, { view: "detail", follow: follow, detailTab: detailTab
|
|
105
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Issue not found." }), _jsx(HelpBar, { view: "detail", follow: follow, detailTab: detailTab })] }));
|
|
141
106
|
}
|
|
142
107
|
const key = issue.issueKey ?? issue.projectId;
|
|
143
108
|
const meta = [];
|
|
@@ -147,10 +112,27 @@ export function IssueDetailView({ issue, timeline, follow, activeRunStartedAt, a
|
|
|
147
112
|
meta.push(`${diffSummary.filesChanged}f +${diffSummary.linesAdded} -${diffSummary.linesRemoved}`);
|
|
148
113
|
if (issueContext?.runCount)
|
|
149
114
|
meta.push(`${issueContext.runCount} runs`);
|
|
115
|
+
const state = STATE_DISPLAY[effectiveState(issue)] ?? { label: issue.factoryState, color: "white" };
|
|
116
|
+
const blocker = blockerText(issue, issueContext);
|
|
150
117
|
const history = useMemo(() => buildStateHistory(rawRuns, rawFeedEvents, issue.factoryState, activeRunId), [rawRuns, rawFeedEvents, issue.factoryState, activeRunId]);
|
|
151
118
|
const graph = useMemo(() => buildPatchRelayStateGraph(history, issue.factoryState), [history, issue.factoryState]);
|
|
152
119
|
const queueObservations = useMemo(() => buildPatchRelayQueueObservations(issue, rawFeedEvents), [issue, rawFeedEvents]);
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
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}`);
|
|
136
|
+
}
|
|
137
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: key }), _jsx(Text, { color: state.color, children: state.label }), facts.length > 0 && _jsx(Text, { dimColor: true, children: facts.join(" \u00b7 ") }), activeRunStartedAt && _jsx(ElapsedTime, { startedAt: activeRunStartedAt }), meta.length > 0 && _jsx(Text, { dimColor: true, children: meta.join(" ") }), follow && _jsx(Text, { color: "yellow", children: "follow" }), _jsx(FreshnessBadge, { connected: connected, lastServerMessageAt: lastServerMessageAt })] }), issue.title && _jsx(Text, { children: issue.title }), blocker && _jsx(Text, { color: "yellow", children: blocker }), issueContext?.latestFailureSummary && (_jsxs(Text, { color: issueContext.latestFailureSource === "queue_eviction" ? "yellow" : "red", children: ["Latest failure: ", issueContext.latestFailureSummary, issueContext.latestFailureHeadSha ? ` @ ${issueContext.latestFailureHeadSha.slice(0, 8)}` : ""] })), detailTab === "timeline" ? (_jsxs(_Fragment, { children: [plan && plan.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "Plan" }), _jsx(Text, { children: progressBar(plan.filter((s) => s.status === "completed").length, plan.length, 16) }), _jsxs(Text, { dimColor: true, children: [plan.filter((s) => s.status === "completed").length, "/", plan.length] })] }), plan.map((entry, i) => (_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: planStepColor(entry.status), children: ["[", planStepSymbol(entry.status), "]"] }), _jsx(Text, { children: entry.step })] }, `plan-${i}`)))] })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Timeline, { entries: timeline, follow: follow }) })] })) : (_jsxs(_Fragment, { children: [_jsx(FactoryStateGraph, { main: graph.main, prLoops: graph.prLoops, queueLoop: graph.queueLoop, exits: graph.exits }), _jsx(QueueObservationView, { observations: queueObservations }), _jsx(Box, { marginTop: 1, children: _jsx(StateHistoryView, { history: history, plan: plan, activeRunId: activeRunId }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(HelpBar, { view: "detail", follow: follow, detailTab: detailTab }) })] }));
|
|
156
138
|
}
|
|
@@ -1,260 +1,110 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { summarizeIssueStatusNote } from "./issue-status-note.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
blocked: "yellow",
|
|
7
|
-
ready: "blueBright",
|
|
8
|
-
delegated: "cyan",
|
|
9
|
-
implementing: "cyan",
|
|
10
|
-
pr_open: "cyan",
|
|
11
|
-
changes_requested: "yellow",
|
|
12
|
-
repairing_ci: "cyan",
|
|
13
|
-
awaiting_queue: "cyan",
|
|
14
|
-
repairing_queue: "cyan",
|
|
15
|
-
done: "green",
|
|
16
|
-
failed: "red",
|
|
17
|
-
escalated: "red",
|
|
18
|
-
awaiting_input: "yellow",
|
|
19
|
-
};
|
|
20
|
-
const STATE_SHORT = {
|
|
21
|
-
blocked: "blocked",
|
|
22
|
-
ready: "ready",
|
|
23
|
-
delegated: "delegated",
|
|
24
|
-
implementing: "implementing",
|
|
25
|
-
pr_open: "pr open",
|
|
26
|
-
changes_requested: "review changes",
|
|
27
|
-
repairing_ci: "repairing checks",
|
|
28
|
-
awaiting_queue: "queued for merge",
|
|
29
|
-
repairing_queue: "repairing merge queue",
|
|
30
|
-
done: "done",
|
|
31
|
-
failed: "failed",
|
|
32
|
-
escalated: "escalated",
|
|
33
|
-
awaiting_input: "awaiting input",
|
|
34
|
-
};
|
|
35
|
-
const STATUS_SHORT = {
|
|
36
|
-
running: "\u25b8",
|
|
37
|
-
completed: "\u2713",
|
|
38
|
-
failed: "\u2717",
|
|
39
|
-
released: "\u2013",
|
|
40
|
-
};
|
|
41
|
-
function stateColor(state) {
|
|
42
|
-
return STATE_COLORS[state] ?? "white";
|
|
43
|
-
}
|
|
4
|
+
import { relativeTime, truncate } from "./format-utils.js";
|
|
5
|
+
// ─── State display ──────────────────────────────────────────────
|
|
44
6
|
const TERMINAL_STATES = new Set(["done", "failed", "escalated", "awaiting_input"]);
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const state = STATE_SHORT[effectiveState] ?? effectiveState;
|
|
52
|
-
// Terminal states: just the label, no run symbol
|
|
53
|
-
if (TERMINAL_STATES.has(issue.factoryState))
|
|
54
|
-
return state;
|
|
55
|
-
// Active/in-progress: show run status symbol
|
|
56
|
-
const status = issue.activeRunType ? "running" : issue.latestRunStatus;
|
|
57
|
-
const statusSym = status ? (STATUS_SHORT[status] ?? "") : "";
|
|
58
|
-
if (statusSym)
|
|
59
|
-
return `${state} ${statusSym}`;
|
|
60
|
-
return state;
|
|
61
|
-
}
|
|
62
|
-
function buildStatusChips(issue) {
|
|
63
|
-
const effectiveState = issue.blockedByCount > 0 && !issue.activeRunType
|
|
64
|
-
? "blocked"
|
|
65
|
-
: issue.readyForExecution && !issue.activeRunType
|
|
66
|
-
? "ready"
|
|
67
|
-
: issue.factoryState;
|
|
68
|
-
const chips = [{
|
|
69
|
-
text: `${stateIcon(effectiveState)} ${STATE_SHORT[effectiveState] ?? effectiveState}`,
|
|
70
|
-
color: stateColor(effectiveState),
|
|
71
|
-
}];
|
|
72
|
-
if (issue.prNumber !== undefined) {
|
|
73
|
-
chips.push({ text: `PR #${issue.prNumber}`, color: "cyan" });
|
|
74
|
-
}
|
|
75
|
-
const reviewChip = buildReviewChip(issue.prReviewState);
|
|
76
|
-
if (reviewChip)
|
|
77
|
-
chips.push(reviewChip);
|
|
78
|
-
const checkChip = buildCheckChip(issue.prCheckStatus);
|
|
79
|
-
if (checkChip)
|
|
80
|
-
chips.push(checkChip);
|
|
81
|
-
const checksProgressChip = buildChecksProgressChip(issue);
|
|
82
|
-
if (checksProgressChip)
|
|
83
|
-
chips.push(checksProgressChip);
|
|
84
|
-
const mergeChip = buildMergeChip(issue);
|
|
85
|
-
if (mergeChip)
|
|
86
|
-
chips.push(mergeChip);
|
|
87
|
-
if (issue.blockedByCount > 0) {
|
|
88
|
-
chips.push({
|
|
89
|
-
text: `blocked by ${issue.blockedByKeys.join(", ")}`,
|
|
90
|
-
color: "yellow",
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return chips;
|
|
7
|
+
function effectiveState(issue) {
|
|
8
|
+
if (issue.blockedByCount > 0 && !issue.activeRunType)
|
|
9
|
+
return "blocked";
|
|
10
|
+
if (issue.readyForExecution && !issue.activeRunType)
|
|
11
|
+
return "ready";
|
|
12
|
+
return issue.factoryState;
|
|
94
13
|
}
|
|
95
|
-
function
|
|
14
|
+
function stateDisplay(issue) {
|
|
15
|
+
const state = effectiveState(issue);
|
|
96
16
|
switch (state) {
|
|
97
|
-
case "
|
|
98
|
-
case "
|
|
99
|
-
case "
|
|
100
|
-
|
|
101
|
-
case "
|
|
102
|
-
|
|
103
|
-
case "
|
|
104
|
-
|
|
105
|
-
case "
|
|
106
|
-
case "
|
|
107
|
-
|
|
108
|
-
case "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return "+";
|
|
112
|
-
default:
|
|
113
|
-
return "\u2022";
|
|
17
|
+
case "blocked": return { label: "blocked", color: "yellow" };
|
|
18
|
+
case "ready": return { label: "ready", color: "blueBright" };
|
|
19
|
+
case "delegated": return { label: "delegated", color: "cyan" };
|
|
20
|
+
case "implementing": return { label: "implementing", color: "cyan" };
|
|
21
|
+
case "pr_open": return { label: "PR open", color: "cyan" };
|
|
22
|
+
case "changes_requested": return { label: "review changes", color: "yellow" };
|
|
23
|
+
case "repairing_ci": return { label: "repairing CI", color: "yellow" };
|
|
24
|
+
case "awaiting_queue": return { label: "queued for merge", color: "cyan" };
|
|
25
|
+
case "repairing_queue": return { label: "repairing queue", color: "yellow" };
|
|
26
|
+
case "done": return { label: "merged", color: "green" };
|
|
27
|
+
case "failed": return { label: "failed", color: "red" };
|
|
28
|
+
case "escalated": return { label: "escalated", color: "red" };
|
|
29
|
+
case "awaiting_input": return { label: "awaiting input", color: "yellow" };
|
|
30
|
+
default: return { label: state, color: "white" };
|
|
114
31
|
}
|
|
115
32
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
case "commented":
|
|
123
|
-
return { text: "\u2022 review commented", color: "yellow" };
|
|
124
|
-
case "dismissed":
|
|
125
|
-
return { text: "\u2013 review dismissed", color: "yellow" };
|
|
126
|
-
default:
|
|
127
|
-
return null;
|
|
33
|
+
// ─── Context facts (what matters right now) ─────────────────────
|
|
34
|
+
function buildFacts(issue) {
|
|
35
|
+
const facts = [];
|
|
36
|
+
// PR number
|
|
37
|
+
if (issue.prNumber !== undefined) {
|
|
38
|
+
facts.push({ text: `PR #${issue.prNumber}` });
|
|
128
39
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
case "passed":
|
|
133
|
-
case "success":
|
|
134
|
-
return { text: "\u2713 checks passed", color: "green" };
|
|
135
|
-
case "failed":
|
|
136
|
-
case "failure":
|
|
137
|
-
return { text: "\u2717 checks failed", color: "red" };
|
|
138
|
-
case "pending":
|
|
139
|
-
case "in_progress":
|
|
140
|
-
case "queued":
|
|
141
|
-
return { text: "\u25cf checks running", color: "yellow" };
|
|
142
|
-
default:
|
|
143
|
-
return null;
|
|
40
|
+
// Review state — only show when it matters (not yet approved, or changes requested)
|
|
41
|
+
if (issue.prReviewState === "approved") {
|
|
42
|
+
facts.push({ text: "approved", color: "green" });
|
|
144
43
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const summary = issue.prChecksSummary;
|
|
148
|
-
if (!summary || summary.total <= 0)
|
|
149
|
-
return null;
|
|
150
|
-
const text = summary.failed > 0
|
|
151
|
-
? `checks ${summary.failed}/${summary.total} failed`
|
|
152
|
-
: summary.pending > 0
|
|
153
|
-
? `checks ${summary.completed}/${summary.total} settled`
|
|
154
|
-
: `checks ${summary.passed}/${summary.total} passed`;
|
|
155
|
-
const color = summary.failed > 0 ? "red" : summary.pending > 0 ? "yellow" : "green";
|
|
156
|
-
return { text, color };
|
|
157
|
-
}
|
|
158
|
-
function buildMergeChip(issue) {
|
|
159
|
-
if (issue.prNumber === undefined)
|
|
160
|
-
return null;
|
|
161
|
-
switch (issue.factoryState) {
|
|
162
|
-
case "awaiting_queue":
|
|
163
|
-
return { text: "\u25a4 queued for merge", color: "cyan" };
|
|
164
|
-
case "repairing_queue":
|
|
165
|
-
return { text: "! merge queue repair", color: "yellow" };
|
|
166
|
-
case "done":
|
|
167
|
-
return { text: "\u2713 merged", color: "green" };
|
|
168
|
-
case "pr_open":
|
|
169
|
-
if (issue.prReviewState === "approved" && issue.prCheckStatus === "passed") {
|
|
170
|
-
return { text: "\u2713 merge ready", color: "green" };
|
|
171
|
-
}
|
|
172
|
-
return { text: "\u2022 PR open", color: "cyan" };
|
|
173
|
-
default:
|
|
174
|
-
return null;
|
|
44
|
+
else if (issue.prReviewState === "changes_requested") {
|
|
45
|
+
facts.push({ text: "changes requested", color: "yellow" });
|
|
175
46
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (issue.blockedByCount > 0) {
|
|
179
|
-
return {
|
|
180
|
-
text: `Waiting on ${issue.blockedByKeys.join(", ")}`,
|
|
181
|
-
color: "yellow",
|
|
182
|
-
};
|
|
47
|
+
else if (issue.prNumber !== undefined && !issue.prReviewState && !TERMINAL_STATES.has(issue.factoryState)) {
|
|
48
|
+
facts.push({ text: "awaiting review", color: "yellow" });
|
|
183
49
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
?? (failedChecks.length > 0 ? failedChecks.slice(0, 2).join(", ") : undefined)
|
|
188
|
-
?? "PR checks";
|
|
189
|
-
return {
|
|
190
|
-
text: `${failedCheck} failed`,
|
|
191
|
-
color: "red",
|
|
192
|
-
};
|
|
50
|
+
// Check status — compact
|
|
51
|
+
if (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success") {
|
|
52
|
+
facts.push({ text: "checks passed", color: "green" });
|
|
193
53
|
}
|
|
194
|
-
if (issue.prCheckStatus === "
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
54
|
+
else if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
|
|
55
|
+
const failedNames = issue.prChecksSummary?.failedNames ?? [];
|
|
56
|
+
const checkInfo = issue.latestFailureCheckName
|
|
57
|
+
?? (failedNames.length > 0 ? failedNames.slice(0, 2).join(", ") : "checks");
|
|
58
|
+
facts.push({ text: `${checkInfo} failed`, color: "red" });
|
|
199
59
|
}
|
|
200
|
-
if (issue.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
color: "yellow"
|
|
204
|
-
}
|
|
60
|
+
else if (issue.prCheckStatus === "pending" || issue.prCheckStatus === "in_progress") {
|
|
61
|
+
const summary = issue.prChecksSummary;
|
|
62
|
+
if (summary && summary.total > 0) {
|
|
63
|
+
facts.push({ text: `checks ${summary.completed}/${summary.total}`, color: "yellow" });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
facts.push({ text: "checks running", color: "yellow" });
|
|
67
|
+
}
|
|
205
68
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
color: "yellow",
|
|
210
|
-
};
|
|
69
|
+
// Blocker
|
|
70
|
+
if (issue.blockedByCount > 0) {
|
|
71
|
+
facts.push({ text: `waiting on ${issue.blockedByKeys.join(", ")}`, color: "yellow" });
|
|
211
72
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
73
|
+
return facts;
|
|
74
|
+
}
|
|
75
|
+
// ─── What's blocking progress ───────────────────────────────────
|
|
76
|
+
function blockerText(issue) {
|
|
77
|
+
if (issue.blockedByCount > 0)
|
|
78
|
+
return `Waiting on ${issue.blockedByKeys.join(", ")}`;
|
|
79
|
+
if (issue.factoryState === "repairing_queue")
|
|
80
|
+
return "Merge queue conflict, repairing branch";
|
|
81
|
+
if (issue.factoryState === "repairing_ci") {
|
|
82
|
+
const check = issue.latestFailureCheckName ?? "CI";
|
|
83
|
+
return `Repairing ${check}`;
|
|
217
84
|
}
|
|
218
|
-
if (issue.
|
|
219
|
-
return
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
85
|
+
if (issue.factoryState === "awaiting_queue")
|
|
86
|
+
return "Waiting for merge queue";
|
|
87
|
+
if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
|
|
88
|
+
const check = issue.latestFailureCheckName ?? "checks";
|
|
89
|
+
return `${check} failed`;
|
|
223
90
|
}
|
|
91
|
+
if (issue.prReviewState === "changes_requested")
|
|
92
|
+
return "Review changes requested";
|
|
224
93
|
return null;
|
|
225
94
|
}
|
|
226
|
-
|
|
227
|
-
switch (issue.factoryState) {
|
|
228
|
-
case "delegated":
|
|
229
|
-
return { current: 1, total: 4, label: "delegated" };
|
|
230
|
-
case "implementing":
|
|
231
|
-
return { current: 1, total: 4, label: "implementing" };
|
|
232
|
-
case "pr_open":
|
|
233
|
-
case "changes_requested":
|
|
234
|
-
case "repairing_ci":
|
|
235
|
-
return { current: 2, total: 4, label: "pr checks" };
|
|
236
|
-
case "awaiting_queue":
|
|
237
|
-
case "repairing_queue":
|
|
238
|
-
return { current: 3, total: 4, label: "merge queue" };
|
|
239
|
-
case "done":
|
|
240
|
-
return { current: 4, total: 4, label: "merged" };
|
|
241
|
-
case "failed":
|
|
242
|
-
case "escalated":
|
|
243
|
-
case "awaiting_input":
|
|
244
|
-
return { current: 4, total: 4, label: "stopped" };
|
|
245
|
-
default:
|
|
246
|
-
return { current: 1, total: 4, label: "queued" };
|
|
247
|
-
}
|
|
248
|
-
}
|
|
95
|
+
// ─── Render ─────────────────────────────────────────────────────
|
|
249
96
|
export function IssueRow({ issue, selected, titleWidth }) {
|
|
250
97
|
const key = issue.issueKey ?? issue.projectId;
|
|
251
|
-
const
|
|
252
|
-
const tw = titleWidth ?? 40;
|
|
98
|
+
const tw = titleWidth ?? 60;
|
|
253
99
|
const title = issue.title ? truncate(issue.title, tw) : "";
|
|
254
100
|
const detail = selected ? summarizeIssueStatusNote(issue.statusNote) : undefined;
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
const blocker =
|
|
258
|
-
const
|
|
259
|
-
|
|
101
|
+
const state = stateDisplay(issue);
|
|
102
|
+
const facts = buildFacts(issue);
|
|
103
|
+
const blocker = selected ? blockerText(issue) : null;
|
|
104
|
+
const isTerminal = TERMINAL_STATES.has(issue.factoryState);
|
|
105
|
+
// Terminal issues: compact single line
|
|
106
|
+
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: state.color, children: state.label })] }));
|
|
108
|
+
}
|
|
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: state.color, children: state.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] }));
|
|
260
110
|
}
|
|
@@ -24,19 +24,37 @@ function itemPrefix(item) {
|
|
|
24
24
|
return "$ ";
|
|
25
25
|
return "";
|
|
26
26
|
}
|
|
27
|
+
function formatItemDuration(ms) {
|
|
28
|
+
if (ms === undefined || ms === null)
|
|
29
|
+
return "";
|
|
30
|
+
const seconds = Math.floor(ms / 1000);
|
|
31
|
+
if (seconds < 1)
|
|
32
|
+
return "";
|
|
33
|
+
if (seconds < 60)
|
|
34
|
+
return ` ${seconds}s`;
|
|
35
|
+
const minutes = Math.floor(seconds / 60);
|
|
36
|
+
return ` ${minutes}m`;
|
|
37
|
+
}
|
|
27
38
|
function itemText(item) {
|
|
28
39
|
switch (item.type) {
|
|
29
40
|
case "agentMessage":
|
|
30
41
|
case "plan":
|
|
31
42
|
case "reasoning":
|
|
32
43
|
return summarizeText(item);
|
|
33
|
-
case "commandExecution":
|
|
34
|
-
|
|
44
|
+
case "commandExecution": {
|
|
45
|
+
const cmd = cleanCommand(item.command ?? "?");
|
|
46
|
+
const exit = item.exitCode !== undefined && item.exitCode !== null && item.exitCode !== 0
|
|
47
|
+
? ` exit ${item.exitCode}` : "";
|
|
48
|
+
const dur = formatItemDuration(item.durationMs);
|
|
49
|
+
return `${cmd}${exit}${dur}`;
|
|
50
|
+
}
|
|
35
51
|
case "fileChange":
|
|
36
52
|
return summarizeFileChange(item);
|
|
37
53
|
case "mcpToolCall":
|
|
38
|
-
case "dynamicToolCall":
|
|
39
|
-
|
|
54
|
+
case "dynamicToolCall": {
|
|
55
|
+
const dur = formatItemDuration(item.durationMs);
|
|
56
|
+
return `${summarizeToolCall(item)}${dur}`;
|
|
57
|
+
}
|
|
40
58
|
case "userMessage":
|
|
41
59
|
return `you: ${summarizeText(item)}`;
|
|
42
60
|
default:
|
|
@@ -4,16 +4,14 @@ import { Box, Static, Text, useStdout } from "ink";
|
|
|
4
4
|
import { buildTimelineRows } from "./timeline-presentation.js";
|
|
5
5
|
import { TimelineRow } from "./TimelineRow.js";
|
|
6
6
|
const ACTIVE_TAIL = 8;
|
|
7
|
-
export function Timeline({ entries, follow
|
|
7
|
+
export function Timeline({ entries, follow }) {
|
|
8
8
|
const { stdout } = useStdout();
|
|
9
9
|
const rows = stdout?.rows ?? 24;
|
|
10
10
|
const maxActive = Math.max(ACTIVE_TAIL, rows - 12);
|
|
11
|
-
const displayRows = useMemo(() => buildTimelineRows(entries
|
|
12
|
-
// Split: finalized entries go to Static (terminal scrollback), active entries re-render
|
|
11
|
+
const displayRows = useMemo(() => buildTimelineRows(entries), [entries]);
|
|
13
12
|
const splitIndex = useMemo(() => {
|
|
14
13
|
if (!follow)
|
|
15
|
-
return 0;
|
|
16
|
-
// Find the boundary: keep the last maxActive entries in the active area
|
|
14
|
+
return 0;
|
|
17
15
|
return Math.max(0, displayRows.length - maxActive);
|
|
18
16
|
}, [displayRows.length, follow, maxActive]);
|
|
19
17
|
const finalized = displayRows.slice(0, splitIndex);
|
|
@@ -21,5 +19,5 @@ export function Timeline({ entries, follow, mode }) {
|
|
|
21
19
|
if (displayRows.length === 0) {
|
|
22
20
|
return _jsx(Text, { dimColor: true, children: "No timeline events yet." });
|
|
23
21
|
}
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", children: [finalized.length > 0 && (_jsx(Static, { items: finalized, children: (entry) => _jsx(TimelineRow, { entry: entry
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", children: [finalized.length > 0 && (_jsx(Static, { items: finalized, children: (entry) => _jsx(TimelineRow, { entry: entry }, entry.id) })), active.map((entry) => (_jsx(TimelineRow, { entry: entry }, entry.id)))] }));
|
|
25
23
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { ItemLine } from "./ItemLine.js";
|
|
4
|
-
function formatTime(iso) {
|
|
5
|
-
return new Date(iso).toLocaleTimeString("en-GB", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
6
|
-
}
|
|
7
4
|
function formatDuration(startedAt, endedAt) {
|
|
8
5
|
const ms = new Date(endedAt).getTime() - new Date(startedAt).getTime();
|
|
9
6
|
const seconds = Math.floor(ms / 1000);
|
|
@@ -21,7 +18,7 @@ const RUN_LABELS = {
|
|
|
21
18
|
review_fix: "review fix",
|
|
22
19
|
queue_repair: "merge fix",
|
|
23
20
|
};
|
|
24
|
-
function
|
|
21
|
+
function runDotColor(status) {
|
|
25
22
|
if (status === "completed")
|
|
26
23
|
return "green";
|
|
27
24
|
if (status === "failed")
|
|
@@ -32,13 +29,6 @@ function runStatusColor(status) {
|
|
|
32
29
|
return "yellow";
|
|
33
30
|
return "white";
|
|
34
31
|
}
|
|
35
|
-
function runStatusLabel(status) {
|
|
36
|
-
if (status === "running")
|
|
37
|
-
return "running";
|
|
38
|
-
if (status === "released")
|
|
39
|
-
return "released";
|
|
40
|
-
return status;
|
|
41
|
-
}
|
|
42
32
|
function detailColor(detail) {
|
|
43
33
|
if (detail.tone === "command")
|
|
44
34
|
return "white";
|
|
@@ -51,54 +41,35 @@ function detailPrefix(detail) {
|
|
|
51
41
|
return "$ ";
|
|
52
42
|
return "";
|
|
53
43
|
}
|
|
54
|
-
function verboseItemLabel(type) {
|
|
55
|
-
switch (type) {
|
|
56
|
-
case "agentMessage":
|
|
57
|
-
return "message";
|
|
58
|
-
case "commandExecution":
|
|
59
|
-
return "command";
|
|
60
|
-
case "fileChange":
|
|
61
|
-
return "files";
|
|
62
|
-
case "mcpToolCall":
|
|
63
|
-
case "dynamicToolCall":
|
|
64
|
-
return "tool";
|
|
65
|
-
case "userMessage":
|
|
66
|
-
return "you";
|
|
67
|
-
case "plan":
|
|
68
|
-
return "plan";
|
|
69
|
-
case "reasoning":
|
|
70
|
-
return "reasoning";
|
|
71
|
-
default:
|
|
72
|
-
return type;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
44
|
function FeedRow({ entry }) {
|
|
76
45
|
const label = entry.feed.status ?? entry.feed.feedKind;
|
|
77
|
-
const repeatSuffix = entry.repeatCount && entry.repeatCount > 1 ? `
|
|
78
|
-
return (_jsxs(Box, {
|
|
46
|
+
const repeatSuffix = entry.repeatCount && entry.repeatCount > 1 ? ` \u00d7${entry.repeatCount}` : "";
|
|
47
|
+
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "cyan", children: "\u25cf" }), _jsx(Text, { color: "cyan", children: ` ${label}` }), _jsx(Text, { dimColor: true, children: ` ${entry.feed.summary}${repeatSuffix}` })] }));
|
|
79
48
|
}
|
|
80
|
-
function RunRow({ entry,
|
|
49
|
+
function RunRow({ entry, }) {
|
|
81
50
|
const run = entry.run;
|
|
82
|
-
const
|
|
51
|
+
const dotColor = runDotColor(run.status);
|
|
83
52
|
const duration = run.endedAt ? formatDuration(run.startedAt, run.endedAt) : undefined;
|
|
84
|
-
const
|
|
85
|
-
|
|
53
|
+
const showItems = entry.items.length > 0;
|
|
54
|
+
const showDetails = !showItems && entry.details.length > 0;
|
|
55
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: "\u25cf" }), _jsx(Text, { bold: true, color: "yellow", children: ` ${RUN_LABELS[run.runType] ?? run.runType}` }), _jsx(Text, { bold: true, color: dotColor, children: ` ${run.status}` }), duration ? _jsx(Text, { dimColor: true, children: ` ${duration}` }) : null] }), showItems && entry.items.map((itemEntry, index) => (_jsx(Box, { paddingLeft: 2, children: _jsx(ItemLine, { item: itemEntry.item }) }, `${entry.id}-item-${index}`))), showDetails && entry.details.map((detail, index) => (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { wrap: "wrap", ...(detailColor(detail) ? { color: detailColor(detail) } : { dimColor: true }), bold: detail.tone === "message", children: [detailPrefix(detail), detail.text] }) }, `${entry.id}-detail-${index}`)))] }));
|
|
86
56
|
}
|
|
87
|
-
function ItemRow({ entry,
|
|
88
|
-
return (
|
|
57
|
+
function ItemRow({ entry, }) {
|
|
58
|
+
return (_jsx(Box, { paddingLeft: 2, children: _jsx(ItemLine, { item: entry.item }) }));
|
|
89
59
|
}
|
|
90
60
|
function CIChecksRow({ entry }) {
|
|
91
61
|
const ci = entry.ciChecks;
|
|
92
|
-
|
|
62
|
+
const dotColor = CHECK_COLORS[ci.overall] ?? "white";
|
|
63
|
+
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: dotColor, children: "\u25cf" }), _jsx(Text, { color: dotColor, bold: true, children: ` checks` }), _jsx(Text, { children: ` ` }), ci.checks.map((check, i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { children: ` ` }) : null, _jsx(Text, { color: CHECK_COLORS[check.status] ?? "white", children: CHECK_SYMBOLS[check.status] ?? " " }), _jsx(Text, { dimColor: true, children: ` ${check.name}` })] }, `c-${i}`)))] }));
|
|
93
64
|
}
|
|
94
|
-
export function TimelineRow({ entry
|
|
65
|
+
export function TimelineRow({ entry }) {
|
|
95
66
|
switch (entry.kind) {
|
|
96
67
|
case "feed":
|
|
97
68
|
return _jsx(FeedRow, { entry: entry });
|
|
98
69
|
case "run":
|
|
99
|
-
return _jsx(RunRow, { entry: entry
|
|
70
|
+
return _jsx(RunRow, { entry: entry });
|
|
100
71
|
case "item":
|
|
101
|
-
return _jsx(ItemRow, { entry: entry
|
|
72
|
+
return _jsx(ItemRow, { entry: entry });
|
|
102
73
|
case "ci-checks":
|
|
103
74
|
return _jsx(CIChecksRow, { entry: entry });
|
|
104
75
|
}
|
|
@@ -8,7 +8,7 @@ export function buildTimelineFromRehydration(runs, feedEvents, liveThread, activ
|
|
|
8
8
|
at: run.startedAt,
|
|
9
9
|
kind: "run-start",
|
|
10
10
|
runId: run.id,
|
|
11
|
-
run: { runType: run.runType, status: run.status, startedAt: run.startedAt, endedAt: run.endedAt },
|
|
11
|
+
run: { runType: run.runType, status: run.status, startedAt: run.startedAt, endedAt: run.endedAt, threadId: run.threadId },
|
|
12
12
|
});
|
|
13
13
|
if (run.endedAt) {
|
|
14
14
|
entries.push({
|
|
@@ -16,7 +16,7 @@ export function buildTimelineFromRehydration(runs, feedEvents, liveThread, activ
|
|
|
16
16
|
at: run.endedAt,
|
|
17
17
|
kind: "run-end",
|
|
18
18
|
runId: run.id,
|
|
19
|
-
run: { runType: run.runType, status: run.status, startedAt: run.startedAt, endedAt: run.endedAt },
|
|
19
|
+
run: { runType: run.runType, status: run.status, startedAt: run.startedAt, endedAt: run.endedAt, threadId: run.threadId },
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
// Items from completed run event history, with report fallback
|
|
@@ -1,103 +1,5 @@
|
|
|
1
|
-
export function buildTimelineRows(entries
|
|
2
|
-
|
|
3
|
-
return collapseRepeatedFeedRows(rows);
|
|
4
|
-
}
|
|
5
|
-
function buildVerboseTimelineRows(entries) {
|
|
6
|
-
const rows = [];
|
|
7
|
-
const runs = new Map();
|
|
8
|
-
for (const entry of entries) {
|
|
9
|
-
if (entry.kind === "run-start" && entry.runId !== undefined) {
|
|
10
|
-
const existing = runs.get(entry.runId);
|
|
11
|
-
if (!existing) {
|
|
12
|
-
const run = { ...entry.run };
|
|
13
|
-
runs.set(entry.runId, {
|
|
14
|
-
id: `run-${entry.runId}`,
|
|
15
|
-
at: run.startedAt,
|
|
16
|
-
run,
|
|
17
|
-
items: [],
|
|
18
|
-
endedAt: run.endedAt,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
if (entry.kind === "run-end" && entry.runId !== undefined) {
|
|
24
|
-
const existing = runs.get(entry.runId);
|
|
25
|
-
if (existing) {
|
|
26
|
-
existing.run = { ...entry.run };
|
|
27
|
-
existing.endedAt = entry.run?.endedAt;
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
const run = { ...entry.run };
|
|
31
|
-
runs.set(entry.runId, {
|
|
32
|
-
id: `run-${entry.runId}`,
|
|
33
|
-
at: run.startedAt,
|
|
34
|
-
run,
|
|
35
|
-
items: [],
|
|
36
|
-
endedAt: run.endedAt,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (entry.kind === "item" && entry.runId !== undefined && runs.has(entry.runId)) {
|
|
42
|
-
runs.get(entry.runId).items.push(entry.item);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
switch (entry.kind) {
|
|
46
|
-
case "feed":
|
|
47
|
-
if (shouldHideFeed(entry.feed)) {
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
rows.push({
|
|
51
|
-
id: entry.id,
|
|
52
|
-
kind: "feed",
|
|
53
|
-
at: entry.at,
|
|
54
|
-
finalized: true,
|
|
55
|
-
feed: entry.feed,
|
|
56
|
-
});
|
|
57
|
-
break;
|
|
58
|
-
case "ci-checks":
|
|
59
|
-
rows.push({
|
|
60
|
-
id: entry.id,
|
|
61
|
-
kind: "ci-checks",
|
|
62
|
-
at: entry.at,
|
|
63
|
-
finalized: true,
|
|
64
|
-
ciChecks: entry.ciChecks,
|
|
65
|
-
});
|
|
66
|
-
break;
|
|
67
|
-
case "item":
|
|
68
|
-
rows.push({
|
|
69
|
-
id: entry.id,
|
|
70
|
-
kind: "item",
|
|
71
|
-
at: entry.at,
|
|
72
|
-
finalized: entry.item?.status !== "inProgress",
|
|
73
|
-
item: entry.item,
|
|
74
|
-
});
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
for (const [runId, run] of runs) {
|
|
79
|
-
rows.push({
|
|
80
|
-
id: run.id,
|
|
81
|
-
kind: "run",
|
|
82
|
-
at: run.at,
|
|
83
|
-
finalized: run.items.every((item) => item.status !== "inProgress") && run.run.status !== "running",
|
|
84
|
-
run: { ...run.run, ...(run.endedAt ? { endedAt: run.endedAt } : {}) },
|
|
85
|
-
details: [],
|
|
86
|
-
items: summarizeVerboseItems(entries
|
|
87
|
-
.filter((entry) => entry.kind === "item" && entry.runId === runId)
|
|
88
|
-
.map((entry) => ({ at: entry.at, item: entry.item }))),
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
rows.sort((left, right) => {
|
|
92
|
-
const cmp = left.at.localeCompare(right.at);
|
|
93
|
-
if (cmp !== 0)
|
|
94
|
-
return cmp;
|
|
95
|
-
const kindCmp = rowKindOrder(left.kind) - rowKindOrder(right.kind);
|
|
96
|
-
if (kindCmp !== 0)
|
|
97
|
-
return kindCmp;
|
|
98
|
-
return left.id.localeCompare(right.id);
|
|
99
|
-
});
|
|
100
|
-
return rows;
|
|
1
|
+
export function buildTimelineRows(entries) {
|
|
2
|
+
return collapseRepeatedFeedRows(buildCompactTimelineRows(entries));
|
|
101
3
|
}
|
|
102
4
|
function buildCompactTimelineRows(entries) {
|
|
103
5
|
const rows = [];
|
|
@@ -181,7 +83,7 @@ function buildCompactTimelineRows(entries) {
|
|
|
181
83
|
finalized: status !== "running",
|
|
182
84
|
run: { ...run.run, status, ...(run.endedAt ? { endedAt: run.endedAt } : {}) },
|
|
183
85
|
details: summarizeRunDetails(run.items),
|
|
184
|
-
items:
|
|
86
|
+
items: run.items.map((item) => ({ at: run.at, item })),
|
|
185
87
|
});
|
|
186
88
|
}
|
|
187
89
|
rows.sort((left, right) => {
|
|
@@ -335,39 +237,6 @@ function rowKindOrder(kind) {
|
|
|
335
237
|
return 3;
|
|
336
238
|
}
|
|
337
239
|
}
|
|
338
|
-
function summarizeVerboseItems(items) {
|
|
339
|
-
const directTypes = new Set(["userMessage", "commandExecution", "fileChange", "plan"]);
|
|
340
|
-
const kept = items.filter((entry) => directTypes.has(entry.item.type));
|
|
341
|
-
const latestAgentMessage = findLatestVerboseItem(items, (entry) => entry.item.type === "agentMessage" && Boolean(entry.item.text?.trim()));
|
|
342
|
-
if (latestAgentMessage) {
|
|
343
|
-
kept.push(latestAgentMessage);
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
const latestReasoning = findLatestVerboseItem(items, (entry) => entry.item.type === "reasoning" && Boolean(entry.item.text?.trim()));
|
|
347
|
-
if (latestReasoning) {
|
|
348
|
-
kept.push(latestReasoning);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
const deduped = new Map();
|
|
352
|
-
for (const entry of kept) {
|
|
353
|
-
deduped.set(entry.item.id, entry);
|
|
354
|
-
}
|
|
355
|
-
return Array.from(deduped.values()).sort((left, right) => {
|
|
356
|
-
const cmp = left.at.localeCompare(right.at);
|
|
357
|
-
if (cmp !== 0)
|
|
358
|
-
return cmp;
|
|
359
|
-
return left.item.id.localeCompare(right.item.id);
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
function findLatestVerboseItem(items, predicate) {
|
|
363
|
-
for (let i = items.length - 1; i >= 0; i -= 1) {
|
|
364
|
-
const item = items[i];
|
|
365
|
-
if (predicate(item)) {
|
|
366
|
-
return item;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return undefined;
|
|
370
|
-
}
|
|
371
240
|
function collapseRepeatedFeedRows(rows) {
|
|
372
241
|
const collapsed = [];
|
|
373
242
|
for (const row of rows) {
|
|
@@ -8,7 +8,6 @@ function capArray(arr, max) {
|
|
|
8
8
|
}
|
|
9
9
|
const DETAIL_INITIAL = {
|
|
10
10
|
detailTab: "timeline",
|
|
11
|
-
timelineMode: "compact",
|
|
12
11
|
timeline: [],
|
|
13
12
|
rawRuns: [],
|
|
14
13
|
rawFeedEvents: [],
|
|
@@ -140,8 +139,8 @@ export function watchReducer(state, action) {
|
|
|
140
139
|
return { ...state, feedEvents: capArray([...state.feedEvents, action.event], MAX_FEED_EVENTS) };
|
|
141
140
|
case "switch-detail-tab":
|
|
142
141
|
return { ...state, detailTab: action.tab };
|
|
143
|
-
|
|
144
|
-
return
|
|
142
|
+
default:
|
|
143
|
+
return state;
|
|
145
144
|
}
|
|
146
145
|
}
|
|
147
146
|
// ─── Feed Event → Issue List + Timeline ───────────────────────────
|