patchrelay 0.69.0 → 0.69.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.
- package/dist/build-info.json +3 -3
- package/dist/cli/commands/issues.js +2 -2
- package/dist/cli/connect-flow.js +2 -2
- package/dist/cli/operator-client.js +4 -6
- package/dist/cli/watch/IssueDetailView.js +1 -1
- package/dist/cli/watch/StateHistoryView.js +0 -1
- package/dist/cli/watch/render-rich-text.js +6 -6
- package/dist/github-worktree-auth.js +7 -5
- package/dist/hook-runner.js +1 -1
- package/dist/idle-reconciliation.js +2 -3
- package/dist/install.js +2 -2
- package/dist/operator-retry-event.js +3 -3
- package/dist/orchestration-parent-wake.js +1 -3
- package/dist/reactive-pr-state.js +1 -1
- package/dist/webhooks/desired-stage-recorder.js +1 -1
- package/infra/patchrelay.service +6 -6
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -68,7 +68,7 @@ export async function handleLiveCommand(params) {
|
|
|
68
68
|
throw new Error(`${params.parsed.flags.get("watch") === true ? "watch" : "status"} requires <issueKey>.`);
|
|
69
69
|
}
|
|
70
70
|
const watch = params.parsed.flags.get("watch") === true;
|
|
71
|
-
|
|
71
|
+
for (;;) {
|
|
72
72
|
const result = await params.data.live(issueKey);
|
|
73
73
|
if (!result) {
|
|
74
74
|
throw new Error(`No active stage found for ${issueKey}`);
|
|
@@ -78,7 +78,7 @@ export async function handleLiveCommand(params) {
|
|
|
78
78
|
break;
|
|
79
79
|
}
|
|
80
80
|
await delay(2000);
|
|
81
|
-
}
|
|
81
|
+
}
|
|
82
82
|
return 0;
|
|
83
83
|
}
|
|
84
84
|
export async function handleWorktreeCommand(params) {
|
package/dist/cli/connect-flow.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function runConnectFlow(params) {
|
|
|
29
29
|
writeOutput(params.stdout, `${result.projectId ? `Repo: ${result.projectId}\n` : ""}${opened ? "Opened browser for Linear OAuth.\n" : "Open this URL in a browser:\n"}${opened ? result.authorizeUrl : `${result.authorizeUrl}\n`}Waiting for OAuth approval...\n`);
|
|
30
30
|
const deadline = Date.now() + (params.timeoutSeconds ?? 180) * 1000;
|
|
31
31
|
const pollIntervalMs = params.connectPollIntervalMs ?? 1000;
|
|
32
|
-
|
|
32
|
+
for (;;) {
|
|
33
33
|
const status = await params.data.connectStatus(result.state);
|
|
34
34
|
if (status.status === "completed") {
|
|
35
35
|
const label = status.installation?.workspaceName ?? status.installation?.actorName ?? `installation #${status.installation?.id ?? "unknown"}`;
|
|
@@ -50,5 +50,5 @@ export async function runConnectFlow(params) {
|
|
|
50
50
|
throw new Error(`Timed out waiting for Linear OAuth after ${params.timeoutSeconds ?? 180} seconds.`);
|
|
51
51
|
}
|
|
52
52
|
await delay(pollIntervalMs);
|
|
53
|
-
}
|
|
53
|
+
}
|
|
54
54
|
}
|
|
@@ -5,9 +5,8 @@ export class CliOperatorApiClient {
|
|
|
5
5
|
}
|
|
6
6
|
close() { }
|
|
7
7
|
async connect(projectId) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
});
|
|
8
|
+
const query = projectId ? { projectId } : undefined;
|
|
9
|
+
return await this.requestJson("/api/oauth/linear/start", query);
|
|
11
10
|
}
|
|
12
11
|
async connectStatus(state) {
|
|
13
12
|
if (!state) {
|
|
@@ -31,9 +30,8 @@ export class CliOperatorApiClient {
|
|
|
31
30
|
return await this.requestJson("/api/linear/workspaces");
|
|
32
31
|
}
|
|
33
32
|
async syncLinearWorkspace(workspace) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}, { method: "POST" });
|
|
33
|
+
const query = workspace ? { workspace } : undefined;
|
|
34
|
+
return await this.requestJson("/api/linear/workspaces/sync", query, { method: "POST" });
|
|
37
35
|
}
|
|
38
36
|
async disconnectLinearWorkspace(workspace) {
|
|
39
37
|
return await this.requestJson(`/api/linear/workspaces/${encodeURIComponent(workspace)}`, undefined, { method: "DELETE" });
|
|
@@ -5,7 +5,7 @@ import { HelpBar, buildHelpBarText } from "./HelpBar.js";
|
|
|
5
5
|
import { buildDetailLines } from "./detail-rows.js";
|
|
6
6
|
import { buildDetailStatusSegments, buildDetailStatusText } from "./detail-status.js";
|
|
7
7
|
import { measureRenderedTextRows } from "./layout-measure.js";
|
|
8
|
-
export function IssueDetailView({ issue, timeline, follow, scrollOffset, unreadBelow, activeRunStartedAt, activeRunId, tokenUsage, diffSummary, plan, issueContext, detailTab, rawRuns, rawFeedEvents, connected, lastServerMessageAt, reservedRows = 0,
|
|
8
|
+
export function IssueDetailView({ issue, timeline, follow, scrollOffset, unreadBelow, activeRunStartedAt, activeRunId, tokenUsage, diffSummary, plan, issueContext, detailTab, rawRuns, rawFeedEvents, connected, lastServerMessageAt, reservedRows = 0, onLayoutChange, }) {
|
|
9
9
|
const { stdout } = useStdout();
|
|
10
10
|
const width = Math.max(20, stdout?.columns ?? 80);
|
|
11
11
|
const totalRows = stdout?.rows ?? 24;
|
|
@@ -95,7 +95,6 @@ function MainPathNode({ node, isLast, runOffset, plan, activeRunId, }) {
|
|
|
95
95
|
const stateLabel = STATE_LABELS[node.state] ?? node.state;
|
|
96
96
|
const marker = node.isCurrent ? "\u25c9" : "\u25cb";
|
|
97
97
|
const stateColor = node.isCurrent ? "green" : "white";
|
|
98
|
-
const hasActiveRun = node.runs.some((r) => r.id === activeRunId);
|
|
99
98
|
const gutter = isLast && node.sideTrips.length === 0 ? " " : " \u2502 ";
|
|
100
99
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: stateColor, bold: node.isCurrent, children: [" ", marker, " "] }), _jsx(Text, { color: stateColor, bold: node.isCurrent, children: stateLabel }), _jsxs(Text, { dimColor: true, children: [" ", formatTime(node.enteredAt)] })] }), node.reason && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(Text, { dimColor: true, children: node.reason })] })), node.runs.length > 5 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(RunSummary, { runs: node.runs })] })), node.runs.map((run, ri) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(RunLine, { run: run, index: runOffset + ri, gutter: gutter })] }), run.id === activeRunId && plan && plan.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(PlanSteps, { plan: plan })] }))] }, `run-${run.id}`))), node.sideTrips.length > 0 && (_jsx(Box, { flexDirection: "column", children: node.sideTrips.map((trip, ti) => {
|
|
101
100
|
// Count runs before this side-trip for numbering
|
|
@@ -8,7 +8,7 @@ export function renderTextLines(text, options) {
|
|
|
8
8
|
const lines = [];
|
|
9
9
|
for (let index = 0; index < sourceLines.length; index += 1) {
|
|
10
10
|
const sourceLine = sourceLines[index] ?? "";
|
|
11
|
-
const wrapped = wrapSegments(tokenizeSegments([{ text: sourceLine, ...
|
|
11
|
+
const wrapped = wrapSegments(tokenizeSegments([{ text: sourceLine, ...options.style }]), width, index === 0 ? options.firstPrefix : options.continuationPrefix ?? options.firstPrefix, options.continuationPrefix ?? options.firstPrefix, `${options.key}-${index}`);
|
|
12
12
|
lines.push(...wrapped);
|
|
13
13
|
}
|
|
14
14
|
return lines.length > 0 ? lines : [{ key: `${options.key}-0`, segments: [] }];
|
|
@@ -91,7 +91,7 @@ export function renderRichTextLines(text, options) {
|
|
|
91
91
|
const bulletMatch = line.match(/^\s*[-*]\s+(.*)$/);
|
|
92
92
|
if (bulletMatch?.[1]) {
|
|
93
93
|
flushParagraph();
|
|
94
|
-
lines.push(...wrapSegments(tokenizeSegments(parseInlineMarkdown(bulletMatch[1], options.style)), width, appendSegments(options.firstPrefix, [{ text: "• ", ...
|
|
94
|
+
lines.push(...wrapSegments(tokenizeSegments(parseInlineMarkdown(bulletMatch[1], options.style)), width, appendSegments(options.firstPrefix, [{ text: "• ", ...options.style }]), appendSegments(options.continuationPrefix ?? options.firstPrefix, [{ text: " ", ...options.style }]), `${options.key}-b-${blockIndex}`));
|
|
95
95
|
blockIndex += 1;
|
|
96
96
|
continue;
|
|
97
97
|
}
|
|
@@ -110,7 +110,7 @@ function parseInlineMarkdown(text, style) {
|
|
|
110
110
|
for (const match of text.matchAll(pattern)) {
|
|
111
111
|
const index = match.index ?? 0;
|
|
112
112
|
if (index > lastIndex) {
|
|
113
|
-
segments.push({ text: text.slice(lastIndex, index), ...
|
|
113
|
+
segments.push({ text: text.slice(lastIndex, index), ...style });
|
|
114
114
|
}
|
|
115
115
|
if (match[1] && match[2]) {
|
|
116
116
|
segments.push({ text: match[1], color: "cyan" });
|
|
@@ -119,14 +119,14 @@ function parseInlineMarkdown(text, style) {
|
|
|
119
119
|
segments.push({ text: match[3], color: "yellow" });
|
|
120
120
|
}
|
|
121
121
|
else if (match[4]) {
|
|
122
|
-
segments.push({ text: match[4], ...
|
|
122
|
+
segments.push({ text: match[4], ...style, bold: true });
|
|
123
123
|
}
|
|
124
124
|
lastIndex = index + match[0].length;
|
|
125
125
|
}
|
|
126
126
|
if (lastIndex < text.length) {
|
|
127
|
-
segments.push({ text: text.slice(lastIndex), ...
|
|
127
|
+
segments.push({ text: text.slice(lastIndex), ...style });
|
|
128
128
|
}
|
|
129
|
-
return segments.length > 0 ? segments : [{ text, ...
|
|
129
|
+
return segments.length > 0 ? segments : [{ text, ...style }];
|
|
130
130
|
}
|
|
131
131
|
function wrapSegments(tokens, width, firstPrefix, continuationPrefix, keyPrefix) {
|
|
132
132
|
const initialPrefix = cloneSegments(firstPrefix);
|
|
@@ -8,11 +8,13 @@ export function buildGitHubBotCredentialHelper(tokenFile) {
|
|
|
8
8
|
}
|
|
9
9
|
export async function configureGitHubBotAuthForWorktree(params) {
|
|
10
10
|
const helper = buildGitHubBotCredentialHelper(params.botIdentity.tokenFile);
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
await execCommand(params.gitBin, [...
|
|
11
|
+
const gitConfigArgs = ["-C", params.worktreePath, "config"];
|
|
12
|
+
const gitWorktreeConfigArgs = [...gitConfigArgs, "--worktree"];
|
|
13
|
+
await execCommand(params.gitBin, [...gitConfigArgs, "extensions.worktreeConfig", "true"], { timeoutMs: 5_000 });
|
|
14
|
+
await execCommand(params.gitBin, [...gitWorktreeConfigArgs, "user.name", params.botIdentity.name], { timeoutMs: 5_000 });
|
|
15
|
+
await execCommand(params.gitBin, [...gitWorktreeConfigArgs, "user.email", params.botIdentity.email], { timeoutMs: 5_000 });
|
|
14
16
|
// Clear inherited GitHub-specific helpers such as `gh auth git-credential`
|
|
15
17
|
// so git HTTPS operations use the same bot token as the wrapped `gh` CLI.
|
|
16
|
-
await execCommand(params.gitBin, [...
|
|
17
|
-
await execCommand(params.gitBin, [...
|
|
18
|
+
await execCommand(params.gitBin, [...gitWorktreeConfigArgs, "--replace-all", "credential.https://github.com.helper", ""], { timeoutMs: 5_000 });
|
|
19
|
+
await execCommand(params.gitBin, [...gitWorktreeConfigArgs, "--add", "credential.https://github.com.helper", helper], { timeoutMs: 5_000 });
|
|
18
20
|
}
|
package/dist/hook-runner.js
CHANGED
|
@@ -8,7 +8,7 @@ export async function runProjectHook(repoPath, hookName, options) {
|
|
|
8
8
|
}
|
|
9
9
|
const result = await execCommand(hookPath, [], {
|
|
10
10
|
cwd: options.cwd,
|
|
11
|
-
env: { ...sanitizedParentEnv(), ...
|
|
11
|
+
env: { ...sanitizedParentEnv(), ...options.env },
|
|
12
12
|
timeoutMs: options.timeoutMs ?? 120_000,
|
|
13
13
|
});
|
|
14
14
|
return {
|
|
@@ -580,9 +580,8 @@ export class IdleIssueReconciler {
|
|
|
580
580
|
prReviewState: "approved",
|
|
581
581
|
});
|
|
582
582
|
if (issue.factoryState !== "awaiting_queue" || hasFailureProvenance(issue)) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
});
|
|
583
|
+
const options = hasFailureProvenance(issue) ? { clearFailureProvenance: true } : undefined;
|
|
584
|
+
this.advanceIdleIssue(issue, "awaiting_queue", options);
|
|
586
585
|
}
|
|
587
586
|
return;
|
|
588
587
|
}
|
package/dist/install.js
CHANGED
|
@@ -206,7 +206,7 @@ export async function upsertProjectInConfig(options) {
|
|
|
206
206
|
const existingProject = existingIndex >= 0 ? existingProjects[existingIndex] : undefined;
|
|
207
207
|
const resolvedProjectId = existingProject ? String(existingProject.id ?? projectId) : projectId;
|
|
208
208
|
const nextProject = {
|
|
209
|
-
...
|
|
209
|
+
...existingProject,
|
|
210
210
|
id: resolvedProjectId,
|
|
211
211
|
repo_path: repoPath,
|
|
212
212
|
};
|
|
@@ -328,7 +328,7 @@ export async function upsertRepositoryInConfig(options) {
|
|
|
328
328
|
const existingIndex = existingRepositories.findIndex((repository) => String(repository.github_repo ?? "") === githubRepo);
|
|
329
329
|
const existing = existingIndex >= 0 ? existingRepositories[existingIndex] : undefined;
|
|
330
330
|
const nextRepository = {
|
|
331
|
-
...
|
|
331
|
+
...existing,
|
|
332
332
|
github_repo: githubRepo,
|
|
333
333
|
local_path: localPath,
|
|
334
334
|
...(workspace ? { workspace } : {}),
|
|
@@ -16,8 +16,8 @@ export function buildOperatorRetryEvent(issue, runType, source = "operator_retry
|
|
|
16
16
|
return {
|
|
17
17
|
eventType: "merge_steward_incident",
|
|
18
18
|
eventJson: JSON.stringify({
|
|
19
|
-
...
|
|
20
|
-
...
|
|
19
|
+
...queueIncident,
|
|
20
|
+
...failureContext,
|
|
21
21
|
source,
|
|
22
22
|
requiresFreshHead: true,
|
|
23
23
|
promptContext: [
|
|
@@ -35,7 +35,7 @@ export function buildOperatorRetryEvent(issue, runType, source = "operator_retry
|
|
|
35
35
|
return {
|
|
36
36
|
eventType: "settled_red_ci",
|
|
37
37
|
eventJson: JSON.stringify({
|
|
38
|
-
...
|
|
38
|
+
...failureContext,
|
|
39
39
|
source,
|
|
40
40
|
}),
|
|
41
41
|
dedupeKey: `${source}:ci_repair:${issue.linearIssueId}:${issue.lastGitHubFailureSignature ?? issue.prHeadSha ?? "unknown-sha"}`,
|
|
@@ -40,9 +40,7 @@ export function queueSettledOrchestrationIssue(params) {
|
|
|
40
40
|
const dispatched = params.wakeDispatcher.recordEventAndDispatch(params.issue.projectId, params.issue.linearIssueId, {
|
|
41
41
|
eventType: "delegated",
|
|
42
42
|
eventJson: JSON.stringify({
|
|
43
|
-
|
|
44
|
-
? { promptContext: params.promptContext }
|
|
45
|
-
: { promptContext: "The orchestration child set has settled enough to begin planning." }),
|
|
43
|
+
promptContext: params.promptContext ?? "The orchestration child set has settled enough to begin planning.",
|
|
46
44
|
}),
|
|
47
45
|
dedupeKey: `delegated:orchestration_settle:${params.issue.linearIssueId}`,
|
|
48
46
|
});
|
|
@@ -32,7 +32,7 @@ export function buildReviewFixBranchUpkeepContext(prNumber, baseBranch, pr, cont
|
|
|
32
32
|
"Do not stop just because the requested code change is already present. Review can only move forward after a new pushed head.",
|
|
33
33
|
].join(" ");
|
|
34
34
|
return {
|
|
35
|
-
...
|
|
35
|
+
...context,
|
|
36
36
|
branchUpkeepRequired: true,
|
|
37
37
|
reviewFixMode: "branch_upkeep",
|
|
38
38
|
wakeReason: "branch_upkeep",
|
|
@@ -175,7 +175,7 @@ export class DesiredStageRecorder {
|
|
|
175
175
|
...(hydratedIssue.estimate != null ? { estimate: hydratedIssue.estimate } : {}),
|
|
176
176
|
...(hydratedIssue.stateName ? { currentLinearState: hydratedIssue.stateName } : {}),
|
|
177
177
|
...(hydratedIssue.stateType ? { currentLinearStateType: hydratedIssue.stateType } : {}),
|
|
178
|
-
...
|
|
178
|
+
...linkedPrAdoption?.issueUpdates,
|
|
179
179
|
delegatedToPatchRelay: delegated,
|
|
180
180
|
...resolvedPlan,
|
|
181
181
|
});
|
package/infra/patchrelay.service
CHANGED
|
@@ -25,13 +25,13 @@ Environment=PATH=/home/your-user/.local/share/patchrelay/bin:/home/your-user/.lo
|
|
|
25
25
|
#
|
|
26
26
|
# When credstore is not configured, PatchRelay falls back to reading secrets
|
|
27
27
|
# from env vars (EnvironmentFile, op run, etc.) via the resolve-secret module.
|
|
28
|
-
LoadCredentialEncrypted=linear-webhook-secret
|
|
29
|
-
LoadCredentialEncrypted=token-encryption-key
|
|
30
|
-
LoadCredentialEncrypted=linear-oauth-client-id
|
|
31
|
-
LoadCredentialEncrypted=linear-oauth-client-secret
|
|
28
|
+
LoadCredentialEncrypted=linear-webhook-secret:/etc/credstore.encrypted/linear-webhook-secret.cred
|
|
29
|
+
LoadCredentialEncrypted=token-encryption-key:/etc/credstore.encrypted/token-encryption-key.cred
|
|
30
|
+
LoadCredentialEncrypted=linear-oauth-client-id:/etc/credstore.encrypted/linear-oauth-client-id.cred
|
|
31
|
+
LoadCredentialEncrypted=linear-oauth-client-secret:/etc/credstore.encrypted/linear-oauth-client-secret.cred
|
|
32
32
|
# Uncomment when GitHub App integration is configured:
|
|
33
|
-
# LoadCredentialEncrypted=github-app-pem
|
|
34
|
-
# LoadCredentialEncrypted=github-app-webhook-secret
|
|
33
|
+
# LoadCredentialEncrypted=github-app-pem:/etc/credstore.encrypted/github-app-pem.cred
|
|
34
|
+
# LoadCredentialEncrypted=github-app-webhook-secret:/etc/credstore.encrypted/github-app-webhook-secret.cred
|
|
35
35
|
|
|
36
36
|
ExecStart=/usr/bin/env patchrelay serve
|
|
37
37
|
Restart=on-failure
|