omp-conductor 0.18.1 → 0.19.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 +106 -41
- package/REFERENCE.md +866 -31
- package/agents/to-spec.md +6 -2
- package/package.json +1 -1
- package/schema/config.schema.json +32 -1
- package/src/admission.ts +212 -26
- package/src/arm-challenge.ts +250 -57
- package/src/ask.ts +288 -1
- package/src/briefs/orchestrator.md +27 -13
- package/src/briefs/to-spec.md +6 -2
- package/src/cli.ts +127 -2
- package/src/command-help.ts +9 -1
- package/src/command-manifest.ts +52 -8
- package/src/commands/arm.ts +6 -2
- package/src/commands/context.ts +2 -0
- package/src/commands/intake.ts +4 -19
- package/src/commands/message.ts +26 -2
- package/src/commands/reconcile-units.ts +104 -0
- package/src/commands/release-composition.ts +232 -0
- package/src/commands/resume.ts +2 -27
- package/src/commands/setup.ts +101 -16
- package/src/commands/stats.ts +11 -30
- package/src/commands/tail.ts +31 -1
- package/src/commands/upgrade.ts +20 -3
- package/src/commands/verb.ts +2 -1
- package/src/commands/watch.ts +4 -17
- package/src/config-schema.ts +38 -6
- package/src/config.ts +103 -8
- package/src/credential-class.ts +366 -0
- package/src/daemon.ts +1368 -529
- package/src/dashboard/app.js +504 -2
- package/src/dashboard/controls.ts +336 -0
- package/src/dashboard/index.html +30 -0
- package/src/dashboard/server.ts +271 -30
- package/src/dashboard/style.css +116 -0
- package/src/dashboard/transcript.ts +173 -0
- package/src/decisions.ts +19 -11
- package/src/doctor.ts +431 -148
- package/src/escalate.ts +22 -11
- package/src/failure-class.ts +59 -0
- package/src/fleet.ts +587 -230
- package/src/host.ts +6 -455
- package/src/omp-settings.ts +19 -0
- package/src/omp.ts +40 -56
- package/src/orchestrator-tick.ts +564 -121
- package/src/pause.ts +233 -0
- package/src/session-host.ts +6 -41
- package/src/settlement.ts +159 -2
- package/src/setup-answers.ts +97 -0
- package/src/setup-host.ts +343 -1160
- package/src/setup-install.ts +204 -27
- package/src/setup-wizard.ts +252 -51
- package/src/setup.ts +87 -4
- package/src/spend-telemetry.ts +117 -0
- package/src/stats.ts +35 -0
- package/src/status-render.ts +485 -19
- package/src/store.ts +1229 -55
- package/src/telegram-freshness.ts +269 -0
- package/src/to-spec.ts +50 -2
- package/src/types.ts +759 -10
- package/src/unblock.ts +22 -0
- package/src/unit-reconcile.ts +303 -0
- package/src/upgrade-verify.ts +8 -1
- package/src/upgrade.ts +299 -12
- package/src/verbs/actions.ts +124 -10
- package/src/verbs/protocol.ts +70 -2
- package/src/verbs/server.ts +485 -11
- package/src/wake.ts +48 -0
- package/src/worker.ts +401 -14
package/src/setup-install.ts
CHANGED
|
@@ -20,7 +20,8 @@ import { platform } from "node:os";
|
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { loadConfig, stateDir } from "./config.ts";
|
|
22
22
|
import { pauseInstance, pauseSourceToken, setPaused, statusSnapshot } from "./daemon.ts";
|
|
23
|
-
import { fleetLayers, DEFAULT_HERDR_UNIT } from "./fleet.ts";
|
|
23
|
+
import { fleetLayers, DEFAULT_HERDR_SESSION, DEFAULT_HERDR_UNIT } from "./fleet.ts";
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
24
25
|
import { livingDaemon } from "./lifecycle.ts";
|
|
25
26
|
import { dbPath, openStore } from "./store.ts";
|
|
26
27
|
import {
|
|
@@ -51,8 +52,9 @@ import { runPrivileged, type PrivilegedDeps, type PrivilegedStep } from "./privi
|
|
|
51
52
|
import {
|
|
52
53
|
agentRenameVerdict,
|
|
53
54
|
checkEscalation,
|
|
55
|
+
checkSessionRestartContext,
|
|
56
|
+
herdrOwnership,
|
|
54
57
|
DEFAULT_AGENT_RENAME_DEPS,
|
|
55
|
-
defaultIdentityProbes,
|
|
56
58
|
defaultServiceRuntime,
|
|
57
59
|
planHostRuntime,
|
|
58
60
|
totalConfiguredWorkers,
|
|
@@ -62,8 +64,9 @@ import {
|
|
|
62
64
|
RECOVER_SCRIPT_INSTALL_PATH,
|
|
63
65
|
type AgentRenameDeps,
|
|
64
66
|
type EscalationDeps,
|
|
67
|
+
type HerdrOwnership,
|
|
68
|
+
type HerdrOwnershipFacts,
|
|
65
69
|
type ServiceRuntime,
|
|
66
|
-
type WorkerIdentityProbes,
|
|
67
70
|
} from "./setup-host.ts";
|
|
68
71
|
import type { WizardUi } from "./wizard-ui.ts";
|
|
69
72
|
import type { Caps, ProjectConfig, Store } from "./types.ts";
|
|
@@ -84,14 +87,6 @@ export interface InstallDeps {
|
|
|
84
87
|
escalation?: EscalationDeps;
|
|
85
88
|
/** `"linux"` gates the systemd half. Injectable so the refusal is testable. */
|
|
86
89
|
platform?: () => string;
|
|
87
|
-
/**
|
|
88
|
-
* The worker identity plan's probes (#798). `undefined` means the real
|
|
89
|
-
* host's read-only facts — production: every `setup host` installs and
|
|
90
|
-
* grants the dedicated worker account. `null` disables the identity plan
|
|
91
|
-
* entirely (the historical step list, for tests pinning it), and an object
|
|
92
|
-
* is used as given (tests pinning a specific host state).
|
|
93
|
-
*/
|
|
94
|
-
workerIdentityProbes?: WorkerIdentityProbes | null;
|
|
95
90
|
unitDir?: string;
|
|
96
91
|
/**
|
|
97
92
|
* Where the recovery playbook installs. Injectable so the idempotency gate
|
|
@@ -133,6 +128,31 @@ export interface InstallDeps {
|
|
|
133
128
|
* default is the same `herdr --session` CLI recover.sh and the tick use.
|
|
134
129
|
*/
|
|
135
130
|
agentRename?: AgentRenameDeps;
|
|
131
|
+
/**
|
|
132
|
+
* The environment the caller's context is read from (#834): `$HERDR_ENV` says
|
|
133
|
+
* this shell is a pane inside the unit the install restarts. Injectable so
|
|
134
|
+
* the refusal is testable without a live Herdr session.
|
|
135
|
+
*/
|
|
136
|
+
env?: Record<string, string | undefined>;
|
|
137
|
+
/**
|
|
138
|
+
* The live supervision facts and the one remedy for them (#893). Injectable
|
|
139
|
+
* so the ownership transfer and its readiness gate are testable without
|
|
140
|
+
* touching real systemd or a real herdr server — production reads
|
|
141
|
+
* `herdr session list --json` and `systemctl show`.
|
|
142
|
+
*/
|
|
143
|
+
supervision?: SupervisionDeps;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The reads and the one write the #893 ownership transfer needs. */
|
|
147
|
+
export interface SupervisionDeps {
|
|
148
|
+
/** Who is serving the fleet session right now. */
|
|
149
|
+
facts: (session: string) => HerdrOwnershipFacts;
|
|
150
|
+
/** Bounded readiness polling between reads. */
|
|
151
|
+
sleep: (ms: number) => Promise<void>;
|
|
152
|
+
/** How many times the post-restart gate re-reads before giving up. */
|
|
153
|
+
attempts?: number;
|
|
154
|
+
/** Milliseconds between those reads. */
|
|
155
|
+
intervalMs?: number;
|
|
136
156
|
}
|
|
137
157
|
|
|
138
158
|
/**
|
|
@@ -199,13 +219,6 @@ export async function runHostInstall(
|
|
|
199
219
|
// happens only after consent (#510), so the prompt's "Nothing has been run
|
|
200
220
|
// yet" is true of the staged tree when it is printed, and a declined run
|
|
201
221
|
// leaves every staged file byte-identical.
|
|
202
|
-
// The worker identity plan rides the same consent (#798): production probes
|
|
203
|
-
// the real host (account present? grants current?); a test may pass its own
|
|
204
|
-
// probes, or `null` to plan the historical step list only.
|
|
205
|
-
const identityProbes: WorkerIdentityProbes | null =
|
|
206
|
-
deps.workerIdentityProbes === null
|
|
207
|
-
? null
|
|
208
|
-
: deps.workerIdentityProbes ?? defaultIdentityProbes(deps.runtime ?? defaultServiceRuntime(telegramStateDir));
|
|
209
222
|
const plan = planHostRuntime(
|
|
210
223
|
project,
|
|
211
224
|
caps,
|
|
@@ -215,7 +228,6 @@ export async function runHostInstall(
|
|
|
215
228
|
unitDir,
|
|
216
229
|
deps.recoverScriptInstallPath ?? RECOVER_SCRIPT_INSTALL_PATH,
|
|
217
230
|
deps.multiProject ?? hostMultiProject(),
|
|
218
|
-
identityProbes,
|
|
219
231
|
);
|
|
220
232
|
// No pane-shell key is planned (unusable login shell, unparseable config):
|
|
221
233
|
// the operator hears why before the consent prompt, not after an install
|
|
@@ -257,6 +269,17 @@ export async function runHostInstall(
|
|
|
257
269
|
return { kind: "installed", wrote: [] };
|
|
258
270
|
}
|
|
259
271
|
|
|
272
|
+
// The caller must survive the transaction it started (#834). Placed here on
|
|
273
|
+
// purpose: after the plan and the no-op gate — so a host with no herdr unit,
|
|
274
|
+
// and a re-run with nothing to install, are never refused for a restart that
|
|
275
|
+
// is not going to happen — and before the drain fence and the post-consent
|
|
276
|
+
// staging, so a refusal has mutated nothing at all.
|
|
277
|
+
const context = checkSessionRestartContext("setup host", plan.steps, deps.env);
|
|
278
|
+
if (context.kind === "refuse") {
|
|
279
|
+
ui.notify(context.message, "error");
|
|
280
|
+
return { kind: "refused", reason: context.message };
|
|
281
|
+
}
|
|
282
|
+
|
|
260
283
|
// argv, not shell: `installCommands` renders `sudo …` strings for humans to
|
|
261
284
|
// read, and re-parsing those into an argv is how a path with a space becomes
|
|
262
285
|
// two arguments. The plan carries the exact argv once, in
|
|
@@ -298,7 +321,47 @@ export async function runHostInstall(
|
|
|
298
321
|
attributeRestartKills(fence.scope, project?.name ?? "", deps.drain?.drainStore);
|
|
299
322
|
};
|
|
300
323
|
|
|
301
|
-
|
|
324
|
+
// Who is serving the fleet session, before anything is staged or run (#893).
|
|
325
|
+
// A `systemctl restart` that returns 0 does not prove supervision moved: an
|
|
326
|
+
// unmanaged `herdr --session <name> server` holding the session makes every
|
|
327
|
+
// start of the unit exit 1, and `Type=simple` returns before the child finds
|
|
328
|
+
// out. So the collision is resolved by stopping the unmanaged session inside
|
|
329
|
+
// this transaction, or the transaction refuses before it mutates anything.
|
|
330
|
+
const supervision = deps.supervision ?? defaultSupervisionDeps();
|
|
331
|
+
const session = plan.herdrSession ?? DEFAULT_HERDR_SESSION;
|
|
332
|
+
let transferSteps: PrivilegedStep[] = [];
|
|
333
|
+
if (plan.herdrUnit !== undefined) {
|
|
334
|
+
const ownership = herdrOwnership(supervision.facts(session));
|
|
335
|
+
if (ownership.kind === "unknown") {
|
|
336
|
+
// The remedy here stops somebody's live session, so an unreadable fact is
|
|
337
|
+
// a refusal, never an assumption. Nothing has been staged or installed.
|
|
338
|
+
const reason =
|
|
339
|
+
`refused: ${ownership.detail}, so this install cannot tell whether ${DEFAULT_HERDR_UNIT} owns the ` +
|
|
340
|
+
`fleet session or an unmanaged server does. Stopping the wrong one kills a live fleet, so nothing ` +
|
|
341
|
+
`was staged or installed. Check \`systemctl status ${DEFAULT_HERDR_UNIT}\` and ` +
|
|
342
|
+
`\`herdr session list\`, then re-run.`;
|
|
343
|
+
ui.notify(reason, "error");
|
|
344
|
+
return { kind: "refused", reason };
|
|
345
|
+
}
|
|
346
|
+
if (ownership.kind === "unmanaged") {
|
|
347
|
+
ui.notify(
|
|
348
|
+
`The fleet session is served outside systemd: ${ownership.detail}. This install stops that server ` +
|
|
349
|
+
`first so ${DEFAULT_HERDR_UNIT} can take the session over — every pane in it restarts under the unit.`,
|
|
350
|
+
"warning",
|
|
351
|
+
);
|
|
352
|
+
transferSteps = [
|
|
353
|
+
{
|
|
354
|
+
// The fleet account's own session, so no escalation: `setup host`
|
|
355
|
+
// already runs as that account (the escalation guard proves it).
|
|
356
|
+
title: `stop the unmanaged herdr session "${session}" so ${DEFAULT_HERDR_UNIT} can own it`,
|
|
357
|
+
argv: ["herdr", "session", "stop", session],
|
|
358
|
+
unprivileged: true,
|
|
359
|
+
},
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const outcome = await runPrivileged([...transferSteps, ...plan.steps], ui, {
|
|
302
365
|
...(deps.privileged === undefined ? {} : { deps: deps.privileged }),
|
|
303
366
|
title: "Install and start the supervised session?",
|
|
304
367
|
answerKey: "install-host",
|
|
@@ -317,14 +380,22 @@ export async function runHostInstall(
|
|
|
317
380
|
]),
|
|
318
381
|
]),
|
|
319
382
|
"The unit runs as the account that staged it; nothing here changes that.",
|
|
320
|
-
...(
|
|
383
|
+
...(transferSteps.length === 0
|
|
384
|
+
? []
|
|
385
|
+
: [
|
|
386
|
+
`Stops the unmanaged herdr session "${session}" first — systemd cannot adopt a running process, so the ` +
|
|
387
|
+
"session has to be handed over. Every pane in it comes back under the unit.",
|
|
388
|
+
]),
|
|
389
|
+
// Retirement is named before the confirm that authorises it (#895): an
|
|
390
|
+
// operator approving this batch is approving an unmount, and a removal
|
|
391
|
+
// nobody announced is not consented to.
|
|
392
|
+
...(plan.retire === undefined
|
|
321
393
|
? []
|
|
322
394
|
: [
|
|
323
|
-
`
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
: plan.workerIdentity.pending),
|
|
395
|
+
`Retires host state an earlier release installed and this one no longer ships: ` +
|
|
396
|
+
`${[...plan.retire.units, ...plan.retire.staged].join(", ")}.`,
|
|
397
|
+
"Worker sessions run as the fleet account again, so the read-only bind that served the separate " +
|
|
398
|
+
"worker account is unmounted and its unit removed. The account itself is left alone.",
|
|
328
399
|
]),
|
|
329
400
|
"",
|
|
330
401
|
liveWorkers === 0
|
|
@@ -343,6 +414,27 @@ export async function runHostInstall(
|
|
|
343
414
|
if (fence !== undefined && !fence.initialPaused) drainDeps.setPaused(false, fence.scope.pauseKey);
|
|
344
415
|
return { kind: "failed", reason: `${outcome.step.title} exited ${outcome.exitCode}` };
|
|
345
416
|
}
|
|
417
|
+
// Success is not "the steps exited 0" (#893). `Type=simple` returns before the
|
|
418
|
+
// session server can discover a collision and die, so the transaction proves
|
|
419
|
+
// the canonical unit is still up AND owns the session — bounded, because an
|
|
420
|
+
// auto-restart loop keeps looking momentarily alive.
|
|
421
|
+
if (plan.herdrUnit !== undefined) {
|
|
422
|
+
const settled = await proveSupervision(supervision, session);
|
|
423
|
+
if (settled.kind !== "unit") {
|
|
424
|
+
// Dispatch stays paused, deliberately and unlike a failed step: the fleet
|
|
425
|
+
// is being served by something nobody supervises, and admitting workers
|
|
426
|
+
// into that is the outcome this gate exists to prevent. The operator is
|
|
427
|
+
// told what was observed rather than a generic failure.
|
|
428
|
+
const reason =
|
|
429
|
+
`${DEFAULT_HERDR_UNIT} did not take over the fleet session: ${settled.detail}. ` +
|
|
430
|
+
`The units are installed, but supervision did not transfer, so dispatch stays paused. ` +
|
|
431
|
+
`Check \`systemctl status ${DEFAULT_HERDR_UNIT}\` and \`herdr session list\`, then re-run \`setup host\` ` +
|
|
432
|
+
`from a shell outside the session.`;
|
|
433
|
+
ui.notify(reason, "error");
|
|
434
|
+
return { kind: "failed", reason };
|
|
435
|
+
}
|
|
436
|
+
ui.notify(`${DEFAULT_HERDR_UNIT} owns the fleet session — ${settled.detail}.`, "info");
|
|
437
|
+
}
|
|
346
438
|
if (fence !== undefined && !fence.initialPaused) drainDeps.setPaused(false, fence.scope.pauseKey);
|
|
347
439
|
ui.notify(`Installed and started ${STAGED_SERVICE_NAME}.`, "info");
|
|
348
440
|
// #541: the tick plan may have restamped agentName away from the shared
|
|
@@ -376,6 +468,84 @@ export async function runHostInstall(
|
|
|
376
468
|
return { kind: "installed", wrote };
|
|
377
469
|
}
|
|
378
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Poll until the canonical unit owns the fleet session, or the bound runs out
|
|
473
|
+
* (#893). Returns the last verdict either way, so the caller reports what was
|
|
474
|
+
* actually observed rather than "timed out".
|
|
475
|
+
*
|
|
476
|
+
* `absent` and `unmanaged` are both failures here, for different reasons: the
|
|
477
|
+
* first is a unit that started and died (the collision's own signature under
|
|
478
|
+
* `Type=simple`), the second is a session still held outside it.
|
|
479
|
+
*/
|
|
480
|
+
async function proveSupervision(deps: SupervisionDeps, session: string): Promise<HerdrOwnership> {
|
|
481
|
+
const attempts = deps.attempts ?? SUPERVISION_PROBE_ATTEMPTS;
|
|
482
|
+
const intervalMs = deps.intervalMs ?? SUPERVISION_PROBE_INTERVAL_MS;
|
|
483
|
+
let verdict = herdrOwnership(deps.facts(session));
|
|
484
|
+
for (let attempt = 1; attempt < attempts && verdict.kind !== "unit"; attempt += 1) {
|
|
485
|
+
await deps.sleep(intervalMs);
|
|
486
|
+
verdict = herdrOwnership(deps.facts(session));
|
|
487
|
+
}
|
|
488
|
+
return verdict;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** How many reads the readiness gate takes, and how far apart. Twelve seconds
|
|
492
|
+
* total: long enough for a session server to bind its socket, short enough
|
|
493
|
+
* that a restart loop is reported rather than waited out. */
|
|
494
|
+
const SUPERVISION_PROBE_ATTEMPTS = 12;
|
|
495
|
+
const SUPERVISION_PROBE_INTERVAL_MS = 1_000;
|
|
496
|
+
|
|
497
|
+
/** The real reads: herdr's own session list, and systemd's view of the unit. */
|
|
498
|
+
function defaultSupervisionDeps(): SupervisionDeps {
|
|
499
|
+
return {
|
|
500
|
+
facts: (session) => ({
|
|
501
|
+
sessionRunning: herdrSessionRunning(session),
|
|
502
|
+
unit: herdrUnitState(),
|
|
503
|
+
}),
|
|
504
|
+
sleep: Bun.sleep,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** Whether herdr reports a live server for one session, or `undefined` when it
|
|
509
|
+
* could not be asked — never `false` on a failed read (#893). */
|
|
510
|
+
function herdrSessionRunning(session: string): boolean | undefined {
|
|
511
|
+
const ran = spawnSync("herdr", ["session", "list", "--json"], { encoding: "utf8", timeout: 10_000 });
|
|
512
|
+
if (ran.status !== 0 || typeof ran.stdout !== "string") return undefined;
|
|
513
|
+
try {
|
|
514
|
+
const parsed: unknown = JSON.parse(ran.stdout);
|
|
515
|
+
const sessions = parsed !== null && typeof parsed === "object" ? Reflect.get(parsed, "sessions") : undefined;
|
|
516
|
+
if (!Array.isArray(sessions)) return undefined;
|
|
517
|
+
const row = sessions.find(
|
|
518
|
+
(entry) => entry !== null && typeof entry === "object" && Reflect.get(entry, "name") === session,
|
|
519
|
+
);
|
|
520
|
+
if (row === undefined) return false;
|
|
521
|
+
return Reflect.get(row as object, "running") === true;
|
|
522
|
+
} catch {
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** The canonical unit's live state, or `undefined` when systemctl could not
|
|
528
|
+
* answer. A property systemd omits reads as unreadable rather than as zero. */
|
|
529
|
+
function herdrUnitState(): HerdrOwnershipFacts["unit"] {
|
|
530
|
+
const ran = spawnSync(
|
|
531
|
+
"systemctl",
|
|
532
|
+
["show", DEFAULT_HERDR_UNIT, "-p", "ActiveState", "-p", "SubState", "-p", "MainPID", "-p", "NRestarts"],
|
|
533
|
+
{ encoding: "utf8", timeout: 10_000 },
|
|
534
|
+
);
|
|
535
|
+
if (ran.status !== 0 || typeof ran.stdout !== "string") return undefined;
|
|
536
|
+
const values = new Map<string, string>();
|
|
537
|
+
for (const line of ran.stdout.split("\n")) {
|
|
538
|
+
const eq = line.indexOf("=");
|
|
539
|
+
if (eq > 0) values.set(line.slice(0, eq), line.slice(eq + 1));
|
|
540
|
+
}
|
|
541
|
+
const activeState = values.get("ActiveState");
|
|
542
|
+
const subState = values.get("SubState");
|
|
543
|
+
const mainPid = Number(values.get("MainPID"));
|
|
544
|
+
const nRestarts = Number(values.get("NRestarts"));
|
|
545
|
+
if (activeState === undefined || subState === undefined || !Number.isFinite(mainPid)) return undefined;
|
|
546
|
+
return { activeState, subState, mainPid, nRestarts: Number.isFinite(nRestarts) ? nRestarts : 0 };
|
|
547
|
+
}
|
|
548
|
+
|
|
379
549
|
/**
|
|
380
550
|
* The pause/drain fence's default accessors, overridable per test through
|
|
381
551
|
* {@link InstallDeps.drain.deps}. Mirrors `upgrade.ts`'s {@link DrainDeps}
|
|
@@ -395,7 +565,14 @@ function hostInstallDrainDeps(ui: WizardUi, deps: InstallDeps): DrainDeps {
|
|
|
395
565
|
: { running: true, project: daemon.project, generation: `${daemon.pid}@${daemon.startedAt}` };
|
|
396
566
|
},
|
|
397
567
|
pauseState: (project) => pauseInstance(project),
|
|
398
|
-
setPaused: (v, project) =>
|
|
568
|
+
setPaused: (v, project) =>
|
|
569
|
+
// `owner` is this process (#938): the fence lives exactly as long as this
|
|
570
|
+
// transaction, so a kill leaves it behind with nothing to explain it.
|
|
571
|
+
setPaused(
|
|
572
|
+
v,
|
|
573
|
+
{ source: pauseSourceToken("setup host"), reason: "setup host, draining", owner: process.pid },
|
|
574
|
+
project,
|
|
575
|
+
),
|
|
399
576
|
sleep: Bun.sleep,
|
|
400
577
|
log: (message) => ui.notify(message, "info"),
|
|
401
578
|
};
|