@botbuddy/cli 1.44.1 → 1.45.1
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/package.json +1 -1
- package/src/wait-core.mjs +8 -4
- package/src/wait.mjs +34 -6
package/package.json
CHANGED
package/src/wait-core.mjs
CHANGED
|
@@ -190,7 +190,8 @@ const VALIDATORS = {
|
|
|
190
190
|
const id = hex ? `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` : raw;
|
|
191
191
|
return { id };
|
|
192
192
|
},
|
|
193
|
-
// BOT-1064 — linear: wake on a logical state change of a named
|
|
193
|
+
// BOT-1064 / BOT-2112 — linear: wake on a logical state change of a named
|
|
194
|
+
// issue in any Linear team bound to the authenticated tenant.
|
|
194
195
|
// The spine subject_key is the issue identifier as stored on the tickets
|
|
195
196
|
// projection (upper-cased at write), so normalize to upper-case here too.
|
|
196
197
|
linear(p) {
|
|
@@ -435,10 +436,13 @@ function prSubjectMatches(subjectKey, params) {
|
|
|
435
436
|
return subjectKey.startsWith(`${params.repo}#`);
|
|
436
437
|
}
|
|
437
438
|
|
|
438
|
-
// Does a commit field on a signal payload
|
|
439
|
-
//
|
|
439
|
+
// Does a commit field on a signal payload identify the same commit as the
|
|
440
|
+
// condition's SHA? GitHub may send either side as a 7–40-character prefix, so
|
|
441
|
+
// accept either prefix direction while refusing malformed payload values.
|
|
440
442
|
function shaPrefixMatches(value, sha) {
|
|
441
|
-
|
|
443
|
+
if (typeof value !== "string") return false;
|
|
444
|
+
const candidate = value.trim().toLowerCase();
|
|
445
|
+
return /^[0-9a-f]{7,40}$/.test(candidate) && (candidate.startsWith(sha) || sha.startsWith(candidate));
|
|
442
446
|
}
|
|
443
447
|
|
|
444
448
|
// BOT-1975: optional head scoping for pr-state. pr_state signals carry the PR
|
package/src/wait.mjs
CHANGED
|
@@ -78,8 +78,8 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
|
|
|
78
78
|
by the PR read model. Omit pr= to wake on ANY of your PRs
|
|
79
79
|
in the repo; or pass pr=<owner/repo#n> in one value. A
|
|
80
80
|
conflict/clean flip sets payload.mergeable_changed. The
|
|
81
|
-
payload carries head_ref_oid; sha=<head> (7-40 hex
|
|
82
|
-
|
|
81
|
+
payload carries head_ref_oid; sha=<head> (7-40 hex; either
|
|
82
|
+
side may be an abbreviated prefix) wakes only on signals for that head, so a
|
|
83
83
|
previous head's check result can't wake you. A push alone
|
|
84
84
|
emits nothing — the head rides on the next transition.
|
|
85
85
|
pr-review:repo=<owner/repo>[,pr=<n>][,reviewer=<login>][,sha=<commit>]
|
|
@@ -96,7 +96,7 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
|
|
|
96
96
|
existing review will not re-wake it.
|
|
97
97
|
reviewer=<login> wakes only on that reviewer (case-
|
|
98
98
|
insensitive; a trailing [bot] is ignored). sha=<commit>
|
|
99
|
-
wakes only on verdicts for that commit (submitted reviews
|
|
99
|
+
wakes only on verdicts for that commit (either side may be an abbreviated prefix; submitted reviews
|
|
100
100
|
and clean passes carry commit_sha; thread comments do not,
|
|
101
101
|
so they never match sha=); the usage-limit decline always
|
|
102
102
|
passes sha=. Any other key is refused (exit 4).
|
|
@@ -108,8 +108,9 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
|
|
|
108
108
|
guidance:request=<post_id> a command-post guidance request being answered
|
|
109
109
|
(scoped to the asking agent; fetch the body with
|
|
110
110
|
get_guidance_response)
|
|
111
|
-
linear:issue=<KEY>
|
|
112
|
-
assignee change (e.g.
|
|
111
|
+
linear:issue=<KEY> an issue in any Linear team bound to your tenant;
|
|
112
|
+
wakes on a state change, new comment, or assignee change (e.g.
|
|
113
|
+
issue=BOT-123; tenant-scoped).
|
|
113
114
|
Flags linear_feed_lagging if the ingest feed has stalled.
|
|
114
115
|
unblocked:<KEY> a ticket's LAST open blocker clearing (open-blocker
|
|
115
116
|
count >0 → 0): a blocker moving to Done/Canceled/Duplicate
|
|
@@ -378,6 +379,10 @@ async function registerWait(opts, conditions, deadlineIso) {
|
|
|
378
379
|
// wake-capable run for. The wait could only ever expire, and no amount of
|
|
379
380
|
// waiting repairs a typo — a hard stop, with the server's candidates.
|
|
380
381
|
"unknown_workflow",
|
|
382
|
+
// BOT-2112: these are arm-time authorization/target errors. Retrying as
|
|
383
|
+
// an untracked live-only wait would silently park on a signal it cannot
|
|
384
|
+
// receive, so preserve the relay's typed error and exit 4.
|
|
385
|
+
"linear_team_not_bound",
|
|
381
386
|
// BOT-1612: a `chat:channel=<id>` wait whose bound agent is not one of the
|
|
382
387
|
// channel's participants could only ever park to timeout (the emitter never
|
|
383
388
|
// scopes a message to a non-participant). The relay refuses it at arm time;
|
|
@@ -395,6 +400,29 @@ async function registerWait(opts, conditions, deadlineIso) {
|
|
|
395
400
|
throw err;
|
|
396
401
|
}
|
|
397
402
|
}
|
|
403
|
+
if (res.status === 404) {
|
|
404
|
+
const body = await res.json().catch(() => ({}));
|
|
405
|
+
if (body.error === "linear_issue_not_found") {
|
|
406
|
+
const err = new Error(body.detail || body.error);
|
|
407
|
+
err.invalidCondition = true;
|
|
408
|
+
err.errorCode = body.error;
|
|
409
|
+
if (Array.isArray(body.candidates)) err.candidates = body.candidates;
|
|
410
|
+
throw err;
|
|
411
|
+
}
|
|
412
|
+
// BOT-1523 retains its existing not-found contract. Keep every 404's body
|
|
413
|
+
// handling in one branch: parsing it above and falling through would consume
|
|
414
|
+
// the body before this typed unblocked refusal could see it.
|
|
415
|
+
if (body.error === "unblocked_ticket_not_found") {
|
|
416
|
+
const err = new Error(body.detail || body.error);
|
|
417
|
+
err.hydrationRegister = true;
|
|
418
|
+
err.errorCode = body.error;
|
|
419
|
+
err.ticket = body.ticket ?? null;
|
|
420
|
+
err.retryable = body.retryable ?? null;
|
|
421
|
+
err.hydration = body.hydration ?? null;
|
|
422
|
+
err.notFound = true;
|
|
423
|
+
throw err;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
398
426
|
if (res.status === 403) {
|
|
399
427
|
const body = await res.json().catch(() => ({}));
|
|
400
428
|
if (new Set(["wait_tenant_unresolved", "wait_cross_tenant", "wait_tenant_mismatch", "wait_resume_identity_mismatch"]).has(body.error)) {
|
|
@@ -427,7 +455,7 @@ async function registerWait(opts, conditions, deadlineIso) {
|
|
|
427
455
|
err.minimumProtocol = body.minimum_wait_protocol ?? null;
|
|
428
456
|
throw err;
|
|
429
457
|
}
|
|
430
|
-
if (res.status === 409
|
|
458
|
+
if (res.status === 409) {
|
|
431
459
|
// BOT-1523: `unblocked:<KEY>` hydration at registration could not arm the wait
|
|
432
460
|
// truthfully. These are NOT the untracked-live-only fallback: arming a live-only
|
|
433
461
|
// unblocked wait would skip the server's initial evaluation and could resume a
|