patchrelay 0.20.4 → 0.20.6
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 -1
- package/dist/cli/watch/IssueDetailView.js +10 -55
- package/dist/cli/watch/IssueListView.js +12 -3
- package/dist/cli/watch/ItemLine.js +8 -4
- package/dist/cli/watch/Timeline.js +8 -6
- package/dist/http.js +2 -2
- package/dist/run-orchestrator.js +10 -0
- package/dist/service.js +17 -2
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/cli/watch/App.js
CHANGED
|
@@ -159,5 +159,5 @@ export function App({ baseUrl, bearerToken, initialIssueKey }) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
|
-
return (_jsx(Box, { flexDirection: "column", children: state.view === "list" ? (_jsx(IssueListView, { issues: filtered, allIssues: state.issues, selectedIndex: state.selectedIndex, connected: state.connected, filter: state.filter, totalCount: state.issues.length })) : state.view === "detail" ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(IssueDetailView, { issue: state.issues.find((i) => i.issueKey === state.activeDetailKey), timeline: state.timeline, follow: state.follow, activeRunStartedAt: state.activeRunStartedAt, tokenUsage: state.tokenUsage, diffSummary: state.diffSummary, plan: state.plan, issueContext: state.issueContext
|
|
162
|
+
return (_jsx(Box, { flexDirection: "column", children: state.view === "list" ? (_jsx(IssueListView, { issues: filtered, allIssues: state.issues, selectedIndex: state.selectedIndex, connected: state.connected, filter: state.filter, totalCount: state.issues.length })) : state.view === "detail" ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(IssueDetailView, { issue: state.issues.find((i) => i.issueKey === state.activeDetailKey), timeline: state.timeline, follow: state.follow, activeRunStartedAt: state.activeRunStartedAt, tokenUsage: state.tokenUsage, diffSummary: state.diffSummary, plan: state.plan, issueContext: state.issueContext }), 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 }))] })) : (_jsx(FeedView, { events: state.feedEvents, connected: state.connected })) }));
|
|
163
163
|
}
|
|
@@ -35,62 +35,17 @@ function planStepColor(status) {
|
|
|
35
35
|
return "yellow";
|
|
36
36
|
return "white";
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
delegated: "blue", preparing: "blue",
|
|
40
|
-
implementing: "yellow", awaiting_input: "yellow",
|
|
41
|
-
pr_open: "cyan",
|
|
42
|
-
changes_requested: "magenta", repairing_ci: "magenta", repairing_queue: "magenta",
|
|
43
|
-
awaiting_queue: "green", done: "green",
|
|
44
|
-
failed: "red", escalated: "red",
|
|
45
|
-
};
|
|
46
|
-
function CompactSidebar({ issues, activeKey }) {
|
|
47
|
-
return (_jsx(Box, { flexDirection: "column", width: 24, paddingRight: 1, children: issues.map((issue) => {
|
|
48
|
-
const key = issue.issueKey ?? issue.projectId;
|
|
49
|
-
const isCurrent = key === activeKey;
|
|
50
|
-
const sc = SIDEBAR_STATE_COLORS[issue.factoryState] ?? "white";
|
|
51
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: isCurrent ? "blueBright" : "white", bold: isCurrent, children: isCurrent ? "\u25b8" : " " }), _jsx(Text, { bold: isCurrent, children: key.padEnd(9) }), _jsx(Text, { color: sc, children: issue.factoryState.slice(0, 10) })] }, key));
|
|
52
|
-
}) }));
|
|
53
|
-
}
|
|
54
|
-
const PRIORITY_LABELS = {
|
|
55
|
-
1: { label: "urgent", color: "red" },
|
|
56
|
-
2: { label: "high", color: "yellow" },
|
|
57
|
-
3: { label: "medium", color: "cyan" },
|
|
58
|
-
4: { label: "low", color: "" },
|
|
59
|
-
};
|
|
60
|
-
function ContextPanel({ issue, ctx }) {
|
|
61
|
-
const parts = [];
|
|
62
|
-
if (ctx.priority != null && ctx.priority > 0) {
|
|
63
|
-
const p = PRIORITY_LABELS[ctx.priority];
|
|
64
|
-
parts.push(p ? `${p.label}` : `p${ctx.priority}`);
|
|
65
|
-
}
|
|
66
|
-
if (issue.prNumber) {
|
|
67
|
-
let pr = `#${issue.prNumber}`;
|
|
68
|
-
if (issue.prReviewState === "approved")
|
|
69
|
-
pr += " \u2713";
|
|
70
|
-
else if (issue.prReviewState === "changes_requested")
|
|
71
|
-
pr += " \u2717";
|
|
72
|
-
parts.push(pr);
|
|
73
|
-
}
|
|
74
|
-
if (ctx.runCount > 0)
|
|
75
|
-
parts.push(`${ctx.runCount} runs`);
|
|
76
|
-
const retries = [
|
|
77
|
-
ctx.ciRepairAttempts > 0 ? `ci:${ctx.ciRepairAttempts}` : "",
|
|
78
|
-
ctx.queueRepairAttempts > 0 ? `q:${ctx.queueRepairAttempts}` : "",
|
|
79
|
-
ctx.reviewFixAttempts > 0 ? `rev:${ctx.reviewFixAttempts}` : "",
|
|
80
|
-
].filter(Boolean).join(" ");
|
|
81
|
-
if (retries)
|
|
82
|
-
parts.push(retries);
|
|
83
|
-
return (_jsxs(Box, { flexDirection: "column", children: [parts.length > 0 && _jsx(Text, { dimColor: true, children: parts.join(" ") }), ctx.description && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [ctx.description.slice(0, 160), ctx.description.length > 160 ? "\u2026" : ""] }))] }));
|
|
84
|
-
}
|
|
85
|
-
function DetailPanel({ issue, timeline, follow, activeRunStartedAt, tokenUsage, diffSummary, plan, issueContext, }) {
|
|
38
|
+
export function IssueDetailView({ issue, timeline, follow, activeRunStartedAt, tokenUsage, diffSummary, plan, issueContext, }) {
|
|
86
39
|
if (!issue) {
|
|
87
|
-
return _jsx(Text, { color: "red", children: "Issue not found." });
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Issue not found." }), _jsx(HelpBar, { view: "detail", follow: follow })] }));
|
|
88
41
|
}
|
|
89
42
|
const key = issue.issueKey ?? issue.projectId;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
43
|
+
const meta = [];
|
|
44
|
+
if (tokenUsage)
|
|
45
|
+
meta.push(`${formatTokens(tokenUsage.inputTokens)} in / ${formatTokens(tokenUsage.outputTokens)} out`);
|
|
46
|
+
if (diffSummary && diffSummary.filesChanged > 0)
|
|
47
|
+
meta.push(`${diffSummary.filesChanged}f +${diffSummary.linesAdded} -${diffSummary.linesRemoved}`);
|
|
48
|
+
if (issueContext?.runCount)
|
|
49
|
+
meta.push(`${issueContext.runCount} runs`);
|
|
50
|
+
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: ["#", issue.prNumber] }), activeRunStartedAt && _jsx(ElapsedTime, { startedAt: activeRunStartedAt }), meta.length > 0 && _jsx(Text, { dimColor: true, children: meta.join(" ") }), follow && _jsx(Text, { color: "yellow", children: "follow" })] }), issue.title && _jsx(Text, { children: issue.title }), plan && plan.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: 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 }) }), _jsx(Box, { marginTop: 1, children: _jsx(HelpBar, { view: "detail", follow: follow }) })] }));
|
|
96
51
|
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useStdout } from "ink";
|
|
3
3
|
import { IssueRow } from "./IssueRow.js";
|
|
4
4
|
import { StatusBar } from "./StatusBar.js";
|
|
5
5
|
import { HelpBar } from "./HelpBar.js";
|
|
6
|
-
// Fixed columns: selector(2) + key(10) + state(11) + run(11) + pr(7) + ago(4) + gaps(6) = ~51
|
|
7
6
|
const FIXED_COLS = 51;
|
|
7
|
+
const CHROME_ROWS = 4;
|
|
8
8
|
export function IssueListView({ issues, allIssues, selectedIndex, connected, filter, totalCount }) {
|
|
9
9
|
const { stdout } = useStdout();
|
|
10
10
|
const cols = stdout?.columns ?? 80;
|
|
11
|
+
const rows = stdout?.rows ?? 24;
|
|
11
12
|
const titleWidth = Math.max(0, cols - FIXED_COLS);
|
|
12
|
-
|
|
13
|
+
const maxVisible = Math.max(1, rows - CHROME_ROWS);
|
|
14
|
+
let startIndex = 0;
|
|
15
|
+
if (issues.length > maxVisible) {
|
|
16
|
+
startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), issues.length - maxVisible));
|
|
17
|
+
}
|
|
18
|
+
const visible = issues.slice(startIndex, startIndex + maxVisible);
|
|
19
|
+
const hiddenAbove = startIndex;
|
|
20
|
+
const hiddenBelow = Math.max(0, issues.length - startIndex - maxVisible);
|
|
21
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StatusBar, { issues: issues, totalCount: totalCount, filter: filter, connected: connected, allIssues: allIssues }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: issues.length === 0 ? (_jsx(Text, { dimColor: true, children: "No issues match the current filter." })) : (_jsxs(_Fragment, { children: [hiddenAbove > 0 && _jsxs(Text, { dimColor: true, children: [" ", hiddenAbove, " more above"] }), visible.map((issue, i) => (_jsx(IssueRow, { issue: issue, selected: startIndex + i === selectedIndex, titleWidth: titleWidth }, issue.issueKey ?? `${issue.projectId}-${startIndex + i}`))), hiddenBelow > 0 && _jsxs(Text, { dimColor: true, children: [" ", hiddenBelow, " more below"] })] })) }), _jsx(Box, { marginTop: 1, children: _jsx(HelpBar, { view: "list" }) })] }));
|
|
13
22
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
const STATUS_SYMBOL = {
|
|
4
4
|
completed: "\u2713",
|
|
@@ -23,7 +23,7 @@ function truncate(text, max) {
|
|
|
23
23
|
return line.length > max ? `${line.slice(0, max - 3)}...` : line;
|
|
24
24
|
}
|
|
25
25
|
function renderAgentMessage(item) {
|
|
26
|
-
return (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "message: " }), _jsx(Text, { children:
|
|
26
|
+
return (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "message: " }), _jsx(Text, { wrap: "wrap", children: item.text ?? "" })] }));
|
|
27
27
|
}
|
|
28
28
|
function renderCommand(item) {
|
|
29
29
|
const cmd = item.command ?? "?";
|
|
@@ -64,9 +64,13 @@ export function ItemLine({ item, isLast }) {
|
|
|
64
64
|
case "plan":
|
|
65
65
|
content = renderPlan(item);
|
|
66
66
|
break;
|
|
67
|
-
case "userMessage":
|
|
68
|
-
|
|
67
|
+
case "userMessage": {
|
|
68
|
+
const userText = item.text?.trim();
|
|
69
|
+
if (!userText)
|
|
70
|
+
return _jsx(_Fragment, {});
|
|
71
|
+
content = (_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "you: " }), _jsx(Text, { wrap: "wrap", children: userText })] }));
|
|
69
72
|
break;
|
|
73
|
+
}
|
|
70
74
|
default:
|
|
71
75
|
content = renderDefault(item);
|
|
72
76
|
break;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
2
|
+
import { Box, Text, useStdout } from "ink";
|
|
3
3
|
import { TimelineRow } from "./TimelineRow.js";
|
|
4
|
-
const
|
|
4
|
+
const DETAIL_CHROME_ROWS = 10;
|
|
5
5
|
export function Timeline({ entries, follow }) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const { stdout } = useStdout();
|
|
7
|
+
const rows = stdout?.rows ?? 24;
|
|
8
|
+
const maxVisible = Math.max(5, rows - DETAIL_CHROME_ROWS);
|
|
9
|
+
const tailSize = follow ? Math.min(maxVisible, entries.length) : Math.min(maxVisible, entries.length);
|
|
10
|
+
const visible = entries.length > tailSize ? entries.slice(-tailSize) : entries;
|
|
9
11
|
const skipped = entries.length - visible.length;
|
|
10
12
|
if (entries.length === 0) {
|
|
11
13
|
return _jsx(Text, { dimColor: true, children: "No timeline events yet." });
|
|
12
14
|
}
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "column", children: [skipped > 0 && _jsxs(Text, { dimColor: true, children: [" ... ", skipped, " earlier
|
|
15
|
+
return (_jsxs(Box, { flexDirection: "column", children: [skipped > 0 && _jsxs(Text, { dimColor: true, children: [" ... ", skipped, " earlier"] }), visible.map((entry) => (_jsx(TimelineRow, { entry: entry }, entry.id)))] }));
|
|
14
16
|
}
|
package/dist/http.js
CHANGED
|
@@ -243,6 +243,8 @@ export async function buildHttpServer(config, service, logger) {
|
|
|
243
243
|
return reply.code(401).send({ ok: false, reason: "operator_auth_required" });
|
|
244
244
|
}
|
|
245
245
|
});
|
|
246
|
+
}
|
|
247
|
+
if (managementRoutesEnabled) {
|
|
246
248
|
app.get("/api/issues/:issueKey", async (request, reply) => {
|
|
247
249
|
const issueKey = request.params.issueKey;
|
|
248
250
|
const result = await service.getIssueOverview(issueKey);
|
|
@@ -296,8 +298,6 @@ export async function buildHttpServer(config, service, logger) {
|
|
|
296
298
|
}
|
|
297
299
|
return reply.send({ ok: true, ...link });
|
|
298
300
|
});
|
|
299
|
-
}
|
|
300
|
-
if (managementRoutesEnabled) {
|
|
301
301
|
app.post("/api/issues/:issueKey/retry", async (request, reply) => {
|
|
302
302
|
const issueKey = request.params.issueKey;
|
|
303
303
|
const result = service.retryIssue(issueKey);
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -546,6 +546,16 @@ export class RunOrchestrator {
|
|
|
546
546
|
// Reactive loops (CI repair, review fix) will handle follow-up if needed.
|
|
547
547
|
if (latestTurn?.status === "interrupted") {
|
|
548
548
|
this.logger.warn({ issueKey: issue.issueKey, runType: run.runType, threadId: run.threadId }, "Run has interrupted turn — marking as failed");
|
|
549
|
+
// Interrupted runs are not real failures — undo the budget increment.
|
|
550
|
+
if (run.runType === "ci_repair" && issue.ciRepairAttempts > 0) {
|
|
551
|
+
this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, ciRepairAttempts: issue.ciRepairAttempts - 1 });
|
|
552
|
+
}
|
|
553
|
+
else if (run.runType === "queue_repair" && issue.queueRepairAttempts > 0) {
|
|
554
|
+
this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, queueRepairAttempts: issue.queueRepairAttempts - 1 });
|
|
555
|
+
}
|
|
556
|
+
else if (run.runType === "review_fix" && issue.reviewFixAttempts > 0) {
|
|
557
|
+
this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, reviewFixAttempts: issue.reviewFixAttempts - 1 });
|
|
558
|
+
}
|
|
549
559
|
this.failRunAndClear(run, "Codex turn was interrupted");
|
|
550
560
|
const failedIssue = this.db.getIssue(run.projectId, run.linearIssueId) ?? issue;
|
|
551
561
|
void this.emitLinearActivity(failedIssue, buildRunFailureActivity(run.runType, "The Codex turn was interrupted."));
|
package/dist/service.js
CHANGED
|
@@ -278,12 +278,27 @@ export class PatchRelayService {
|
|
|
278
278
|
return undefined;
|
|
279
279
|
if (issue.activeRunId)
|
|
280
280
|
return { error: "Issue already has an active run" };
|
|
281
|
-
|
|
281
|
+
// Infer run type from current state instead of always resetting to implementation
|
|
282
|
+
let runType = "implementation";
|
|
283
|
+
let factoryState = "delegated";
|
|
284
|
+
if (issue.prNumber && issue.prCheckStatus === "failed") {
|
|
285
|
+
runType = "ci_repair";
|
|
286
|
+
factoryState = "repairing_ci";
|
|
287
|
+
}
|
|
288
|
+
else if (issue.prNumber && issue.prReviewState === "changes_requested") {
|
|
289
|
+
runType = "review_fix";
|
|
290
|
+
factoryState = "changes_requested";
|
|
291
|
+
}
|
|
292
|
+
else if (issue.prNumber) {
|
|
293
|
+
// PR exists but no specific failure — re-run implementation
|
|
294
|
+
runType = "implementation";
|
|
295
|
+
factoryState = "implementing";
|
|
296
|
+
}
|
|
282
297
|
this.db.upsertIssue({
|
|
283
298
|
projectId: issue.projectId,
|
|
284
299
|
linearIssueId: issue.linearIssueId,
|
|
285
300
|
pendingRunType: runType,
|
|
286
|
-
factoryState:
|
|
301
|
+
factoryState: factoryState,
|
|
287
302
|
});
|
|
288
303
|
this.runtime.enqueueIssue(issue.projectId, issue.linearIssueId);
|
|
289
304
|
return { issueKey, runType };
|