@vzn/vx 0.0.645 → 0.0.648

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.
Files changed (186) hide show
  1. package/README.md +39 -50
  2. package/launcher.cjs +9 -2
  3. package/launcher.sh +2 -2
  4. package/package.json +5 -5
  5. package/schemas/cache.json +5 -5
  6. package/schemas/error.json +1 -1
  7. package/schemas/info.json +16 -2
  8. package/schemas/last.json +127 -5
  9. package/schemas/plan.json +28 -2
  10. package/schemas/show.json +10 -1
  11. package/skills/vx/SKILL.md +19 -3
  12. package/src/bin.ts +54 -13
  13. package/src/cache/archive.ts +324 -23
  14. package/src/cache/cache.ts +330 -65
  15. package/src/cache/git-inputs.ts +274 -97
  16. package/src/cache/index.ts +7 -2
  17. package/src/cache/inputs.ts +515 -83
  18. package/src/cache/key-fold.ts +15 -6
  19. package/src/cache/layer.ts +74 -11
  20. package/src/cache/layered-cache.ts +154 -59
  21. package/src/cache/linked-inputs.ts +1 -1
  22. package/src/cache/output-index.ts +44 -14
  23. package/src/cache/plugin-cache.ts +49 -23
  24. package/src/cache/remote-hits.ts +72 -0
  25. package/src/cache/remote-lane.ts +100 -15
  26. package/src/cache/run-history.ts +77 -23
  27. package/src/cache/run-store.ts +11 -10
  28. package/src/cache/schema.ts +42 -6
  29. package/src/cache/tar-stream.ts +5 -0
  30. package/src/cache/umask.ts +1 -2
  31. package/src/cli/cache.ts +58 -27
  32. package/src/cli/completions.ts +12 -5
  33. package/src/cli/core-alias.ts +5 -2
  34. package/src/cli/docs.ts +42 -16
  35. package/src/cli/help.ts +62 -25
  36. package/src/cli/index.ts +79 -45
  37. package/src/cli/info.ts +12 -6
  38. package/src/cli/init.ts +222 -34
  39. package/src/cli/last.ts +443 -47
  40. package/src/cli/lock.ts +197 -52
  41. package/src/cli/plan-format.ts +91 -49
  42. package/src/cli/plugin-commands.ts +160 -11
  43. package/src/cli/plugin-templates.ts +13 -13
  44. package/src/cli/registry.ts +209 -0
  45. package/src/cli/run.ts +111 -41
  46. package/src/cli/select.ts +148 -49
  47. package/src/cli/show.ts +59 -16
  48. package/src/cli/task-verb.ts +16 -6
  49. package/src/cli/upgrade.ts +155 -54
  50. package/src/cli/watch-cycle.ts +10 -5
  51. package/src/cli/watch-filter.ts +71 -3
  52. package/src/cli/watch-fs.ts +22 -6
  53. package/src/cli/watch-judge.ts +48 -6
  54. package/src/cli/watch.ts +163 -42
  55. package/src/cli/why.ts +211 -73
  56. package/src/cli/workspace-config.ts +54 -15
  57. package/src/config.ts +13 -1
  58. package/src/exec/env.ts +7 -2
  59. package/src/exec/executor.ts +262 -15
  60. package/src/exec/index.ts +8 -2
  61. package/src/exec/kill-tree.ts +15 -0
  62. package/src/exec/local-executor.ts +1 -1
  63. package/src/exec/proc-sample.ts +70 -6
  64. package/src/exec/runner.ts +256 -53
  65. package/src/exec/sandbox-binds.ts +7 -3
  66. package/src/exec/sandbox-paths.ts +1 -1
  67. package/src/exec/sandbox-runtime.ts +222 -28
  68. package/src/exec/sandbox-violations.ts +52 -20
  69. package/src/graph/critical-path.ts +79 -0
  70. package/src/graph/default-build.ts +19 -0
  71. package/src/graph/definition.ts +38 -0
  72. package/src/graph/folds.ts +38 -5
  73. package/src/graph/index.ts +4 -1
  74. package/src/graph/priorities.ts +74 -8
  75. package/src/graph/scheduler.ts +326 -65
  76. package/src/graph/task-graph.ts +228 -35
  77. package/src/handoff.ts +97 -9
  78. package/src/history/failure-mode.ts +6 -3
  79. package/src/history/history.ts +268 -50
  80. package/src/history/index.ts +2 -0
  81. package/src/history/metrics.ts +142 -45
  82. package/src/index.ts +42 -10
  83. package/src/orchestrator/admission.ts +10 -6
  84. package/src/orchestrator/affected-tasks.ts +11 -6
  85. package/src/orchestrator/deferred-outputs.ts +2 -0
  86. package/src/orchestrator/doctor.ts +134 -42
  87. package/src/orchestrator/execute-args.ts +6 -0
  88. package/src/orchestrator/execute-task.ts +155 -42
  89. package/src/orchestrator/fingerprint-watch.ts +1 -1
  90. package/src/orchestrator/graph-memo.ts +140 -0
  91. package/src/orchestrator/graph-stage.ts +18 -1
  92. package/src/orchestrator/held-servers.ts +86 -0
  93. package/src/orchestrator/hit-restore.ts +227 -122
  94. package/src/orchestrator/index.ts +20 -3
  95. package/src/orchestrator/local-floor.ts +198 -0
  96. package/src/orchestrator/local-shortcircuit.ts +51 -8
  97. package/src/orchestrator/lockfile-claim.ts +96 -16
  98. package/src/orchestrator/miss-save.ts +52 -12
  99. package/src/orchestrator/options.ts +8 -6
  100. package/src/orchestrator/output-host.ts +43 -14
  101. package/src/orchestrator/output-log.ts +90 -10
  102. package/src/orchestrator/persistent-task.ts +97 -28
  103. package/src/orchestrator/persistent.ts +16 -4
  104. package/src/orchestrator/placement.ts +257 -14
  105. package/src/orchestrator/plan.ts +64 -7
  106. package/src/orchestrator/plugin-host.ts +230 -40
  107. package/src/orchestrator/plugin.ts +150 -37
  108. package/src/orchestrator/prepare.ts +178 -32
  109. package/src/orchestrator/projects.ts +65 -40
  110. package/src/orchestrator/remote-prefetch.ts +119 -25
  111. package/src/orchestrator/run-artifacts.ts +95 -32
  112. package/src/orchestrator/run-context.ts +39 -41
  113. package/src/orchestrator/run-failures.ts +65 -23
  114. package/src/orchestrator/run-id.ts +6 -3
  115. package/src/orchestrator/run-keys.ts +300 -0
  116. package/src/orchestrator/run-live.ts +294 -0
  117. package/src/orchestrator/run-lock.ts +13 -7
  118. package/src/orchestrator/run-records.ts +35 -23
  119. package/src/orchestrator/run-report.ts +3 -3
  120. package/src/orchestrator/run-steps.ts +58 -22
  121. package/src/orchestrator/run-stop.ts +22 -5
  122. package/src/orchestrator/run.ts +409 -79
  123. package/src/orchestrator/sandbox-fix.ts +7 -5
  124. package/src/orchestrator/sandbox-request.ts +78 -22
  125. package/src/orchestrator/shell-verdict.ts +5 -5
  126. package/src/orchestrator/signals.ts +26 -1
  127. package/src/orchestrator/stable-keys.ts +92 -8
  128. package/src/orchestrator/steps.ts +2 -2
  129. package/src/orchestrator/task-artifacts.ts +206 -0
  130. package/src/orchestrator/task-hash.ts +89 -14
  131. package/src/orchestrator/task-log-buffer.ts +72 -36
  132. package/src/orchestrator/task-process.ts +22 -0
  133. package/src/orchestrator/telemetry-host.ts +24 -3
  134. package/src/orchestrator/telemetry.ts +113 -38
  135. package/src/orchestrator/upstream.ts +15 -0
  136. package/src/report/events.ts +32 -13
  137. package/src/report/forecast.ts +36 -35
  138. package/src/report/framed-output.ts +173 -46
  139. package/src/report/index.ts +1 -0
  140. package/src/report/logger.ts +217 -186
  141. package/src/report/path-links.ts +12 -4
  142. package/src/report/plain-output.ts +13 -5
  143. package/src/report/status-line.ts +94 -46
  144. package/src/report/summary.ts +174 -38
  145. package/src/report/terminal-signals.ts +13 -2
  146. package/src/settings.ts +14 -6
  147. package/src/util/bun-version.ts +4 -1
  148. package/src/util/colors.ts +20 -3
  149. package/src/util/config-frame.ts +41 -4
  150. package/src/{graph → util}/dependency-spec.ts +22 -13
  151. package/src/util/edit-distance.ts +13 -1
  152. package/src/util/env-flag.ts +9 -0
  153. package/src/util/errors.ts +59 -3
  154. package/src/util/essential-env.ts +57 -0
  155. package/src/util/git-ignored.ts +1 -1
  156. package/src/util/hash.ts +35 -0
  157. package/src/util/hide-env.ts +45 -5
  158. package/src/util/index.ts +22 -5
  159. package/src/util/num.ts +8 -0
  160. package/src/util/paths.ts +121 -21
  161. package/src/util/report.ts +6 -1
  162. package/src/util/secret-mask.ts +62 -1
  163. package/src/util/secrets.ts +7 -2
  164. package/src/util/size.ts +11 -3
  165. package/src/util/tail.ts +6 -1
  166. package/src/util/timing.ts +13 -1
  167. package/src/util/ui.ts +65 -4
  168. package/src/util/which.ts +1 -1
  169. package/src/util/write-file.ts +67 -0
  170. package/src/workspace/affected.ts +710 -107
  171. package/src/workspace/bracket-literal.ts +84 -0
  172. package/src/workspace/canonical-dir.ts +37 -0
  173. package/src/workspace/config-bytecode.ts +282 -0
  174. package/src/workspace/config-eval.ts +29 -8
  175. package/src/workspace/config-imports.ts +199 -58
  176. package/src/workspace/config-schema.ts +242 -39
  177. package/src/workspace/filter.ts +37 -8
  178. package/src/workspace/index.ts +11 -2
  179. package/src/workspace/json-data.ts +3 -4
  180. package/src/workspace/lockfile.ts +57 -8
  181. package/src/workspace/migration.ts +72 -49
  182. package/src/workspace/package-graph.ts +5 -4
  183. package/src/workspace/project-loader.ts +314 -47
  184. package/src/workspace/repo-id.ts +1 -1
  185. package/src/workspace/workspace.ts +176 -33
  186. package/src/cli/foreign-flags.ts +0 -727
@@ -7,7 +7,7 @@
7
7
  // tests/run-records.test.ts holds to this one.
8
8
 
9
9
  import type { InvocationRecord, RunRecord } from '../cache/index.js'
10
- import { isGroupTask, type TaskOutcome } from '../graph/index.js'
10
+ import { isGroupTask, taskDefinition, type TaskOutcome } from '../graph/index.js'
11
11
  import { VERSION } from '../version.js'
12
12
  import type { CiContext, GitContext, HostContext } from './run-context.js'
13
13
  import { taskTelemetryOf, type TaskTelemetry } from './telemetry.js'
@@ -83,18 +83,15 @@ export function assembleRunRecords(input: RunRecordsInput): RunRecords {
83
83
  if (input.withTelemetry) telemetryTasks.push(taskTelemetryOf(o))
84
84
  const blockedBy = o.blockedBy ?? input.tainted?.get(o.node.id)
85
85
  const remoteError = remoteErrorOf(input.remoteTraffic, o.hash)
86
- runs.push({
87
- ...(o.hash !== undefined ? { hash: o.hash } : {}),
86
+ // Built field by field: a spread per optional field was ~2 µs of each
87
+ // of a 1,200-hit warm run's rows.
88
+ const hit = isCacheHit(o.status)
89
+ const r: RunRecord = {
88
90
  project: o.node.projectName,
89
91
  task: o.node.taskName,
90
92
  status: o.status,
91
93
  exitCode: o.exitCode,
92
94
  durationMs: o.durationMs,
93
- // A run forwards its args to the tasks it was asked for, never to a
94
- // dependency: the row says which ran with them.
95
- ...(input.forwardArgs !== undefined && input.forwardArgs.length > 0 && o.node.requested
96
- ? { forwardArgs: input.forwardArgs }
97
- : {}),
98
95
  // Anchor to the REAL per-task wall-clock window: run-start wall time +
99
96
  // the task's ns offset (captured for hits and executed tasks alike).
100
97
  // The `end - duration` fallback applies to outcomes without an offset —
@@ -111,22 +108,37 @@ export function assembleRunRecords(input: RunRecordsInput): RunRecords {
111
108
  ? startedAtMs + Math.round(Number(o.wallclockEndNs) / 1e6)
112
109
  : endedAtMs,
113
110
  runId,
114
- ...(o.cpuMs !== undefined ? { cpuMs: o.cpuMs } : {}),
115
- ...(o.peakRssBytes !== undefined ? { peakRssBytes: o.peakRssBytes } : {}),
116
- ...(o.wallclockStartNs !== undefined ? { wallclockStartNs: o.wallclockStartNs } : {}),
117
- ...(o.wallclockEndNs !== undefined ? { wallclockEndNs: o.wallclockEndNs } : {}),
118
- cacheHit: isCacheHit(o.status),
119
- ...(isCacheHit(o.status) ? { restored: o.restored === true } : {}),
120
- ...(o.attempts !== undefined ? { attempts: o.attempts } : {}),
111
+ cacheHit: hit,
121
112
  cached: o.node.config.cache !== undefined,
122
- // A task that ran behind a failure names it too, so the flakiness
123
- // projection counts its outcome on no key (failure-mode.ts).
124
- ...(blockedBy !== undefined ? { blockedBy } : {}),
125
- ...(o.timedOut === true ? { timedOut: true as const } : {}),
126
- ...(o.sandboxViolations !== undefined ? { sandboxViolations: o.sandboxViolations } : {}),
127
- ...(o.notReady !== undefined ? { notReady: o.notReady } : {}),
128
- ...(remoteError !== undefined ? { remoteError } : {}),
129
- })
113
+ }
114
+ if (o.hash !== undefined) r.hash = o.hash
115
+ // A run forwards its args to the tasks it was asked for, never to a
116
+ // dependency: the row says which ran with them.
117
+ if (input.forwardArgs !== undefined && input.forwardArgs.length > 0 && o.node.requested)
118
+ r.forwardArgs = input.forwardArgs
119
+ if (o.cpuMs !== undefined) r.cpuMs = o.cpuMs
120
+ if (o.peakRssBytes !== undefined) r.peakRssBytes = o.peakRssBytes
121
+ if (o.peakCores !== undefined) r.peakCores = o.peakCores
122
+ if (o.avgRssBytes !== undefined) r.avgRssBytes = o.avgRssBytes
123
+ if (o.where !== undefined) r.placement = o.where
124
+ if (o.whereCores !== undefined) r.placementCores = o.whereCores
125
+ if (o.whereMemoryBytes !== undefined) r.placementMemoryBytes = o.whereMemoryBytes
126
+ if (o.cpuShare !== undefined) r.cpuShare = o.cpuShare
127
+ if (o.wallclockStartNs !== undefined) r.wallclockStartNs = o.wallclockStartNs
128
+ if (o.wallclockEndNs !== undefined) r.wallclockEndNs = o.wallclockEndNs
129
+ if (hit) r.restored = o.restored === true
130
+ if (o.attempts !== undefined) r.attempts = o.attempts
131
+ // A task that ran behind a failure names it too, so the flakiness
132
+ // projection counts its outcome on no key (failure-mode.ts).
133
+ if (blockedBy !== undefined) r.blockedBy = blockedBy
134
+ if (o.timedOut === true) r.timedOut = true
135
+ if (o.sandboxViolations !== undefined) r.sandboxViolations = o.sandboxViolations
136
+ if (o.notReady !== undefined) r.notReady = o.notReady
137
+ if (remoteError !== undefined) r.remoteError = remoteError
138
+ if (o.keylessBy !== undefined) r.keylessBy = o.keylessBy
139
+ const def = taskDefinition(o.node)
140
+ if (def !== undefined) r.def = def
141
+ runs.push(r)
130
142
  if (o.status === 'failed') failedCount++
131
143
  if (o.status === 'cache-hit') hitLocalCount++
132
144
  if (o.status === 'cache-hit-remote') hitRemoteCount++
@@ -1,9 +1,9 @@
1
1
  // The report every plugin's teardown reads, and the run's own bytes kept
2
- // in a remote that takes its writes (owner 2026-10-15): a failed task's
2
+ // in a remote that takes its writes (2026-10-15): a failed task's
3
3
  // log and the files its outputs left (`cache.upload.failures`), and the
4
- // log of a task that saved no entry, on every run (M, 2026-10-16: "Always
4
+ // log of a task that saved no entry, on every run (2026-10-16: "Always
5
5
  // keep"). Each task's bytes leave when it ends, off its path, like its
6
- // entry's upload (owner 2026-10-16); the run only waits for them where it
6
+ // entry's upload (2026-10-16); the run only waits for them where it
7
7
  // waits for every upload, and the footer counts them as the task's remote
8
8
  // traffic.
9
9
 
@@ -1,14 +1,18 @@
1
- // The steps a run did not wait on (`vx:prefetch`), for `vx last` (M,
2
- // 2026-10-16): they never show in a run's output. One file per run that
3
- // had one, `<cacheDir>/steps/<runId>.json`, the newest 50 kept, as
1
+ // What a run said that its rows do not hold, for `vx last`: the steps it
2
+ // did not wait on (`vx:prefetch`, M 2026-10-16), which never show in a
3
+ // run's output, and its warnings, which scroll past on a terminal and are
4
+ // gone (an agent reading the run afterwards saw only the footer's count,
5
+ // 2026-10-28). One file per run that had any, `<cacheDir>/steps/<runId>.json`
6
+ // and `<cacheDir>/warnings/<runId>.json`, the newest 50 of each kept, as
4
7
  // run-failures.ts keeps failed output: files, not cache.db, so the index
5
8
  // layout stays as it was. A run with none writes nothing.
6
9
 
7
- import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
10
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
8
11
  import path from 'node:path'
9
12
  import { isFsRefusal } from '../util/index.js'
13
+ import { trimKept } from './run-failures.js'
10
14
 
11
- /** Runs whose background steps are kept; run ids are UUIDv7, so names sort by age. */
15
+ /** Runs whose steps (or warnings) are kept; run ids are UUIDv7, so names sort by age. */
12
16
  const KEPT = 50
13
17
 
14
18
  /** One of vx's own steps that ran beside the tasks. */
@@ -18,40 +22,72 @@ export interface RunStep {
18
22
  ok: boolean
19
23
  }
20
24
 
21
- const dirOf = (cacheDir: string): string => path.join(cacheDir, 'steps')
25
+ type Kept = 'steps' | 'warnings'
22
26
 
23
- /** Keep `steps` for `runId`; a refused write costs only them. */
24
- export function writeRunSteps(
27
+ function keep(
25
28
  cacheDir: string,
29
+ kind: Kept,
26
30
  runId: string,
27
- steps: readonly RunStep[],
31
+ value: readonly unknown[],
28
32
  warn: (message: string) => void,
29
33
  ): void {
30
- if (steps.length === 0) return
34
+ if (value.length === 0) return
31
35
  try {
32
- const dir = dirOf(cacheDir)
36
+ const dir = path.join(cacheDir, kind)
33
37
  mkdirSync(dir, { recursive: true })
34
38
  const file = path.join(dir, `${runId}.json`)
35
39
  const tmp = `${file}.${process.pid}.tmp`
36
- writeFileSync(tmp, JSON.stringify(steps))
40
+ writeFileSync(tmp, JSON.stringify(value))
37
41
  renameSync(tmp, file)
38
- const names = readdirSync(dir)
39
- .filter((n) => n.endsWith('.json'))
40
- .sort()
41
- for (const n of names.slice(0, -KEPT)) rmSync(path.join(dir, n), { force: true })
42
+ trimKept(dir, KEPT)
42
43
  } catch (err) {
43
44
  if (!isFsRefusal(err)) throw err
44
- warn(`[vx] background steps not kept: ${err.message} — the verdict above stands`)
45
+ warn(
46
+ `[vx] ${kind === 'steps' ? 'background steps' : 'warnings'} not kept: ${err.message} — the verdict above stands`,
47
+ )
45
48
  }
46
49
  }
47
50
 
48
- /** The background steps `runId` kept; none when it had none or they are gone. */
49
- export function runSteps(cacheDir: string, runId: string): RunStep[] {
51
+ function kept<T>(cacheDir: string, kind: Kept, runId: string): T[] {
50
52
  try {
51
- return JSON.parse(
52
- readFileSync(path.join(dirOf(cacheDir), `${runId}.json`), 'utf8'),
53
- ) as RunStep[]
53
+ return JSON.parse(readFileSync(path.join(cacheDir, kind, `${runId}.json`), 'utf8')) as T[]
54
54
  } catch {
55
55
  return []
56
56
  }
57
57
  }
58
+
59
+ /** Keep `steps` for `runId`; a refused write costs only them. */
60
+ export function writeRunSteps(
61
+ cacheDir: string,
62
+ runId: string,
63
+ steps: readonly RunStep[],
64
+ warn: (message: string) => void,
65
+ ): void {
66
+ keep(cacheDir, 'steps', runId, steps, warn)
67
+ }
68
+
69
+ /** The background steps `runId` kept; none when it had none or they are gone. */
70
+ export function runSteps(cacheDir: string, runId: string): RunStep[] {
71
+ return kept<RunStep>(cacheDir, 'steps', runId)
72
+ }
73
+
74
+ /** Keep the run's warnings as plain text, colour stripped; a refused write costs only them. */
75
+ export function writeRunWarnings(
76
+ cacheDir: string,
77
+ runId: string,
78
+ warnings: readonly string[],
79
+ warn: (message: string) => void,
80
+ ): void {
81
+ keep(
82
+ cacheDir,
83
+ 'warnings',
84
+ runId,
85
+ warnings.map((w) => Bun.stripANSI(w)),
86
+ warn,
87
+ )
88
+ }
89
+
90
+ /** The warnings `runId` said; none when it said none or they are gone. */
91
+ export function runWarnings(cacheDir: string, runId: string): string[] {
92
+ return kept<string>(cacheDir, 'warnings', runId)
93
+ }
@@ -25,6 +25,14 @@ import {
25
25
 
26
26
  type Child = ReturnType<typeof Bun.spawn>
27
27
 
28
+ /**
29
+ * The abort reason that stops a run's own work and spares the servers it
30
+ * was handed (`RunOptions.keep`), which it hands back. `vx watch` stops a
31
+ * cycle waiting on a server that never becomes ready (WD-15); any other
32
+ * stop took the unchanged servers down with it, and they restarted.
33
+ */
34
+ export const CYCLE_INTERRUPT = 'vx:cycle-interrupt'
35
+
28
36
  export interface RunStop {
29
37
  readonly signal: AbortSignal
30
38
  readonly liveChildren: Set<Child>
@@ -33,8 +41,10 @@ export interface RunStop {
33
41
  * The process signal that stopped the run. A run a process signal
34
42
  * stopped records no history: until item 849 the handler exited before
35
43
  * any outcome landed, and a Ctrl-C'd run then read in `vx last` as
36
- * FAILED with 0 tasks (item 854). An embedder's abort is its own call
37
- * and keeps its record.
44
+ * FAILED with 0 tasks (item 854). So does an embedder's abort whose
45
+ * reason names one (`'SIGINT'`, `'SIGTERM'`, `'SIGHUP'`): the signal the
46
+ * embedder received, as the watch loop relays a Ctrl-C to its cycle. Any
47
+ * other abort is the embedder's own call and keeps its record.
38
48
  */
39
49
  readonly stoppedBy: StopSignal | undefined
40
50
  /**
@@ -71,6 +81,8 @@ export function runStop(args: {
71
81
  teardowns: number
72
82
  /** Called first when the run is stopped. */
73
83
  onStop: () => void
84
+ /** A server the run was handed: a `CYCLE_INTERRUPT` leaves it running. */
85
+ handed: (child: Child) => boolean
74
86
  }): RunStop {
75
87
  const { log, external } = args
76
88
  const liveChildren = new Set<Child>()
@@ -90,10 +102,14 @@ export function runStop(args: {
90
102
  }
91
103
  })()
92
104
  const controller = new AbortController()
93
- const forwardAbort = (): void => controller.abort(external?.reason)
105
+ let stoppedBy: StopSignal | undefined
106
+ const forwardAbort = (): void => {
107
+ const reason: unknown = external?.reason
108
+ if (reason === 'SIGINT' || reason === 'SIGTERM' || reason === 'SIGHUP') stoppedBy = reason
109
+ controller.abort(reason)
110
+ }
94
111
  if (external?.aborted === true) forwardAbort()
95
112
  else external?.addEventListener('abort', forwardAbort, { once: true })
96
- let stoppedBy: StopSignal | undefined
97
113
  let left = (): void => {}
98
114
  const runLeft = new Promise<void>((resolve) => {
99
115
  left = resolve
@@ -120,8 +136,9 @@ export function runStop(args: {
120
136
  const onAbort = (): void => {
121
137
  args.onStop()
122
138
  endedBeforeStop = new Set([...persistentRegistry.values()].filter(hasEnded))
139
+ const spare = controller.signal.reason === CYCLE_INTERRUPT ? args.handed : () => false
123
140
  aborting = terminateChildren(
124
- () => [...liveChildren, ...persistentRegistry.values()],
141
+ () => [...liveChildren, ...persistentRegistry.values()].filter((c) => !spare(c)),
125
142
  forwardedSignal(controller.signal.reason),
126
143
  )
127
144
  }