@bridge_gpt/mcp-server 0.2.59 → 0.2.60
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 +173 -51
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +53 -2
- package/build/conductor/doctor.js +255 -53
- package/build/conductor/epic-runtime.js +11 -2
- package/build/conductor/install-doctor.js +24 -2
- package/build/conductor/local-merge.js +319 -26
- package/build/conductor/merge-execution-contract.js +61 -0
- package/build/conductor/merge-execution-eligibility.js +129 -0
- package/build/conductor/readiness-sections.js +159 -2
- package/build/conductor/readiness.js +95 -14
- package/build/conductor/recovery-operations.js +8 -2
- package/build/connect-bitbucket-api.js +6 -1
- package/build/connect-bitbucket.js +398 -88
- package/build/credential-store.js +16 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +183 -9
- package/build/drive-epic.js +23 -0
- package/build/epic-implementer-bin.js +10 -10
- package/build/epic-implementer-claude-hook-bin.js +1 -1
- package/build/epic-implementer-env.js +43 -14
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +31 -0
- package/build/executor/credentials.js +11 -0
- package/build/executor/env.js +60 -0
- package/build/executor/index.js +21 -4
- package/build/executor/job-runner.js +126 -0
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +11 -2
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +5 -0
- package/build/index.js +194 -179
- package/build/install-bridge-conductor.js +201 -8
- package/build/install-bridge.js +356 -30
- package/build/mcp-install-state.js +42 -4
- package/build/merge-pull-request.js +103 -10
- package/build/merge-workflow-codeowners.js +224 -0
- package/build/merge-workflow-config.js +133 -0
- package/build/merge-workflow-drift.js +66 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +572 -0
- package/build/pipelines.generated.js +2 -2
- package/build/plane/cli.js +24 -2
- package/build/plane/preflight.js +68 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +163 -9
- package/build/version.generated.js +3 -3
- package/package.json +3 -3
- package/build/conduct-epic/bridge-client.js +0 -773
- package/build/conduct-epic/checkpoint-store.js +0 -542
- package/build/conduct-epic/cli.js +0 -3145
- package/build/conduct-epic/cut-protocol.js +0 -392
- package/build/conduct-epic/lock.js +0 -302
- package/build/conduct-epic/pr-state.js +0 -286
- package/build/conduct-epic/spawn.js +0 -113
- package/build/conductor/cli.js +0 -1278
- package/build/conductor-bundle-cli.js +0 -256
- package/build/direct-ticket-tools.js +0 -70
- package/build/executor/reconcile-overlap-governance.js +0 -129
- package/build/implement-epic/bridge-client.js +0 -773
- package/build/implement-epic/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- package/build/implement-epic/cut-protocol.js +0 -392
- package/build/implement-epic/lock.js +0 -302
- package/build/implement-epic/pr-state.js +0 -286
- package/build/implement-epic/spawn.js +0 -113
- package/build/plan-epic-conductor-eligibility.js +0 -213
|
@@ -19,6 +19,7 @@ import os from "node:os";
|
|
|
19
19
|
import { runHeartbeatLoop } from "./heartbeat.js";
|
|
20
20
|
import { isExecutorNamedError, toExecutorFailure, secretFreeErrorMessage, MissingVerdictArtifact, PreSpawnVerification, RequiredMcpRegistration, McpSurfaceMismatch, WorkerStartupFatal, WORKER_STARTUP_FATAL_MESSAGE, formatMcpSurfaceMismatch, ClaudeNotAuthenticated, CLAUDE_NOT_AUTHENTICATED_MESSAGE, ExecutorAdapterUnavailable, ExecutorNamedError, WorktreeBusy, StaleArtifactCleanupFailed, WORKTREE_BUSY_MESSAGE, WorkerEnvFilePresent, WORKER_ENV_FILE_PRESENT_MESSAGE, } from "./job-errors.js";
|
|
21
21
|
import { buildConductorMergeAccessForExecutorJob, buildDefaultMergeLocalDeps, runExecutorMergeJob, MERGE_RETRYABLE, MERGE_FAILED, } from "./merge-job.js";
|
|
22
|
+
import { createProductionCiRerunDeps, runCiRerunJob, } from "./ci-rerun-job.js";
|
|
22
23
|
import { createObservationState } from "./observation.js";
|
|
23
24
|
import { getWorktreeMcpRegistrationTargets, normalizeWorktreePathForRegistration, provisionMcpRegistrationForWorktree, } from "../mcp-provisioning.js";
|
|
24
25
|
import { resolveExecutorAgentAdapter } from "../agent-launchers/executor-adapter-registry.js";
|
|
@@ -615,6 +616,16 @@ control) {
|
|
|
615
616
|
if (job.job_type === "smoke") {
|
|
616
617
|
return runSmokeJob(job, httpClient, options, deps, ownership, observation, control);
|
|
617
618
|
}
|
|
619
|
+
// BAPI-1160: `ci_rerun` is a deterministic executor action, dispatched here
|
|
620
|
+
// beside `merge`/`smoke` and BEFORE the generic spawn path. It ensures no
|
|
621
|
+
// worktree and spawns no worker, and it must NEVER fall back to one: spawning
|
|
622
|
+
// an LLM worker to "fix" a failure the server already proved to be
|
|
623
|
+
// infrastructure is exactly the behavior this job type replaces. `job-types.ts`
|
|
624
|
+
// keeps it out of both `SPAWN_JOB_TYPES` and `RECOVERY_JOB_TYPES`, so reaching
|
|
625
|
+
// the spawn path below is structurally impossible rather than merely unlikely.
|
|
626
|
+
if (job.job_type === "ci_rerun") {
|
|
627
|
+
return runCiRerunExecutorJob(job, httpClient, options, deps, ownership, observation, seams, control);
|
|
628
|
+
}
|
|
618
629
|
// BAPI-1152: `reconcile_overlap` takes its OWN branch, after `merge`/`smoke`
|
|
619
630
|
// and before the generic spawn path. It needs a prepared, locked worktree like
|
|
620
631
|
// a spawn job, but its body is the reconcile module's state machine: the
|
|
@@ -652,6 +663,121 @@ control) {
|
|
|
652
663
|
});
|
|
653
664
|
return { status: "failed", reason: "unsupported_job_type" };
|
|
654
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Run a deterministic `ci_rerun` job (BAPI-1160).
|
|
668
|
+
*
|
|
669
|
+
* Supervised through the SAME ownership, heartbeat, dead-man, and timeout
|
|
670
|
+
* protocol as {@link runMergeJob} — via {@link createFlowProcess} — so a
|
|
671
|
+
* deterministic job is not a second, weaker lifecycle. The body itself spawns no
|
|
672
|
+
* subprocess of its own beyond the bounded `gh` invocations inside
|
|
673
|
+
* `ci-rerun-job.ts`, and there is NO fallback to a worker on failure.
|
|
674
|
+
*
|
|
675
|
+
* The completion carries only the job's fact envelope. It names no gate,
|
|
676
|
+
* requests no transition, and asserts no verdict: the reconciler re-observes the
|
|
677
|
+
* head independently and remains the sole transition authority (R14 rule 3).
|
|
678
|
+
*/
|
|
679
|
+
async function runCiRerunExecutorJob(job, httpClient, options, deps, ownership, observation, seams, control) {
|
|
680
|
+
// Resolve + enforce payload.timeout_seconds BEFORE any provider work, exactly
|
|
681
|
+
// as the merge branch does.
|
|
682
|
+
const timeout = resolveJobTimeoutSeconds(job, options.defaultJobTimeoutSeconds);
|
|
683
|
+
if (!timeout.ok) {
|
|
684
|
+
await httpClient.fail(job, {
|
|
685
|
+
error_kind: "ContractError.Timeout",
|
|
686
|
+
error_message: timeout.error,
|
|
687
|
+
classification: "crashed",
|
|
688
|
+
});
|
|
689
|
+
return { status: "failed", reason: "timeout_contract" };
|
|
690
|
+
}
|
|
691
|
+
const runCiRerun = seams.runCiRerun ??
|
|
692
|
+
((payload) => runCiRerunJob(payload, createProductionCiRerunDeps()));
|
|
693
|
+
const flow = createFlowProcess(() => runCiRerun(job.payload), (result) => result.ok);
|
|
694
|
+
const procResult = await superviseProcess({
|
|
695
|
+
job,
|
|
696
|
+
httpClient,
|
|
697
|
+
options,
|
|
698
|
+
deps,
|
|
699
|
+
ownership,
|
|
700
|
+
observation,
|
|
701
|
+
proc: flow.proc,
|
|
702
|
+
timeoutSeconds: timeout.timeoutSeconds,
|
|
703
|
+
collectTelemetry: async () => ({}),
|
|
704
|
+
control,
|
|
705
|
+
});
|
|
706
|
+
if (ownership.abandoned) {
|
|
707
|
+
return { status: "abandoned", reason: ownership.abandonReason };
|
|
708
|
+
}
|
|
709
|
+
if (procResult.classification === "timeout") {
|
|
710
|
+
const terminal = await sendTerminalMutationWithAuthReport({
|
|
711
|
+
kind: "fail",
|
|
712
|
+
send: () => httpClient.fail(job, {
|
|
713
|
+
error_kind: "CiRerunFailed",
|
|
714
|
+
error_message: "ci_rerun exceeded its job timeout",
|
|
715
|
+
classification: "timeout",
|
|
716
|
+
telemetry: observation.snapshot(),
|
|
717
|
+
}),
|
|
718
|
+
deps,
|
|
719
|
+
options,
|
|
720
|
+
ownership,
|
|
721
|
+
log: deps.log,
|
|
722
|
+
}, job, control);
|
|
723
|
+
return terminalToRunResult(terminal, "failed");
|
|
724
|
+
}
|
|
725
|
+
const outcome = flow.getOutcome();
|
|
726
|
+
if (outcome === undefined) {
|
|
727
|
+
const terminal = await sendTerminalMutationWithAuthReport({
|
|
728
|
+
kind: "fail",
|
|
729
|
+
send: () => httpClient.fail(job, {
|
|
730
|
+
error_kind: "CiRerunFailed",
|
|
731
|
+
error_message: "ci_rerun completed without an outcome",
|
|
732
|
+
classification: "crashed",
|
|
733
|
+
telemetry: observation.snapshot(),
|
|
734
|
+
}),
|
|
735
|
+
deps,
|
|
736
|
+
options,
|
|
737
|
+
ownership,
|
|
738
|
+
log: deps.log,
|
|
739
|
+
}, job, control);
|
|
740
|
+
return terminalToRunResult(terminal, "failed");
|
|
741
|
+
}
|
|
742
|
+
if (outcome.ok) {
|
|
743
|
+
const completion = {
|
|
744
|
+
job_type: "ci_rerun",
|
|
745
|
+
exit_code: 0,
|
|
746
|
+
classification: "clean_exit",
|
|
747
|
+
// The FACT ENVELOPE, verbatim. No gate, no transition, no verdict — the
|
|
748
|
+
// backend model is `extra="forbid"`, so anything else fails validation
|
|
749
|
+
// rather than being silently ignored.
|
|
750
|
+
result: outcome.result,
|
|
751
|
+
telemetry: observation.snapshot(),
|
|
752
|
+
};
|
|
753
|
+
const terminal = await sendTerminalMutationWithAuthReport({
|
|
754
|
+
kind: "complete",
|
|
755
|
+
send: () => httpClient.complete(job, completion),
|
|
756
|
+
deps,
|
|
757
|
+
options,
|
|
758
|
+
ownership,
|
|
759
|
+
log: deps.log,
|
|
760
|
+
}, job, control);
|
|
761
|
+
return terminalToRunResult(terminal, "completed");
|
|
762
|
+
}
|
|
763
|
+
// A refused or failed re-run. The reserved infrastructure attempt stays
|
|
764
|
+
// CONSUMED — see `ci-rerun-job.ts` — and the reason is a closed token, never
|
|
765
|
+
// provider text.
|
|
766
|
+
const terminal = await sendTerminalMutationWithAuthReport({
|
|
767
|
+
kind: "fail",
|
|
768
|
+
send: () => httpClient.fail(job, {
|
|
769
|
+
error_kind: outcome.failure.errorKind,
|
|
770
|
+
error_message: outcome.failure.reason,
|
|
771
|
+
classification: "crashed",
|
|
772
|
+
telemetry: observation.snapshot(),
|
|
773
|
+
}),
|
|
774
|
+
deps,
|
|
775
|
+
options,
|
|
776
|
+
ownership,
|
|
777
|
+
log: deps.log,
|
|
778
|
+
}, job, control);
|
|
779
|
+
return terminalToRunResult(terminal, "failed");
|
|
780
|
+
}
|
|
655
781
|
/**
|
|
656
782
|
* Adapt a `runMerge(...)` promise into an {@link OwnedChildProcess} so the merge
|
|
657
783
|
* flow is supervised by the SAME heartbeat/deadman/timeout path as spawn/smoke
|
|
@@ -26,6 +26,15 @@
|
|
|
26
26
|
* resolution, but it does NOT re-enter `/implement-ticket`: it always carries an
|
|
27
27
|
* explicit server-rendered `payload.prompt`, which wins over the synthesis in
|
|
28
28
|
* `worker-command.ts`, and `job-runner.ts` dispatches it to its own body.
|
|
29
|
+
*
|
|
30
|
+
* BAPI-1160: `ci_rerun` is DELIBERATELY ABSENT from this set and from
|
|
31
|
+
* `SPAWN_JOB_TYPES` below. It is a deterministic actuator — one bounded
|
|
32
|
+
* `gh run rerun --failed` and a bounded poll — with no worktree, no branch to
|
|
33
|
+
* reuse, no prompt, and no worker. It has a dedicated dispatch branch in
|
|
34
|
+
* `job-runner.ts` beside `merge` and `smoke`, ahead of the generic spawn path,
|
|
35
|
+
* and it must NEVER fall back to a worker subprocess: spawning an LLM worker to
|
|
36
|
+
* "fix" a failure the server already PROVED to be infrastructure is the exact
|
|
37
|
+
* behavior this job type exists to replace.
|
|
29
38
|
*/
|
|
30
39
|
export const RECOVERY_JOB_TYPES = new Set([
|
|
31
40
|
"remediate",
|
|
@@ -37,8 +46,9 @@ export const RECOVERY_JOB_TYPES = new Set([
|
|
|
37
46
|
* Real headless-spawn job types (worktree + worker LLM): the first-pass
|
|
38
47
|
* `implement`, the `resume` continuation (own pre-spawn protocol), the
|
|
39
48
|
* `spec_review` verdict job, and the recovery jobs. `merge` (deterministic,
|
|
40
|
-
* no worktree/worker)
|
|
41
|
-
*
|
|
49
|
+
* no worktree/worker), `smoke` (no-op acceptance), and BAPI-1160's `ci_rerun`
|
|
50
|
+
* (deterministic, no worktree/worker) are intentionally EXCLUDED — they have
|
|
51
|
+
* dedicated dispatch branches BEFORE the generic spawn path.
|
|
42
52
|
*
|
|
43
53
|
* BAPI-1152: `reconcile_overlap` is here because it needs a prepared, locked
|
|
44
54
|
* worktree and MAY spawn a worker — but only CONDITIONALLY. A clean merge whose
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* {@link MERGE_FAILED} `error_kind` (via {@link classifyMergeFailureErrorKind})
|
|
22
22
|
* so the companion re-fire lane can route retry-vs-rebase without string parsing.
|
|
23
23
|
*/
|
|
24
|
-
import { makeLocalMergeExecutor, resolveLocalMergeMethod, } from "../conductor/local-merge.js";
|
|
24
|
+
import { makeLocalMergeExecutor, resolveLocalMergeCredential, resolveLocalMergeMethod, } from "../conductor/local-merge.js";
|
|
25
25
|
import { resolveConductorBridgeApiAccess, } from "../conductor/bridge-api-client.js";
|
|
26
26
|
import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullRequest, formatEpicIntegrationPullRequestOutcome, EPIC_BRANCH_PREFIX, } from "../epic-integration-pr.js";
|
|
27
27
|
import { secretFreeErrorMessage } from "./job-errors.js";
|
|
@@ -353,5 +353,14 @@ export async function runExecutorMergeJob(job, seams) {
|
|
|
353
353
|
* unchanged (no App token is ever placed here).
|
|
354
354
|
*/
|
|
355
355
|
export function buildDefaultMergeLocalDeps(deps, signal) {
|
|
356
|
-
|
|
356
|
+
// BAPI-1136: the executor's merge job runs on the operator's own machine, which
|
|
357
|
+
// is typed `local`, so it reads the local configuration key and no other. The
|
|
358
|
+
// environment is still passed for the allowlisted operational base (PATH, HOME,
|
|
359
|
+
// certificates); it no longer supplies the identity, and an ambient GH_TOKEN in
|
|
360
|
+
// it is dropped rather than inherited.
|
|
361
|
+
return {
|
|
362
|
+
env: deps.env,
|
|
363
|
+
credential: resolveLocalMergeCredential(deps.env, "local"),
|
|
364
|
+
signal,
|
|
365
|
+
};
|
|
357
366
|
}
|
|
@@ -36,6 +36,24 @@
|
|
|
36
36
|
* The command forms address user-domain (per-user) services directly:
|
|
37
37
|
* `launchctl` against `gui/<uid>/<label>` on darwin, and `systemctl --user`
|
|
38
38
|
* on linux.
|
|
39
|
+
*
|
|
40
|
+
* FOUR INDEPENDENT FACTS (BAPI-1137). A durable host is described by four
|
|
41
|
+
* separate observations, and no one of them implies another:
|
|
42
|
+
*
|
|
43
|
+
* 1. **Unit-content compliance** — what the generated/on-disk unit DECLARES
|
|
44
|
+
* (`service-unit.ts`). A perfect unit file can be sitting unloaded.
|
|
45
|
+
* 2. **Service-manager state** — what the manager is running RIGHT NOW
|
|
46
|
+
* ({@link inspectExecutorServiceState}). The manager may still be running a
|
|
47
|
+
* previously loaded definition rather than the current bytes on disk.
|
|
48
|
+
* 3. **Session persistence** — whether the host keeps running it once nobody is
|
|
49
|
+
* logged in ({@link inspectExecutorServiceDurability}). An `active` service
|
|
50
|
+
* with lingering off is active exactly until the operator logs out.
|
|
51
|
+
* 4. **Server-observed readiness** — whether BRIDGE has seen an executor claim.
|
|
52
|
+
* Only this one can produce `executor_ready`, and rule 4 above is why:
|
|
53
|
+
* nothing local, however green, is evidence for it.
|
|
54
|
+
*
|
|
55
|
+
* They are reported separately everywhere, and collapsing any pair of them would
|
|
56
|
+
* reintroduce exactly the dishonest-green this module is built to refuse.
|
|
39
57
|
*/
|
|
40
58
|
import { posixShellQuote } from "../platform-escaping.js";
|
|
41
59
|
/**
|
|
@@ -62,6 +80,10 @@ function renderCommand(command) {
|
|
|
62
80
|
/**
|
|
63
81
|
* Build the platform-specific start sequence.
|
|
64
82
|
*
|
|
83
|
+
* UNCHANGED by BAPI-1137: start remains separately consented, one-shot, and
|
|
84
|
+
* non-retrying. The durability inspection added lower in this file is read-only
|
|
85
|
+
* and has no path to this function or to {@link startExecutorService}.
|
|
86
|
+
*
|
|
65
87
|
* darwin: `launchctl bootstrap gui/<uid> <plist-path>`
|
|
66
88
|
* linux: `systemctl --user daemon-reload`, then
|
|
67
89
|
* `systemctl --user enable --now <unit-name>`
|
|
@@ -267,3 +289,117 @@ export async function inspectExecutorServiceState(target, run) {
|
|
|
267
289
|
return { state: "inactive", probe };
|
|
268
290
|
return { state: "unknown", probe };
|
|
269
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* The Linux remediation, verbatim and in one place so the lifecycle module, the
|
|
294
|
+
* installer, and doctor cannot print three subtly different commands.
|
|
295
|
+
*/
|
|
296
|
+
export const EXECUTOR_LINGER_REMEDIATION = "run `loginctl enable-linger $USER` yourself to keep this user's services running " +
|
|
297
|
+
"without an active login session (Bridge never runs it for you)";
|
|
298
|
+
/**
|
|
299
|
+
* The macOS prerequisite. A LaunchAgent bootstrapped into `gui/<uid>` lives in a
|
|
300
|
+
* user-login domain, which `launchctl(1)` documents as created when the user logs
|
|
301
|
+
* in at the GUI — so it needs that login to exist, and after a reboot it needs
|
|
302
|
+
* the user to log in again (automatic login, or a human at the keyboard).
|
|
303
|
+
*/
|
|
304
|
+
export const EXECUTOR_MACOS_SESSION_REMEDIATION = "keep this user logged in at the GUI — a launchd LaunchAgent runs in the gui/<uid> " +
|
|
305
|
+
"login domain, so after a reboot the user must log in again (e.g. enable automatic " +
|
|
306
|
+
"login) for the executor to come back";
|
|
307
|
+
/**
|
|
308
|
+
* Build the strictly read-only durability prerequisite probe for a target.
|
|
309
|
+
*
|
|
310
|
+
* linux: `loginctl show-user <uid> --property=Linger` — a QUERY. `show-user`
|
|
311
|
+
* reads properties; it cannot enable or disable anything, which is what makes it
|
|
312
|
+
* safe to run unprompted where `enable-linger` would not be.
|
|
313
|
+
* darwin: `launchctl print gui/<uid>` — the login domain itself, not a service
|
|
314
|
+
* inside it. A zero exit means the GUI login domain exists for this uid.
|
|
315
|
+
*
|
|
316
|
+
* PURE: consults no runner, no environment, and no filesystem, so an
|
|
317
|
+
* incompletely described target is refused before anything could be executed.
|
|
318
|
+
*/
|
|
319
|
+
export function buildExecutorServiceDurabilityProbe(target) {
|
|
320
|
+
if (typeof target.uid !== "number" || !Number.isInteger(target.uid) || target.uid < 0) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
if (target.platform === "linux") {
|
|
324
|
+
return {
|
|
325
|
+
stage: "loginctl show-user --property=Linger",
|
|
326
|
+
executable: "loginctl",
|
|
327
|
+
args: ["show-user", String(target.uid), "--property=Linger"],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (target.platform === "darwin") {
|
|
331
|
+
return {
|
|
332
|
+
stage: "launchctl print gui",
|
|
333
|
+
executable: "launchctl",
|
|
334
|
+
args: ["print", `gui/${target.uid}`],
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Inspect the local session/linger durability prerequisite. STRICTLY read-only:
|
|
341
|
+
* it issues exactly one query and never enables lingering, starts a service,
|
|
342
|
+
* reloads a manager, or writes anything.
|
|
343
|
+
*
|
|
344
|
+
* Everything the runner returns beyond a recognized state token is DISCARDED —
|
|
345
|
+
* stdout past the token, stderr entirely, and any thrown value — because a
|
|
346
|
+
* service manager's output can name paths, units, and environment values that
|
|
347
|
+
* have no place in operator-facing text.
|
|
348
|
+
*/
|
|
349
|
+
export async function inspectExecutorServiceDurability(target, run) {
|
|
350
|
+
if (target.platform !== "darwin" && target.platform !== "linux") {
|
|
351
|
+
return { prerequisite: "not-applicable", probe: null, remediation: null };
|
|
352
|
+
}
|
|
353
|
+
const probe = buildExecutorServiceDurabilityProbe(target);
|
|
354
|
+
if (!probe)
|
|
355
|
+
return { prerequisite: "unverifiable", probe: null, remediation: null };
|
|
356
|
+
let exitCode;
|
|
357
|
+
let stdout;
|
|
358
|
+
try {
|
|
359
|
+
const result = await run(probe.executable, probe.args);
|
|
360
|
+
exitCode = typeof result?.exitCode === "number" ? result.exitCode : 1;
|
|
361
|
+
stdout = typeof result?.stdout === "string" ? result.stdout : "";
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return { prerequisite: "unverifiable", probe, remediation: null };
|
|
365
|
+
}
|
|
366
|
+
if (target.platform === "darwin") {
|
|
367
|
+
// The login domain either exists for this uid or it does not; launchctl
|
|
368
|
+
// exposes no third answer here, and a non-zero exit is reported as an
|
|
369
|
+
// unavailable session rather than dressed up as a confirmed absence of
|
|
370
|
+
// durability the probe did not establish.
|
|
371
|
+
//
|
|
372
|
+
// A ZERO exit is NOT `configured`. It says the gui/<uid> domain exists at
|
|
373
|
+
// this instant — which it does whenever an operator runs `install conductor`
|
|
374
|
+
// or `doctor` from their own logged-in session — and a LaunchAgent in that
|
|
375
|
+
// domain is tied to the login, not to the machine being powered on. Calling
|
|
376
|
+
// it `configured` would let a renderer that means "survives logout" assert
|
|
377
|
+
// the opposite of what launchd guarantees, so darwin gets its own state and
|
|
378
|
+
// carries the session prerequisite as the thing to keep true.
|
|
379
|
+
if (exitCode === 0) {
|
|
380
|
+
return {
|
|
381
|
+
prerequisite: "session-bound",
|
|
382
|
+
probe,
|
|
383
|
+
remediation: EXECUTOR_MACOS_SESSION_REMEDIATION,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
prerequisite: "not-configured",
|
|
388
|
+
probe,
|
|
389
|
+
remediation: EXECUTOR_MACOS_SESSION_REMEDIATION,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (exitCode !== 0)
|
|
393
|
+
return { prerequisite: "unverifiable", probe, remediation: null };
|
|
394
|
+
// `loginctl show-user --property=Linger` prints exactly `Linger=yes|no`. Only
|
|
395
|
+
// those two tokens are mapped; anything else is unverifiable rather than a
|
|
396
|
+
// guess derived from a format this module does not recognize.
|
|
397
|
+
const match = stdout.match(/^\s*Linger=(\S+)\s*$/m);
|
|
398
|
+
const value = match?.[1]?.toLowerCase();
|
|
399
|
+
if (value === "yes")
|
|
400
|
+
return { prerequisite: "configured", probe, remediation: null };
|
|
401
|
+
if (value === "no") {
|
|
402
|
+
return { prerequisite: "not-configured", probe, remediation: EXECUTOR_LINGER_REMEDIATION };
|
|
403
|
+
}
|
|
404
|
+
return { prerequisite: "unverifiable", probe, remediation: null };
|
|
405
|
+
}
|