@defrex/autobuild 0.8.0 → 0.10.0
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/SPEC.md +323 -26
- package/bin/ab-build-runner.ts +8 -2
- package/docs/architecture.md +166 -14
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +175 -28
- package/docs/hosted-dispatcher.md +69 -13
- package/docs/operator-api.md +2 -2
- package/docs/remote-store-protocol.md +204 -16
- package/docs/remote-ticket-protocol.md +1 -1
- package/docs/setup.md +12 -9
- package/docs/website.md +14 -0
- package/package.json +45 -23
- package/packages/core/src/cli/artifact.ts +130 -6
- package/packages/core/src/cli/binary.ts +9 -1
- package/packages/core/src/cli/build-control.ts +110 -13
- package/packages/core/src/cli/context.ts +46 -0
- package/packages/core/src/cli/dashboard/facts.ts +215 -0
- package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
- package/packages/core/src/cli/dashboard/model.ts +141 -153
- package/packages/core/src/cli/dashboard/poll.ts +76 -12
- package/packages/core/src/cli/dashboard/render.ts +34 -5
- package/packages/core/src/cli/dispatch-frontend.ts +54 -5
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +392 -75
- package/packages/core/src/cli/harvest.ts +122 -8
- package/packages/core/src/cli/help.ts +62 -9
- package/packages/core/src/cli/init-readiness-remote.ts +110 -0
- package/packages/core/src/cli/init-readiness-shared.ts +72 -0
- package/packages/core/src/cli/init-validation.ts +266 -222
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/main.ts +70 -11
- package/packages/core/src/cli/mcp.ts +23 -1
- package/packages/core/src/cli/plugin.ts +7 -4
- package/packages/core/src/cli/remote-poll.ts +277 -0
- package/packages/core/src/cli/repo-state.ts +22 -0
- package/packages/core/src/cli/repository-status.ts +39 -1
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +150 -28
- package/packages/core/src/cli/terminals.ts +2 -1
- package/packages/core/src/cli/ticket-assets.ts +247 -0
- package/packages/core/src/cli/ticket.ts +453 -8
- package/packages/core/src/cli/wait.ts +157 -41
- package/packages/core/src/cli/watch.ts +138 -168
- package/packages/core/src/config/live.ts +23 -0
- package/packages/core/src/config/pipeline-source.ts +388 -0
- package/packages/core/src/config/schema.ts +251 -19
- package/packages/core/src/events/catalog.ts +1 -0
- package/packages/core/src/events/globs.ts +47 -0
- package/packages/core/src/events/payloads.ts +52 -2
- package/packages/core/src/events/repository.ts +52 -1
- package/packages/core/src/events/sessions.ts +69 -10
- package/packages/core/src/events/workspace-remote.ts +12 -0
- package/packages/core/src/integration/harness.ts +115 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +157 -0
- package/packages/core/src/kernel/auto-merge.ts +99 -22
- package/packages/core/src/kernel/dispatch-settings.ts +42 -17
- package/packages/core/src/kernel/dispatch-status.ts +23 -0
- package/packages/core/src/kernel/engine.ts +9 -438
- package/packages/core/src/kernel/generators/build-log.ts +259 -0
- package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
- package/packages/core/src/kernel/generators/session-log.ts +72 -0
- package/packages/core/src/kernel/harvest.ts +62 -18
- package/packages/core/src/kernel/incremental-contract.ts +75 -0
- package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
- package/packages/core/src/kernel/incremental.ts +53 -0
- package/packages/core/src/kernel/log-index.ts +620 -0
- package/packages/core/src/kernel/phases.ts +16 -2
- package/packages/core/src/kernel/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +254 -73
- package/packages/core/src/operator/annotations.ts +6 -0
- package/packages/core/src/operator/control.ts +19 -5
- package/packages/core/src/operator/index.ts +71 -6
- package/packages/core/src/operator/query.ts +253 -24
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/operator/registry.ts +55 -10
- package/packages/core/src/operator/requests.ts +9 -6
- package/packages/core/src/operator/sandbox-host.ts +140 -0
- package/packages/core/src/operator/sandbox.ts +247 -14
- package/packages/core/src/operator/ticket-source-backend.ts +47 -0
- package/packages/core/src/orchestrator/approvals.ts +115 -0
- package/packages/core/src/orchestrator/conversation.ts +167 -0
- package/packages/core/src/orchestrator/model.ts +86 -0
- package/packages/core/src/orchestrator/prompt.ts +61 -0
- package/packages/core/src/orchestrator/tools.ts +117 -0
- package/packages/core/src/orchestrator/turn-runner.ts +457 -0
- package/packages/core/src/plugin-sdk/index.ts +51 -1
- package/packages/core/src/plugins/load.ts +135 -7
- package/packages/core/src/plugins/manifest.ts +183 -10
- package/packages/core/src/plugins/registry.ts +73 -4
- package/packages/core/src/ports/forge/create.ts +1 -1
- package/packages/core/src/ports/forge/fake.ts +31 -0
- package/packages/core/src/ports/forge/github-transport.ts +13 -2
- package/packages/core/src/ports/forge/github.ts +13 -2
- package/packages/core/src/ports/forge/local-git.ts +31 -1
- package/packages/core/src/ports/runner/claude.ts +2 -2
- package/packages/core/src/ports/runner/codex.ts +6 -2
- package/packages/core/src/ports/runner/fake.ts +7 -1
- package/packages/core/src/ports/runner/pi.ts +8 -6
- package/packages/core/src/ports/runner/provider-error.ts +13 -3
- package/packages/core/src/ports/tickets/fake.ts +5 -1
- package/packages/core/src/ports/tickets/file.ts +169 -48
- package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
- package/packages/core/src/ports/tickets/linear.ts +33 -1
- package/packages/core/src/ports/tickets/operations.ts +1 -1
- package/packages/core/src/ports/tickets/remote.ts +8 -1
- package/packages/core/src/ports/types.ts +49 -3
- package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
- package/packages/core/src/ports/workspace/create.ts +74 -39
- package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
- package/packages/core/src/ports/workspace/fake.ts +169 -4
- package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
- package/packages/core/src/ports/workspace/github-origin.ts +36 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
- package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
- package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
- package/packages/core/src/processes/build-child.ts +17 -2
- package/packages/core/src/processes/build-execution-state.ts +68 -15
- package/packages/core/src/processes/build-runner.ts +238 -93
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +501 -201
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-child.ts +5 -0
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/harvest-runner.ts +66 -19
- package/packages/core/src/processes/harvest.ts +196 -24
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +360 -0
- package/packages/core/src/processes/publication-state.ts +164 -52
- package/packages/core/src/processes/repo-view.ts +1436 -0
- package/packages/core/src/processes/sandbox-state.ts +36 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +41 -0
- package/packages/core/src/store/build-scope.ts +90 -0
- package/packages/core/src/store/contract.ts +1205 -0
- package/packages/core/src/store/digest.ts +102 -0
- package/packages/core/src/store/local/schema.ts +46 -2
- package/packages/core/src/store/local/store.ts +445 -4
- package/packages/core/src/store/memory.ts +246 -0
- package/packages/core/src/store/new-build.ts +17 -0
- package/packages/core/src/store/phase-session.ts +94 -0
- package/packages/core/src/store/remote/client.ts +310 -2
- package/packages/core/src/store/remote/index.ts +14 -2
- package/packages/core/src/store/remote/protocol.ts +115 -12
- package/packages/core/src/store/remote/version.ts +6 -2
- package/packages/core/src/store/repo-state-events.ts +205 -0
- package/packages/core/src/store/retention.ts +7 -0
- package/packages/core/src/store/session-handle.ts +94 -0
- package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/streams/session-writer.ts +42 -2
- package/packages/core/src/store/streams/wait.ts +4 -5
- package/packages/core/src/store/ticket-assets.ts +458 -0
- package/packages/core/src/store/types.ts +160 -1
- package/packages/core/src/testing/index.ts +59 -0
- package/skills/code-review/SKILL.md +5 -1
- package/skills/guide/SKILL.md +152 -40
- package/skills/guide/references/operator-api.md +2 -2
- package/skills/guide/references/plugin-authoring.md +17 -0
- package/skills/guide/references/remote-store-protocol.md +204 -16
- package/skills/guide/references/remote-ticket-protocol.md +1 -1
- package/skills/guide/references/setup.md +12 -9
- package/skills/implement/SKILL.md +3 -0
- package/skills/operate/SKILL.md +126 -0
- package/skills/plan/SKILL.md +4 -0
- package/skills/plan-review/SKILL.md +5 -1
- package/skills/spec/SKILL.md +46 -3
- package/skills/tickets/SKILL.md +57 -5
- package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
- package/packages/core/src/cli/store-opening.contract.ts +0 -289
- package/packages/core/src/cli/testkit.ts +0 -194
- package/packages/core/src/markdown.ts +0 -67
- package/packages/core/src/operator/client.ts +0 -311
- package/packages/core/src/operator/server.ts +0 -616
- package/packages/core/src/operator/sessions.ts +0 -223
- package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
- package/packages/core/src/ports/tickets/remote-server.ts +0 -185
- package/packages/core/src/store/remote/server.ts +0 -923
- package/packages/core/src/testing/store-failures.ts +0 -32
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* The command shares `ab watch`'s machinery — target selection, ambient
|
|
11
11
|
* scoping (via the extracted `assertReadScope`), the cursor codec, the record
|
|
12
|
-
* projection, the
|
|
12
|
+
* projection, the cadence, and the read-failure policy — but ends on the
|
|
13
13
|
* first satisfied condition instead of streaming. Conditions are derived from
|
|
14
14
|
* the same reducer that produces `ab build status` (`status`, `prState`,
|
|
15
15
|
* `phase`), so "blocked" means an open escalation exactly as it does
|
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
* wait`, a state condition that already holds when the command starts returns
|
|
18
18
|
* immediately with the build's latest event.
|
|
19
19
|
*
|
|
20
|
+
* Cadence follows the store kind, exactly as `ab watch` does since AUT-334
|
|
21
|
+
* (verified and corrected for `ab wait` in AUT-368). Against a local store
|
|
22
|
+
* the loop is the sequential interval tick. Against an `http(s)` store every
|
|
23
|
+
* tracked build long-polls instead: one held `getEvents` request per stream
|
|
24
|
+
* is in flight (tasks run concurrently), `--interval` is the floor between
|
|
25
|
+
* request starts (elapsed request time counts toward the gap), and a hold's
|
|
26
|
+
* window is the remote default (25 s, whole seconds, under the store's
|
|
27
|
+
* ceiling) capped at the remaining `--timeout` budget. Every stop — a
|
|
28
|
+
* satisfied condition, an all-terminal named set, the timeout, an interrupt —
|
|
29
|
+
* cancels the in-flight holds promptly instead of waiting out a full window.
|
|
30
|
+
*
|
|
20
31
|
* Read-only: it appends no event, claims or renews no lease, and creates no
|
|
21
32
|
* build or repository record. Output carries no ANSI, ever, in either form.
|
|
22
33
|
*/
|
|
@@ -26,7 +37,13 @@ import type { BuildState, PrLifecycle } from '../kernel/reducer'
|
|
|
26
37
|
import { reduceBuild } from '../kernel/reducer'
|
|
27
38
|
import { isPhase, type BuildStatus, type Phase } from '../ontology'
|
|
28
39
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
29
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
createRemotePollRunner,
|
|
42
|
+
defaultDelay,
|
|
43
|
+
makeFailureStreak,
|
|
44
|
+
type RemotePollReadOpts,
|
|
45
|
+
} from './remote-poll'
|
|
46
|
+
import { buildInRepository, isRemoteStoreRef, normalizeGitRemoteUrl } from './repo-state'
|
|
30
47
|
import type { BuildRecord } from '../store/types'
|
|
31
48
|
import { withAmbientReadStore, type StoreOpener } from './store-opening'
|
|
32
49
|
import {
|
|
@@ -245,23 +262,6 @@ interface WaitStream {
|
|
|
245
262
|
status: BuildStatus
|
|
246
263
|
}
|
|
247
264
|
|
|
248
|
-
function defaultDelay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
249
|
-
return new Promise((resolve) => {
|
|
250
|
-
if (signal?.aborted === true) {
|
|
251
|
-
resolve()
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
const finish = (): void => {
|
|
255
|
-
clearTimeout(timer)
|
|
256
|
-
signal?.removeEventListener('abort', onAbort)
|
|
257
|
-
resolve()
|
|
258
|
-
}
|
|
259
|
-
const timer = setTimeout(finish, ms)
|
|
260
|
-
const onAbort = (): void => finish()
|
|
261
|
-
signal?.addEventListener('abort', onAbort, { once: true })
|
|
262
|
-
})
|
|
263
|
-
}
|
|
264
|
-
|
|
265
265
|
/**
|
|
266
266
|
* `ab wait` — block until a targeted build satisfies a condition, then print
|
|
267
267
|
* one record and exit 0; exit 2 when every named build went terminal without
|
|
@@ -386,6 +386,12 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
386
386
|
if (streams.has(slug)) return
|
|
387
387
|
const record = await store.getBuild(slug)
|
|
388
388
|
if (record === null) return
|
|
389
|
+
// AUT-545: this full-log read is deliberate, accepted growth — the
|
|
390
|
+
// retained prefix is consumed at full replay depth by every later
|
|
391
|
+
// reduction (approvals, findings, answered escalations, PR facts,
|
|
392
|
+
// observations), so bounding it would need reducer-partition store
|
|
393
|
+
// machinery out of AUT-545's scope. Required for `--since` replay and
|
|
394
|
+
// the registration state check over the full reduction.
|
|
389
395
|
const events = await store.getEvents(slug)
|
|
390
396
|
const stream = registerStream(slug, events, slugs.includes(slug))
|
|
391
397
|
if (resumeSeq !== undefined) {
|
|
@@ -410,6 +416,9 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
410
416
|
if (satisfied === null) checkRegistrationState(stream)
|
|
411
417
|
}
|
|
412
418
|
|
|
419
|
+
/** Builds discovery read in full and found terminal; never re-read. */
|
|
420
|
+
const retired = new Set<string>()
|
|
421
|
+
|
|
413
422
|
/**
|
|
414
423
|
* No-slug membership: every nonterminal build of this repository joins
|
|
415
424
|
* the wait, baselined at its current maximum so no history replays, and
|
|
@@ -419,18 +428,42 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
419
428
|
* build to appear.
|
|
420
429
|
*/
|
|
421
430
|
const discoverBuilds = async (): Promise<void> => {
|
|
431
|
+
// AUT-545: the terminal filter is the AUT-487 digest, not a full-log read —
|
|
432
|
+
// one flat batch read replaces a full getEvents per candidate build, per
|
|
433
|
+
// discovery pass; `terminal` is reduceBuild's exact terminal rule
|
|
434
|
+
// (store/digest.ts, pinned by contract). The per-build full read below
|
|
435
|
+
// remains deliberate for builds the digest cannot retire: its retained
|
|
436
|
+
// prefix is consumed at full replay depth by every later reduction
|
|
437
|
+
// (approvals, findings, answered escalations, PR facts, observations), so
|
|
438
|
+
// bounding it would need reducer-partition store machinery out of
|
|
439
|
+
// AUT-545's scope. The reduceBuild re-check stays: it is the
|
|
440
|
+
// legacy-record fallback (a record whose `repo` predates origin
|
|
441
|
+
// normalization is in `mine` via `repoOrigin` but absent from the digest
|
|
442
|
+
// map, which keys on `record.repo`) and it keeps this filter unable to
|
|
443
|
+
// diverge from reduceBuild.
|
|
444
|
+
const digests = await store.getRepoBuildDigests(repo)
|
|
422
445
|
for (const record of (await store.listBuilds()).filter(mine)) {
|
|
423
|
-
if (streams.has(record.slug)) continue
|
|
446
|
+
if (streams.has(record.slug) || retired.has(record.slug)) continue
|
|
447
|
+
const digest = digests.get(record.slug)
|
|
448
|
+
if (digest !== undefined && digest.terminal !== undefined) continue
|
|
424
449
|
const events = await store.getEvents(record.slug)
|
|
425
450
|
const status = reduceBuild(events).status
|
|
426
|
-
if (!NONTERMINAL_STATUSES.includes(status))
|
|
451
|
+
if (!NONTERMINAL_STATUSES.includes(status)) {
|
|
452
|
+
// Terminal is permanent (reduceBuild never clears it): a legacy
|
|
453
|
+
// record the digest cannot retire is read once, not every pass.
|
|
454
|
+
retired.add(record.slug)
|
|
455
|
+
continue
|
|
456
|
+
}
|
|
427
457
|
const stream = registerStream(record.slug, events, false)
|
|
428
458
|
if (satisfied === null) checkRegistrationState(stream)
|
|
429
459
|
}
|
|
430
460
|
}
|
|
431
461
|
|
|
432
|
-
const pollBuild = async (
|
|
433
|
-
|
|
462
|
+
const pollBuild = async (
|
|
463
|
+
stream: WaitStream,
|
|
464
|
+
readOpts?: RemotePollReadOpts,
|
|
465
|
+
): Promise<void> => {
|
|
466
|
+
const fresh = await store.getEvents(stream.slug, stream.lastSeq, readOpts)
|
|
434
467
|
for (const event of fresh) {
|
|
435
468
|
stream.events.push(event)
|
|
436
469
|
stream.lastSeq = event.seq
|
|
@@ -482,13 +515,7 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
482
515
|
/** One poll cycle: discovery, then per-stream reads. A failed read is
|
|
483
516
|
* reported once per failure streak, advances nothing, and is retried at
|
|
484
517
|
* the next interval; a fully successful cycle re-arms the report. */
|
|
485
|
-
|
|
486
|
-
const reportReadFailure = (error: unknown): void => {
|
|
487
|
-
if (readFailureReported) return
|
|
488
|
-
readFailureReported = true
|
|
489
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
490
|
-
opts.stderr(`ab wait: a store read failed (${message}); retrying at the next interval`)
|
|
491
|
-
}
|
|
518
|
+
const tickStreak = makeFailureStreak('wait', opts.stderr)
|
|
492
519
|
|
|
493
520
|
const tick = async (): Promise<void> => {
|
|
494
521
|
let allReadsOk = true
|
|
@@ -497,7 +524,7 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
497
524
|
await discoverBuilds()
|
|
498
525
|
} catch (error) {
|
|
499
526
|
allReadsOk = false
|
|
500
|
-
|
|
527
|
+
tickStreak.onFailure(error)
|
|
501
528
|
}
|
|
502
529
|
}
|
|
503
530
|
for (const stream of [...streams.values()]) {
|
|
@@ -506,10 +533,10 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
506
533
|
await pollBuild(stream)
|
|
507
534
|
} catch (error) {
|
|
508
535
|
allReadsOk = false
|
|
509
|
-
|
|
536
|
+
tickStreak.onFailure(error)
|
|
510
537
|
}
|
|
511
538
|
}
|
|
512
|
-
if (allReadsOk)
|
|
539
|
+
if (allReadsOk) tickStreak.onSuccess()
|
|
513
540
|
}
|
|
514
541
|
|
|
515
542
|
const finishSatisfied = (): number =>
|
|
@@ -561,17 +588,106 @@ export async function abWait(opts: AbWaitOpts): Promise<number> {
|
|
|
561
588
|
const timeoutText = opts.timeout ?? '30m'
|
|
562
589
|
const deadline = timeoutMs === 0 ? Number.POSITIVE_INFINITY : now().getTime() + timeoutMs
|
|
563
590
|
const aborted = (): boolean => opts.signal?.aborted === true
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
591
|
+
|
|
592
|
+
if (!isRemoteStoreRef(storeRef)) {
|
|
593
|
+
// Local cadence: one sequential tick per interval.
|
|
594
|
+
while (!hasMatch()) {
|
|
595
|
+
if (aborted() || now().getTime() >= deadline) break
|
|
596
|
+
await sleep(interval, opts.signal)
|
|
597
|
+
if (aborted()) break
|
|
598
|
+
await tick()
|
|
599
|
+
if (!hasMatch() && namedAllTerminal()) {
|
|
600
|
+
const terminal = pickTerminalRecord()
|
|
601
|
+
return finish(null, terminal.event, terminal.state)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
// Remote cadence (AUT-334, verified and corrected in AUT-368): one
|
|
606
|
+
// long-poll task per tracked stream, running concurrently — a quiet
|
|
607
|
+
// stream keeps one held `getEvents` request in flight, so an appended
|
|
608
|
+
// event is seen within about a second of its append instead of at the
|
|
609
|
+
// next interval tick. Gap-fill spaces each stream's request starts at
|
|
610
|
+
// least `interval` apart (elapsed request time counts toward the
|
|
611
|
+
// gap), so a server that answers immediately still sees today's
|
|
612
|
+
// request rate. Discovery keeps its interval cadence. The pre-launch
|
|
613
|
+
// guard mirrors the local loop's top-of-cycle checks: a satisfied
|
|
614
|
+
// match, an abort, an elapsed deadline, or an all-terminal named set
|
|
615
|
+
// ends the wait before any held request (an already-satisfied wait
|
|
616
|
+
// issues zero held reads).
|
|
617
|
+
// Every stop is prompt: a satisfied condition, an all-terminal named
|
|
618
|
+
// set, an external abort, or an elapsed --timeout cancels the
|
|
619
|
+
// in-flight held reads (via `requestStop`) and wakes the gap sleeps
|
|
620
|
+
// (via the runner's stop controller) instead of waiting out a full
|
|
621
|
+
// hold. The held read's own bound is additionally capped at the
|
|
622
|
+
// wait's remaining time budget, so a hold can never outlive the
|
|
623
|
+
// deadline.
|
|
624
|
+
const shouldStop =
|
|
625
|
+
hasMatch() || aborted() || now().getTime() >= deadline || namedAllTerminal()
|
|
626
|
+
const remoteRunner = createRemotePollRunner({
|
|
627
|
+
command: 'wait',
|
|
628
|
+
stderr: opts.stderr,
|
|
629
|
+
now,
|
|
630
|
+
sleep,
|
|
631
|
+
intervalMs: interval,
|
|
632
|
+
deadlineMs: deadline,
|
|
633
|
+
aborted,
|
|
634
|
+
shouldStop: () =>
|
|
635
|
+
hasMatch() || aborted() || now().getTime() >= deadline || namedAllTerminal(),
|
|
636
|
+
drain: 'quiesce',
|
|
637
|
+
})
|
|
638
|
+
const requestStop = (): void => remoteRunner.requestStop()
|
|
639
|
+
|
|
640
|
+
const launch = (stream: WaitStream): void => {
|
|
641
|
+
remoteRunner.launch(
|
|
642
|
+
stream.slug,
|
|
643
|
+
(readOpts) => pollBuild(stream, readOpts),
|
|
644
|
+
() => hasMatch() || namedAllTerminal(),
|
|
645
|
+
)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// An external abort (SIGINT) is a stop like any other: it cancels
|
|
649
|
+
// the in-flight held reads and wakes the gap sleeps.
|
|
650
|
+
opts.signal?.addEventListener('abort', requestStop, { once: true })
|
|
651
|
+
|
|
652
|
+
if (!shouldStop) {
|
|
653
|
+
for (const stream of streams.values()) launch(stream)
|
|
654
|
+
|
|
655
|
+
if (slugs.length === 0) {
|
|
656
|
+
// Discovery keeps its interval cadence, launching a new per-
|
|
657
|
+
// stream task for each newly discovered nonterminal build. A
|
|
658
|
+
// discovered build that already satisfies ends the wait. The
|
|
659
|
+
// launch loop stays unconditional, after the catch — exactly
|
|
660
|
+
// where the inline loop ran: streams registered before a
|
|
661
|
+
// mid-discovery throw still get tasks whose polls then run
|
|
662
|
+
// independently.
|
|
663
|
+
void remoteRunner.runDiscovery({
|
|
664
|
+
step: async (): Promise<void> => {
|
|
665
|
+
await discoverBuilds()
|
|
666
|
+
},
|
|
667
|
+
launchPending: (): void => {
|
|
668
|
+
for (const stream of streams.values()) launch(stream)
|
|
669
|
+
},
|
|
670
|
+
endCheck: () => hasMatch(),
|
|
671
|
+
})
|
|
672
|
+
}
|
|
572
673
|
}
|
|
674
|
+
|
|
675
|
+
await remoteRunner.drain()
|
|
676
|
+
opts.signal?.removeEventListener('abort', requestStop)
|
|
573
677
|
}
|
|
678
|
+
|
|
574
679
|
if (hasMatch()) return finishSatisfied()
|
|
680
|
+
if (namedAllTerminal()) {
|
|
681
|
+
// Decision 5, loop form: every named build went terminal without
|
|
682
|
+
// satisfying anything. The local loop checks this after each tick;
|
|
683
|
+
// on the remote path the runner records only that a stop was
|
|
684
|
+
// requested — its `shouldStop`/`endCheck` callbacks re-derive the
|
|
685
|
+
// conditions live — so after the quiesce drain this decision re-reads
|
|
686
|
+
// the same live predicates (`hasMatch`, `namedAllTerminal`, `aborted`,
|
|
687
|
+
// the deadline) to pick the exit.
|
|
688
|
+
const terminal = pickTerminalRecord()
|
|
689
|
+
return finish(null, terminal.event, terminal.state)
|
|
690
|
+
}
|
|
575
691
|
if (aborted()) {
|
|
576
692
|
opts.stderr('ab wait: interrupted — no condition was satisfied before the signal')
|
|
577
693
|
if (json) opts.stdout(JSON.stringify({ cursor: encodeCurrent() }))
|