@vzn/vx 0.0.646 → 0.0.649
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 +29 -22
- package/launcher.cjs +9 -2
- package/launcher.sh +2 -2
- package/package.json +5 -5
- package/schemas/cache.json +5 -5
- package/schemas/error.json +1 -1
- package/schemas/info.json +16 -2
- package/schemas/last.json +112 -5
- package/schemas/plan.json +28 -2
- package/schemas/show.json +10 -1
- package/skills/vx/SKILL.md +18 -2
- package/src/bin.ts +53 -12
- package/src/cache/archive.ts +324 -23
- package/src/cache/cache.ts +268 -53
- package/src/cache/git-inputs.ts +219 -87
- package/src/cache/index.ts +3 -2
- package/src/cache/inputs.ts +460 -78
- package/src/cache/key-fold.ts +15 -6
- package/src/cache/layer.ts +49 -9
- package/src/cache/layered-cache.ts +123 -58
- package/src/cache/linked-inputs.ts +1 -1
- package/src/cache/output-index.ts +19 -3
- package/src/cache/plugin-cache.ts +49 -23
- package/src/cache/remote-hits.ts +72 -0
- package/src/cache/remote-lane.ts +90 -14
- package/src/cache/run-history.ts +70 -23
- package/src/cache/run-store.ts +11 -10
- package/src/cache/schema.ts +25 -6
- package/src/cache/tar-stream.ts +5 -0
- package/src/cache/umask.ts +1 -2
- package/src/cli/cache.ts +58 -27
- package/src/cli/completions.ts +12 -5
- package/src/cli/docs.ts +42 -16
- package/src/cli/help.ts +58 -19
- package/src/cli/index.ts +79 -33
- package/src/cli/info.ts +12 -6
- package/src/cli/init.ts +186 -31
- package/src/cli/last.ts +439 -45
- package/src/cli/lock.ts +197 -52
- package/src/cli/plan-format.ts +91 -49
- package/src/cli/plugin-commands.ts +70 -14
- package/src/cli/plugin-templates.ts +13 -13
- package/src/cli/registry.ts +209 -0
- package/src/cli/run.ts +109 -34
- package/src/cli/select.ts +143 -46
- package/src/cli/show.ts +59 -16
- package/src/cli/task-verb.ts +16 -6
- package/src/cli/upgrade.ts +151 -49
- package/src/cli/watch-cycle.ts +10 -5
- package/src/cli/watch-filter.ts +71 -3
- package/src/cli/watch-fs.ts +22 -6
- package/src/cli/watch-judge.ts +48 -6
- package/src/cli/watch.ts +163 -42
- package/src/cli/why.ts +211 -73
- package/src/cli/workspace-config.ts +54 -15
- package/src/config.ts +13 -1
- package/src/exec/env.ts +7 -2
- package/src/exec/executor.ts +154 -10
- package/src/exec/index.ts +6 -2
- package/src/exec/kill-tree.ts +15 -0
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/proc-sample.ts +3 -1
- package/src/exec/runner.ts +253 -54
- package/src/exec/sandbox-binds.ts +7 -3
- package/src/exec/sandbox-paths.ts +1 -1
- package/src/exec/sandbox-runtime.ts +211 -24
- package/src/exec/sandbox-violations.ts +52 -20
- package/src/graph/critical-path.ts +79 -0
- package/src/graph/default-build.ts +19 -0
- package/src/graph/definition.ts +38 -0
- package/src/graph/folds.ts +38 -5
- package/src/graph/index.ts +4 -1
- package/src/graph/priorities.ts +74 -8
- package/src/graph/scheduler.ts +270 -60
- package/src/graph/task-graph.ts +228 -35
- package/src/handoff.ts +97 -9
- package/src/history/failure-mode.ts +6 -3
- package/src/history/history.ts +167 -61
- package/src/history/index.ts +2 -0
- package/src/history/metrics.ts +90 -30
- package/src/index.ts +30 -13
- package/src/orchestrator/admission.ts +10 -6
- package/src/orchestrator/affected-tasks.ts +11 -6
- package/src/orchestrator/deferred-outputs.ts +2 -0
- package/src/orchestrator/doctor.ts +134 -42
- package/src/orchestrator/execute-args.ts +4 -1
- package/src/orchestrator/execute-task.ts +130 -38
- package/src/orchestrator/fingerprint-watch.ts +1 -1
- package/src/orchestrator/graph-memo.ts +140 -0
- package/src/orchestrator/graph-stage.ts +18 -1
- package/src/orchestrator/held-servers.ts +1 -1
- package/src/orchestrator/hit-restore.ts +217 -119
- package/src/orchestrator/index.ts +4 -3
- package/src/orchestrator/local-floor.ts +119 -25
- package/src/orchestrator/local-shortcircuit.ts +28 -4
- package/src/orchestrator/lockfile-claim.ts +96 -16
- package/src/orchestrator/miss-save.ts +42 -8
- package/src/orchestrator/options.ts +8 -6
- package/src/orchestrator/output-host.ts +43 -14
- package/src/orchestrator/output-log.ts +90 -10
- package/src/orchestrator/persistent-task.ts +84 -29
- package/src/orchestrator/persistent.ts +16 -4
- package/src/orchestrator/placement.ts +185 -17
- package/src/orchestrator/plan.ts +64 -7
- package/src/orchestrator/plugin-host.ts +214 -42
- package/src/orchestrator/plugin.ts +113 -37
- package/src/orchestrator/prepare.ts +168 -30
- package/src/orchestrator/projects.ts +65 -40
- package/src/orchestrator/remote-prefetch.ts +119 -25
- package/src/orchestrator/run-artifacts.ts +95 -32
- package/src/orchestrator/run-context.ts +39 -41
- package/src/orchestrator/run-failures.ts +65 -23
- package/src/orchestrator/run-id.ts +6 -3
- package/src/orchestrator/run-keys.ts +149 -13
- package/src/orchestrator/run-live.ts +294 -0
- package/src/orchestrator/run-lock.ts +13 -7
- package/src/orchestrator/run-records.ts +35 -29
- package/src/orchestrator/run-report.ts +3 -3
- package/src/orchestrator/run-steps.ts +58 -22
- package/src/orchestrator/run-stop.ts +22 -5
- package/src/orchestrator/run.ts +331 -88
- package/src/orchestrator/sandbox-fix.ts +7 -5
- package/src/orchestrator/sandbox-request.ts +70 -20
- package/src/orchestrator/shell-verdict.ts +5 -5
- package/src/orchestrator/signals.ts +26 -1
- package/src/orchestrator/stable-keys.ts +92 -8
- package/src/orchestrator/steps.ts +2 -2
- package/src/orchestrator/task-artifacts.ts +90 -26
- package/src/orchestrator/task-hash.ts +78 -13
- package/src/orchestrator/task-log-buffer.ts +72 -36
- package/src/orchestrator/task-process.ts +7 -1
- package/src/orchestrator/telemetry-host.ts +22 -2
- package/src/orchestrator/telemetry.ts +53 -21
- package/src/orchestrator/upstream.ts +15 -0
- package/src/report/events.ts +32 -13
- package/src/report/forecast.ts +31 -9
- package/src/report/framed-output.ts +141 -50
- package/src/report/index.ts +1 -0
- package/src/report/logger.ts +170 -179
- package/src/report/path-links.ts +12 -4
- package/src/report/plain-output.ts +13 -5
- package/src/report/status-line.ts +81 -53
- package/src/report/summary.ts +114 -32
- package/src/report/terminal-signals.ts +13 -2
- package/src/settings.ts +7 -6
- package/src/util/bun-version.ts +4 -1
- package/src/util/colors.ts +4 -2
- package/src/util/config-frame.ts +41 -4
- package/src/{graph → util}/dependency-spec.ts +22 -13
- package/src/util/edit-distance.ts +13 -1
- package/src/util/env-flag.ts +9 -0
- package/src/util/errors.ts +59 -3
- package/src/util/essential-env.ts +57 -0
- package/src/util/git-ignored.ts +1 -1
- package/src/util/hash.ts +35 -0
- package/src/util/hide-env.ts +45 -5
- package/src/util/index.ts +21 -5
- package/src/util/num.ts +8 -0
- package/src/util/paths.ts +91 -21
- package/src/util/report.ts +6 -1
- package/src/util/secret-mask.ts +62 -1
- package/src/util/secrets.ts +7 -2
- package/src/util/size.ts +11 -3
- package/src/util/tail.ts +6 -1
- package/src/util/timing.ts +13 -1
- package/src/util/ui.ts +61 -3
- package/src/util/which.ts +1 -1
- package/src/util/write-file.ts +67 -0
- package/src/workspace/affected.ts +710 -107
- package/src/workspace/bracket-literal.ts +84 -0
- package/src/workspace/canonical-dir.ts +37 -0
- package/src/workspace/config-bytecode.ts +17 -11
- package/src/workspace/config-eval.ts +29 -8
- package/src/workspace/config-imports.ts +199 -58
- package/src/workspace/config-schema.ts +242 -39
- package/src/workspace/filter.ts +37 -8
- package/src/workspace/index.ts +11 -1
- package/src/workspace/json-data.ts +3 -4
- package/src/workspace/lockfile.ts +57 -8
- package/src/workspace/migration.ts +69 -18
- package/src/workspace/package-graph.ts +5 -4
- package/src/workspace/project-loader.ts +226 -37
- package/src/workspace/repo-id.ts +1 -1
- package/src/workspace/workspace.ts +176 -33
package/src/exec/executor.ts
CHANGED
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
// process primitives — the module-boundary matrix forbids `exec` → `cache`,
|
|
14
14
|
// which is why sandbox baselines arrive pre-resolved on the request.
|
|
15
15
|
|
|
16
|
+
import type { SandboxConfig } from '../config.js'
|
|
16
17
|
import type { CaptureConfig, RunResult } from './runner.js'
|
|
17
18
|
import type { ResolvedSandboxConfig, SandboxViolation } from './sandbox-runtime.js'
|
|
18
|
-
import { UserError, type ui } from '../util/index.js'
|
|
19
|
+
import { thrownMessage, UserError, type ui } from '../util/index.js'
|
|
19
20
|
|
|
20
21
|
/** Sandbox baselines + the user's resolved sandbox block, when the task is sandboxed. */
|
|
21
22
|
export interface ExecuteSandbox {
|
|
@@ -94,6 +95,12 @@ export interface ServerSpec {
|
|
|
94
95
|
readonly readyWhen?: string
|
|
95
96
|
/** Bounds the readiness wait. */
|
|
96
97
|
readonly timeoutMs?: number
|
|
98
|
+
/**
|
|
99
|
+
* Its `exec.sandbox`, and the directories of the projects nested in its
|
|
100
|
+
* own (a wall its grants do not reach): it starts inside that sandbox,
|
|
101
|
+
* built where it runs, as a one-shot task's.
|
|
102
|
+
*/
|
|
103
|
+
readonly sandbox?: { readonly config: SandboxConfig; readonly nested: readonly string[] }
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
export interface ExecuteRequest {
|
|
@@ -109,6 +116,15 @@ export interface ExecuteRequest {
|
|
|
109
116
|
* this machine — needs a stable address, and the key is that address.
|
|
110
117
|
*/
|
|
111
118
|
readonly cacheKey?: string
|
|
119
|
+
/**
|
|
120
|
+
* Core saves no entry under `cacheKey` after this attempt: the run's
|
|
121
|
+
* policy writes no layer (`--no-cache`, `--cache-policy local:r`), or an
|
|
122
|
+
* upstream failed under `--continue=always`, ran unkeyed or is keyless.
|
|
123
|
+
* An executor that saves a task's result for its dependants keeps it off
|
|
124
|
+
* that key too: a distributed run's worker saved it, and the next run
|
|
125
|
+
* hit an entry the user had said not to write (2026-10-24).
|
|
126
|
+
*/
|
|
127
|
+
readonly unsaved?: boolean
|
|
112
128
|
/**
|
|
113
129
|
* Cache READS are disabled for this run (`--force` / `--no-cache`). An
|
|
114
130
|
* executor that keeps its OWN remote record of executions must not serve
|
|
@@ -123,6 +139,11 @@ export interface ExecuteRequest {
|
|
|
123
139
|
* execution under the same key.
|
|
124
140
|
*/
|
|
125
141
|
readonly remoteOnly?: boolean
|
|
142
|
+
/**
|
|
143
|
+
* The platform to run it on, one of the task's `exec.platforms` and the
|
|
144
|
+
* executor's `platforms`: its key includes it. Absent when the task names none.
|
|
145
|
+
*/
|
|
146
|
+
readonly platform?: string
|
|
126
147
|
/**
|
|
127
148
|
* Whether this task's outputs must land on the submitter's disk now.
|
|
128
149
|
* 'eager' (or absent) = materialise as today. 'deferred' = leave them
|
|
@@ -198,9 +219,17 @@ export interface ExecuteResult extends RunResult {
|
|
|
198
219
|
* existed. `deferred` means they are NOT on disk and `materialize()`
|
|
199
220
|
* fetches them; core calls it lazily, at most once, and then saves an
|
|
200
221
|
* ordinary cache entry, so a deferred task leaves no permanent third
|
|
201
|
-
* state behind.
|
|
222
|
+
* state behind. An executor may answer `deferred` to an `eager` request
|
|
223
|
+
* too, when it cannot bring them here: core saves nothing from this disk.
|
|
202
224
|
*/
|
|
203
225
|
readonly outputs?: { kind: 'disk' } | { kind: 'deferred'; materialize: () => Promise<void> }
|
|
226
|
+
/**
|
|
227
|
+
* Tracked files the command changed where it ran, workspace-relative
|
|
228
|
+
* POSIX: an executor that runs it elsewhere says so, as this disk shows
|
|
229
|
+
* none of it. One the key read withholds the save under the key, as an
|
|
230
|
+
* input changed here does.
|
|
231
|
+
*/
|
|
232
|
+
readonly changed?: readonly string[]
|
|
204
233
|
/**
|
|
205
234
|
* Executor-reported placement label — which machine ran the command
|
|
206
235
|
* (a REAPI worker id, a pool member). Absent = this host. Rides
|
|
@@ -214,7 +243,16 @@ export interface ExecuteResult extends RunResult {
|
|
|
214
243
|
* them: the run history keeps them, so a task that used all of one
|
|
215
244
|
* reads as wanting a bigger one (`TaskResources.saturated*`).
|
|
216
245
|
*/
|
|
217
|
-
readonly machine?: {
|
|
246
|
+
readonly machine?: {
|
|
247
|
+
readonly cores: number
|
|
248
|
+
readonly memoryBytes: number
|
|
249
|
+
/**
|
|
250
|
+
* The share of a core each runnable thread got there over the task's
|
|
251
|
+
* run (1 when a core was free). On a crowded machine the CPU a task
|
|
252
|
+
* used is its share, not its need: the run history divides by this.
|
|
253
|
+
*/
|
|
254
|
+
readonly cpuShare?: number
|
|
255
|
+
}
|
|
218
256
|
}
|
|
219
257
|
|
|
220
258
|
/** What an executor sees when a task is PLACED — once per task, before scheduling. */
|
|
@@ -251,7 +289,7 @@ export interface TaskExecutor {
|
|
|
251
289
|
* it runs the task (`ExecuteRequest.servers`). It is offered the
|
|
252
290
|
* dependants of a persistent task with no `exec.sandbox` and no
|
|
253
291
|
* `exec.remote: false`; that task then runs here only for a dependant
|
|
254
|
-
* placed here (
|
|
292
|
+
* placed here (2026-10-22: nothing runs on a distributed run's
|
|
255
293
|
* orchestrator).
|
|
256
294
|
*/
|
|
257
295
|
readonly servers?: boolean
|
|
@@ -263,6 +301,31 @@ export interface TaskExecutor {
|
|
|
263
301
|
* here: their key holds this machine's answer (C-2).
|
|
264
302
|
*/
|
|
265
303
|
readonly runtime?: boolean
|
|
304
|
+
/**
|
|
305
|
+
* A `remote` executor that runs every task of the run, so none runs on
|
|
306
|
+
* this machine; only cache hits restore here (2026-10-24: "no
|
|
307
|
+
* task should ever run on the orchestrator"). Nothing is pinned for it:
|
|
308
|
+
* it is offered `exec.remote: false` and `exec.interactive` tasks too,
|
|
309
|
+
* and starts every server a dependant needs. A task it cannot take (a
|
|
310
|
+
* platform it does not offer, one it declines, a server requested on its
|
|
311
|
+
* own) refuses the run, and a task it gives back fails.
|
|
312
|
+
*/
|
|
313
|
+
readonly exclusive?: boolean
|
|
314
|
+
/**
|
|
315
|
+
* A `remote` executor that enforces `ExecuteRequest.timeoutMs` where it
|
|
316
|
+
* runs the command, timed from its start, and reports `timedOut`. Core
|
|
317
|
+
* then arms no clock of its own: armed at the request, it counted the
|
|
318
|
+
* wait for a worker still booting and killed a 1 s task with a 3 s
|
|
319
|
+
* timeout before it began (2026-10-24). The run's stop still aborts.
|
|
320
|
+
*/
|
|
321
|
+
readonly timeouts?: boolean
|
|
322
|
+
/**
|
|
323
|
+
* The platforms this executor runs tasks on (`exec.platforms`' names). It
|
|
324
|
+
* is offered a task that declares `exec.platforms` only when one of them
|
|
325
|
+
* is listed, and runs it on `ExecuteRequest.platform`; an executor that
|
|
326
|
+
* declares none is never offered such a task off this machine.
|
|
327
|
+
*/
|
|
328
|
+
readonly platforms?: readonly string[]
|
|
266
329
|
/**
|
|
267
330
|
* How many tasks this executor runs at once. Its tasks then occupy a pool
|
|
268
331
|
* of this size instead of the local worker slots, so a remote pool is not
|
|
@@ -273,9 +336,10 @@ export interface TaskExecutor {
|
|
|
273
336
|
/** Per-task opt-out at placement time. Absent = accepts every task it is offered. */
|
|
274
337
|
accepts?(task: TaskPlacement): boolean
|
|
275
338
|
/**
|
|
276
|
-
* The tasks placed here that have NOT finished yet — called
|
|
277
|
-
*
|
|
278
|
-
*
|
|
339
|
+
* The tasks placed here that will run and have NOT finished yet — called
|
|
340
|
+
* first once the run's cache has said which placed tasks it holds (those
|
|
341
|
+
* restore, so they are left out), then again after each completion (any
|
|
342
|
+
* outcome, a failure too) and when a task it left out executes after all.
|
|
279
343
|
*
|
|
280
344
|
* An executor that provisions something per task — a container, an
|
|
281
345
|
* allocation, a pod — otherwise has to guess. `capacity` says how much it
|
|
@@ -284,8 +348,14 @@ export interface TaskExecutor {
|
|
|
284
348
|
* moment the run can no longer use it, rather than holding it until
|
|
285
349
|
* teardown. Absent = the executor does not care and nothing is computed
|
|
286
350
|
* for it.
|
|
351
|
+
*
|
|
352
|
+
* `missed`: of `remaining`, those whose keys the run's up-front probe
|
|
353
|
+
* found in no store (each store asked answered for every key), or, when
|
|
354
|
+
* the remote has no batch probe, those the index keeps no hit of: they
|
|
355
|
+
* will run. The rest may still restore, or were not probed (a key an upstream's outputs can
|
|
356
|
+
* change). The same set each call, changed in place.
|
|
287
357
|
*/
|
|
288
|
-
demand?(remaining: ReadonlySet<string>): void
|
|
358
|
+
demand?(remaining: ReadonlySet<string>, missed: ReadonlySet<string>): void
|
|
289
359
|
/**
|
|
290
360
|
* Rows the run's footer shows for this executor, `[label, text]`, the
|
|
291
361
|
* text plain or a `ui` inline (a tone colors it): what only it knows, such as the machines it ran tasks on. Asked on every
|
|
@@ -299,6 +369,20 @@ export interface TaskExecutor {
|
|
|
299
369
|
* sink is on. A throw reports none.
|
|
300
370
|
*/
|
|
301
371
|
machines?(): readonly MachineReport[]
|
|
372
|
+
/**
|
|
373
|
+
* The run's end had it placed its tasks perfectly, from the run's
|
|
374
|
+
* start: what each took, replayed on the machines it could have had, for
|
|
375
|
+
* the run summary beside the real time. Asked once at the end, only when a sink is on. A throw, or
|
|
376
|
+
* anything but a finite number, reports none.
|
|
377
|
+
*/
|
|
378
|
+
idealMs?(): number | undefined
|
|
379
|
+
/**
|
|
380
|
+
* Its own Chrome-trace events for `vx run --profile` (the machines it ran
|
|
381
|
+
* tasks on, in time), asked once at the end, only when the flag is set.
|
|
382
|
+
* `ts` is in microseconds of `performance.now()`; `pid` from 1, which
|
|
383
|
+
* the run moves past its own process. A throw adds none.
|
|
384
|
+
*/
|
|
385
|
+
profile?(): readonly Record<string, unknown>[]
|
|
302
386
|
/**
|
|
303
387
|
* Where a task it was handed runs (`small#2`), for the live row; asked
|
|
304
388
|
* per frame. `null` while it holds the task for a machine to begin it:
|
|
@@ -318,8 +402,9 @@ export interface MachineReport {
|
|
|
318
402
|
memoryBytes: number
|
|
319
403
|
done: number
|
|
320
404
|
failed: number
|
|
405
|
+
/** From when it joined to when it left or the run ended: warm-up not counted. */
|
|
321
406
|
onlineMs: number
|
|
322
|
-
/** Time with at least one task
|
|
407
|
+
/** Time with at least one task on it, from its send to its result. */
|
|
323
408
|
busyMs: number
|
|
324
409
|
/** Cores in use at once, at the most and on average over its time online. */
|
|
325
410
|
cpuPeak: number
|
|
@@ -329,6 +414,12 @@ export interface MachineReport {
|
|
|
329
414
|
memoryAvgBytes: number
|
|
330
415
|
/** Core-time its tasks used over the core-time it had, 0 to 1. */
|
|
331
416
|
utilization: number
|
|
417
|
+
/** Cores the executor booked for the tasks it ran, on average over its time online. */
|
|
418
|
+
bookedAvg?: number
|
|
419
|
+
/** From when the executor asked for it to when it joined, for one it started. */
|
|
420
|
+
warmUpMs?: number
|
|
421
|
+
/** When it was asked for (else when it joined), from the executor's start. */
|
|
422
|
+
startMs?: number
|
|
332
423
|
}
|
|
333
424
|
|
|
334
425
|
/**
|
|
@@ -365,6 +456,8 @@ export function assertExecuteResult(
|
|
|
365
456
|
`executor '${executor}' returned an invalid result for ${taskId}: ${what} — a plugin bug, not a task failure`,
|
|
366
457
|
)
|
|
367
458
|
}
|
|
459
|
+
const shown = (v: unknown): string =>
|
|
460
|
+
typeof v === 'object' && v !== null ? 'an object' : String(v as string)
|
|
368
461
|
if (typeof res !== 'object' || res === null)
|
|
369
462
|
fail(`${res === null ? 'null' : typeof res} (expected an object)`)
|
|
370
463
|
const r = res as Record<string, unknown>
|
|
@@ -382,6 +475,46 @@ export function assertExecuteResult(
|
|
|
382
475
|
}
|
|
383
476
|
if (!Array.isArray(r['violations']))
|
|
384
477
|
fail(`violations is ${typeof r['violations']} (expected an array)`)
|
|
478
|
+
// The optional fields reach the task's row, telemetry and the run
|
|
479
|
+
// history: a `where` object lost the whole run's history to a bind error
|
|
480
|
+
// naming no executor, and a `null` violation failed the task as a TypeError.
|
|
481
|
+
for (const [i, v] of (r['violations'] as unknown[]).entries()) {
|
|
482
|
+
if (typeof v !== 'object' || v === null || typeof (v as { line?: unknown }).line !== 'string')
|
|
483
|
+
fail(`violations[${i}] is not an object with a string line`)
|
|
484
|
+
}
|
|
485
|
+
const where = r['where']
|
|
486
|
+
if (where !== undefined && (typeof where !== 'string' || where === ''))
|
|
487
|
+
fail(`where is ${where === '' ? 'empty' : typeof where} (expected a non-empty string)`)
|
|
488
|
+
for (const key of ['cpuMs', 'peakRssBytes']) {
|
|
489
|
+
const n = r[key]
|
|
490
|
+
if (n !== undefined && !(typeof n === 'number' && Number.isFinite(n) && n >= 0))
|
|
491
|
+
fail(`${key} is ${shown(n)} (expected a non-negative number)`)
|
|
492
|
+
}
|
|
493
|
+
const signal = r['signal']
|
|
494
|
+
if (
|
|
495
|
+
signal !== undefined &&
|
|
496
|
+
typeof signal !== 'string' &&
|
|
497
|
+
!(typeof signal === 'number' && Number.isInteger(signal) && signal > 0)
|
|
498
|
+
)
|
|
499
|
+
fail(`signal is ${shown(signal)} (expected a name or a signal number)`)
|
|
500
|
+
if (r['timedOut'] !== undefined && typeof r['timedOut'] !== 'boolean')
|
|
501
|
+
fail(`timedOut is ${typeof r['timedOut']} (expected a boolean)`)
|
|
502
|
+
const machine = r['machine']
|
|
503
|
+
if (machine !== undefined) {
|
|
504
|
+
if (typeof machine !== 'object' || machine === null) fail('machine is not an object')
|
|
505
|
+
const m = machine as Record<string, unknown>
|
|
506
|
+
// A worker that cannot read its memory reports 0 (vx-dte's join takes it).
|
|
507
|
+
for (const [key, least] of [
|
|
508
|
+
['cores', 'positive'],
|
|
509
|
+
['memoryBytes', 'non-negative'],
|
|
510
|
+
['cpuShare', 'positive'],
|
|
511
|
+
] as const) {
|
|
512
|
+
const n = m[key]
|
|
513
|
+
if (key === 'cpuShare' && n === undefined) continue
|
|
514
|
+
if (!(typeof n === 'number' && Number.isFinite(n) && (least === 'positive' ? n > 0 : n >= 0)))
|
|
515
|
+
fail(`machine.${key} is ${shown(n)} (expected a ${least} number)`)
|
|
516
|
+
}
|
|
517
|
+
}
|
|
385
518
|
const outputs = r['outputs'] as { kind?: unknown; materialize?: unknown } | undefined
|
|
386
519
|
if (outputs !== undefined) {
|
|
387
520
|
if (typeof outputs !== 'object' || outputs === null) fail('outputs is not an object')
|
|
@@ -391,6 +524,12 @@ export function assertExecuteResult(
|
|
|
391
524
|
} else if (outputs.kind !== 'disk')
|
|
392
525
|
fail(`outputs.kind is ${String(outputs.kind)} (expected disk or deferred)`)
|
|
393
526
|
}
|
|
527
|
+
const changed = r['changed']
|
|
528
|
+
if (
|
|
529
|
+
changed !== undefined &&
|
|
530
|
+
(!Array.isArray(changed) || changed.some((f) => typeof f !== 'string'))
|
|
531
|
+
)
|
|
532
|
+
fail('changed is not an array of paths')
|
|
394
533
|
}
|
|
395
534
|
|
|
396
535
|
/**
|
|
@@ -424,7 +563,7 @@ export function selectExecutor(
|
|
|
424
563
|
throw new Error('returned a Promise; accepts is synchronous')
|
|
425
564
|
}
|
|
426
565
|
} catch (err) {
|
|
427
|
-
const m =
|
|
566
|
+
const m = thrownMessage(err)
|
|
428
567
|
throw new UserError(`${label(executor)} failed in accepts for ${task.taskId}: ${m}`)
|
|
429
568
|
}
|
|
430
569
|
if (taken) return executor
|
|
@@ -433,3 +572,8 @@ export function selectExecutor(
|
|
|
433
572
|
`no executor accepted ${task.taskId} (declared: ${executors.map((e) => e.name).join(', ')}). Core's local executor accepts every task, so this means it is missing from the list.`,
|
|
434
573
|
)
|
|
435
574
|
}
|
|
575
|
+
|
|
576
|
+
/** This machine as `exec.platforms` names it: `os-arch` as Bun reports them, `linux-x64`. */
|
|
577
|
+
export function hostPlatform(): string {
|
|
578
|
+
return `${process.platform}-${process.arch}`
|
|
579
|
+
}
|
package/src/exec/index.ts
CHANGED
|
@@ -9,7 +9,9 @@ export {
|
|
|
9
9
|
VX_RUN_WORKSPACE_ENV,
|
|
10
10
|
} from './env.js'
|
|
11
11
|
export {
|
|
12
|
+
lineEnds,
|
|
12
13
|
runCommand,
|
|
14
|
+
restoreTerminalModes,
|
|
13
15
|
runPersistent,
|
|
14
16
|
PersistentReadyError,
|
|
15
17
|
withForwardArgs,
|
|
@@ -19,6 +21,7 @@ export {
|
|
|
19
21
|
type CaptureConfig,
|
|
20
22
|
execWord,
|
|
21
23
|
maskCaptured,
|
|
24
|
+
isDroppedOutputChunk,
|
|
22
25
|
BoundedCapture,
|
|
23
26
|
type CapturedChunk,
|
|
24
27
|
} from './runner.js'
|
|
@@ -48,11 +51,12 @@ export {
|
|
|
48
51
|
} from './sandbox-paths.js'
|
|
49
52
|
export { bindableWrites, punchWalls } from './sandbox-binds.js'
|
|
50
53
|
export { isLocalExecutor, localExecutor } from './local-executor.js'
|
|
51
|
-
export { holdGroups, killTree, untilGroupsGone } from './kill-tree.js'
|
|
52
|
-
export { sampleTrees } from './proc-sample.js'
|
|
54
|
+
export { holdGroups, killTree, pauseTree, untilGroupsGone } from './kill-tree.js'
|
|
55
|
+
export { sampleTrees, type TreeUsage } from './proc-sample.js'
|
|
53
56
|
export {
|
|
54
57
|
assertExecuteResult,
|
|
55
58
|
executorFallback,
|
|
59
|
+
hostPlatform,
|
|
56
60
|
isExecutorFallback,
|
|
57
61
|
selectExecutor,
|
|
58
62
|
type ExecuteRequest,
|
package/src/exec/kill-tree.ts
CHANGED
|
@@ -328,6 +328,21 @@ export function killTree(child: Child, signal: 'SIGINT' | 'SIGTERM' | 'SIGKILL')
|
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Stop or continue `child`'s group with vx (Ctrl-Z, `fg`). Straight to
|
|
333
|
+
* the group, never down a sandbox's channel: its watcher passes one
|
|
334
|
+
* signal and is gone. Under a Linux sandbox that stops bwrap, and the
|
|
335
|
+
* command inside blocks once its output pipe fills.
|
|
336
|
+
*/
|
|
337
|
+
export function pauseTree(child: Child, signal: 'SIGSTOP' | 'SIGCONT'): void {
|
|
338
|
+
if (!(child.pid > 0) || goneGroups.has(child)) return
|
|
339
|
+
try {
|
|
340
|
+
process.kill(-child.pid, signal)
|
|
341
|
+
} catch {
|
|
342
|
+
// The group is gone, or not ours: nothing of it to stop.
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
331
346
|
/**
|
|
332
347
|
* How often a group whose leader has exited is asked again whether any
|
|
333
348
|
* member is left. Only a group that outlived its leader is polled.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// process. Not a plugin — "run it on this machine" is not a capability
|
|
3
3
|
// someone else supplies differently, it is what running MEANS when no
|
|
4
4
|
// plugin claims the task, so it sits at the TAIL of every executor list
|
|
5
|
-
// (
|
|
5
|
+
// (2026-09-05). An executor plugin places work ELSEWHERE; its
|
|
6
6
|
// absence is "here".
|
|
7
7
|
import { runCommand, signalExitCode, stopSignal } from './runner.js'
|
|
8
8
|
import { runSandboxed } from './sandbox-runtime.js'
|
package/src/exec/proc-sample.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// counted; nor is the CPU of a descendant that already exited.
|
|
9
9
|
|
|
10
10
|
import { readdir, readFile } from 'node:fs/promises'
|
|
11
|
-
import { procfsIsOwn } from '../util/index.js'
|
|
11
|
+
import { helperEnv, procfsIsOwn } from '../util/index.js'
|
|
12
12
|
|
|
13
13
|
export interface TreeUsage {
|
|
14
14
|
/** CPU time of the live tree, user + system, in ms. */
|
|
@@ -125,7 +125,9 @@ async function linuxRssKiB(pid: number): Promise<number> {
|
|
|
125
125
|
async function psTable(): Promise<Map<number, Proc> | undefined> {
|
|
126
126
|
let text: string
|
|
127
127
|
try {
|
|
128
|
+
// It runs beside the tasks, readable by them (`ps eww`): PATH alone.
|
|
128
129
|
const ps = Bun.spawn(['ps', '-A', '-o', 'pid=,ppid=,rss=,time='], {
|
|
130
|
+
env: helperEnv(),
|
|
129
131
|
stdin: 'ignore',
|
|
130
132
|
stdout: 'pipe',
|
|
131
133
|
stderr: 'ignore',
|