patchrelay 0.21.0 → 0.21.2

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.21.0",
4
- "commit": "37cae5cfd0e6",
5
- "builtAt": "2026-03-26T10:21:53.343Z"
3
+ "version": "0.21.2",
4
+ "commit": "19bd5f240e29",
5
+ "builtAt": "2026-03-26T10:43:19.246Z"
6
6
  }
@@ -25,11 +25,24 @@ function truncate(text, max) {
25
25
  function renderAgentMessage(item) {
26
26
  return (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "message: " }), _jsx(Text, { wrap: "wrap", children: item.text ?? "" })] }));
27
27
  }
28
+ function cleanCommand(raw) {
29
+ // Strip /bin/bash -lc '...' wrapper — show the inner command
30
+ const bashMatch = raw.match(/^\/bin\/(?:ba)?sh\s+-\w*c\s+['"](.+?)['"]$/s);
31
+ if (bashMatch?.[1])
32
+ return bashMatch[1];
33
+ // Strip /bin/bash -lc "..." (double quotes)
34
+ const bashMatch2 = raw.match(/^\/bin\/(?:ba)?sh\s+-\w*c\s+"(.+?)"$/s);
35
+ if (bashMatch2?.[1])
36
+ return bashMatch2[1];
37
+ return raw;
38
+ }
28
39
  function renderCommand(item) {
29
- const cmd = item.command ?? "?";
30
- const exit = item.exitCode !== undefined ? ` exit:${item.exitCode}` : "";
40
+ const cmd = cleanCommand(item.command ?? "?");
41
+ const exitCode = item.exitCode;
42
+ const exitLabel = exitCode !== undefined && exitCode !== 0 ? ` exit:${exitCode}` : "";
31
43
  const duration = item.durationMs !== undefined ? ` ${(item.durationMs / 1000).toFixed(1)}s` : "";
32
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "$ " }), _jsx(Text, { children: truncate(cmd, 60) }), exit && _jsx(Text, { dimColor: true, children: exit }), duration && _jsx(Text, { dimColor: true, children: duration })] }), item.output && item.status === "inProgress" && (_jsxs(Text, { dimColor: true, children: [" ", truncate(item.output.split("\n").filter(Boolean).at(-1) ?? "", 100)] }))] }));
44
+ const suffix = `${exitLabel}${duration}`;
45
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "$ " }), _jsx(Text, { children: cmd }), exitLabel && _jsx(Text, { color: "red", children: exitLabel }), !exitLabel && suffix && _jsx(Text, { dimColor: true, children: suffix })] }), item.output && item.status === "inProgress" && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [" ", item.output.split("\n").filter(Boolean).at(-1) ?? ""] }))] }));
33
46
  }
34
47
  function renderFileChange(item) {
35
48
  const count = item.changes?.length ?? 0;
@@ -275,6 +275,13 @@ export class RunOrchestrator {
275
275
  }
276
276
  // Emit ephemeral progress activity to Linear for notable in-flight events
277
277
  this.maybeEmitProgressActivity(notification, run);
278
+ // Sync codex plan to Linear session when it updates
279
+ if (notification.method === "turn/plan/updated") {
280
+ const issue = this.db.getIssue(run.projectId, run.linearIssueId);
281
+ if (issue) {
282
+ void this.syncLinearSessionWithCodexPlan(issue, notification.params);
283
+ }
284
+ }
278
285
  if (notification.method !== "turn/completed")
279
286
  return;
280
287
  const thread = await this.readThreadWithRetry(threadId);
@@ -687,6 +694,44 @@ export class RunOrchestrator {
687
694
  this.logger.warn({ issueKey: issue.issueKey, error: msg }, "Failed to update Linear plan");
688
695
  }
689
696
  }
697
+ async syncLinearSessionWithCodexPlan(issue, params) {
698
+ if (!issue.agentSessionId)
699
+ return;
700
+ const plan = params.plan;
701
+ if (!Array.isArray(plan))
702
+ return;
703
+ const STATUS_MAP = {
704
+ pending: "pending",
705
+ inProgress: "inProgress",
706
+ completed: "completed",
707
+ };
708
+ const steps = plan.map((entry) => {
709
+ const e = entry;
710
+ const step = typeof e.step === "string" ? e.step : String(e.step ?? "");
711
+ const status = typeof e.status === "string" ? (STATUS_MAP[e.status] ?? "pending") : "pending";
712
+ return { content: step, status };
713
+ });
714
+ // Prepend a "Prepare workspace" completed step and append a "Merge" pending step
715
+ // to frame the codex plan within the PatchRelay lifecycle
716
+ const fullPlan = [
717
+ { content: "Prepare workspace", status: "completed" },
718
+ ...steps,
719
+ { content: "Merge", status: "pending" },
720
+ ];
721
+ try {
722
+ const linear = await this.linearProvider.forProject(issue.projectId);
723
+ if (!linear?.updateAgentSession)
724
+ return;
725
+ await linear.updateAgentSession({
726
+ agentSessionId: issue.agentSessionId,
727
+ plan: fullPlan,
728
+ });
729
+ }
730
+ catch (error) {
731
+ const msg = error instanceof Error ? error.message : String(error);
732
+ this.logger.warn({ issueKey: issue.issueKey, error: msg }, "Failed to sync codex plan to Linear");
733
+ }
734
+ }
690
735
  async readThreadWithRetry(threadId, maxRetries = 3) {
691
736
  for (let attempt = 0; attempt < maxRetries; attempt++) {
692
737
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.21.0",
3
+ "version": "0.21.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {