@indigoai-us/hq-cli 5.181.0 → 5.183.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/CHANGELOG.md +12 -0
- package/dist/commands/mesh.d.ts +11 -0
- package/dist/commands/mesh.js +7 -5
- package/dist/lib/core-utils/hook-timeout-debug-context-flag.d.ts +20 -0
- package/dist/lib/core-utils/hook-timeout-debug-context-flag.js +80 -0
- package/dist/lib/core-utils/sentry-report.d.ts +24 -2
- package/dist/lib/core-utils/sentry-report.js +254 -16
- package/dist/lib/mesh/live/daemon/index.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/index.js +1 -1
- package/dist/lib/mesh/live/daemon/install.d.ts +5 -0
- package/dist/lib/mesh/live/daemon/install.js +15 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.183.0] — 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `hq core sentry report` can attach bounded hook-timeout timing context to Sentry events when the company-scoped `hook-timeout-debug-context` flag is enabled. The flag defaults off.
|
|
10
|
+
|
|
11
|
+
## [5.182.0] — 2026-09-24
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `hq mesh daemon status` now asks the service manager whether the daemon is loaded (`launchctl print` on macOS, `systemctl --user is-active` on Linux). Before, it never probed and always reported "installed (not loaded)" or "installed (inactive)", even while the daemon was running.
|
|
16
|
+
|
|
5
17
|
## [5.181.0] — 2026-09-24
|
|
6
18
|
|
|
7
19
|
### Changed
|
package/dist/commands/mesh.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { type MeshCompany, type MeshThread } from "../lib/mesh/api.js";
|
|
10
|
+
import { type DaemonInstallPlatform, type InstallPaths } from "../lib/mesh/live/daemon/index.js";
|
|
11
|
+
import { type RunCommand } from "../lib/service-manager/launchd.js";
|
|
10
12
|
export declare function formatCheckLines(threads: MeshThread[], company: MeshCompany, projectId?: string): string[];
|
|
11
13
|
/** Injectable confirmation seam for `hq mesh context correct` (stubbed in tests). */
|
|
12
14
|
export type MeshConfirmFn = (message: string) => Promise<boolean>;
|
|
@@ -24,4 +26,13 @@ export declare function runContextCorrect(opts: {
|
|
|
24
26
|
confirm?: MeshConfirmFn;
|
|
25
27
|
}): Promise<void>;
|
|
26
28
|
export declare function registerMeshCommand(program: Command): void;
|
|
29
|
+
export declare function runDaemonStatus(opts: {
|
|
30
|
+
json?: boolean;
|
|
31
|
+
}, deps?: {
|
|
32
|
+
platform?: DaemonInstallPlatform;
|
|
33
|
+
paths?: InstallPaths;
|
|
34
|
+
/** launchctl/systemctl runner for the loaded probe (tests inject). */
|
|
35
|
+
run?: RunCommand;
|
|
36
|
+
uid?: number;
|
|
37
|
+
}): Promise<void>;
|
|
27
38
|
//# sourceMappingURL=mesh.d.ts.map
|
package/dist/commands/mesh.js
CHANGED
|
@@ -35,7 +35,7 @@ import { generateUlid } from "../lib/mesh/live/ulid.js";
|
|
|
35
35
|
import { readLegacyBacklog } from "../lib/mesh/live/emit-replay.js";
|
|
36
36
|
import { resolveMeshEmitMode, writeMeshConfigMode, } from "../lib/mesh/live/daemon/mode.js";
|
|
37
37
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
38
|
-
import { buildInstallPaths, collectDaemonDoctor, daemonServiceStatus, findAncestorHqRoot, resolveInstallHqRoot, detectPlatform, formatCliIdentityLines, formatDaemonDoctor, installDaemonIfStale, installDaemonService, readDaemonState, resolveAbsoluteNodeBinary, resolveRunningCliEntry, runMeshDaemon, uninstallDaemonService,
|
|
38
|
+
import { buildInstallPaths, collectDaemonDoctor, daemonServiceIsLoaded, daemonServiceStatus, findAncestorHqRoot, resolveInstallHqRoot, detectPlatform, formatCliIdentityLines, formatDaemonDoctor, installDaemonIfStale, installDaemonService, readDaemonState, resolveAbsoluteNodeBinary, resolveRunningCliEntry, runMeshDaemon, uninstallDaemonService, } from "../lib/mesh/live/daemon/index.js";
|
|
39
39
|
import { defaultRunCommand } from "../lib/service-manager/launchd.js";
|
|
40
40
|
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
41
41
|
import { formatMigrateConfirmation, submitSessionMigration, } from "../lib/work-context/migrate.js";
|
|
@@ -2122,12 +2122,14 @@ async function runDaemonUninstall(opts) {
|
|
|
2122
2122
|
}
|
|
2123
2123
|
console.log(result.message);
|
|
2124
2124
|
}
|
|
2125
|
-
async function runDaemonStatus(opts) {
|
|
2125
|
+
export async function runDaemonStatus(opts, deps = {}) {
|
|
2126
|
+
const paths = deps.paths ?? buildInstallPaths({});
|
|
2126
2127
|
const result = daemonServiceStatus({
|
|
2127
|
-
platform: detectPlatform(),
|
|
2128
|
-
paths
|
|
2128
|
+
platform: deps.platform ?? detectPlatform(),
|
|
2129
|
+
paths,
|
|
2130
|
+
isLoaded: daemonServiceIsLoaded(deps.run ?? defaultRunCommand, deps.uid),
|
|
2129
2131
|
});
|
|
2130
|
-
const state = readDaemonState(daemonDir
|
|
2132
|
+
const state = readDaemonState(paths.daemonDir);
|
|
2131
2133
|
const presenceRefusal = state?.presenceRefusal ?? null;
|
|
2132
2134
|
if (opts.json) {
|
|
2133
2135
|
console.log(JSON.stringify({
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type FlagClient, type FlagClientOptions } from "@indigoai-us/hq-flags-client";
|
|
2
|
+
export declare const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG = "hook-timeout-debug-context";
|
|
3
|
+
export declare const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK = false;
|
|
4
|
+
export declare const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_TIMEOUT_MS = 150;
|
|
5
|
+
type DebugContextFlagClient = Pick<FlagClient, "ready" | "isEnabled" | "close">;
|
|
6
|
+
export interface HookTimeoutDebugContextFlagDependencies {
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
createClient?: (options: FlagClientOptions) => DebugContextFlagClient;
|
|
9
|
+
getToken?: FlagClientOptions["getToken"];
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
reportFailure?: (error: unknown) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read the optional diagnostic gate with a short, cancellable hq-flags request.
|
|
15
|
+
* Missing identity/configuration and every client failure use the default-off
|
|
16
|
+
* result so a flag outage cannot delay or suppress the underlying timeout.
|
|
17
|
+
*/
|
|
18
|
+
export declare function hookTimeoutDebugContextEnabled(dependencies?: HookTimeoutDebugContextFlagDependencies, callerSignal?: AbortSignal): Promise<boolean>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=hook-timeout-debug-context-flag.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { loadCachedTokens } from "@indigoai-us/hq-cloud";
|
|
2
|
+
import { createFlagClient, } from "@indigoai-us/hq-flags-client";
|
|
3
|
+
export const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG = "hook-timeout-debug-context";
|
|
4
|
+
export const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK = false;
|
|
5
|
+
export const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_TIMEOUT_MS = 150;
|
|
6
|
+
function safeErrorClass(error) {
|
|
7
|
+
const name = error instanceof Error ? error.name : "UnknownError";
|
|
8
|
+
return /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(name) ? name : "UnknownError";
|
|
9
|
+
}
|
|
10
|
+
function reportFlagFailure(error) {
|
|
11
|
+
process.stderr.write(`HQ hook timeout debug flag lookup failed (${safeErrorClass(error)}); using default off.\n`);
|
|
12
|
+
}
|
|
13
|
+
function combineSignals(first, second) {
|
|
14
|
+
return first ? AbortSignal.any([first, second]) : second;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read the optional diagnostic gate with a short, cancellable hq-flags request.
|
|
18
|
+
* Missing identity/configuration and every client failure use the default-off
|
|
19
|
+
* result so a flag outage cannot delay or suppress the underlying timeout.
|
|
20
|
+
*/
|
|
21
|
+
export async function hookTimeoutDebugContextEnabled(dependencies = {}, callerSignal) {
|
|
22
|
+
const env = dependencies.env ?? process.env;
|
|
23
|
+
const endpoint = env.HQ_FLAGS_API_URL?.trim() || "";
|
|
24
|
+
const companyUid = env.HQ_COMPANY_UID?.trim() || "";
|
|
25
|
+
if (!endpoint || !/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
|
|
26
|
+
return HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK;
|
|
27
|
+
}
|
|
28
|
+
const companySlug = env.HQ_COMPANY_SLUG?.trim() || "";
|
|
29
|
+
const deadline = AbortSignal.timeout(HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_TIMEOUT_MS);
|
|
30
|
+
const signal = callerSignal ? AbortSignal.any([callerSignal, deadline]) : deadline;
|
|
31
|
+
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
32
|
+
let client;
|
|
33
|
+
let failed = false;
|
|
34
|
+
let failureReported = false;
|
|
35
|
+
let enabled = HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK;
|
|
36
|
+
const reportFailureOnce = (error) => {
|
|
37
|
+
failed = true;
|
|
38
|
+
if (failureReported)
|
|
39
|
+
return;
|
|
40
|
+
failureReported = true;
|
|
41
|
+
(dependencies.reportFailure ?? reportFlagFailure)(error);
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
client = (dependencies.createClient ?? createFlagClient)({
|
|
45
|
+
endpoint,
|
|
46
|
+
companyUid,
|
|
47
|
+
...(companySlug && /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/.test(companySlug)
|
|
48
|
+
? { companyIdentifiers: [companySlug] }
|
|
49
|
+
: {}),
|
|
50
|
+
env: {},
|
|
51
|
+
getToken: dependencies.getToken ?? (() => loadCachedTokens()?.idToken ?? ""),
|
|
52
|
+
fetch: (input, init) => doFetch(input, {
|
|
53
|
+
...init,
|
|
54
|
+
signal: combineSignals(init?.signal ?? undefined, signal),
|
|
55
|
+
}),
|
|
56
|
+
refreshIntervalMs: 0,
|
|
57
|
+
requestTimeoutMs: HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_TIMEOUT_MS,
|
|
58
|
+
onError: reportFailureOnce,
|
|
59
|
+
});
|
|
60
|
+
await client.ready();
|
|
61
|
+
if (!failed && !signal.aborted) {
|
|
62
|
+
enabled = client.isEnabled(HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG, {
|
|
63
|
+
fallback: HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
reportFailureOnce(error);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
try {
|
|
72
|
+
client?.close();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
reportFailureOnce(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return failed || signal.aborted ? HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK : enabled;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=hook-timeout-debug-context-flag.js.map
|
|
@@ -27,14 +27,32 @@ export declare const MAX_SENTRY_REPORT_METADATA_VALUE_BYTES = 256;
|
|
|
27
27
|
* is owner-approved telemetry for the exact hook script and may include a
|
|
28
28
|
* company directory; it is never a substitute for hook input or a transcript.
|
|
29
29
|
*/
|
|
30
|
-
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "shell", "nproc", "hook_sequence"];
|
|
30
|
+
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "shell", "nproc", "hook_sequence", "hook_timeout_debug_context"];
|
|
31
31
|
export declare const MAX_SENTRY_REPORT_METADATA_KEYS: number;
|
|
32
32
|
export type HookSequenceEntry = {
|
|
33
33
|
script: string;
|
|
34
34
|
event: string;
|
|
35
35
|
ms: number;
|
|
36
36
|
};
|
|
37
|
-
export type
|
|
37
|
+
export type HookTimeoutDebugPhaseTiming = {
|
|
38
|
+
phase: string;
|
|
39
|
+
elapsed_ms: number;
|
|
40
|
+
};
|
|
41
|
+
export type HookTimeoutDebugContext = {
|
|
42
|
+
hook_name: string;
|
|
43
|
+
hook_event: string;
|
|
44
|
+
budget_ms: number;
|
|
45
|
+
elapsed_ms: number;
|
|
46
|
+
remaining_ms: number;
|
|
47
|
+
phase_timings: HookTimeoutDebugPhaseTiming[];
|
|
48
|
+
wait_point: string;
|
|
49
|
+
waiting_child_basename: string;
|
|
50
|
+
waiting_child_elapsed_ms: number;
|
|
51
|
+
load_average?: number | "unavailable";
|
|
52
|
+
spawn_ms?: number | "unavailable";
|
|
53
|
+
process_count?: number | "unavailable";
|
|
54
|
+
};
|
|
55
|
+
export type SentryReportMetadataValue = string | number | boolean | HookSequenceEntry[] | HookTimeoutDebugContext;
|
|
38
56
|
export type SentryReportMetadata = Record<string, SentryReportMetadataValue>;
|
|
39
57
|
export type SentryReportEvent = {
|
|
40
58
|
type: string;
|
|
@@ -53,6 +71,8 @@ export type SentryReportOptions = {
|
|
|
53
71
|
rateLimitDirectory?: string;
|
|
54
72
|
/** Test seam for deterministic rate-limit windows. */
|
|
55
73
|
rateLimitNow?: () => number;
|
|
74
|
+
/** Optional caller cancellation forwarded to the bounded flag lookup. */
|
|
75
|
+
signal?: AbortSignal;
|
|
56
76
|
};
|
|
57
77
|
export type SentryReportDependencies = {
|
|
58
78
|
sentry: {
|
|
@@ -67,6 +87,8 @@ export type SentryReportDependencies = {
|
|
|
67
87
|
rateLimitDirectory?: string;
|
|
68
88
|
/** Test seam; production uses Date.now. */
|
|
69
89
|
rateLimitNow?: () => number;
|
|
90
|
+
/** Test seam for the company-scoped, bounded debug-context gate. */
|
|
91
|
+
timeoutDebugContextEnabled?: (signal?: AbortSignal) => Promise<boolean>;
|
|
70
92
|
};
|
|
71
93
|
export type SentryReportResult = {
|
|
72
94
|
code: 0 | 1;
|
|
@@ -23,6 +23,7 @@ import * as Sentry from "@sentry/node";
|
|
|
23
23
|
import { CLI_VERSION } from "../../cli-version.js";
|
|
24
24
|
import { beforeSend } from "../../sentry-before-send.js";
|
|
25
25
|
import { resolveLiveRoot } from "../../utils/hq-roots.js";
|
|
26
|
+
import { hookTimeoutDebugContextEnabled } from "./hook-timeout-debug-context-flag.js";
|
|
26
27
|
export const DEFAULT_SENTRY_REPORT_TIMEOUT_MS = 750;
|
|
27
28
|
export const MAX_SENTRY_REPORT_TIMEOUT_MS = 5_000;
|
|
28
29
|
export const MAX_SENTRY_REPORT_PAYLOAD_BYTES = 2_048;
|
|
@@ -47,6 +48,8 @@ const HOOK_TIMEOUT_EXCEEDED_FINGERPRINT = "hook-timeout-exceeded";
|
|
|
47
48
|
const HOOK_TIMEOUT_WARNING_FINGERPRINT = "hook-timeout";
|
|
48
49
|
const HOOK_TIMEOUT_MACHINE_WIDE_FINGERPRINT = "hq-hook-slow";
|
|
49
50
|
const MAX_HOOK_SEQUENCE_ENTRIES = 20;
|
|
51
|
+
const MAX_HOOK_TIMEOUT_DEBUG_PHASES = 24;
|
|
52
|
+
const MAX_HOOK_TIMEOUT_DEBUG_MS = 86_400_000;
|
|
50
53
|
const HOOK_TIMEOUT_RATE_LIMIT_WINDOW_MS = 10 * 60 * 1_000;
|
|
51
54
|
const RATE_LIMIT_LOCK_WAIT_MS = 500;
|
|
52
55
|
const RATE_LIMIT_LOCK_POLL_MS = 5;
|
|
@@ -65,6 +68,72 @@ const KNOWN_HOOK_SCRIPTS = new Set([
|
|
|
65
68
|
"45-lanes-senior-monitor.sh",
|
|
66
69
|
"lanes-senior-monitor-stop-gate.sh",
|
|
67
70
|
]);
|
|
71
|
+
const DEBUG_CONTEXT_HOOK_NAMES = new Set([
|
|
72
|
+
...KNOWN_HOOK_SCRIPTS,
|
|
73
|
+
"reindex.sh",
|
|
74
|
+
]);
|
|
75
|
+
const DEBUG_CONTEXT_CHILD_NAMES = new Set([
|
|
76
|
+
...DEBUG_CONTEXT_HOOK_NAMES,
|
|
77
|
+
"bash",
|
|
78
|
+
"bash.exe",
|
|
79
|
+
"sh",
|
|
80
|
+
"cmd",
|
|
81
|
+
"cmd.exe",
|
|
82
|
+
"powershell",
|
|
83
|
+
"powershell.exe",
|
|
84
|
+
"wmic.exe",
|
|
85
|
+
"jq",
|
|
86
|
+
"jq.exe",
|
|
87
|
+
"node",
|
|
88
|
+
"node.exe",
|
|
89
|
+
"git",
|
|
90
|
+
"git.exe",
|
|
91
|
+
"python",
|
|
92
|
+
"python.exe",
|
|
93
|
+
"python3",
|
|
94
|
+
"python3.exe",
|
|
95
|
+
"true",
|
|
96
|
+
"sleep",
|
|
97
|
+
"cat",
|
|
98
|
+
"grep",
|
|
99
|
+
"sed",
|
|
100
|
+
"awk",
|
|
101
|
+
"find",
|
|
102
|
+
"timeout",
|
|
103
|
+
"curl",
|
|
104
|
+
"gh",
|
|
105
|
+
"hq",
|
|
106
|
+
"npm",
|
|
107
|
+
"pnpm",
|
|
108
|
+
"npx",
|
|
109
|
+
]);
|
|
110
|
+
const DEBUG_CONTEXT_HOOK_EVENTS = new Set([
|
|
111
|
+
"PreToolUse",
|
|
112
|
+
"PostToolUse",
|
|
113
|
+
"UserPromptSubmit",
|
|
114
|
+
"SessionStart",
|
|
115
|
+
"SessionEnd",
|
|
116
|
+
"Stop",
|
|
117
|
+
"PreCompact",
|
|
118
|
+
"Notification",
|
|
119
|
+
"PermissionRequest",
|
|
120
|
+
"SubagentStart",
|
|
121
|
+
"SubagentStop",
|
|
122
|
+
"TaskCompleted",
|
|
123
|
+
"TeammateIdle",
|
|
124
|
+
]);
|
|
125
|
+
const DEBUG_CONTEXT_PHASES = new Set([
|
|
126
|
+
"startup",
|
|
127
|
+
"source",
|
|
128
|
+
"config_load",
|
|
129
|
+
"policy_load",
|
|
130
|
+
"external_command",
|
|
131
|
+
"output_write",
|
|
132
|
+
"wait",
|
|
133
|
+
"child_wait",
|
|
134
|
+
"probe",
|
|
135
|
+
"parse",
|
|
136
|
+
]);
|
|
68
137
|
/**
|
|
69
138
|
* The only hook diagnostics that can be attached as metadata. The list omits
|
|
70
139
|
* command text, prompts, payloads, company names, and repositories. `hook_path`
|
|
@@ -96,6 +165,7 @@ export const ALLOWED_SENTRY_REPORT_METADATA_KEYS = [
|
|
|
96
165
|
"shell",
|
|
97
166
|
"nproc",
|
|
98
167
|
"hook_sequence",
|
|
168
|
+
"hook_timeout_debug_context",
|
|
99
169
|
];
|
|
100
170
|
const ALLOWED_METADATA_KEYS = new Set(ALLOWED_SENTRY_REPORT_METADATA_KEYS);
|
|
101
171
|
const SENTRY_REPORT_METADATA_KEY_HEADROOM = 2;
|
|
@@ -107,6 +177,7 @@ const defaultDependencies = {
|
|
|
107
177
|
sentry: Sentry,
|
|
108
178
|
stdout: process.stdout,
|
|
109
179
|
stderr: process.stderr,
|
|
180
|
+
timeoutDebugContextEnabled: (signal) => hookTimeoutDebugContextEnabled({}, signal),
|
|
110
181
|
};
|
|
111
182
|
/**
|
|
112
183
|
* Read no more than the accepted event size. Returning early keeps a malformed
|
|
@@ -153,6 +224,90 @@ function isHookSequenceEntry(value) {
|
|
|
153
224
|
Number.isFinite(value.ms) &&
|
|
154
225
|
value.ms >= 0);
|
|
155
226
|
}
|
|
227
|
+
function boundedDebugName(value, known) {
|
|
228
|
+
if (typeof value !== "string" || value.length === 0)
|
|
229
|
+
return "other";
|
|
230
|
+
return known.has(basenameFromPath(value)) ? basenameFromPath(value) : "other";
|
|
231
|
+
}
|
|
232
|
+
function boundedDebugPhase(value) {
|
|
233
|
+
return typeof value === "string" && DEBUG_CONTEXT_PHASES.has(value) ? value : "other";
|
|
234
|
+
}
|
|
235
|
+
function boundedDebugHookEvent(value) {
|
|
236
|
+
return typeof value === "string" && DEBUG_CONTEXT_HOOK_EVENTS.has(value) ? value : "other";
|
|
237
|
+
}
|
|
238
|
+
function boundedDebugMilliseconds(value, allowNegative = false) {
|
|
239
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
240
|
+
return null;
|
|
241
|
+
const bounded = allowNegative
|
|
242
|
+
? Math.abs(value) <= MAX_HOOK_TIMEOUT_DEBUG_MS
|
|
243
|
+
: value >= 0 && value <= MAX_HOOK_TIMEOUT_DEBUG_MS;
|
|
244
|
+
return bounded ? value : null;
|
|
245
|
+
}
|
|
246
|
+
function boundedDebugProbe(value) {
|
|
247
|
+
if (value === "unavailable")
|
|
248
|
+
return value;
|
|
249
|
+
return boundedDebugMilliseconds(value) ?? undefined;
|
|
250
|
+
}
|
|
251
|
+
function boundedDebugPhaseTimings(value) {
|
|
252
|
+
if (!Array.isArray(value) || value.length > MAX_HOOK_TIMEOUT_DEBUG_PHASES)
|
|
253
|
+
return null;
|
|
254
|
+
const timings = [];
|
|
255
|
+
for (const entry of value) {
|
|
256
|
+
if (!isRecord(entry))
|
|
257
|
+
return null;
|
|
258
|
+
const elapsed = boundedDebugMilliseconds(entry.elapsed_ms);
|
|
259
|
+
if (elapsed === null)
|
|
260
|
+
return null;
|
|
261
|
+
const timing = {
|
|
262
|
+
phase: boundedDebugPhase(entry.phase),
|
|
263
|
+
elapsed_ms: elapsed,
|
|
264
|
+
};
|
|
265
|
+
timings.push(timing);
|
|
266
|
+
}
|
|
267
|
+
return timings;
|
|
268
|
+
}
|
|
269
|
+
/** Return only the closed, bounded shell-to-CLI context shape. */
|
|
270
|
+
function boundedHookTimeoutDebugContext(value) {
|
|
271
|
+
if (!isRecord(value))
|
|
272
|
+
return null;
|
|
273
|
+
const budget = boundedDebugMilliseconds(value.budget_ms);
|
|
274
|
+
const elapsed = boundedDebugMilliseconds(value.elapsed_ms);
|
|
275
|
+
const remaining = boundedDebugMilliseconds(value.remaining_ms, true);
|
|
276
|
+
const childElapsed = boundedDebugMilliseconds(value.waiting_child_elapsed_ms);
|
|
277
|
+
const phaseTimings = boundedDebugPhaseTimings(value.phase_timings);
|
|
278
|
+
if (budget === null ||
|
|
279
|
+
elapsed === null ||
|
|
280
|
+
remaining === null ||
|
|
281
|
+
childElapsed === null ||
|
|
282
|
+
phaseTimings === null) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
const loadAverage = boundedDebugProbe(value.load_average);
|
|
286
|
+
const spawnMs = boundedDebugProbe(value.spawn_ms);
|
|
287
|
+
const processCount = boundedDebugProbe(value.process_count);
|
|
288
|
+
const hasLoadAverage = typeof loadAverage === "number";
|
|
289
|
+
const hasWindowsProbe = spawnMs !== undefined && processCount !== undefined;
|
|
290
|
+
if (!hasLoadAverage && !hasWindowsProbe)
|
|
291
|
+
return null;
|
|
292
|
+
if (typeof processCount === "number" &&
|
|
293
|
+
(!Number.isInteger(processCount) || processCount > 1_000_000)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
hook_name: boundedDebugName(value.hook_name, DEBUG_CONTEXT_HOOK_NAMES),
|
|
298
|
+
hook_event: boundedDebugHookEvent(value.hook_event),
|
|
299
|
+
budget_ms: budget,
|
|
300
|
+
elapsed_ms: elapsed,
|
|
301
|
+
remaining_ms: remaining,
|
|
302
|
+
phase_timings: phaseTimings,
|
|
303
|
+
wait_point: boundedDebugPhase(value.wait_point),
|
|
304
|
+
waiting_child_basename: boundedDebugName(value.waiting_child_basename, DEBUG_CONTEXT_CHILD_NAMES),
|
|
305
|
+
waiting_child_elapsed_ms: childElapsed,
|
|
306
|
+
...(loadAverage !== undefined ? { load_average: loadAverage } : {}),
|
|
307
|
+
...(spawnMs !== undefined ? { spawn_ms: spawnMs } : {}),
|
|
308
|
+
...(processCount !== undefined ? { process_count: processCount } : {}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
156
311
|
function boundedHookSequence(value) {
|
|
157
312
|
if (!Array.isArray(value))
|
|
158
313
|
return null;
|
|
@@ -168,6 +323,8 @@ function boundedHookSequence(value) {
|
|
|
168
323
|
function isAllowedMetadataValue(key, value) {
|
|
169
324
|
if (key === "hook_sequence")
|
|
170
325
|
return boundedHookSequence(value) !== null;
|
|
326
|
+
if (key === "hook_timeout_debug_context")
|
|
327
|
+
return boundedHookTimeoutDebugContext(value) !== null;
|
|
171
328
|
if (!isScalarMetadataValue(value))
|
|
172
329
|
return false;
|
|
173
330
|
if (key === "tool_name" || key === "session_id" || key === "hook_path") {
|
|
@@ -236,9 +393,10 @@ export function parseSentryReportEvent(stdin) {
|
|
|
236
393
|
}
|
|
237
394
|
if (!isAllowedMetadataValue(key, rawMetadata[key]))
|
|
238
395
|
return invalidInput();
|
|
239
|
-
metadata[key] =
|
|
240
|
-
key
|
|
241
|
-
|
|
396
|
+
metadata[key] = key === "hook_sequence"
|
|
397
|
+
? boundedHookSequence(rawMetadata[key])
|
|
398
|
+
: key === "hook_timeout_debug_context"
|
|
399
|
+
? boundedHookTimeoutDebugContext(rawMetadata[key])
|
|
242
400
|
: rawMetadata[key];
|
|
243
401
|
}
|
|
244
402
|
if (typeof metadata.slow_child === "string") {
|
|
@@ -319,6 +477,11 @@ function elapsedBucket(elapsedMs) {
|
|
|
319
477
|
return "20-60s";
|
|
320
478
|
return ">=60s";
|
|
321
479
|
}
|
|
480
|
+
function remainingBucket(remainingMs) {
|
|
481
|
+
return remainingMs < 0
|
|
482
|
+
? `overdue-${elapsedBucket(Math.abs(remainingMs))}`
|
|
483
|
+
: elapsedBucket(remainingMs);
|
|
484
|
+
}
|
|
322
485
|
function loadBucket(loadAverage) {
|
|
323
486
|
if (loadAverage === undefined)
|
|
324
487
|
return "unknown";
|
|
@@ -368,6 +531,53 @@ function spawnMsBucket(metadata) {
|
|
|
368
531
|
return "500-999ms";
|
|
369
532
|
return ">=1000ms";
|
|
370
533
|
}
|
|
534
|
+
function debugSpawnMsBucket(value) {
|
|
535
|
+
if (typeof value !== "number")
|
|
536
|
+
return "unavailable";
|
|
537
|
+
if (value < 100)
|
|
538
|
+
return "<100ms";
|
|
539
|
+
if (value < 500)
|
|
540
|
+
return "100-499ms";
|
|
541
|
+
if (value < 1_000)
|
|
542
|
+
return "500-999ms";
|
|
543
|
+
return ">=1000ms";
|
|
544
|
+
}
|
|
545
|
+
function processCountBucket(value) {
|
|
546
|
+
if (typeof value !== "number")
|
|
547
|
+
return "unavailable";
|
|
548
|
+
if (value < 5)
|
|
549
|
+
return "0-4";
|
|
550
|
+
if (value < 17)
|
|
551
|
+
return "5-16";
|
|
552
|
+
if (value < 65)
|
|
553
|
+
return "17-64";
|
|
554
|
+
return "65+";
|
|
555
|
+
}
|
|
556
|
+
function hookTimeoutDebugContextTags(context) {
|
|
557
|
+
const tags = {
|
|
558
|
+
timeout_debug_hook_name: context.hook_name,
|
|
559
|
+
timeout_debug_hook_event: context.hook_event,
|
|
560
|
+
timeout_debug_budget_bucket: elapsedBucket(context.budget_ms),
|
|
561
|
+
timeout_debug_elapsed_bucket: elapsedBucket(context.elapsed_ms),
|
|
562
|
+
timeout_debug_remaining_bucket: remainingBucket(context.remaining_ms),
|
|
563
|
+
timeout_debug_wait_point: context.wait_point,
|
|
564
|
+
timeout_debug_waiting_child: context.waiting_child_basename,
|
|
565
|
+
timeout_debug_waiting_child_elapsed_bucket: elapsedBucket(context.waiting_child_elapsed_ms),
|
|
566
|
+
timeout_debug_load_bucket: context.load_average === "unavailable"
|
|
567
|
+
? "unavailable"
|
|
568
|
+
: loadBucket(context.load_average),
|
|
569
|
+
timeout_debug_spawn_ms_bucket: debugSpawnMsBucket(context.spawn_ms),
|
|
570
|
+
timeout_debug_process_count_bucket: processCountBucket(context.process_count),
|
|
571
|
+
};
|
|
572
|
+
const phaseElapsed = new Map();
|
|
573
|
+
for (const timing of context.phase_timings) {
|
|
574
|
+
phaseElapsed.set(timing.phase, Math.max(phaseElapsed.get(timing.phase) ?? 0, timing.elapsed_ms));
|
|
575
|
+
}
|
|
576
|
+
for (const [phase, elapsedMs] of phaseElapsed) {
|
|
577
|
+
tags[`timeout_debug_phase_${phase}_bucket`] = elapsedBucket(elapsedMs);
|
|
578
|
+
}
|
|
579
|
+
return tags;
|
|
580
|
+
}
|
|
371
581
|
function safeSlowChild(metadata) {
|
|
372
582
|
const value = metadata.slow_child;
|
|
373
583
|
if (typeof value !== "string" || value.length === 0)
|
|
@@ -464,10 +674,10 @@ function sentryEnvelope(event, overrides, additionalTags = {}) {
|
|
|
464
674
|
};
|
|
465
675
|
}
|
|
466
676
|
/** Build the same scrubbed event projection that the live SDK path uses. */
|
|
467
|
-
function scrubbedSentryEnvelope(event, overrides) {
|
|
677
|
+
function scrubbedSentryEnvelope(event, overrides, additionalTags = {}) {
|
|
468
678
|
return beforeSend({
|
|
469
679
|
message: event.message,
|
|
470
|
-
...sentryEnvelope(event, overrides),
|
|
680
|
+
...sentryEnvelope(event, overrides, additionalTags),
|
|
471
681
|
}, {});
|
|
472
682
|
}
|
|
473
683
|
/**
|
|
@@ -740,19 +950,47 @@ export async function reportSentryEvent(stdin, options = {}, dependencies = defa
|
|
|
740
950
|
const noun = result.droppedMetadataKeys === 1 ? "key" : "keys";
|
|
741
951
|
dependencies.stderr.write(`Dropped ${result.droppedMetadataKeys} unsupported Sentry report metadata ${noun}.\n`);
|
|
742
952
|
}
|
|
743
|
-
|
|
953
|
+
let event = result.event;
|
|
954
|
+
let debugContextTags = {};
|
|
955
|
+
const debugContext = event.metadata.hook_timeout_debug_context;
|
|
956
|
+
if (debugContext && isHookTimeoutEvent(event)) {
|
|
957
|
+
let enabled;
|
|
958
|
+
try {
|
|
959
|
+
enabled =
|
|
960
|
+
(await dependencies.timeoutDebugContextEnabled?.(options.signal)) ?? false;
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
// An optional diagnostic flag read must never suppress the base report.
|
|
964
|
+
enabled = false;
|
|
965
|
+
}
|
|
966
|
+
if (enabled) {
|
|
967
|
+
debugContextTags = hookTimeoutDebugContextTags(debugContext);
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
const metadata = { ...event.metadata };
|
|
971
|
+
delete metadata.hook_timeout_debug_context;
|
|
972
|
+
event = { ...event, metadata };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else if (debugContext) {
|
|
976
|
+
const metadata = { ...event.metadata };
|
|
977
|
+
delete metadata.hook_timeout_debug_context;
|
|
978
|
+
event = { ...event, metadata };
|
|
979
|
+
}
|
|
980
|
+
result.event = event;
|
|
981
|
+
const overrides = hookTimeoutExceededOverrides(event);
|
|
744
982
|
if (options.dryRun) {
|
|
745
|
-
const scrubbed = scrubbedSentryEnvelope(
|
|
983
|
+
const scrubbed = scrubbedSentryEnvelope(event, overrides, debugContextTags);
|
|
746
984
|
if (scrubbed)
|
|
747
985
|
dependencies.stdout.write(`${JSON.stringify(scrubbed)}\n`);
|
|
748
986
|
return result;
|
|
749
987
|
}
|
|
750
|
-
if (isHookTimeoutEvent(
|
|
751
|
-
addHookSequenceBreadcrumbs(
|
|
752
|
-
const kind = hookTimeoutKind(
|
|
988
|
+
if (isHookTimeoutEvent(event)) {
|
|
989
|
+
addHookSequenceBreadcrumbs(event, dependencies);
|
|
990
|
+
const kind = hookTimeoutKind(event);
|
|
753
991
|
let rateLimit;
|
|
754
992
|
try {
|
|
755
|
-
rateLimit = timeoutRateLimitLease(
|
|
993
|
+
rateLimit = timeoutRateLimitLease(event, kind, options, dependencies);
|
|
756
994
|
}
|
|
757
995
|
catch {
|
|
758
996
|
dependencies.stderr.write("Sentry timeout rate limit state unavailable; sending without rate limiting.\n");
|
|
@@ -764,14 +1002,14 @@ export async function reportSentryEvent(stdin, options = {}, dependencies = defa
|
|
|
764
1002
|
};
|
|
765
1003
|
}
|
|
766
1004
|
if (!rateLimit.capture) {
|
|
767
|
-
return reportSuppressedTimeout(result,
|
|
1005
|
+
return reportSuppressedTimeout(result, event, timeoutMs, dependencies, kind);
|
|
768
1006
|
}
|
|
769
1007
|
const tags = rateLimit.suppressedSinceLast > 0
|
|
770
1008
|
? { suppressed_since_last: String(rateLimit.suppressedSinceLast) }
|
|
771
1009
|
: {};
|
|
772
|
-
const envelope = sentryEnvelope(
|
|
1010
|
+
const envelope = sentryEnvelope(event, overrides, { ...debugContextTags, ...tags });
|
|
773
1011
|
try {
|
|
774
|
-
dependencies.sentry.captureMessage(
|
|
1012
|
+
dependencies.sentry.captureMessage(event.message, envelope);
|
|
775
1013
|
rateLimit.commitCapture();
|
|
776
1014
|
if (await flushWithin(dependencies.sentry, timeoutMs))
|
|
777
1015
|
return result;
|
|
@@ -783,9 +1021,9 @@ export async function reportSentryEvent(stdin, options = {}, dependencies = defa
|
|
|
783
1021
|
dependencies.stderr.write("Sentry report failed.\n");
|
|
784
1022
|
return invalidInput();
|
|
785
1023
|
}
|
|
786
|
-
const envelope = sentryEnvelope(
|
|
1024
|
+
const envelope = sentryEnvelope(event, overrides, debugContextTags);
|
|
787
1025
|
try {
|
|
788
|
-
dependencies.sentry.captureMessage(
|
|
1026
|
+
dependencies.sentry.captureMessage(event.message, envelope);
|
|
789
1027
|
if (await flushWithin(dependencies.sentry, timeoutMs))
|
|
790
1028
|
return result;
|
|
791
1029
|
}
|
|
@@ -12,7 +12,7 @@ export type { DaemonStateFile, PresenceRefusalState } from "./state.js";
|
|
|
12
12
|
export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
|
|
13
13
|
export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
|
|
14
14
|
export type { BoardReader, BoardRefreshDeps, BoardStorySnapshot } from "./board-refresh.js";
|
|
15
|
-
export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonIfStale, installDaemonService, isNewerCliVersion, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveAbsoluteNodeBinary, resolveHqBinary, resolveNodeBinary, resolveRunningCliEntry, parseSchtasksLastResult, parseWindowsTaskHqRoot, readRecordedWindowsHqRoot, schtasksLastResultFailed, uninstallDaemonService, windowsLaunchMode, windowsLogonNotice, } from "./install.js";
|
|
15
|
+
export { buildInstallPaths, daemonServiceIsLoaded, daemonServiceStatus, detectPlatform, installDaemonIfStale, installDaemonService, isNewerCliVersion, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveAbsoluteNodeBinary, resolveHqBinary, resolveNodeBinary, resolveRunningCliEntry, parseSchtasksLastResult, parseWindowsTaskHqRoot, readRecordedWindowsHqRoot, schtasksLastResultFailed, uninstallDaemonService, windowsLaunchMode, windowsLogonNotice, } from "./install.js";
|
|
16
16
|
export type { DaemonInstallPlatform, InstallIfStaleResult, InstallPaths, InstallResult, } from "./install.js";
|
|
17
17
|
export { daemonConfigPath, findAncestorHqRoot, isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, resolveInstallHqRoot, verifyHqRoot, } from "./config.js";
|
|
18
18
|
export type { InstallHqRootResolution } from "./config.js";
|
|
@@ -6,7 +6,7 @@ export { CLOSE_LOG_ALWAYS_COUNT, CLOSE_LOG_INTERVAL_MS, CONNECT_STABLE_GRACE_MS,
|
|
|
6
6
|
export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
|
|
7
7
|
export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
|
|
8
8
|
export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
|
|
9
|
-
export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonIfStale, installDaemonService, isNewerCliVersion, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveAbsoluteNodeBinary, resolveHqBinary, resolveNodeBinary, resolveRunningCliEntry, parseSchtasksLastResult, parseWindowsTaskHqRoot, readRecordedWindowsHqRoot, schtasksLastResultFailed, uninstallDaemonService, windowsLaunchMode, windowsLogonNotice, } from "./install.js";
|
|
9
|
+
export { buildInstallPaths, daemonServiceIsLoaded, daemonServiceStatus, detectPlatform, installDaemonIfStale, installDaemonService, isNewerCliVersion, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveAbsoluteNodeBinary, resolveHqBinary, resolveNodeBinary, resolveRunningCliEntry, parseSchtasksLastResult, parseWindowsTaskHqRoot, readRecordedWindowsHqRoot, schtasksLastResultFailed, uninstallDaemonService, windowsLaunchMode, windowsLogonNotice, } from "./install.js";
|
|
10
10
|
export { daemonConfigPath, findAncestorHqRoot, isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, resolveInstallHqRoot, verifyHqRoot, } from "./config.js";
|
|
11
11
|
export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatCliIdentityLines, formatCliMismatchLine, formatDaemonDoctor, listHqBinariesOnPath, } from "./doctor.js";
|
|
12
12
|
export { scopeBundleToAgentIdentity, } from "./credentials.js";
|
|
@@ -312,6 +312,11 @@ export declare function uninstallDaemonService(opts: {
|
|
|
312
312
|
/** Windows: terminate the recorded daemon pid after `schtasks /End` (tests inject). */
|
|
313
313
|
kill?: (pid: number) => void;
|
|
314
314
|
}): InstallResult;
|
|
315
|
+
/**
|
|
316
|
+
* Live service-manager probe for `daemonServiceStatus`: `launchctl print
|
|
317
|
+
* gui/<uid>/<label>` on macOS, `systemctl --user is-active` on Linux.
|
|
318
|
+
*/
|
|
319
|
+
export declare function daemonServiceIsLoaded(run: RunCommand, uid?: number): (platform: DaemonInstallPlatform, dest: string) => boolean;
|
|
315
320
|
export declare function daemonServiceStatus(opts: {
|
|
316
321
|
platform?: DaemonInstallPlatform;
|
|
317
322
|
paths?: InstallPaths;
|
|
@@ -22,8 +22,8 @@ import { isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, verifyHqR
|
|
|
22
22
|
import { defaultPidLockDeps, pidLockStatus, readPidLock } from "./pid-lock.js";
|
|
23
23
|
import { retireLegacyListen } from "./legacy-listen.js";
|
|
24
24
|
import { readDaemonState } from "./state.js";
|
|
25
|
-
import { activateLaunchd, launchdPlistProgramArguments, launchdProgramArgumentsDiffer, parseLaunchctlPrintArguments, } from "../../../service-manager/launchd.js";
|
|
26
|
-
import { activateSystemd } from "../../../service-manager/systemd.js";
|
|
25
|
+
import { activateLaunchd, launchdIsLoaded, launchdPlistProgramArguments, launchdProgramArgumentsDiffer, parseLaunchctlPrintArguments, } from "../../../service-manager/launchd.js";
|
|
26
|
+
import { activateSystemd, systemdIsActive } from "../../../service-manager/systemd.js";
|
|
27
27
|
import { nodeBinaryProblem, preferStableNodeBinary } from "../../../node-binary.js";
|
|
28
28
|
/**
|
|
29
29
|
* `schtasks /End` terminates the task's root process only. The daemon is
|
|
@@ -990,6 +990,19 @@ export function uninstallDaemonService(opts) {
|
|
|
990
990
|
message: "Nothing to uninstall on this platform",
|
|
991
991
|
};
|
|
992
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* Live service-manager probe for `daemonServiceStatus`: `launchctl print
|
|
995
|
+
* gui/<uid>/<label>` on macOS, `systemctl --user is-active` on Linux.
|
|
996
|
+
*/
|
|
997
|
+
export function daemonServiceIsLoaded(run, uid) {
|
|
998
|
+
return (platform) => {
|
|
999
|
+
if (platform === "darwin")
|
|
1000
|
+
return launchdIsLoaded(LAUNCHD_LABEL, run, uid);
|
|
1001
|
+
if (platform === "linux")
|
|
1002
|
+
return systemdIsActive(SYSTEMD_UNIT_NAME, run);
|
|
1003
|
+
return false;
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
993
1006
|
export function daemonServiceStatus(opts) {
|
|
994
1007
|
const platform = opts.platform ?? detectPlatform();
|
|
995
1008
|
const paths = opts.paths ?? buildInstallPaths({});
|