mergestorm 0.3.45 → 0.3.47
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 +1 -1
- package/dist/api.d.ts +6 -0
- package/dist/api.js +19 -0
- package/dist/commands/stack.d.ts +2 -1
- package/dist/commands/stack.js +22 -4
- package/dist/stack-blockers.js +2 -1
- package/dist/stack-watch-loop.d.ts +5 -0
- package/dist/stack-watch-loop.js +76 -7
- package/dist/stack-watch-obligation.d.ts +1 -1
- package/dist/stack-watch.js +2 -0
- package/dist/ui/banner.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a comp
|
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
48
|
-
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.
|
|
48
|
+
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.47 │
|
|
49
49
|
│ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
|
|
50
50
|
│ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
|
|
51
51
|
│ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
|
package/dist/api.d.ts
CHANGED
|
@@ -242,11 +242,17 @@ export declare function ensureUpperPark(stackId: string, cfg?: Config): Promise<
|
|
|
242
242
|
}>;
|
|
243
243
|
export declare const SURGE_INSTALL_URL = "https://github.com/apps/mergestorm-surge/installations/new";
|
|
244
244
|
export declare function surgeNotInstalledMessage(owner: string, repo: string, installUrl?: string): string;
|
|
245
|
+
export declare function readLandedQueueEntry(cfg: Config, stackId: string): Promise<MergeQueueEntryDto | null>;
|
|
245
246
|
export declare function findStackPull(cfg: Config, input: {
|
|
246
247
|
owner: string;
|
|
247
248
|
repo: string;
|
|
248
249
|
head: string;
|
|
249
250
|
}): Promise<number | null>;
|
|
251
|
+
export declare function claimStackPulls(cfg: Config, input: {
|
|
252
|
+
owner: string;
|
|
253
|
+
repo: string;
|
|
254
|
+
heads: string[];
|
|
255
|
+
}): Promise<void>;
|
|
250
256
|
export declare function openStackPull(cfg: Config, input: {
|
|
251
257
|
owner: string;
|
|
252
258
|
repo: string;
|
package/dist/api.js
CHANGED
|
@@ -510,6 +510,13 @@ function stackPullFailure(status, body, owner, repo, failLabel) {
|
|
|
510
510
|
const detail = typeof rejection.message === "string" ? rejection.message : JSON.stringify(body);
|
|
511
511
|
return new CommandError(`${failLabel} (HTTP ${status}): ${detail}`);
|
|
512
512
|
}
|
|
513
|
+
export async function readLandedQueueEntry(cfg, stackId) {
|
|
514
|
+
const { status, body } = await apiFetch(cfg, `/api/v1/stacks/queue?stackId=${encodeURIComponent(stackId)}&landed=1`);
|
|
515
|
+
if (status !== 200)
|
|
516
|
+
throw new CommandError(`Failed to read the landed merge queue entry (HTTP ${status})`);
|
|
517
|
+
const landed = body?.landed;
|
|
518
|
+
return landed && landed.state === "landed" ? landed : null;
|
|
519
|
+
}
|
|
513
520
|
export async function findStackPull(cfg, input) {
|
|
514
521
|
const query = new URLSearchParams(input).toString();
|
|
515
522
|
const { status, body } = await apiFetch(cfg, `/api/v1/stacks/pulls?${query}`);
|
|
@@ -519,6 +526,18 @@ export async function findStackPull(cfg, input) {
|
|
|
519
526
|
const n = body?.number;
|
|
520
527
|
return typeof n === "number" && n > 0 ? n : null;
|
|
521
528
|
}
|
|
529
|
+
export async function claimStackPulls(cfg, input) {
|
|
530
|
+
const { status, body } = await apiFetch(cfg, "/api/v1/stacks/pull-claims", {
|
|
531
|
+
method: "POST",
|
|
532
|
+
json: input,
|
|
533
|
+
});
|
|
534
|
+
if (status !== 200) {
|
|
535
|
+
const detail = body?.message
|
|
536
|
+
?? body?.error
|
|
537
|
+
?? `HTTP ${status}`;
|
|
538
|
+
throw new CommandError(`Could not claim ${input.heads.join(", ")}: ${String(detail)}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
522
541
|
export async function openStackPull(cfg, input) {
|
|
523
542
|
const { status, body } = await apiFetch(cfg, "/api/v1/stacks/pulls", {
|
|
524
543
|
method: "POST",
|
package/dist/commands/stack.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type StackHeldBlocker, type StackRepairHint } from "../stack-blockers.js";
|
|
2
2
|
import { type StackWatchObligation } from "../stack-watch-obligation.js";
|
|
3
|
-
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, listMergeQueueEntries, listStacks, openStackPull, setStackPolicy, type StackDto, type StackPolicyPatch } from "../api.js";
|
|
3
|
+
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, listMergeQueueEntries, listStacks, openStackPull, claimStackPulls, setStackPolicy, type StackDto, type StackPolicyPatch } from "../api.js";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { createPrs, findOpenPrNumber } from "../gh.js";
|
|
6
6
|
import { parseGithubOriginRepo } from "../git.js";
|
|
@@ -137,6 +137,7 @@ export type StackSubmitDeps = {
|
|
|
137
137
|
createPrs?: typeof createPrs;
|
|
138
138
|
findStackPull?: typeof findStackPull;
|
|
139
139
|
openStackPull?: typeof openStackPull;
|
|
140
|
+
claimStackPulls?: typeof claimStackPulls;
|
|
140
141
|
loadConfig?: typeof loadConfig;
|
|
141
142
|
listStacks?: typeof listStacks;
|
|
142
143
|
adoptStack?: typeof adoptStack;
|
package/dist/commands/stack.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stackBlockers, stackBlockersSummary } from "../stack-blockers.js";
|
|
2
2
|
import { STACK_WATCH_NOT_DONE_SENTENCE, STACK_WATCH_NOTIFY_PATTERN, stackTerminalReason, stackWatchObligation, } from "../stack-watch-obligation.js";
|
|
3
|
-
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, landNextStack, listMergeQueueEntries, listStacks, openStackPull, restackStack, setStackPolicy, } from "../api.js";
|
|
3
|
+
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, landNextStack, listMergeQueueEntries, listStacks, openStackPull, claimStackPulls, restackStack, setStackPolicy, } from "../api.js";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { rateLimitedMessage, REVIEW_EXIT, CommandError } from "../errors.js";
|
|
6
6
|
import { createPrs, findOpenPrNumber, ghReady, githubApiFromEnv, } from "../gh.js";
|
|
@@ -58,8 +58,9 @@ const STACK_USAGE = `usage:
|
|
|
58
58
|
(exit 5 on timeout), so do not merge the streams (2>&1) before parsing stdout.
|
|
59
59
|
mergestorm stack watch <stack-id> [--until attention|landed] [--ignore <blocker-substring>] [--max <minutes>] [--head <sha>] [--json]
|
|
60
60
|
Loops stack wait with the cursor and stays silent while the stack is waiting or in progress.
|
|
61
|
-
Prints one MS-WATCH ATTENTION line and exits 3 on attention or
|
|
62
|
-
|
|
61
|
+
Prints one MS-WATCH ATTENTION line and exits 3 on attention or on reads that keep failing (a permanent
|
|
62
|
+
error twice, or 15 minutes of timeouts and 5xx, which it retries with backoff); prints MS-WATCH LANDED
|
|
63
|
+
and exits 0 only for a confirmed landing, including a stack Mergestorm deleted after its queue entry landed. Other terminal reasons exit 3 with ATTENTION. Run it as a background command.
|
|
63
64
|
--json writes exactly one JSON document to stdout and sends the MS-WATCH lines to stderr.
|
|
64
65
|
mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--auto-resolve-conflicts on|off|default] [--auto-fix-ci on|off|default] [--json]
|
|
65
66
|
mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--auto-resolve-conflicts on|off] [--auto-fix-ci on|off] [--json]
|
|
@@ -640,6 +641,7 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
640
641
|
const createPrsFn = deps.createPrs ?? createPrs;
|
|
641
642
|
const findStackPullFn = deps.findStackPull ?? findStackPull;
|
|
642
643
|
const openStackPullFn = deps.openStackPull ?? openStackPull;
|
|
644
|
+
const claimStackPullsFn = deps.claimStackPulls ?? claimStackPulls;
|
|
643
645
|
const loadConfigFn = deps.loadConfig ?? loadConfig;
|
|
644
646
|
const listStacksFn = deps.listStacks ?? listStacks;
|
|
645
647
|
const adoptStackFn = deps.adoptStack ?? adoptStack;
|
|
@@ -736,6 +738,20 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
736
738
|
for (const plan of plans) {
|
|
737
739
|
existingPrs.set(plan.branch, await opener.findOpen(plan.branch));
|
|
738
740
|
}
|
|
741
|
+
const newHeads = plans
|
|
742
|
+
.filter((plan) => existingPrs.get(plan.branch) == null)
|
|
743
|
+
.map((plan) => plan.branch);
|
|
744
|
+
if (newHeads.length > 0) {
|
|
745
|
+
try {
|
|
746
|
+
const config = await loadConfigFn();
|
|
747
|
+
for (let i = 0; i < newHeads.length; i += 20) {
|
|
748
|
+
await claimStackPullsFn(config, { owner, repo, heads: newHeads.slice(i, i + 20) });
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
catch (err) {
|
|
752
|
+
progress(ansi.dim(` Could not hold the new PRs off the dashboard until adopt (${err instanceof Error ? err.message : String(err)}); continuing.`));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
739
755
|
for (const layer of layers) {
|
|
740
756
|
if (!branchExistsFn(layer.branch, cwd)) {
|
|
741
757
|
throw new CommandError(`Stack layer branch missing locally: ${layer.branch}`);
|
|
@@ -1042,7 +1058,9 @@ export async function cmdStackWatch(args, deps = {}) {
|
|
|
1042
1058
|
? `Stopped watching stack ${stackId}; it is not landed.`
|
|
1043
1059
|
: result.envelope?.watch.reason === "not_found"
|
|
1044
1060
|
? `Stack ${stackId} was not found; landing is unconfirmed. Verify each PR before reporting it landed.`
|
|
1045
|
-
:
|
|
1061
|
+
: result.envelope?.watch.reason === "landing_unconfirmed"
|
|
1062
|
+
? `Stack ${stackId} is gone, but landing is unconfirmed. Verify each PR before reporting it landed.`
|
|
1063
|
+
: `Stack ${stackId} needs attention; it is not landed.`;
|
|
1046
1064
|
throw new CommandError(reason, result.exitCode, result.outcome === "timeout" ? "review_timeout" : "stack_attention");
|
|
1047
1065
|
}
|
|
1048
1066
|
export async function cmdStackStatus(args, deps = {}) {
|
package/dist/stack-blockers.js
CHANGED
|
@@ -168,7 +168,8 @@ const VORTEX_SKIP_BLOCKERS = {
|
|
|
168
168
|
};
|
|
169
169
|
export function vortexRetryScheduled(layer) {
|
|
170
170
|
const review = layer.vortexReview;
|
|
171
|
-
|
|
171
|
+
const reason = review?.skip_reason?.trim();
|
|
172
|
+
return (reason === "retry_scheduled" || reason === "post_delayed") && sameHead(review?.head_sha, layer.headSha);
|
|
172
173
|
}
|
|
173
174
|
function vortexIdleSkipBlocker(layer) {
|
|
174
175
|
if (layerAgentsBusy(layer).vortex)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Config } from "./config.js";
|
|
2
|
+
import type { MergeQueueEntryDto } from "./stack-dto.js";
|
|
2
3
|
import { pollStackWatch, type StackWatchCursor, type StackWatchEnvelope } from "./stack-watch.js";
|
|
3
4
|
export declare const STACK_WATCH_MARKER = "MS-WATCH";
|
|
4
5
|
export declare const STACK_WATCH_EXIT: {
|
|
@@ -10,6 +11,9 @@ export declare const STACK_WATCH_SLICE_MS = 300000;
|
|
|
10
11
|
export declare const STACK_WATCH_RECHECK_MS = 30000;
|
|
11
12
|
export declare const STACK_WATCH_FAILED_RETRY_MS = 15000;
|
|
12
13
|
export declare const STACK_WATCH_RATE_LIMIT_WAIT_MS = 30000;
|
|
14
|
+
export declare const STACK_WATCH_TRANSIENT_WINDOW_MS: number;
|
|
15
|
+
export declare const STACK_WATCH_TRANSIENT_MAX_WAIT_MS = 60000;
|
|
16
|
+
export declare function isTransientWatchFailure(err: unknown): boolean;
|
|
13
17
|
export type StackWatchUntil = "attention" | "landed";
|
|
14
18
|
export type StackWatchOutcome = "attention" | "landed" | "failed" | "timeout" | "aborted";
|
|
15
19
|
export type RunStackWatchOptions = {
|
|
@@ -22,6 +26,7 @@ export type RunStackWatchOptions = {
|
|
|
22
26
|
write?: (line: string) => void;
|
|
23
27
|
writeMarker?: (line: string) => void;
|
|
24
28
|
poll?: typeof pollStackWatch;
|
|
29
|
+
readLanded?: (cfg: Config, stackId: string) => Promise<MergeQueueEntryDto | null>;
|
|
25
30
|
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
26
31
|
now?: () => number;
|
|
27
32
|
};
|
package/dist/stack-watch-loop.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
+
import { API_TIMEOUT_PREFIX, readLandedQueueEntry } from "./api.js";
|
|
3
|
+
import { isTransientReviewPollError } from "./commands/review-client.js";
|
|
2
4
|
import { pollStackWatch, StackWatchError, StackWatchTimeoutError, } from "./stack-watch.js";
|
|
3
5
|
export const STACK_WATCH_MARKER = "MS-WATCH";
|
|
4
6
|
export const STACK_WATCH_EXIT = { landed: 0, attention: 3, timeout: 5 };
|
|
@@ -6,6 +8,18 @@ export const STACK_WATCH_SLICE_MS = 300_000;
|
|
|
6
8
|
export const STACK_WATCH_RECHECK_MS = 30_000;
|
|
7
9
|
export const STACK_WATCH_FAILED_RETRY_MS = 15_000;
|
|
8
10
|
export const STACK_WATCH_RATE_LIMIT_WAIT_MS = 30_000;
|
|
11
|
+
export const STACK_WATCH_TRANSIENT_WINDOW_MS = 15 * 60_000;
|
|
12
|
+
export const STACK_WATCH_TRANSIENT_MAX_WAIT_MS = 60_000;
|
|
13
|
+
const TRANSIENT_WATCH_FAILURE = /HTTP (?:408|429|5\d\d)|snapshot missing|fetch failed|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|network|connection/i;
|
|
14
|
+
export function isTransientWatchFailure(err) {
|
|
15
|
+
if (!(err instanceof Error))
|
|
16
|
+
return false;
|
|
17
|
+
if (isTransientReviewPollError(err))
|
|
18
|
+
return true;
|
|
19
|
+
if (err.cause !== undefined && isTransientReviewPollError(err.cause))
|
|
20
|
+
return true;
|
|
21
|
+
return err.message.startsWith(API_TIMEOUT_PREFIX) || TRANSIENT_WATCH_FAILURE.test(err.message);
|
|
22
|
+
}
|
|
9
23
|
const ELAPSED_MINUTES = /\b\d+m\b/g;
|
|
10
24
|
export function blockerWithoutElapsedMinutes(blocker) {
|
|
11
25
|
return (blocker ?? "").replace(ELAPSED_MINUTES, "Nm");
|
|
@@ -35,6 +49,7 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
|
|
|
35
49
|
const write = opts.write ?? ((line) => console.log(line));
|
|
36
50
|
const writeMarker = opts.json ? opts.writeMarker ?? ((line) => console.error(line)) : write;
|
|
37
51
|
const poll = opts.poll ?? pollStackWatch;
|
|
52
|
+
const readLanded = opts.readLanded ?? readLandedQueueEntry;
|
|
38
53
|
const now = opts.now ?? Date.now;
|
|
39
54
|
const wait = opts.sleep ?? ((ms, signal) => sleep(ms, undefined, { signal }));
|
|
40
55
|
const deadline = opts.maxMs !== undefined ? now() + opts.maxMs : Number.POSITIVE_INFINITY;
|
|
@@ -50,9 +65,43 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
|
|
|
50
65
|
};
|
|
51
66
|
let cursor = opts.cursor;
|
|
52
67
|
let failures = 0;
|
|
68
|
+
let permanentFailures = 0;
|
|
69
|
+
const seenPrs = new Set();
|
|
70
|
+
const notePrs = (envelope) => {
|
|
71
|
+
if (envelope.prNumber)
|
|
72
|
+
seenPrs.add(envelope.prNumber);
|
|
73
|
+
if (envelope.currentCandidate?.prNumber)
|
|
74
|
+
seenPrs.add(envelope.currentCandidate.prNumber);
|
|
75
|
+
};
|
|
76
|
+
let transientSince = null;
|
|
53
77
|
const ignoredKeys = new Map();
|
|
54
78
|
let reportedKey = null;
|
|
55
|
-
const
|
|
79
|
+
const confirmedLanding = async (envelope) => {
|
|
80
|
+
if (envelope.watch.reason !== "not_found")
|
|
81
|
+
return envelope;
|
|
82
|
+
let entry = null;
|
|
83
|
+
try {
|
|
84
|
+
entry = await readLanded(cfg, id);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return {
|
|
88
|
+
...envelope,
|
|
89
|
+
watch: {
|
|
90
|
+
...envelope.watch,
|
|
91
|
+
reason: "landing_unconfirmed",
|
|
92
|
+
message: "The stack is gone, but its landed merge queue entry could not be read. Landing is unconfirmed. Confirm each PR is merged (gh pr view <n> --json state) before reporting it landed. The watch is done; do not report it landed.",
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const enrolledHead = envelope.cursor?.enrolledHeadSha ?? null;
|
|
97
|
+
const ours = Boolean(entry) && (entry.landedPrNumbers.some((pr) => seenPrs.has(pr)) ||
|
|
98
|
+
(enrolledHead !== null && entry.verifyHeadSha === enrolledHead));
|
|
99
|
+
if (!entry || !ours)
|
|
100
|
+
return envelope;
|
|
101
|
+
return { ...envelope, watch: { ...envelope.watch, reason: "landed", message: `This stack is landed (merge queue landed #${entry.landedPrNumbers.join(", #")}). The watch is done; stop calling stack_wait for it.` } };
|
|
102
|
+
};
|
|
103
|
+
const finished = async (seen) => {
|
|
104
|
+
const envelope = await confirmedLanding(seen);
|
|
56
105
|
const landed = envelope.watch.reason === "landed";
|
|
57
106
|
const outcome = landed ? "landed" : "attention";
|
|
58
107
|
emit(landed ? stackWatchLandedLine(envelope)
|
|
@@ -61,9 +110,20 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
|
|
|
61
110
|
write(envelope.watch.message);
|
|
62
111
|
return { outcome, exitCode: landed ? STACK_WATCH_EXIT.landed : STACK_WATCH_EXIT.attention, envelope };
|
|
63
112
|
};
|
|
64
|
-
const failed = async (message, envelope) => {
|
|
113
|
+
const failed = async (message, envelope, transient) => {
|
|
65
114
|
failures += 1;
|
|
66
|
-
if (
|
|
115
|
+
if (transient) {
|
|
116
|
+
permanentFailures = 0;
|
|
117
|
+
transientSince ??= now();
|
|
118
|
+
if (now() - transientSince < STACK_WATCH_TRANSIENT_WINDOW_MS) {
|
|
119
|
+
await pause(Math.min(STACK_WATCH_TRANSIENT_MAX_WAIT_MS, STACK_WATCH_FAILED_RETRY_MS * 2 ** Math.min(failures - 1, 4)));
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
permanentFailures += 1;
|
|
125
|
+
}
|
|
126
|
+
if (permanentFailures >= 2 || transient) {
|
|
67
127
|
emit(stackWatchFailedLine(id, message), envelope, "failed");
|
|
68
128
|
return { outcome: "failed", exitCode: STACK_WATCH_EXIT.attention, envelope };
|
|
69
129
|
}
|
|
@@ -84,25 +144,34 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
|
|
|
84
144
|
if (isAbort(err, opts.signal))
|
|
85
145
|
throw err;
|
|
86
146
|
if (err instanceof StackWatchTimeoutError) {
|
|
147
|
+
notePrs(err.lastEnvelope);
|
|
87
148
|
cursor = err.lastEnvelope.cursor;
|
|
88
149
|
failures = 0;
|
|
150
|
+
permanentFailures = 0;
|
|
151
|
+
if (err.lastEnvelope.assessment === "available")
|
|
152
|
+
transientSince = null;
|
|
89
153
|
continue;
|
|
90
154
|
}
|
|
91
|
-
if (err instanceof StackWatchError && err.lastEnvelope.watch.done)
|
|
92
|
-
|
|
155
|
+
if (err instanceof StackWatchError && err.lastEnvelope.watch.done) {
|
|
156
|
+
notePrs(err.lastEnvelope);
|
|
157
|
+
return await finished(err.lastEnvelope);
|
|
158
|
+
}
|
|
93
159
|
if (err instanceof StackWatchError && err.lastEnvelope.status === "rate_limited") {
|
|
94
160
|
await pause(err.retryAfterSeconds !== undefined ? Math.max(1_000, err.retryAfterSeconds * 1000) : STACK_WATCH_RATE_LIMIT_WAIT_MS);
|
|
95
161
|
continue;
|
|
96
162
|
}
|
|
97
|
-
const result = await failed(err instanceof Error ? err.message : String(err), err instanceof StackWatchError ? err.lastEnvelope : null);
|
|
163
|
+
const result = await failed(err instanceof Error ? err.message : String(err), err instanceof StackWatchError ? err.lastEnvelope : null, isTransientWatchFailure(err));
|
|
98
164
|
if (result)
|
|
99
165
|
return result;
|
|
100
166
|
continue;
|
|
101
167
|
}
|
|
102
168
|
failures = 0;
|
|
169
|
+
permanentFailures = 0;
|
|
170
|
+
transientSince = null;
|
|
103
171
|
cursor = envelope.cursor;
|
|
172
|
+
notePrs(envelope);
|
|
104
173
|
if (envelope.watch.done)
|
|
105
|
-
return finished(envelope);
|
|
174
|
+
return await finished(envelope);
|
|
106
175
|
if (envelope.status === "attention") {
|
|
107
176
|
const key = stackWatchAttentionKey(envelope);
|
|
108
177
|
const blocker = (envelope.blocker ?? "").toLowerCase();
|
|
@@ -3,7 +3,7 @@ export declare const STACK_WATCH_NOT_DONE_SENTENCE = "This stack is not landed.
|
|
|
3
3
|
export declare const STACK_WATCH_NOTIFY_PATTERN = "MS-WATCH (ATTENTION|LANDED)";
|
|
4
4
|
export declare function stackWatchBackgroundCommand(stackId: string): string;
|
|
5
5
|
export type StackWatchDoneReason = "landed" | "closed" | "archived" | "not_found";
|
|
6
|
-
export type StackWatchReason = StackWatchDoneReason | "open" | "attention" | "unread" | "rate_limited" | "failed";
|
|
6
|
+
export type StackWatchReason = StackWatchDoneReason | "open" | "attention" | "unread" | "rate_limited" | "failed" | "landing_unconfirmed";
|
|
7
7
|
export type StackWatchNextArgs = {
|
|
8
8
|
stack_id: string;
|
|
9
9
|
enrolled_head_sha?: string | null;
|
package/dist/stack-watch.js
CHANGED
|
@@ -83,6 +83,7 @@ export async function pollStackWatch(cfg, stackId, opts = {}) {
|
|
|
83
83
|
catch (err) {
|
|
84
84
|
if (opts.signal?.aborted || (err instanceof Error && err.name === "AbortError"))
|
|
85
85
|
throw err;
|
|
86
|
+
lastEnvelope = { ...lastEnvelope, assessment: "unavailable" };
|
|
86
87
|
if (now() >= deadline && timeoutMs !== 0)
|
|
87
88
|
throw timeout();
|
|
88
89
|
if (timeoutMs !== 0 && isTransientReviewPollError(err) && transientFailures < REVIEW_POLL_MAX_TRANSIENT_RETRIES) {
|
|
@@ -95,6 +96,7 @@ export async function pollStackWatch(cfg, stackId, opts = {}) {
|
|
|
95
96
|
}
|
|
96
97
|
if (response.status === 200)
|
|
97
98
|
return response.body;
|
|
99
|
+
lastEnvelope = { ...lastEnvelope, assessment: "unavailable" };
|
|
98
100
|
const retryWaitMs = transientRetryWaitMs({
|
|
99
101
|
failureCount: transientFailures + 1, retryAfterSeconds: response.retryAfterSeconds, random: opts.random,
|
|
100
102
|
});
|
package/dist/ui/banner.js
CHANGED
|
@@ -173,6 +173,12 @@ const WHATS_NEW = {
|
|
|
173
173
|
"0.3.45": [
|
|
174
174
|
"Cyclone can auto-fix red stack CI",
|
|
175
175
|
],
|
|
176
|
+
"0.3.46": [
|
|
177
|
+
"Watch waits while GitHub delays a review",
|
|
178
|
+
],
|
|
179
|
+
"0.3.47": [
|
|
180
|
+
"New stack PRs are adopted on Work at submit",
|
|
181
|
+
],
|
|
176
182
|
};
|
|
177
183
|
function densityFromVariant(variant) {
|
|
178
184
|
if (variant === "mini" || variant === "nano")
|
package/package.json
CHANGED