patchrelay 0.29.2 → 0.29.3

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.29.2",
4
- "commit": "c98ff7a5985a",
5
- "builtAt": "2026-04-01T00:13:06.178Z"
3
+ "version": "0.29.3",
4
+ "commit": "7f7c95a676dc",
5
+ "builtAt": "2026-04-01T00:22:06.894Z"
6
6
  }
@@ -74,7 +74,8 @@ function verboseItemLabel(type) {
74
74
  }
75
75
  function FeedRow({ entry }) {
76
76
  const label = entry.feed.status ?? entry.feed.feedKind;
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 }) })] }));
77
+ const repeatSuffix = entry.repeatCount && entry.repeatCount > 1 ? ` ×${entry.repeatCount}` : "";
78
+ 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: _jsxs(Text, { wrap: "wrap", children: [entry.feed.summary, repeatSuffix] }) })] }));
78
79
  }
79
80
  function RunRow({ entry, mode, }) {
80
81
  const run = entry.run;
@@ -1,5 +1,6 @@
1
1
  export function buildTimelineRows(entries, mode) {
2
- return mode === "compact" ? buildCompactTimelineRows(entries) : buildVerboseTimelineRows(entries);
2
+ const rows = mode === "compact" ? buildCompactTimelineRows(entries) : buildVerboseTimelineRows(entries);
3
+ return collapseRepeatedFeedRows(rows);
3
4
  }
4
5
  function buildVerboseTimelineRows(entries) {
5
6
  const rows = [];
@@ -43,6 +44,9 @@ function buildVerboseTimelineRows(entries) {
43
44
  }
44
45
  switch (entry.kind) {
45
46
  case "feed":
47
+ if (shouldHideFeed(entry.feed)) {
48
+ break;
49
+ }
46
50
  rows.push({
47
51
  id: entry.id,
48
52
  kind: "feed",
@@ -79,9 +83,9 @@ function buildVerboseTimelineRows(entries) {
79
83
  finalized: run.items.every((item) => item.status !== "inProgress") && run.run.status !== "running",
80
84
  run: { ...run.run, ...(run.endedAt ? { endedAt: run.endedAt } : {}) },
81
85
  details: [],
82
- items: entries
86
+ items: summarizeVerboseItems(entries
83
87
  .filter((entry) => entry.kind === "item" && entry.runId === runId)
84
- .map((entry) => ({ at: entry.at, item: entry.item })),
88
+ .map((entry) => ({ at: entry.at, item: entry.item }))),
85
89
  });
86
90
  }
87
91
  rows.sort((left, right) => {
@@ -135,7 +139,7 @@ function buildCompactTimelineRows(entries) {
135
139
  runs.get(entry.runId).items.push(entry.item);
136
140
  continue;
137
141
  }
138
- if (entry.kind === "feed" && shouldHideFeedInCompact(entry.feed)) {
142
+ if (entry.kind === "feed" && shouldHideFeed(entry.feed)) {
139
143
  continue;
140
144
  }
141
145
  if (entry.kind === "feed") {
@@ -191,15 +195,25 @@ function buildCompactTimelineRows(entries) {
191
195
  });
192
196
  return rows;
193
197
  }
194
- function shouldHideFeedInCompact(feed) {
198
+ function shouldHideFeed(feed) {
195
199
  if (feed.feedKind === "stage" && feed.status === "starting") {
196
200
  return true;
197
201
  }
202
+ if (feed.feedKind === "stage" && feed.status === "reconciled" && isNoOpReconciliation(feed.summary)) {
203
+ return true;
204
+ }
198
205
  if (feed.feedKind === "turn" && (feed.status === "completed" || feed.status === "failed")) {
199
206
  return true;
200
207
  }
208
+ if (feed.feedKind === "queue" && feed.status === "queue_label_requested") {
209
+ return true;
210
+ }
201
211
  return false;
202
212
  }
213
+ function isNoOpReconciliation(summary) {
214
+ const match = summary.match(/^Reconciliation:\s+([a-z_]+)\s+→\s+([a-z_]+)$/i);
215
+ return Boolean(match?.[1] && match[1] === match[2]);
216
+ }
203
217
  function resolveCompactRunStatus(run, items) {
204
218
  if (run.endedAt || run.status === "completed" || run.status === "failed" || run.status === "released") {
205
219
  return run.status;
@@ -321,6 +335,60 @@ function rowKindOrder(kind) {
321
335
  return 3;
322
336
  }
323
337
  }
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
+ function collapseRepeatedFeedRows(rows) {
372
+ const collapsed = [];
373
+ for (const row of rows) {
374
+ if (row.kind !== "feed") {
375
+ collapsed.push(row);
376
+ continue;
377
+ }
378
+ const previous = collapsed.at(-1);
379
+ if (previous?.kind === "feed"
380
+ && previous.feed.feedKind === row.feed.feedKind
381
+ && previous.feed.status === row.feed.status
382
+ && previous.feed.summary === row.feed.summary
383
+ && previous.feed.detail === row.feed.detail) {
384
+ previous.at = row.at;
385
+ previous.repeatCount = (previous.repeatCount ?? 1) + 1;
386
+ continue;
387
+ }
388
+ collapsed.push({ ...row, ...(row.repeatCount ? { repeatCount: row.repeatCount } : {}) });
389
+ }
390
+ return collapsed;
391
+ }
324
392
  function cleanCommand(raw) {
325
393
  const bashMatch = raw.match(/^\/bin\/(?:ba)?sh\s+-\w*c\s+['"](.+?)['"]$/s);
326
394
  if (bashMatch?.[1])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.29.2",
3
+ "version": "0.29.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {