patchrelay 0.14.2 → 0.15.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 +4 -1
- package/dist/cli/watch/HelpBar.js +2 -2
- package/dist/cli/watch/IssueDetailView.js +14 -4
- package/dist/cli/watch/ThreadView.js +8 -2
- package/dist/cli/watch/TurnSection.js +7 -2
- package/dist/cli/watch/watch-state.js +33 -1
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/cli/watch/App.js
CHANGED
|
@@ -41,7 +41,10 @@ export function App({ baseUrl, bearerToken, initialIssueKey }) {
|
|
|
41
41
|
if (key.escape || key.backspace || key.delete) {
|
|
42
42
|
dispatch({ type: "exit-detail" });
|
|
43
43
|
}
|
|
44
|
+
else if (input === "f") {
|
|
45
|
+
dispatch({ type: "toggle-follow" });
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
});
|
|
46
|
-
return (_jsx(Box, { flexDirection: "column", children: state.view === "list" ? (_jsx(IssueListView, { issues: filtered, selectedIndex: state.selectedIndex, connected: state.connected, filter: state.filter, totalCount: state.issues.length })) : (_jsx(IssueDetailView, { issue: state.issues.find((i) => i.issueKey === state.activeDetailKey), thread: state.thread, report: state.report })) }));
|
|
49
|
+
return (_jsx(Box, { flexDirection: "column", children: state.view === "list" ? (_jsx(IssueListView, { issues: filtered, selectedIndex: state.selectedIndex, connected: state.connected, filter: state.filter, totalCount: state.issues.length })) : (_jsx(IssueDetailView, { issue: state.issues.find((i) => i.issueKey === state.activeDetailKey), thread: state.thread, report: state.report, follow: state.follow })) }));
|
|
47
50
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
export function HelpBar({ view }) {
|
|
3
|
+
export function HelpBar({ view, follow }) {
|
|
4
4
|
return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: view === "list"
|
|
5
5
|
? "j/k: navigate Enter: detail Tab: filter q: quit"
|
|
6
|
-
:
|
|
6
|
+
: `Esc: back f: follow ${follow ? "on" : "off"} q: quit` }) }));
|
|
7
7
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { ThreadView } from "./ThreadView.js";
|
|
4
4
|
import { HelpBar } from "./HelpBar.js";
|
|
@@ -6,13 +6,23 @@ function truncate(text, max) {
|
|
|
6
6
|
const line = text.replace(/\n/g, " ").trim();
|
|
7
7
|
return line.length > max ? `${line.slice(0, max - 3)}...` : line;
|
|
8
8
|
}
|
|
9
|
+
function formatTokens(n) {
|
|
10
|
+
if (n >= 1_000_000)
|
|
11
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
12
|
+
if (n >= 1_000)
|
|
13
|
+
return `${(n / 1_000).toFixed(1)}k`;
|
|
14
|
+
return String(n);
|
|
15
|
+
}
|
|
16
|
+
function ThreadStatusBar({ thread, follow }) {
|
|
17
|
+
return (_jsxs(Box, { gap: 2, children: [thread.tokenUsage && (_jsxs(Text, { dimColor: true, children: ["tokens: ", formatTokens(thread.tokenUsage.inputTokens), " in / ", formatTokens(thread.tokenUsage.outputTokens), " out"] })), thread.diffSummary && thread.diffSummary.filesChanged > 0 && (_jsxs(Text, { dimColor: true, children: ["diff: ", thread.diffSummary.filesChanged, " file", thread.diffSummary.filesChanged !== 1 ? "s" : "", " ", "+", thread.diffSummary.linesAdded, " -", thread.diffSummary.linesRemoved] })), follow && _jsx(Text, { color: "yellow", children: "follow" })] }));
|
|
18
|
+
}
|
|
9
19
|
function ReportView({ report }) {
|
|
10
20
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { dimColor: true, children: "Latest run:" }), _jsx(Text, { bold: true, children: report.runType }), _jsx(Text, { color: report.status === "completed" ? "green" : "red", children: report.status })] }), report.summary && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Summary:" }), _jsx(Text, { wrap: "wrap", children: truncate(report.summary, 300) })] })), report.commands.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Commands (", report.commands.length, "):"] }), report.commands.slice(-10).map((cmd, i) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: cmd.exitCode === 0 ? "green" : cmd.exitCode !== undefined ? "red" : "white", children: cmd.exitCode === 0 ? "\u2713" : cmd.exitCode !== undefined ? "\u2717" : " " }), _jsx(Text, { dimColor: true, children: "$ " }), _jsx(Text, { children: truncate(cmd.command, 60) }), cmd.durationMs !== undefined && _jsxs(Text, { dimColor: true, children: [" ", (cmd.durationMs / 1000).toFixed(1), "s"] })] }, `cmd-${i}`)))] })), _jsxs(Box, { marginTop: 1, gap: 2, children: [report.fileChanges > 0 && _jsxs(Text, { dimColor: true, children: [report.fileChanges, " file change", report.fileChanges !== 1 ? "s" : ""] }), report.toolCalls > 0 && _jsxs(Text, { dimColor: true, children: [report.toolCalls, " tool call", report.toolCalls !== 1 ? "s" : ""] }), report.assistantMessages.length > 0 && _jsxs(Text, { dimColor: true, children: [report.assistantMessages.length, " message", report.assistantMessages.length !== 1 ? "s" : ""] })] })] }));
|
|
11
21
|
}
|
|
12
|
-
export function IssueDetailView({ issue, thread, report }) {
|
|
22
|
+
export function IssueDetailView({ issue, thread, report, follow }) {
|
|
13
23
|
if (!issue) {
|
|
14
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Issue not found." }), _jsx(HelpBar, { view: "detail" })] }));
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Issue not found." }), _jsx(HelpBar, { view: "detail", follow: follow })] }));
|
|
15
25
|
}
|
|
16
26
|
const key = issue.issueKey ?? issue.projectId;
|
|
17
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: key }), _jsx(Text, { color: "cyan", children: issue.factoryState }), issue.activeRunType && _jsx(Text, { color: "yellow", children: issue.activeRunType }), issue.prNumber !== undefined && _jsxs(Text, { dimColor: true, children: ["PR #", issue.prNumber] })] }), issue.title && _jsx(Text, { dimColor: true, children: issue.title }), _jsx(Text, { dimColor: true, children: "─".repeat(72) }), thread ? (_jsx(ThreadView, { thread: thread })) : report ? (_jsx(ReportView, { report: report })) : (_jsx(Text, { dimColor: true, children: "Loading..." })), _jsx(Text, { dimColor: true, children: "─".repeat(72) }), _jsx(HelpBar, { view: "detail" })] }));
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { bold: true, children: key }), _jsx(Text, { color: "cyan", children: issue.factoryState }), issue.activeRunType && _jsx(Text, { color: "yellow", children: issue.activeRunType }), issue.prNumber !== undefined && _jsxs(Text, { dimColor: true, children: ["PR #", issue.prNumber] })] }), issue.title && _jsx(Text, { dimColor: true, children: issue.title }), thread && _jsx(ThreadStatusBar, { thread: thread, follow: follow }), _jsx(Text, { dimColor: true, children: "─".repeat(72) }), thread ? (_jsx(ThreadView, { thread: thread, follow: follow })) : report ? (_jsx(ReportView, { report: report })) : (_jsx(Text, { dimColor: true, children: "Loading..." })), _jsx(Text, { dimColor: true, children: "─".repeat(72) }), _jsx(HelpBar, { view: "detail", follow: follow })] }));
|
|
18
28
|
}
|
|
@@ -15,6 +15,12 @@ function planStepColor(status) {
|
|
|
15
15
|
return "yellow";
|
|
16
16
|
return "white";
|
|
17
17
|
}
|
|
18
|
-
export function ThreadView({ thread }) {
|
|
19
|
-
|
|
18
|
+
export function ThreadView({ thread, follow }) {
|
|
19
|
+
const visibleTurns = follow && thread.turns.length > 1
|
|
20
|
+
? thread.turns.slice(-1)
|
|
21
|
+
: thread.turns;
|
|
22
|
+
const turnOffset = follow && thread.turns.length > 1
|
|
23
|
+
? thread.turns.length - 1
|
|
24
|
+
: 0;
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 2, children: [_jsxs(Text, { dimColor: true, children: ["Thread: ", thread.threadId.slice(0, 16)] }), _jsxs(Text, { dimColor: true, children: ["Status: ", thread.status] }), _jsxs(Text, { dimColor: true, children: ["Turns: ", thread.turns.length] })] }), thread.plan && thread.plan.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Plan:" }), thread.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, { flexDirection: "column", marginTop: 1, children: visibleTurns.map((turn, i) => (_jsx(TurnSection, { turn: turn, index: i + turnOffset, follow: follow }, turn.id))) })] }));
|
|
20
26
|
}
|
|
@@ -10,6 +10,11 @@ function turnStatusColor(status) {
|
|
|
10
10
|
return "yellow";
|
|
11
11
|
return "white";
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const FOLLOW_TAIL_SIZE = 8;
|
|
14
|
+
export function TurnSection({ turn, index, follow }) {
|
|
15
|
+
const items = follow && turn.items.length > FOLLOW_TAIL_SIZE
|
|
16
|
+
? turn.items.slice(-FOLLOW_TAIL_SIZE)
|
|
17
|
+
: turn.items;
|
|
18
|
+
const skipped = turn.items.length - items.length;
|
|
19
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { bold: true, children: ["Turn #", index + 1] }), _jsx(Text, { color: turnStatusColor(turn.status), children: turn.status }), _jsxs(Text, { dimColor: true, children: ["(", turn.items.length, " items)"] })] }), skipped > 0 && _jsxs(Text, { dimColor: true, children: [" ... ", skipped, " earlier items"] }), items.map((item, i) => (_jsx(ItemLine, { item: item, isLast: i === items.length - 1 }, item.id)))] }));
|
|
15
20
|
}
|
|
@@ -7,6 +7,7 @@ export const initialWatchState = {
|
|
|
7
7
|
thread: null,
|
|
8
8
|
report: null,
|
|
9
9
|
filter: "non-done",
|
|
10
|
+
follow: true,
|
|
10
11
|
};
|
|
11
12
|
const TERMINAL_FACTORY_STATES = new Set(["done", "failed"]);
|
|
12
13
|
export function filterIssues(issues, filter) {
|
|
@@ -57,6 +58,8 @@ export function watchReducer(state, action) {
|
|
|
57
58
|
return applyCodexNotification(state, action.method, action.params);
|
|
58
59
|
case "cycle-filter":
|
|
59
60
|
return { ...state, filter: nextFilter(state.filter), selectedIndex: 0 };
|
|
61
|
+
case "toggle-follow":
|
|
62
|
+
return { ...state, follow: !state.follow };
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
// ─── Feed Event Application ───────────────────────────────────────
|
|
@@ -123,6 +126,8 @@ function applyCodexNotification(state, method, params) {
|
|
|
123
126
|
return withThread(state, appendItemText(state.thread, params));
|
|
124
127
|
case "thread/status/changed":
|
|
125
128
|
return withThread(state, updateThreadStatus(state.thread, params));
|
|
129
|
+
case "thread/tokenUsage/updated":
|
|
130
|
+
return withThread(state, updateTokenUsage(state.thread, params));
|
|
126
131
|
default:
|
|
127
132
|
return state;
|
|
128
133
|
}
|
|
@@ -185,7 +190,34 @@ function updatePlan(thread, params) {
|
|
|
185
190
|
}
|
|
186
191
|
function updateDiff(thread, params) {
|
|
187
192
|
const diff = typeof params.diff === "string" ? params.diff : undefined;
|
|
188
|
-
return { ...thread, diff };
|
|
193
|
+
return { ...thread, diff, diffSummary: diff ? parseDiffSummary(diff) : undefined };
|
|
194
|
+
}
|
|
195
|
+
function parseDiffSummary(diff) {
|
|
196
|
+
const files = new Set();
|
|
197
|
+
let added = 0;
|
|
198
|
+
let removed = 0;
|
|
199
|
+
for (const line of diff.split("\n")) {
|
|
200
|
+
if (line.startsWith("+++ b/")) {
|
|
201
|
+
files.add(line.slice(6));
|
|
202
|
+
}
|
|
203
|
+
else if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
204
|
+
added += 1;
|
|
205
|
+
}
|
|
206
|
+
else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
207
|
+
removed += 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { filesChanged: files.size, linesAdded: added, linesRemoved: removed };
|
|
211
|
+
}
|
|
212
|
+
function updateTokenUsage(thread, params) {
|
|
213
|
+
const usage = params.usage;
|
|
214
|
+
if (!usage)
|
|
215
|
+
return thread;
|
|
216
|
+
const inputTokens = typeof usage.inputTokens === "number" ? usage.inputTokens
|
|
217
|
+
: typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
218
|
+
const outputTokens = typeof usage.outputTokens === "number" ? usage.outputTokens
|
|
219
|
+
: typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
220
|
+
return { ...thread, tokenUsage: { inputTokens, outputTokens } };
|
|
189
221
|
}
|
|
190
222
|
function updateThreadStatus(thread, params) {
|
|
191
223
|
const statusObj = params.status;
|