patchrelay 0.35.11 → 0.35.13
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/README.md +41 -9
- package/dist/build-info.json +3 -3
- package/dist/cli/args.js +19 -1
- package/dist/cli/commands/issues.js +18 -56
- package/dist/cli/commands/watch.js +5 -0
- package/dist/cli/data.js +160 -47
- package/dist/cli/formatters/text.js +51 -90
- package/dist/cli/help.js +15 -8
- package/dist/cli/index.js +3 -58
- package/dist/cli/operator-client.js +0 -82
- package/dist/cli/watch/App.js +21 -12
- package/dist/cli/watch/HelpBar.js +3 -3
- package/dist/cli/watch/IssueDetailView.js +63 -130
- package/dist/cli/watch/IssueRow.js +82 -27
- package/dist/cli/watch/StatusBar.js +8 -4
- package/dist/cli/watch/detail-rows.js +589 -0
- package/dist/cli/watch/render-rich-text.js +226 -0
- package/dist/cli/watch/state-visualization.js +48 -23
- package/dist/cli/watch/timeline-builder.js +2 -1
- package/dist/cli/watch/use-detail-stream.js +10 -104
- package/dist/cli/watch/use-watch-stream.js +11 -102
- package/dist/cli/watch/watch-state.js +129 -56
- package/dist/codex-thread-utils.js +3 -0
- package/dist/db/migrations.js +239 -2
- package/dist/db.js +628 -39
- package/dist/github-app-token.js +7 -0
- package/dist/github-failure-context.js +44 -1
- package/dist/github-rollup.js +47 -0
- package/dist/github-webhook-handler.js +423 -52
- package/dist/github-webhooks.js +7 -0
- package/dist/http.js +12 -264
- package/dist/idle-reconciliation.js +268 -76
- package/dist/issue-query-service.js +221 -129
- package/dist/issue-session-events.js +151 -0
- package/dist/issue-session.js +99 -0
- package/dist/linear-client.js +39 -25
- package/dist/linear-session-reporting.js +12 -0
- package/dist/linear-session-sync.js +253 -24
- package/dist/linear-workflow.js +33 -0
- package/dist/merge-queue-protocol.js +0 -51
- package/dist/preflight.js +1 -4
- package/dist/queue-health-monitor.js +11 -7
- package/dist/run-orchestrator.js +1364 -147
- package/dist/run-reporting.js +5 -3
- package/dist/service.js +279 -102
- package/dist/status-note.js +56 -0
- package/dist/waiting-reason.js +65 -0
- package/dist/webhook-handler.js +270 -79
- package/package.json +3 -2
- package/dist/cli/commands/feed.js +0 -60
- package/dist/cli/watch/FeedView.js +0 -28
- package/dist/cli/watch/use-feed-stream.js +0 -92
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { formatJson } from "../formatters/json.js";
|
|
2
|
-
import { formatOperatorFeed, formatOperatorFeedEvent } from "../formatters/text.js";
|
|
3
|
-
import { writeOutput } from "../output.js";
|
|
4
|
-
function parseLimit(value) {
|
|
5
|
-
if (typeof value !== "string") {
|
|
6
|
-
return 50;
|
|
7
|
-
}
|
|
8
|
-
const trimmed = value.trim();
|
|
9
|
-
if (!/^\d+$/.test(trimmed)) {
|
|
10
|
-
throw new Error("--limit must be a positive integer.");
|
|
11
|
-
}
|
|
12
|
-
const parsed = Number(trimmed);
|
|
13
|
-
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
14
|
-
throw new Error("--limit must be a positive integer.");
|
|
15
|
-
}
|
|
16
|
-
return parsed;
|
|
17
|
-
}
|
|
18
|
-
function readOptionalStringFlag(parsed, name) {
|
|
19
|
-
const value = parsed.flags.get(name);
|
|
20
|
-
if (value === true) {
|
|
21
|
-
throw new Error(`--${name} requires a value.`);
|
|
22
|
-
}
|
|
23
|
-
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
24
|
-
}
|
|
25
|
-
export async function handleFeedCommand(params) {
|
|
26
|
-
const limit = parseLimit(params.parsed.flags.get("limit"));
|
|
27
|
-
const follow = params.parsed.flags.get("follow") === true;
|
|
28
|
-
const issueKey = readOptionalStringFlag(params.parsed, "issue");
|
|
29
|
-
const projectId = readOptionalStringFlag(params.parsed, "repo");
|
|
30
|
-
const kind = readOptionalStringFlag(params.parsed, "kind");
|
|
31
|
-
const stage = readOptionalStringFlag(params.parsed, "stage");
|
|
32
|
-
const status = readOptionalStringFlag(params.parsed, "status");
|
|
33
|
-
const workflowId = readOptionalStringFlag(params.parsed, "workflow");
|
|
34
|
-
const query = {
|
|
35
|
-
limit,
|
|
36
|
-
...(issueKey ? { issueKey } : {}),
|
|
37
|
-
...(projectId ? { projectId } : {}),
|
|
38
|
-
...(kind ? { kind } : {}),
|
|
39
|
-
...(stage ? { stage } : {}),
|
|
40
|
-
...(status ? { status } : {}),
|
|
41
|
-
...(workflowId ? { workflowId } : {}),
|
|
42
|
-
};
|
|
43
|
-
if (!follow) {
|
|
44
|
-
const result = await params.data.listOperatorFeed(query);
|
|
45
|
-
writeOutput(params.stdout, params.json
|
|
46
|
-
? formatJson(result)
|
|
47
|
-
: formatOperatorFeed(result, { color: "isTTY" in params.stdout && params.stdout.isTTY === true }));
|
|
48
|
-
return 0;
|
|
49
|
-
}
|
|
50
|
-
if (params.json) {
|
|
51
|
-
await params.data.followOperatorFeed((event) => {
|
|
52
|
-
writeOutput(params.stdout, formatJson(event));
|
|
53
|
-
}, query);
|
|
54
|
-
return 0;
|
|
55
|
-
}
|
|
56
|
-
await params.data.followOperatorFeed((event) => {
|
|
57
|
-
writeOutput(params.stdout, formatOperatorFeedEvent(event, { color: "isTTY" in params.stdout && params.stdout.isTTY === true }));
|
|
58
|
-
}, query);
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { HelpBar } from "./HelpBar.js";
|
|
4
|
-
import { FreshnessBadge } from "./FreshnessBadge.js";
|
|
5
|
-
const TAIL_SIZE = 30;
|
|
6
|
-
const KIND_COLORS = {
|
|
7
|
-
stage: "cyan",
|
|
8
|
-
turn: "yellow",
|
|
9
|
-
github: "green",
|
|
10
|
-
webhook: "blue",
|
|
11
|
-
agent: "magenta",
|
|
12
|
-
service: "white",
|
|
13
|
-
workflow: "cyan",
|
|
14
|
-
linear: "blue",
|
|
15
|
-
comment: "cyan",
|
|
16
|
-
};
|
|
17
|
-
function formatTime(iso) {
|
|
18
|
-
return new Date(iso).toLocaleTimeString("en-GB", { hour12: false });
|
|
19
|
-
}
|
|
20
|
-
function FeedEventRow({ event }) {
|
|
21
|
-
const kindColor = KIND_COLORS[event.kind] ?? "white";
|
|
22
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [formatTime(event.at), " "] }), _jsx(Text, { color: kindColor, children: (event.status ?? event.kind).padEnd(14) }), event.issueKey && _jsx(Text, { bold: true, children: ` ${event.issueKey.padEnd(9)}` }), _jsxs(Text, { children: [" ", event.summary] })] }));
|
|
23
|
-
}
|
|
24
|
-
export function FeedView({ events, connected, lastServerMessageAt }) {
|
|
25
|
-
const visible = events.length > TAIL_SIZE ? events.slice(-TAIL_SIZE) : events;
|
|
26
|
-
const skipped = events.length - visible.length;
|
|
27
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, children: "Operator Feed" }), _jsx(FreshnessBadge, { connected: connected, lastServerMessageAt: lastServerMessageAt })] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: events.length === 0 ? (_jsx(Text, { dimColor: true, children: "No feed events yet." })) : (_jsxs(_Fragment, { children: [skipped > 0 && _jsxs(Text, { dimColor: true, children: [" ... ", skipped, " earlier"] }), visible.map((event) => (_jsx(FeedEventRow, { event: event }, event.id)))] })) }), _jsx(Box, { marginTop: 1, children: _jsx(HelpBar, { view: "feed" }) })] }));
|
|
28
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
export function useFeedStream(options) {
|
|
3
|
-
const optionsRef = useRef(options);
|
|
4
|
-
optionsRef.current = options;
|
|
5
|
-
useEffect(() => {
|
|
6
|
-
if (!options.active)
|
|
7
|
-
return;
|
|
8
|
-
const abortController = new AbortController();
|
|
9
|
-
const { baseUrl, bearerToken, dispatch } = optionsRef.current;
|
|
10
|
-
void (async () => {
|
|
11
|
-
try {
|
|
12
|
-
const url = new URL("/api/feed", baseUrl);
|
|
13
|
-
url.searchParams.set("follow", "1");
|
|
14
|
-
url.searchParams.set("limit", "100");
|
|
15
|
-
const headers = { accept: "text/event-stream" };
|
|
16
|
-
if (bearerToken)
|
|
17
|
-
headers.authorization = `Bearer ${bearerToken}`;
|
|
18
|
-
const response = await fetch(url, { headers, signal: abortController.signal });
|
|
19
|
-
if (!response.ok || !response.body)
|
|
20
|
-
return;
|
|
21
|
-
const reader = response.body.getReader();
|
|
22
|
-
const decoder = new TextDecoder();
|
|
23
|
-
let buffer = "";
|
|
24
|
-
let eventType = "";
|
|
25
|
-
let dataLines = [];
|
|
26
|
-
let initialBatch = [];
|
|
27
|
-
let snapshotSent = false;
|
|
28
|
-
while (true) {
|
|
29
|
-
const { done, value } = await reader.read();
|
|
30
|
-
if (done)
|
|
31
|
-
break;
|
|
32
|
-
buffer += decoder.decode(value, { stream: true });
|
|
33
|
-
let newlineIndex = buffer.indexOf("\n");
|
|
34
|
-
while (newlineIndex !== -1) {
|
|
35
|
-
const rawLine = buffer.slice(0, newlineIndex);
|
|
36
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
37
|
-
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
38
|
-
if (!line) {
|
|
39
|
-
if (dataLines.length > 0 && eventType === "feed") {
|
|
40
|
-
try {
|
|
41
|
-
const event = JSON.parse(dataLines.join("\n"));
|
|
42
|
-
if (!snapshotSent) {
|
|
43
|
-
initialBatch.push(event);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
dispatch({ type: "feed-new-event", event });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch { /* ignore parse errors */ }
|
|
50
|
-
dataLines = [];
|
|
51
|
-
eventType = "";
|
|
52
|
-
}
|
|
53
|
-
// After processing a batch of initial events, flush snapshot
|
|
54
|
-
if (!snapshotSent && initialBatch.length > 0) {
|
|
55
|
-
// Use a microtask to batch initial events
|
|
56
|
-
const batch = initialBatch;
|
|
57
|
-
initialBatch = [];
|
|
58
|
-
snapshotSent = true;
|
|
59
|
-
dispatch({ type: "feed-snapshot", events: batch });
|
|
60
|
-
}
|
|
61
|
-
newlineIndex = buffer.indexOf("\n");
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (line.startsWith(":")) {
|
|
65
|
-
// Keepalive or comment - flush initial batch if pending
|
|
66
|
-
if (!snapshotSent && initialBatch.length > 0) {
|
|
67
|
-
snapshotSent = true;
|
|
68
|
-
dispatch({ type: "feed-snapshot", events: initialBatch });
|
|
69
|
-
initialBatch = [];
|
|
70
|
-
}
|
|
71
|
-
newlineIndex = buffer.indexOf("\n");
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (line.startsWith("event:")) {
|
|
75
|
-
eventType = line.slice(6).trim();
|
|
76
|
-
}
|
|
77
|
-
else if (line.startsWith("data:")) {
|
|
78
|
-
dataLines.push(line.slice(5).trimStart());
|
|
79
|
-
}
|
|
80
|
-
newlineIndex = buffer.indexOf("\n");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Stream ended or aborted
|
|
86
|
-
}
|
|
87
|
-
})();
|
|
88
|
-
return () => {
|
|
89
|
-
abortController.abort();
|
|
90
|
-
};
|
|
91
|
-
}, [options.active]);
|
|
92
|
-
}
|