@defrex/autobuild 0.12.0 → 0.14.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 +138 -16
- package/bin/ab-dispatch-kernel.ts +4 -6
- package/docs/architecture.md +155 -73
- package/docs/configuration.md +41 -9
- package/docs/hosted-service.md +31 -5
- package/docs/operator-api.md +8 -1
- package/docs/remote-store-protocol.md +94 -2
- package/docs/remote-ticket-protocol.md +151 -9
- package/docs/setup.md +33 -7
- package/docs/ticket-migration.md +64 -0
- package/package.json +4 -3
- package/packages/core/src/cli/attention.ts +18 -164
- package/packages/core/src/cli/build-control.ts +23 -16
- package/packages/core/src/cli/bulk-control.ts +3 -2
- package/packages/core/src/cli/connect.ts +141 -38
- package/packages/core/src/cli/context.ts +23 -79
- package/packages/core/src/cli/dashboard/model.ts +36 -22
- package/packages/core/src/cli/dashboard/poll.ts +1 -1
- package/packages/core/src/cli/dispatch-frontend.ts +24 -67
- package/packages/core/src/cli/dispatch-process.ts +14 -7
- package/packages/core/src/cli/dispatch-repository-feed.ts +196 -0
- package/packages/core/src/cli/dispatch.ts +304 -82
- package/packages/core/src/cli/dispatcher-liveness.ts +26 -19
- package/packages/core/src/cli/harvest.ts +84 -42
- package/packages/core/src/cli/help.ts +14 -8
- package/packages/core/src/cli/historical-build-state.ts +27 -0
- package/packages/core/src/cli/installation.ts +67 -13
- package/packages/core/src/cli/main.ts +23 -5
- package/packages/core/src/cli/mcp.ts +2 -1
- package/packages/core/src/cli/pr-attachments.ts +10 -13
- package/packages/core/src/cli/pr-summary-publication.ts +27 -60
- package/packages/core/src/cli/pr-summary.ts +43 -13
- package/packages/core/src/cli/publication-settlement.ts +10 -20
- package/packages/core/src/cli/repository-status.ts +38 -2
- package/packages/core/src/cli/self-update.ts +5 -1
- package/packages/core/src/cli/status.ts +62 -32
- package/packages/core/src/cli/terminals.ts +25 -142
- package/packages/core/src/cli/ticket-assets.ts +7 -2
- package/packages/core/src/cli/ticket-editor.ts +82 -0
- package/packages/core/src/cli/ticket.ts +166 -36
- package/packages/core/src/cli/wait.ts +91 -84
- package/packages/core/src/cli/watch.ts +60 -97
- package/packages/core/src/config/pipeline-source.ts +4 -3
- package/packages/core/src/config/schema.ts +35 -3
- package/packages/core/src/connection.ts +3 -3
- package/packages/core/src/events/catalog.ts +3 -2
- package/packages/core/src/events/payloads.ts +6 -0
- package/packages/core/src/events/repository.ts +7 -3
- package/packages/core/src/integration/harness.ts +38 -4
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +2444 -9
- package/packages/core/src/kernel/auto-merge.ts +19 -3
- package/packages/core/src/kernel/dispatcher-liveness.ts +10 -4
- package/packages/core/src/kernel/generators/build-log.ts +1 -1
- package/packages/core/src/kernel/generators/repository-journals.ts +14 -3
- package/packages/core/src/kernel/harvest.ts +25 -23
- package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
- package/packages/core/src/kernel/log-index-value.ts +38 -0
- package/packages/core/src/kernel/read-facts.ts +637 -0
- package/packages/core/src/kernel/reducer-registry.ts +139 -3
- package/packages/core/src/kernel/repository-attention.ts +179 -0
- package/packages/core/src/kernel/service-facts.ts +135 -0
- package/packages/core/src/kernel/terminal-guard.ts +133 -0
- package/packages/core/src/ontology.ts +1 -0
- package/packages/core/src/operator/annotations.ts +12 -0
- package/packages/core/src/operator/control.ts +5 -6
- package/packages/core/src/operator/index.ts +7 -0
- package/packages/core/src/operator/protocol.ts +19 -2
- package/packages/core/src/operator/query.ts +37 -20
- package/packages/core/src/operator/read-view.ts +4 -13
- package/packages/core/src/operator/registry.ts +61 -8
- package/packages/core/src/operator/requests.ts +2 -14
- package/packages/core/src/operator/sandbox.ts +10 -9
- package/packages/core/src/operator/ticket-source-backend.ts +17 -21
- package/packages/core/src/operator/tickets.ts +132 -22
- package/packages/core/src/plugin-sdk/index.ts +19 -0
- package/packages/core/src/plugins/runtimes.ts +5 -1
- package/packages/core/src/ports/runner/session-env.ts +2 -0
- package/packages/core/src/ports/tickets/contract.ts +66 -1
- package/packages/core/src/ports/tickets/create.ts +48 -12
- package/packages/core/src/ports/tickets/fake.ts +30 -1
- package/packages/core/src/ports/tickets/file-assets.ts +159 -0
- package/packages/core/src/ports/tickets/file-codec.ts +90 -0
- package/packages/core/src/ports/tickets/file.ts +114 -120
- package/packages/core/src/ports/tickets/grants.ts +47 -0
- package/packages/core/src/ports/tickets/hosted-index.ts +17 -0
- package/packages/core/src/ports/tickets/identity.ts +20 -0
- package/packages/core/src/ports/tickets/lifecycle.ts +18 -0
- package/packages/core/src/ports/tickets/linear.ts +74 -9
- package/packages/core/src/ports/tickets/migration.ts +336 -0
- package/packages/core/src/ports/tickets/operations.ts +38 -7
- package/packages/core/src/ports/tickets/portable-assets.ts +54 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +95 -0
- package/packages/core/src/ports/tickets/remote.ts +60 -3
- package/packages/core/src/ports/tickets/requested-id.ts +46 -0
- package/packages/core/src/ports/tickets/revision.ts +62 -0
- package/packages/core/src/ports/tickets/states.ts +61 -0
- package/packages/core/src/ports/types.ts +40 -3
- package/packages/core/src/ports/workspace/build-execution.ts +1 -0
- package/packages/core/src/ports/workspace/create.ts +5 -1
- package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +8 -4
- package/packages/core/src/processes/build-child.ts +6 -6
- package/packages/core/src/processes/build-runner.ts +62 -70
- package/packages/core/src/processes/dispatch-failure-budget.ts +5 -4
- package/packages/core/src/processes/dispatcher.ts +223 -205
- package/packages/core/src/processes/execution-no-progress.ts +176 -0
- package/packages/core/src/processes/harvest-child.ts +30 -4
- package/packages/core/src/processes/harvest-execution-state.ts +15 -9
- package/packages/core/src/processes/harvest-runner.ts +58 -74
- package/packages/core/src/processes/harvest.ts +72 -29
- package/packages/core/src/processes/infrastructure-failure-budget.ts +13 -15
- package/packages/core/src/processes/orchestrator-tick.ts +2 -2
- package/packages/core/src/processes/phase-failure-budget.ts +1 -0
- package/packages/core/src/processes/publication-loss.ts +35 -21
- package/packages/core/src/processes/repo-view.ts +229 -1099
- package/packages/core/src/processes/tick-publication.ts +110 -0
- package/packages/core/src/store/adapter.ts +23 -0
- package/packages/core/src/store/build-scope.ts +17 -0
- package/packages/core/src/store/contract.ts +283 -1
- package/packages/core/src/store/local/schema.ts +1 -0
- package/packages/core/src/store/local/store.ts +180 -55
- package/packages/core/src/store/memory.ts +119 -29
- package/packages/core/src/store/phase-session.ts +17 -0
- package/packages/core/src/store/reduced-reads.ts +239 -0
- package/packages/core/src/store/reducer-value-shapes.json +14056 -0
- package/packages/core/src/store/reducer-value-validation.ts +96 -0
- package/packages/core/src/store/reducer-values.ts +145 -0
- package/packages/core/src/store/remote/client.ts +41 -0
- package/packages/core/src/store/remote/compatibility.ts +2 -0
- package/packages/core/src/store/remote/index.ts +19 -0
- package/packages/core/src/store/remote/protocol.ts +16 -0
- package/packages/core/src/store/remote/version.ts +3 -1
- package/packages/core/src/store/repo-state-events.ts +1 -1
- package/packages/core/src/store/repo-tick.ts +72 -0
- package/packages/core/src/store/session-handle.ts +19 -0
- package/packages/core/src/store/snapshots.ts +10 -0
- package/packages/core/src/store/ticket-asset-snapshots.ts +192 -0
- package/packages/core/src/store/ticket-assets.ts +86 -8
- package/packages/core/src/store/types.ts +40 -3
- package/packages/core/src/testing/dispatch-read-budget.ts +215 -0
- package/packages/core/src/testing/index.ts +8 -0
- package/packages/core/src/testing/reduced-read-budget.ts +462 -0
- package/skills/guide/SKILL.md +97 -12
- package/skills/guide/references/operator-api.md +8 -1
- package/skills/guide/references/remote-store-protocol.md +94 -2
- package/skills/guide/references/remote-ticket-protocol.md +151 -9
- package/skills/guide/references/setup.md +33 -7
- package/skills/guide/references/ticket-migration.md +64 -0
- package/skills/tickets/SKILL.md +71 -0
- package/templates/hosted-service/package.json +2 -2
|
@@ -535,7 +535,8 @@ export type PolicyConfig = z.infer<typeof policySchema>
|
|
|
535
535
|
// API key comes from the LINEAR_API_KEY environment variable, never from this
|
|
536
536
|
// file.
|
|
537
537
|
|
|
538
|
-
export const
|
|
538
|
+
export const ticketsInputSchema = z.strictObject({
|
|
539
|
+
url: z.string().optional(),
|
|
539
540
|
/** Builtin or plugin-registered TicketSource name. Registry membership is
|
|
540
541
|
* validated after configured plugins load. */
|
|
541
542
|
source: z
|
|
@@ -569,7 +570,8 @@ export const ticketsSchema = z.strictObject({
|
|
|
569
570
|
.refine(
|
|
570
571
|
(s) => s.trim().length > 0,
|
|
571
572
|
'[tickets].readyState must not be blank — name the one workflow state a ticket must sit in to be dispatched (e.g. "ready").',
|
|
572
|
-
)
|
|
573
|
+
)
|
|
574
|
+
.optional(),
|
|
573
575
|
/**
|
|
574
576
|
* Narrows readiness to tickets assigned to one Linear user — a third
|
|
575
577
|
* conjunctive gate beside readyState and readyLabels. `"me"` is the owner of
|
|
@@ -592,7 +594,7 @@ export const ticketsSchema = z.strictObject({
|
|
|
592
594
|
.min(1)
|
|
593
595
|
.refine((value) => value.trim().length > 0, 'state must be nonblank')
|
|
594
596
|
.optional(),
|
|
595
|
-
/** Hosted project completion state; defaults to
|
|
597
|
+
/** Hosted project completion state; defaults to done. */
|
|
596
598
|
doneState: z
|
|
597
599
|
.string()
|
|
598
600
|
.min(1)
|
|
@@ -634,6 +636,36 @@ export const ticketsSchema = z.strictObject({
|
|
|
634
636
|
* still tell a defaulted dir from an explicit one. */
|
|
635
637
|
dir: z.string().min(1).optional(),
|
|
636
638
|
})
|
|
639
|
+
export const ticketsSchema = ticketsInputSchema
|
|
640
|
+
.superRefine((tickets, ctx) => {
|
|
641
|
+
if (tickets.source === 'hosted' && tickets.url !== undefined) {
|
|
642
|
+
try {
|
|
643
|
+
normalizeServiceURL(tickets.url, 'tickets.url')
|
|
644
|
+
} catch (error) {
|
|
645
|
+
ctx.addIssue({ code: 'custom', path: ['url'], message: (error as Error).message })
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (tickets.source !== 'hosted' && tickets.readyState === undefined)
|
|
649
|
+
ctx.addIssue({
|
|
650
|
+
code: 'custom',
|
|
651
|
+
path: ['readyState'],
|
|
652
|
+
message:
|
|
653
|
+
'[tickets].readyState is required — name the one workflow state a ticket must sit in to be dispatched (e.g. "ready").',
|
|
654
|
+
})
|
|
655
|
+
if (['file', 'linear'].includes(tickets.source) && tickets.url !== undefined)
|
|
656
|
+
ctx.addIssue({
|
|
657
|
+
code: 'custom',
|
|
658
|
+
path: ['url'],
|
|
659
|
+
message: '[tickets].url applies only to source = "hosted"',
|
|
660
|
+
})
|
|
661
|
+
})
|
|
662
|
+
.transform((tickets) => ({
|
|
663
|
+
...tickets,
|
|
664
|
+
readyState: tickets.readyState ?? 'ready',
|
|
665
|
+
...(tickets.source === 'hosted' && tickets.url !== undefined
|
|
666
|
+
? { url: normalizeServiceURL(tickets.url, 'tickets.url') }
|
|
667
|
+
: {}),
|
|
668
|
+
}))
|
|
637
669
|
export type TicketsConfig = z.infer<typeof ticketsSchema>
|
|
638
670
|
|
|
639
671
|
// ── [orchestrator] ──────────────────────────────────────────────────────────
|
|
@@ -19,12 +19,12 @@ export interface ConnectionStarted extends ConnectionOutcome {
|
|
|
19
19
|
interval: number
|
|
20
20
|
expiresAt: string
|
|
21
21
|
}
|
|
22
|
-
export function normalizeServiceURL(raw: string): string {
|
|
22
|
+
export function normalizeServiceURL(raw: string, setting = 'store.url'): string {
|
|
23
23
|
let url: URL
|
|
24
24
|
try {
|
|
25
25
|
url = new URL(raw)
|
|
26
26
|
} catch {
|
|
27
|
-
throw new Error(
|
|
27
|
+
throw new Error(`${setting} must be an absolute HTTPS service URL`)
|
|
28
28
|
}
|
|
29
29
|
if (
|
|
30
30
|
!raw.trim() ||
|
|
@@ -39,7 +39,7 @@ export function normalizeServiceURL(raw: string): string {
|
|
|
39
39
|
)
|
|
40
40
|
) {
|
|
41
41
|
throw new Error(
|
|
42
|
-
|
|
42
|
+
`${setting} must be HTTPS (HTTP is allowed on loopback), without credentials, query or fragment`,
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
return url.href.replace(/\/+$/, '')
|
|
@@ -185,10 +185,11 @@ export function validateEventWrite(input: {
|
|
|
185
185
|
actor.kind === 'dispatcher' &&
|
|
186
186
|
(escalation.source !== 'policy' ||
|
|
187
187
|
(escalation.policyCause !== 'infrastructure-failure-limit' &&
|
|
188
|
-
escalation.policyCause !== 'dispatch-failure-limit'
|
|
188
|
+
escalation.policyCause !== 'dispatch-failure-limit' &&
|
|
189
|
+
escalation.policyCause !== 'execution-no-progress'))
|
|
189
190
|
) {
|
|
190
191
|
throw new EventValidationError(
|
|
191
|
-
'invalid dispatcher escalation: only policy/infrastructure-failure-limit or policy/dispatch-failure-limit may be raised outside the kernel',
|
|
192
|
+
'invalid dispatcher escalation: only policy/infrastructure-failure-limit or policy/dispatch-failure-limit or policy/execution-no-progress may be raised outside the kernel',
|
|
192
193
|
)
|
|
193
194
|
}
|
|
194
195
|
if (escalation.source === 'policy' && escalation.policyCause === undefined) {
|
|
@@ -196,6 +196,12 @@ const finalizeStepCompletedPayloadSchema = z.discriminatedUnion('ok', [
|
|
|
196
196
|
/** One ticket asset revision a build froze (SPEC §6.3): the identity and
|
|
197
197
|
* shape of what `listTicketAssets` returned at the pinning moment. */
|
|
198
198
|
export const pinnedAssetSchema = z.strictObject({
|
|
199
|
+
snapshot: z
|
|
200
|
+
.strictObject({
|
|
201
|
+
kind: z.string().regex(/^ticket-snapshot-[a-f0-9-]+$/),
|
|
202
|
+
revision: z.number().int().nonnegative(),
|
|
203
|
+
})
|
|
204
|
+
.optional(),
|
|
199
205
|
kind: z.string().min(1),
|
|
200
206
|
name: z.string().min(1),
|
|
201
207
|
revision: z.number().int().nonnegative(),
|
|
@@ -411,9 +411,12 @@ export const dispatcherStatusEventPayloadSchemas = {
|
|
|
411
411
|
run: dispatchRun,
|
|
412
412
|
error: z.string().min(1),
|
|
413
413
|
}),
|
|
414
|
-
/**
|
|
415
|
-
*
|
|
416
|
-
'dispatcher.tick-started': z.strictObject({
|
|
414
|
+
/** Recorded boundaries of meaningful turns. The repository tick observation
|
|
415
|
+
* protects active work from forced stop while these facts are buffered. */
|
|
416
|
+
'dispatcher.tick-started': z.strictObject({
|
|
417
|
+
run: dispatchRun,
|
|
418
|
+
startedAt: z.iso.datetime().optional(),
|
|
419
|
+
}),
|
|
417
420
|
'dispatcher.tick-completed': z.strictObject({
|
|
418
421
|
run: dispatchRun,
|
|
419
422
|
queued: z.number().int().nonnegative(),
|
|
@@ -421,6 +424,7 @@ export const dispatcherStatusEventPayloadSchemas = {
|
|
|
421
424
|
* dispatcher child transported a display sample in its tick fact. */
|
|
422
425
|
observations: z.number().int().nonnegative().optional(),
|
|
423
426
|
counters: tickCountersSchema,
|
|
427
|
+
blockedDiagnostics: boundedDiagnostics.optional(),
|
|
424
428
|
janitorDiagnostics: boundedDiagnostics,
|
|
425
429
|
ticketDiagnostics: boundedDiagnostics,
|
|
426
430
|
creationDiagnostics: boundedDiagnostics.optional(),
|
|
@@ -57,7 +57,13 @@ import type {
|
|
|
57
57
|
import { GitWorktreeProvider, spawnExec } from '../ports/workspace/git-worktree'
|
|
58
58
|
import { InProcessBuildExecution } from '../ports/workspace/in-process-build-execution'
|
|
59
59
|
import { BuildRunner, LeaseHeldError, SetupFailureError } from '../processes/build-runner'
|
|
60
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
diagnosticArtifact,
|
|
62
|
+
BUILD_EFFECTIVE_CONFIG_ARTIFACT,
|
|
63
|
+
effectiveBuildConfigContent,
|
|
64
|
+
parseEffectiveBuildConfig,
|
|
65
|
+
} from '../processes/build-execution-state'
|
|
66
|
+
import { composeBuildConfig } from '../config/live'
|
|
61
67
|
import { Dispatcher, type LaunchRunnerResult } from '../processes/dispatcher'
|
|
62
68
|
import {
|
|
63
69
|
getHarvestStatus,
|
|
@@ -120,6 +126,7 @@ export const CONFORMING_BODY = [
|
|
|
120
126
|
export function readyTicket(id: string, over: Partial<Omit<Ticket, 'ref'>> = {}): Ticket {
|
|
121
127
|
const title = over.title ?? 'Add rate limiting'
|
|
122
128
|
return {
|
|
129
|
+
revision: 'fixture',
|
|
123
130
|
ref: { source: 'fake', id, title },
|
|
124
131
|
title,
|
|
125
132
|
body: over.body ?? CONFORMING_BODY,
|
|
@@ -330,7 +337,7 @@ async function repositoryReading(store: BuildStore, repo: string) {
|
|
|
330
337
|
builds[slug] = { facts: await plainFacts(view, slug), state: await view.buildState(slug) }
|
|
331
338
|
}
|
|
332
339
|
return {
|
|
333
|
-
journal:
|
|
340
|
+
journal: (await store.getRepo(repo)) === null ? [] : await store.getRepoStateEvents(repo),
|
|
334
341
|
digests: [...(await view.getRepoBuildDigests(repo)).entries()].sort(([a], [b]) =>
|
|
335
342
|
a.localeCompare(b),
|
|
336
343
|
),
|
|
@@ -353,7 +360,6 @@ export async function assertSnapshotEquivalence(store: BuildStore, repo: string)
|
|
|
353
360
|
if (canonical(got) !== canonical(want)) {
|
|
354
361
|
throw new Error(`snapshot-restored reading differs from full replay (${round})`)
|
|
355
362
|
}
|
|
356
|
-
await restoredView.persistSnapshots({ force: true })
|
|
357
363
|
}
|
|
358
364
|
// The operator routes derive from the same view: each response with
|
|
359
365
|
// snapshots present equals the response with every snapshot deleted.
|
|
@@ -398,6 +404,10 @@ export async function makeHarness(opts: {
|
|
|
398
404
|
* CONFIG_TOML, so every existing scenario is untouched; a scenario proving
|
|
399
405
|
* two-axis routing (§9) supplies its own `[roles.default]`/phase roles. */
|
|
400
406
|
configToml?: string
|
|
407
|
+
/** Force a routing disagreement while retaining the runner's branch config. */
|
|
408
|
+
dispatcherConfig?: (config: Config) => Config
|
|
409
|
+
pinnedConfig?: boolean
|
|
410
|
+
getConfig?: () => Config
|
|
401
411
|
/** Forge gate existence is independent from current PR mergeability. */
|
|
402
412
|
gatePresence?: 'present' | 'absent'
|
|
403
413
|
/** Optional adapter-level runtime composition for focused integration tests.
|
|
@@ -602,6 +612,20 @@ export async function makeHarness(opts: {
|
|
|
602
612
|
runner: new BuildRunner({
|
|
603
613
|
store: activeStore.scopeBuild(slug),
|
|
604
614
|
config,
|
|
615
|
+
...(opts.pinnedConfig
|
|
616
|
+
? {
|
|
617
|
+
getConfig: async () => {
|
|
618
|
+
const artifact = await activeStore.getArtifact(
|
|
619
|
+
slug,
|
|
620
|
+
BUILD_EFFECTIVE_CONFIG_ARTIFACT,
|
|
621
|
+
)
|
|
622
|
+
return composeBuildConfig(
|
|
623
|
+
artifact === null ? config : parseEffectiveBuildConfig(artifact),
|
|
624
|
+
opts.getConfig?.() ?? config,
|
|
625
|
+
)
|
|
626
|
+
},
|
|
627
|
+
}
|
|
628
|
+
: {}),
|
|
605
629
|
// Two-axis registry (§9): every runtime is backed by the SAME scripted
|
|
606
630
|
// runner instance, so the `s_1…s_N` session numbering scenarios rely on
|
|
607
631
|
// is preserved regardless of which runtime a role selects. `scripted`
|
|
@@ -629,6 +653,15 @@ export async function makeHarness(opts: {
|
|
|
629
653
|
}
|
|
630
654
|
}
|
|
631
655
|
const launchRunner = async (slug: string): Promise<LaunchRunnerResult> => {
|
|
656
|
+
if (
|
|
657
|
+
opts.pinnedConfig &&
|
|
658
|
+
(await activeStore.getArtifact(slug, BUILD_EFFECTIVE_CONFIG_ARTIFACT)) === null
|
|
659
|
+
) {
|
|
660
|
+
await activeStore.putArtifact(slug, {
|
|
661
|
+
kind: BUILD_EFFECTIVE_CONFIG_ARTIFACT,
|
|
662
|
+
content: effectiveBuildConfigContent(config),
|
|
663
|
+
})
|
|
664
|
+
}
|
|
632
665
|
launched.push({ slug, ...(await makeRunner(slug)) })
|
|
633
666
|
return 'scheduled'
|
|
634
667
|
}
|
|
@@ -638,10 +671,11 @@ export async function makeHarness(opts: {
|
|
|
638
671
|
tickets: ticketSource,
|
|
639
672
|
workspaces,
|
|
640
673
|
forge: selectedForge,
|
|
641
|
-
config,
|
|
674
|
+
config: opts.dispatcherConfig?.(structuredClone(config)) ?? config,
|
|
642
675
|
repo: origin,
|
|
643
676
|
exec: spawnExec,
|
|
644
677
|
launchRunner,
|
|
678
|
+
...(opts.getConfig ? { getConfig: opts.getConfig } : {}),
|
|
645
679
|
ids,
|
|
646
680
|
clock,
|
|
647
681
|
})
|