patchrelay 0.25.5 → 0.26.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.25.5",
4
- "commit": "ce4e0940795d",
5
- "builtAt": "2026-03-26T20:58:38.297Z"
3
+ "version": "0.26.0",
4
+ "commit": "a2509ed10bce",
5
+ "builtAt": "2026-03-27T10:15:02.197Z"
6
6
  }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
+ import { summarizeIssueStatusNote } from "./issue-status-note.js";
3
4
  const STATE_COLORS = {
4
5
  delegated: "blue",
5
6
  preparing: "blue",
@@ -28,12 +29,6 @@ const STATE_SHORT = {
28
29
  escalated: "escalated",
29
30
  awaiting_input: "paused",
30
31
  };
31
- const RUN_SHORT = {
32
- implementation: "impl",
33
- ci_repair: "ci",
34
- review_fix: "review",
35
- queue_repair: "merge",
36
- };
37
32
  const STATUS_SHORT = {
38
33
  running: "\u25b8",
39
34
  completed: "\u2713",
@@ -43,15 +38,6 @@ const STATUS_SHORT = {
43
38
  function stateColor(state) {
44
39
  return STATE_COLORS[state] ?? "white";
45
40
  }
46
- function formatRun(issue) {
47
- const run = issue.activeRunType ?? issue.latestRunType;
48
- if (!run)
49
- return "";
50
- const runLabel = RUN_SHORT[run] ?? run;
51
- const status = issue.activeRunType ? "running" : issue.latestRunStatus;
52
- const statusLabel = status ? STATUS_SHORT[status] ?? status : "";
53
- return `${runLabel} ${statusLabel}`;
54
- }
55
41
  function formatPr(issue) {
56
42
  if (!issue.prNumber)
57
43
  return "";
@@ -81,7 +67,7 @@ function relativeTime(iso) {
81
67
  function truncate(text, max) {
82
68
  if (max <= 0)
83
69
  return "";
84
- return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
70
+ return text.length > max ? text.slice(0, max) : text;
85
71
  }
86
72
  const TERMINAL_STATES = new Set(["done", "failed", "escalated", "awaiting_input"]);
87
73
  function formatStatus(issue) {
@@ -103,5 +89,6 @@ export function IssueRow({ issue, selected, titleWidth }) {
103
89
  const ago = relativeTime(issue.updatedAt);
104
90
  const tw = titleWidth ?? 30;
105
91
  const title = issue.title ? truncate(issue.title, tw) : "";
106
- return (_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "white", bold: selected, children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key.padEnd(9)}` }), _jsx(Text, { color: stateColor(issue.factoryState), children: ` ${status.padEnd(12)}` }), _jsx(Text, { dimColor: true, children: ` ${pr.padEnd(6)}` }), _jsx(Text, { dimColor: true, children: ` ${ago.padStart(3)}` }), title ? _jsx(Text, { dimColor: true, children: ` ${title}` }) : null] }));
92
+ const detail = selected ? summarizeIssueStatusNote(issue.statusNote) : undefined;
93
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: detail ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "blueBright" : "white", bold: selected, children: selected ? "\u25b8" : " " }), _jsx(Text, { bold: true, children: ` ${key.padEnd(9)}` }), _jsx(Text, { color: stateColor(issue.factoryState), children: ` ${status.padEnd(12)}` }), _jsx(Text, { dimColor: true, children: ` ${pr.padEnd(6)}` }), _jsx(Text, { dimColor: true, children: ` ${ago.padStart(3)}` }), title ? _jsx(Text, { dimColor: true, children: ` ${title}` }) : null] }), detail ? (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: detail }) })) : null] }));
107
94
  }
@@ -51,15 +51,37 @@ function detailPrefix(detail) {
51
51
  return "$ ";
52
52
  return "";
53
53
  }
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
+ }
54
75
  function FeedRow({ entry }) {
55
76
  const label = entry.feed.status ?? entry.feed.feedKind;
56
77
  return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [formatTime(entry.at), " "] }), _jsx(Text, { color: "cyan", bold: true, children: label.padEnd(12) })] }), _jsx(Box, { paddingLeft: 6, children: _jsx(Text, { wrap: "wrap", children: entry.feed.summary }) })] }));
57
78
  }
58
- function RunRow({ entry }) {
79
+ function RunRow({ entry, mode, }) {
59
80
  const run = entry.run;
60
81
  const color = runStatusColor(run.status);
61
82
  const duration = run.endedAt ? formatDuration(run.startedAt, run.endedAt) : undefined;
62
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [formatTime(entry.at), " "] }), _jsx(Text, { bold: true, color: "yellow", children: (RUN_LABELS[run.runType] ?? run.runType).padEnd(12) }), _jsxs(Text, { bold: true, color: color, children: [" ", runStatusLabel(run.status)] }), duration ? _jsx(Text, { dimColor: true, children: ` ${duration}` }) : null] }), entry.details.length > 0 && _jsx(Text, { children: " " }), entry.details.map((detail, index) => (_jsx(Box, { paddingLeft: 6, marginBottom: index === entry.details.length - 1 ? 0 : 1, children: _jsxs(Text, { wrap: "wrap", ...(detailColor(detail) ? { color: detailColor(detail) } : {}), bold: detail.tone === "message", children: [detailPrefix(detail), detail.text] }) }, `${entry.id}-detail-${index}`)))] }));
83
+ const showVerboseItems = mode === "verbose" && entry.items.length > 0;
84
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [formatTime(entry.at), " "] }), _jsx(Text, { bold: true, color: "yellow", children: (RUN_LABELS[run.runType] ?? run.runType).padEnd(12) }), _jsxs(Text, { bold: true, color: color, children: [" ", runStatusLabel(run.status)] }), duration ? _jsx(Text, { dimColor: true, children: ` ${duration}` }) : null] }), entry.details.length > 0 && _jsx(Text, { children: " " }), entry.details.map((detail, index) => (_jsx(Box, { paddingLeft: 6, marginBottom: index === entry.details.length - 1 ? 0 : 1, children: _jsxs(Text, { wrap: "wrap", ...(detailColor(detail) ? { color: detailColor(detail) } : {}), bold: detail.tone === "message", children: [detailPrefix(detail), detail.text] }) }, `${entry.id}-detail-${index}`))), showVerboseItems && _jsx(Text, { children: " " }), showVerboseItems && entry.items.map((itemEntry, index) => (_jsxs(Box, { flexDirection: "column", paddingLeft: 6, marginBottom: index === entry.items.length - 1 ? 0 : 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { dimColor: true, children: [formatTime(itemEntry.at), " "] }), _jsx(Text, { dimColor: true, children: verboseItemLabel(itemEntry.item.type) })] }), _jsx(Box, { paddingLeft: 2, children: _jsx(ItemLine, { item: itemEntry.item }) })] }, `${entry.id}-item-${index}`)))] }));
63
85
  }
64
86
  function ItemRow({ entry, mode, }) {
65
87
  return (_jsxs(Box, { flexDirection: "column", paddingLeft: 6, marginBottom: mode === "verbose" ? 1 : 0, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { dimColor: true, children: [formatTime(entry.at), " "] }), _jsx(Text, { dimColor: true, children: entry.item.type })] }), _jsx(Box, { paddingLeft: 2, children: _jsx(ItemLine, { item: entry.item }) })] }));
@@ -73,7 +95,7 @@ export function TimelineRow({ entry, mode }) {
73
95
  case "feed":
74
96
  return _jsx(FeedRow, { entry: entry });
75
97
  case "run":
76
- return _jsx(RunRow, { entry: entry });
98
+ return _jsx(RunRow, { entry: entry, mode: mode });
77
99
  case "item":
78
100
  return _jsx(ItemRow, { entry: entry, mode: mode });
79
101
  case "ci-checks":
@@ -0,0 +1,15 @@
1
+ function stripMarkdownLinks(text) {
2
+ return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
3
+ }
4
+ export function summarizeIssueStatusNote(raw) {
5
+ if (!raw)
6
+ return undefined;
7
+ const firstParagraph = raw.split(/\n\s*\n/).find((part) => part.trim().length > 0)?.trim();
8
+ if (!firstParagraph)
9
+ return undefined;
10
+ const normalized = stripMarkdownLinks(firstParagraph)
11
+ .replace(/`([^`]+)`/g, "$1")
12
+ .replace(/\s+/g, " ")
13
+ .trim();
14
+ return normalized.length > 0 ? normalized : undefined;
15
+ }
package/dist/service.js CHANGED
@@ -9,6 +9,31 @@ import { buildSessionStatusUrl, createSessionStatusToken, deriveSessionStatusSig
9
9
  import { ServiceRuntime } from "./service-runtime.js";
10
10
  import { WebhookHandler } from "./webhook-handler.js";
11
11
  import { acceptIncomingWebhook } from "./service-webhooks.js";
12
+ function parseObjectJson(value) {
13
+ if (!value)
14
+ return undefined;
15
+ try {
16
+ const parsed = JSON.parse(value);
17
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ function extractStatusNote(summaryJson, reportJson) {
24
+ const summary = parseObjectJson(summaryJson);
25
+ if (typeof summary?.latestAssistantMessage === "string" && summary.latestAssistantMessage.trim()) {
26
+ return summary.latestAssistantMessage;
27
+ }
28
+ const report = parseObjectJson(reportJson);
29
+ const assistantMessages = report?.assistantMessages;
30
+ if (Array.isArray(assistantMessages)) {
31
+ const latest = assistantMessages.findLast((value) => typeof value === "string" && value.trim().length > 0);
32
+ if (typeof latest === "string")
33
+ return latest;
34
+ }
35
+ return undefined;
36
+ }
12
37
  export class PatchRelayService {
13
38
  config;
14
39
  db;
@@ -166,7 +191,9 @@ export class PatchRelayService {
166
191
  i.pr_number, i.pr_review_state, i.pr_check_status,
167
192
  active_run.run_type AS active_run_type,
168
193
  latest_run.run_type AS latest_run_type,
169
- latest_run.status AS latest_run_status
194
+ latest_run.status AS latest_run_status,
195
+ latest_run.summary_json AS latest_run_summary_json,
196
+ latest_run.report_json AS latest_run_report_json
170
197
  FROM issues i
171
198
  LEFT JOIN runs active_run ON active_run.id = i.active_run_id
172
199
  LEFT JOIN runs latest_run ON latest_run.id = (
@@ -176,20 +203,24 @@ export class PatchRelayService {
176
203
  )
177
204
  ORDER BY i.updated_at DESC, i.issue_key ASC`)
178
205
  .all();
179
- return rows.map((row) => ({
180
- ...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
181
- ...(row.title !== null ? { title: String(row.title) } : {}),
182
- projectId: String(row.project_id),
183
- factoryState: String(row.factory_state ?? "delegated"),
184
- ...(row.current_linear_state !== null ? { currentLinearState: String(row.current_linear_state) } : {}),
185
- ...(row.active_run_type !== null ? { activeRunType: String(row.active_run_type) } : {}),
186
- ...(row.latest_run_type !== null ? { latestRunType: String(row.latest_run_type) } : {}),
187
- ...(row.latest_run_status !== null ? { latestRunStatus: String(row.latest_run_status) } : {}),
188
- ...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
189
- ...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
190
- ...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
191
- updatedAt: String(row.updated_at),
192
- }));
206
+ return rows.map((row) => {
207
+ const statusNote = extractStatusNote(typeof row.latest_run_summary_json === "string" ? row.latest_run_summary_json : undefined, typeof row.latest_run_report_json === "string" ? row.latest_run_report_json : undefined);
208
+ return {
209
+ ...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
210
+ ...(row.title !== null ? { title: String(row.title) } : {}),
211
+ ...(statusNote ? { statusNote } : {}),
212
+ projectId: String(row.project_id),
213
+ factoryState: String(row.factory_state ?? "delegated"),
214
+ ...(row.current_linear_state !== null ? { currentLinearState: String(row.current_linear_state) } : {}),
215
+ ...(row.active_run_type !== null ? { activeRunType: String(row.active_run_type) } : {}),
216
+ ...(row.latest_run_type !== null ? { latestRunType: String(row.latest_run_type) } : {}),
217
+ ...(row.latest_run_status !== null ? { latestRunStatus: String(row.latest_run_status) } : {}),
218
+ ...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
219
+ ...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
220
+ ...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
221
+ updatedAt: String(row.updated_at),
222
+ };
223
+ });
193
224
  }
194
225
  subscribeCodexNotifications(listener) {
195
226
  let trackedThreadId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.25.5",
3
+ "version": "0.26.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {