@pushary/agent-hooks 0.89.7 → 0.89.9
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 +30 -0
- package/dist/bin/pushary-bell-hook.js +3 -3
- package/dist/bin/pushary-bell.js +3 -3
- package/dist/bin/pushary-claude.js +5 -5
- package/dist/bin/pushary-clean.js +13 -10
- package/dist/bin/pushary-codex-bridge.js +3 -3
- package/dist/bin/pushary-codex-hook.js +3 -3
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-connect.js +2 -2
- package/dist/bin/pushary-daemon.js +49 -14
- package/dist/bin/pushary-doctor.js +29 -8
- package/dist/bin/pushary-elicitation-hook.js +2 -2
- package/dist/bin/pushary-gemini-bridge.js +3 -3
- package/dist/bin/pushary-gemini-hook.js +3 -3
- package/dist/bin/pushary-hook.js +4 -4
- package/dist/bin/pushary-login.js +2 -2
- package/dist/bin/pushary-logout.js +2 -2
- package/dist/bin/pushary-notification-hook.js +3 -3
- package/dist/bin/pushary-opencode-hook.js +3 -3
- package/dist/bin/pushary-permission-denied-hook.js +4 -4
- package/dist/bin/pushary-permission-hook.js +4 -4
- package/dist/bin/pushary-post-hook.js +3 -3
- package/dist/bin/pushary-prompt-hook.js +3 -3
- package/dist/bin/pushary-session-end-hook.js +3 -3
- package/dist/bin/pushary-session-start-hook.js +3 -3
- package/dist/bin/pushary-setup.js +20 -7
- package/dist/bin/pushary-status.js +4 -4
- package/dist/bin/pushary-stop-hook.js +3 -3
- package/dist/bin/pushary-stopfailure-hook.js +3 -3
- package/dist/bin/pushary-upgrade.js +16 -8
- package/dist/{chunk-BPKMGE7L.js → chunk-4XZCDSNG.js} +1 -1
- package/dist/{chunk-ZH7TWAYK.js → chunk-543KNJKC.js} +390 -37
- package/dist/{chunk-2M66JVZM.js → chunk-DKDDLF5P.js} +1 -1
- package/dist/{chunk-2MEJBUOI.js → chunk-EA3E74P2.js} +1 -1
- package/dist/{chunk-5YUVKPTS.js → chunk-EJT2ER6G.js} +2 -2
- package/dist/{chunk-WQGMUIDW.js → chunk-FX5QSMPY.js} +1 -1
- package/dist/{chunk-42TTFZLV.js → chunk-TF247BET.js} +1 -1
- package/dist/{chunk-TESGYWEM.js → chunk-U5TQCQ25.js} +32 -1
- package/dist/{chunk-L3YUHEB6.js → chunk-V6N4Q223.js} +1 -1
- package/dist/{chunk-6MTNS63X.js → chunk-XX2V4QS5.js} +12 -1
- package/dist/src/index.js +4 -4
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-PZA7G4CN.js";
|
|
8
8
|
import {
|
|
9
9
|
getMachineId
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DKDDLF5P.js";
|
|
11
11
|
import {
|
|
12
12
|
pusharyDir
|
|
13
13
|
} from "./chunk-GMI5QZLG.js";
|
|
@@ -17,24 +17,47 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
readJsonSafe,
|
|
19
19
|
writeFileAtomic,
|
|
20
|
-
writeJsonAtomic
|
|
21
|
-
|
|
20
|
+
writeJsonAtomic,
|
|
21
|
+
writeJsonAtomicDurable
|
|
22
|
+
} from "./chunk-XX2V4QS5.js";
|
|
22
23
|
import {
|
|
23
24
|
getApiKey
|
|
24
25
|
} from "./chunk-2UMNXADU.js";
|
|
25
26
|
|
|
26
27
|
// src/daemon-lifecycle.ts
|
|
27
28
|
import { dirname, join, win32 as win32Path } from "path";
|
|
28
|
-
import { existsSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
29
30
|
import { createHash, randomUUID } from "crypto";
|
|
30
31
|
import { homedir } from "os";
|
|
31
32
|
import { execFileSync } from "child_process";
|
|
32
33
|
var daemonNeedsHandoff = (status) => status.kind === "version-mismatch" || status.kind === "configuration-mismatch" || status.kind === "unresponsive" && status.processAlive === true;
|
|
34
|
+
var DAEMON_REMOTE_PROOF_MAX_AGE_MS = 18e4;
|
|
35
|
+
var DAEMON_REMOTE_PROOF_CLOCK_SKEW_MS = 3e4;
|
|
36
|
+
var remoteProofIsFresh = (timestamp, now) => {
|
|
37
|
+
if (timestamp === null) return false;
|
|
38
|
+
const age = now - Date.parse(timestamp);
|
|
39
|
+
return Number.isFinite(age) && age >= -DAEMON_REMOTE_PROOF_CLOCK_SKEW_MS && age < DAEMON_REMOTE_PROOF_MAX_AGE_MS;
|
|
40
|
+
};
|
|
41
|
+
var daemonHasRemoteProof = (status, now = Date.now()) => status.kind === "running" && remoteProofIsFresh(status.lastSuccessfulHeartbeatAt, now) && remoteProofIsFresh(status.lastSuccessfulControlAt, now);
|
|
42
|
+
var runDaemonServiceCommand = (command, args) => execFileSync(command, [...args], {
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
45
|
+
windowsHide: true
|
|
46
|
+
});
|
|
47
|
+
var commandFailureOutput = (error) => {
|
|
48
|
+
const failure = error;
|
|
49
|
+
return [failure.stdout, failure.stderr, failure.message].map((value) => Buffer.isBuffer(value) ? value.toString("utf-8") : typeof value === "string" ? value : "").filter(Boolean).join("\n");
|
|
50
|
+
};
|
|
51
|
+
var isExpectedProbeFailure = (probe, error) => {
|
|
52
|
+
const failure = error;
|
|
53
|
+
const status = typeof failure.status === "number" ? failure.status : typeof failure.code === "number" ? failure.code : null;
|
|
54
|
+
const exitMatches = probe.negativeExitCodes === void 0 || status !== null && probe.negativeExitCodes.includes(status);
|
|
55
|
+
const outputMatches = probe.negativeOutputPattern === void 0 || probe.negativeOutputPattern.test(commandFailureOutput(error));
|
|
56
|
+
return (probe.negativeExitCodes !== void 0 || probe.negativeOutputPattern !== void 0) && exitMatches && outputMatches;
|
|
57
|
+
};
|
|
33
58
|
var daemonServiceManagerAvailable = (options = {}) => {
|
|
34
59
|
const platform = options.platform ?? process.platform;
|
|
35
|
-
const run = options.run ??
|
|
36
|
-
execFileSync(command, [...args], { stdio: "ignore", windowsHide: true });
|
|
37
|
-
});
|
|
60
|
+
const run = options.run ?? runDaemonServiceCommand;
|
|
38
61
|
try {
|
|
39
62
|
if (platform === "darwin") {
|
|
40
63
|
const uid = options.uid ?? process.getuid?.();
|
|
@@ -48,6 +71,12 @@ var daemonServiceManagerAvailable = (options = {}) => {
|
|
|
48
71
|
}
|
|
49
72
|
if (platform === "win32") {
|
|
50
73
|
run("sc.exe", ["query", "Schedule"]);
|
|
74
|
+
run("powershell.exe", [
|
|
75
|
+
"-NoProfile",
|
|
76
|
+
"-NonInteractive",
|
|
77
|
+
"-Command",
|
|
78
|
+
"Get-Command Get-ScheduledTask -ErrorAction Stop | Out-Null"
|
|
79
|
+
]);
|
|
51
80
|
return true;
|
|
52
81
|
}
|
|
53
82
|
return false;
|
|
@@ -103,8 +132,25 @@ var buildDaemonService = (input) => {
|
|
|
103
132
|
],
|
|
104
133
|
start: [{ command: "launchctl", args: ["kickstart", "-k", target] }],
|
|
105
134
|
stop: [{ command: "launchctl", args: ["kill", "SIGTERM", target], ignoreFailure: true }],
|
|
135
|
+
disable: [{ command: "launchctl", args: ["disable", target] }],
|
|
136
|
+
canDisableWhenUnregistered: true,
|
|
106
137
|
uninstall: [{ command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true }],
|
|
107
|
-
registrationProbe: {
|
|
138
|
+
registrationProbe: {
|
|
139
|
+
command: "launchctl",
|
|
140
|
+
args: ["print", target],
|
|
141
|
+
negativeExitCodes: [113]
|
|
142
|
+
},
|
|
143
|
+
enabledProbe: {
|
|
144
|
+
command: "launchctl",
|
|
145
|
+
args: ["print-disabled", domain],
|
|
146
|
+
disabledOutputPattern: new RegExp(`"${label}"\\s*=>\\s*disabled`)
|
|
147
|
+
},
|
|
148
|
+
runningProbe: {
|
|
149
|
+
command: "launchctl",
|
|
150
|
+
args: ["print", target],
|
|
151
|
+
outputPattern: /\bstate = running\b/,
|
|
152
|
+
negativeExitCodes: [113]
|
|
153
|
+
}
|
|
108
154
|
};
|
|
109
155
|
}
|
|
110
156
|
if (input.platform === "linux") {
|
|
@@ -134,10 +180,20 @@ WantedBy=default.target
|
|
|
134
180
|
],
|
|
135
181
|
start: [{ command: "systemctl", args: ["--user", "start", unit] }],
|
|
136
182
|
stop: [{ command: "systemctl", args: ["--user", "stop", unit], ignoreFailure: true }],
|
|
183
|
+
disable: [{ command: "systemctl", args: ["--user", "disable", unit] }],
|
|
137
184
|
uninstall: [
|
|
138
185
|
{ command: "systemctl", args: ["--user", "disable", "--now", unit], ignoreFailure: true }
|
|
139
186
|
],
|
|
140
|
-
registrationProbe: {
|
|
187
|
+
registrationProbe: {
|
|
188
|
+
command: "systemctl",
|
|
189
|
+
args: ["--user", "is-enabled", unit],
|
|
190
|
+
negativeOutputPattern: /^(?:disabled|not-found)\s*$/m
|
|
191
|
+
},
|
|
192
|
+
runningProbe: {
|
|
193
|
+
command: "systemctl",
|
|
194
|
+
args: ["--user", "is-active", unit],
|
|
195
|
+
negativeOutputPattern: /^(?:inactive|failed|unknown)\s*$/m
|
|
196
|
+
},
|
|
141
197
|
reloadAfterUninstall: { command: "systemctl", args: ["--user", "daemon-reload"] }
|
|
142
198
|
};
|
|
143
199
|
}
|
|
@@ -175,8 +231,38 @@ WantedBy=default.target
|
|
|
175
231
|
],
|
|
176
232
|
start: [{ command: "schtasks.exe", args: ["/Run", "/TN", taskName] }],
|
|
177
233
|
stop: [{ command: "schtasks.exe", args: ["/End", "/TN", taskName], ignoreFailure: true }],
|
|
234
|
+
disable: [{ command: "schtasks.exe", args: ["/Change", "/TN", taskName, "/Disable"] }],
|
|
178
235
|
uninstall: [{ command: "schtasks.exe", args: ["/Delete", "/TN", taskName, "/F"], ignoreFailure: true }],
|
|
179
|
-
registrationProbe: {
|
|
236
|
+
registrationProbe: {
|
|
237
|
+
command: "powershell.exe",
|
|
238
|
+
args: [
|
|
239
|
+
"-NoProfile",
|
|
240
|
+
"-NonInteractive",
|
|
241
|
+
"-Command",
|
|
242
|
+
"$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }"
|
|
243
|
+
],
|
|
244
|
+
negativeExitCodes: [3]
|
|
245
|
+
},
|
|
246
|
+
enabledProbe: {
|
|
247
|
+
command: "powershell.exe",
|
|
248
|
+
args: [
|
|
249
|
+
"-NoProfile",
|
|
250
|
+
"-NonInteractive",
|
|
251
|
+
"-Command",
|
|
252
|
+
"$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }; if ($null -eq $task.Settings -or $null -eq $task.Settings.Enabled) { exit 5 }; if (-not $task.Settings.Enabled) { exit 4 }"
|
|
253
|
+
],
|
|
254
|
+
negativeExitCodes: [3, 4]
|
|
255
|
+
},
|
|
256
|
+
runningProbe: {
|
|
257
|
+
command: "powershell.exe",
|
|
258
|
+
args: [
|
|
259
|
+
"-NoProfile",
|
|
260
|
+
"-NonInteractive",
|
|
261
|
+
"-Command",
|
|
262
|
+
"$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }; if ($task.State -ne 'Running') { exit 4 }"
|
|
263
|
+
],
|
|
264
|
+
negativeExitCodes: [3, 4]
|
|
265
|
+
}
|
|
180
266
|
};
|
|
181
267
|
}
|
|
182
268
|
return null;
|
|
@@ -219,28 +305,26 @@ var resolveDaemonServiceDefinition = (options = {}) => {
|
|
|
219
305
|
uid: options.uid ?? process.getuid?.()
|
|
220
306
|
});
|
|
221
307
|
};
|
|
308
|
+
var runDaemonServiceSteps = (steps, run) => {
|
|
309
|
+
for (const step of steps) {
|
|
310
|
+
try {
|
|
311
|
+
run(step.command, step.args);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
if (!step.ignoreFailure) throw err;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
222
317
|
var applyDaemonService = (action, definition, options = {}) => {
|
|
223
|
-
const run = options.run ??
|
|
224
|
-
execFileSync(command, [...args], { stdio: "ignore", windowsHide: true });
|
|
225
|
-
});
|
|
318
|
+
const run = options.run ?? runDaemonServiceCommand;
|
|
226
319
|
try {
|
|
227
320
|
if (action === "install") writeFileAtomic(definition.definitionPath, definition.contents, 384);
|
|
228
321
|
const steps = action === "uninstall" ? [...definition.stop, ...definition.uninstall] : definition[action];
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
run(step.command, step.args);
|
|
232
|
-
} catch (err) {
|
|
233
|
-
if (!step.ignoreFailure) throw err;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
322
|
+
runDaemonServiceSteps(steps, run);
|
|
236
323
|
if (action === "uninstall") {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} catch {
|
|
242
|
-
}
|
|
243
|
-
if (registered) throw new Error("Native background service remains registered");
|
|
324
|
+
const state = probeDaemonServiceState(definition, run);
|
|
325
|
+
if (state.kind !== "ok") throw new Error(`Could not verify native background service is absent: ${state.detail}`);
|
|
326
|
+
if (state.registered) throw new Error("Native background service remains registered");
|
|
327
|
+
if (state.running) throw new Error("Native background service remains active");
|
|
244
328
|
safeUnlink(definition.definitionPath);
|
|
245
329
|
const reload = definition.reloadAfterUninstall;
|
|
246
330
|
if (reload) run(reload.command, reload.args);
|
|
@@ -330,7 +414,7 @@ var currentConfigurationId = () => {
|
|
|
330
414
|
var parseDaemonState = (value) => {
|
|
331
415
|
if (typeof value !== "object" || value === null) return null;
|
|
332
416
|
const state = value;
|
|
333
|
-
if (!isString(state.instanceId) || !Number.isInteger(state.pid) || state.pid <= 0 || !isString(state.version) || !(state.configurationId === void 0 || state.configurationId === null || isString(state.configurationId)) || !isString(state.machineId) || !isTimestamp(state.startedAt) || !isTimestamp(state.updatedAt) || !(state.lastSuccessfulHeartbeatAt === null || isTimestamp(state.lastSuccessfulHeartbeatAt))) return null;
|
|
417
|
+
if (!isString(state.instanceId) || !Number.isInteger(state.pid) || state.pid <= 0 || !isString(state.version) || !(state.configurationId === void 0 || state.configurationId === null || isString(state.configurationId)) || !isString(state.machineId) || !isTimestamp(state.startedAt) || !isTimestamp(state.updatedAt) || !(state.lastSuccessfulHeartbeatAt === null || isTimestamp(state.lastSuccessfulHeartbeatAt)) || !(state.lastSuccessfulControlAt === void 0 || state.lastSuccessfulControlAt === null || isTimestamp(state.lastSuccessfulControlAt))) return null;
|
|
334
418
|
return {
|
|
335
419
|
instanceId: state.instanceId,
|
|
336
420
|
pid: state.pid,
|
|
@@ -339,7 +423,8 @@ var parseDaemonState = (value) => {
|
|
|
339
423
|
machineId: state.machineId,
|
|
340
424
|
startedAt: state.startedAt,
|
|
341
425
|
updatedAt: state.updatedAt,
|
|
342
|
-
lastSuccessfulHeartbeatAt: state.lastSuccessfulHeartbeatAt
|
|
426
|
+
lastSuccessfulHeartbeatAt: state.lastSuccessfulHeartbeatAt,
|
|
427
|
+
lastSuccessfulControlAt: state.lastSuccessfulControlAt ?? null
|
|
343
428
|
};
|
|
344
429
|
};
|
|
345
430
|
var parseLockOwner = (value) => {
|
|
@@ -361,7 +446,8 @@ var readDaemonStatus = (options = {}) => {
|
|
|
361
446
|
daemonVersion: null,
|
|
362
447
|
startedAt: null,
|
|
363
448
|
updatedAt: null,
|
|
364
|
-
lastSuccessfulHeartbeatAt: null
|
|
449
|
+
lastSuccessfulHeartbeatAt: null,
|
|
450
|
+
lastSuccessfulControlAt: null
|
|
365
451
|
};
|
|
366
452
|
}
|
|
367
453
|
const value = state.kind === "ok" ? parseDaemonState(state.value) : null;
|
|
@@ -378,7 +464,8 @@ var readDaemonStatus = (options = {}) => {
|
|
|
378
464
|
daemonVersion: value.version,
|
|
379
465
|
startedAt: value.startedAt,
|
|
380
466
|
updatedAt: value.updatedAt,
|
|
381
|
-
lastSuccessfulHeartbeatAt: value.lastSuccessfulHeartbeatAt
|
|
467
|
+
lastSuccessfulHeartbeatAt: value.lastSuccessfulHeartbeatAt,
|
|
468
|
+
lastSuccessfulControlAt: value.lastSuccessfulControlAt
|
|
382
469
|
};
|
|
383
470
|
}
|
|
384
471
|
return {
|
|
@@ -390,7 +477,8 @@ var readDaemonStatus = (options = {}) => {
|
|
|
390
477
|
daemonVersion: null,
|
|
391
478
|
startedAt: null,
|
|
392
479
|
updatedAt: null,
|
|
393
|
-
lastSuccessfulHeartbeatAt: null
|
|
480
|
+
lastSuccessfulHeartbeatAt: null,
|
|
481
|
+
lastSuccessfulControlAt: null
|
|
394
482
|
};
|
|
395
483
|
};
|
|
396
484
|
var safeUnlink = (path) => {
|
|
@@ -458,7 +546,8 @@ var acquireDaemonLease = (options = {}) => {
|
|
|
458
546
|
machineId,
|
|
459
547
|
startedAt,
|
|
460
548
|
updatedAt: startedAt,
|
|
461
|
-
lastSuccessfulHeartbeatAt: null
|
|
549
|
+
lastSuccessfulHeartbeatAt: null,
|
|
550
|
+
lastSuccessfulControlAt: null
|
|
462
551
|
};
|
|
463
552
|
try {
|
|
464
553
|
writeJsonAtomic(statePath(dir), state, 384);
|
|
@@ -494,6 +583,14 @@ var acquireDaemonLease = (options = {}) => {
|
|
|
494
583
|
lastSuccessfulHeartbeatAt: timestamp
|
|
495
584
|
});
|
|
496
585
|
},
|
|
586
|
+
recordSuccessfulControl: (at = now()) => {
|
|
587
|
+
const timestamp = new Date(at).toISOString();
|
|
588
|
+
updateState({
|
|
589
|
+
...currentState,
|
|
590
|
+
updatedAt: timestamp,
|
|
591
|
+
lastSuccessfulControlAt: timestamp
|
|
592
|
+
});
|
|
593
|
+
},
|
|
497
594
|
shouldYield: () => {
|
|
498
595
|
if (!isOwner()) return true;
|
|
499
596
|
const target = readJsonSafe(targetPath(dir));
|
|
@@ -563,8 +660,8 @@ var deactivateDaemonService = async (action, options) => {
|
|
|
563
660
|
const platform = options.platform ?? process.platform;
|
|
564
661
|
const supported = ["darwin", "linux", "win32"].includes(platform);
|
|
565
662
|
const managerAvailable = supported && daemonServiceManagerAvailable(options);
|
|
566
|
-
const
|
|
567
|
-
const service =
|
|
663
|
+
const absenceMustBeProven = options.requireAbsenceProof || hasDaemonServiceDefinition(options);
|
|
664
|
+
const service = supported && !managerAvailable && absenceMustBeProven ? { kind: "failed", detail: "Native background-service manager is unavailable" } : managerAvailable ? manageDaemonService(action, options) : { kind: "ok" };
|
|
568
665
|
const stopped = await stopDaemonRunning(options);
|
|
569
666
|
const failures = [
|
|
570
667
|
service.kind === "ok" ? null : service.detail,
|
|
@@ -574,9 +671,251 @@ var deactivateDaemonService = async (action, options) => {
|
|
|
574
671
|
};
|
|
575
672
|
var stopDaemonService = async (options = {}) => deactivateDaemonService("stop", options);
|
|
576
673
|
var disableDaemonService = async (options = {}) => deactivateDaemonService("uninstall", options);
|
|
674
|
+
var parseDaemonServiceTransaction = (value) => {
|
|
675
|
+
if (typeof value !== "object" || value === null) return null;
|
|
676
|
+
const transaction = value;
|
|
677
|
+
if (transaction.version !== 1 || transaction.phase !== "prepared" && transaction.phase !== "committed" || typeof transaction.definitionPath !== "string" || !(transaction.previousContents === null || typeof transaction.previousContents === "string") || typeof transaction.previousRegistered !== "boolean" || !(transaction.previousEnabled === void 0 || typeof transaction.previousEnabled === "boolean") || typeof transaction.previousRunning !== "boolean") return null;
|
|
678
|
+
return {
|
|
679
|
+
...transaction,
|
|
680
|
+
previousEnabled: typeof transaction.previousEnabled === "boolean" ? transaction.previousEnabled : transaction.previousRegistered
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
var probeDaemonServiceState = (definition, run) => {
|
|
684
|
+
let registered = true;
|
|
685
|
+
try {
|
|
686
|
+
run(definition.registrationProbe.command, definition.registrationProbe.args);
|
|
687
|
+
} catch (err) {
|
|
688
|
+
if (!isExpectedProbeFailure(definition.registrationProbe, err)) {
|
|
689
|
+
return {
|
|
690
|
+
kind: "failed",
|
|
691
|
+
detail: `Could not inspect native service registration: ${commandFailureOutput(err) || String(err)}`
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
registered = false;
|
|
695
|
+
}
|
|
696
|
+
let enabled = registered;
|
|
697
|
+
const enabledProbe = definition.enabledProbe;
|
|
698
|
+
if (enabledProbe) {
|
|
699
|
+
try {
|
|
700
|
+
const output = run(enabledProbe.command, enabledProbe.args);
|
|
701
|
+
enabled = !(enabledProbe.disabledOutputPattern && typeof output === "string" && enabledProbe.disabledOutputPattern.test(output));
|
|
702
|
+
} catch (err) {
|
|
703
|
+
if (!isExpectedProbeFailure(enabledProbe, err)) {
|
|
704
|
+
return {
|
|
705
|
+
kind: "failed",
|
|
706
|
+
detail: `Could not inspect whether the native service is enabled: ${commandFailureOutput(err) || String(err)}`
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
enabled = false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
const output = run(definition.runningProbe.command, definition.runningProbe.args);
|
|
714
|
+
const running = definition.runningProbe.outputPattern ? typeof output === "string" && definition.runningProbe.outputPattern.test(output) : true;
|
|
715
|
+
return { kind: "ok", registered, enabled, running };
|
|
716
|
+
} catch (err) {
|
|
717
|
+
if (!isExpectedProbeFailure(definition.runningProbe, err)) {
|
|
718
|
+
return {
|
|
719
|
+
kind: "failed",
|
|
720
|
+
detail: `Could not inspect whether the native service is running: ${commandFailureOutput(err) || String(err)}`
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
return { kind: "ok", registered, enabled, running: false };
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var restoreDaemonServiceSnapshot = (definition, snapshot, run) => {
|
|
727
|
+
const removed = applyDaemonService("uninstall", definition, { run });
|
|
728
|
+
if (removed.kind !== "ok") return removed;
|
|
729
|
+
try {
|
|
730
|
+
if (snapshot.previousContents === null) safeUnlink(definition.definitionPath);
|
|
731
|
+
else writeFileAtomic(definition.definitionPath, snapshot.previousContents, 384);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
return {
|
|
734
|
+
kind: "failed",
|
|
735
|
+
detail: `could not restore the previous definition: ${err instanceof Error ? err.message : String(err)}`
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
if (!snapshot.previousRegistered) {
|
|
739
|
+
const reload = definition.reloadAfterUninstall;
|
|
740
|
+
if (snapshot.previousContents !== null && reload) {
|
|
741
|
+
try {
|
|
742
|
+
run(reload.command, reload.args);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
return { kind: "failed", detail: `could not reload the previous definition: ${commandFailureOutput(err) || String(err)}` };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (snapshot.previousRunning) {
|
|
748
|
+
const started = applyDaemonService("start", definition, { run });
|
|
749
|
+
if (started.kind !== "ok") return started;
|
|
750
|
+
}
|
|
751
|
+
if (!snapshot.previousEnabled && definition.canDisableWhenUnregistered) {
|
|
752
|
+
try {
|
|
753
|
+
runDaemonServiceSteps(definition.disable, run);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
return { kind: "failed", detail: `could not restore the previous disabled state: ${commandFailureOutput(err) || String(err)}` };
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const state2 = probeDaemonServiceState(definition, run);
|
|
759
|
+
if (state2.kind !== "ok") return { kind: "failed", detail: state2.detail };
|
|
760
|
+
if (state2.registered || state2.enabled !== snapshot.previousEnabled || state2.running !== snapshot.previousRunning) {
|
|
761
|
+
return { kind: "failed", detail: "could not restore the previous unregistered service state" };
|
|
762
|
+
}
|
|
763
|
+
return { kind: "ok" };
|
|
764
|
+
}
|
|
765
|
+
if (snapshot.previousContents === null) {
|
|
766
|
+
return { kind: "failed", detail: "the previous registered service had no recoverable definition" };
|
|
767
|
+
}
|
|
768
|
+
const restored = applyDaemonService("install", {
|
|
769
|
+
...definition,
|
|
770
|
+
contents: snapshot.previousContents
|
|
771
|
+
}, { run });
|
|
772
|
+
if (restored.kind !== "ok") return restored;
|
|
773
|
+
if (!snapshot.previousRunning) applyDaemonService("stop", definition, { run });
|
|
774
|
+
if (!snapshot.previousEnabled) {
|
|
775
|
+
try {
|
|
776
|
+
runDaemonServiceSteps(definition.disable, run);
|
|
777
|
+
} catch (err) {
|
|
778
|
+
return { kind: "failed", detail: `could not restore the previous disabled state: ${commandFailureOutput(err) || String(err)}` };
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const state = probeDaemonServiceState(definition, run);
|
|
782
|
+
if (state.kind !== "ok") return { kind: "failed", detail: state.detail };
|
|
783
|
+
if (!state.registered) return { kind: "failed", detail: "the previous service is no longer registered" };
|
|
784
|
+
if (state.enabled !== snapshot.previousEnabled) {
|
|
785
|
+
return { kind: "failed", detail: "could not restore whether the previous service was enabled" };
|
|
786
|
+
}
|
|
787
|
+
if (state.running !== snapshot.previousRunning) {
|
|
788
|
+
return {
|
|
789
|
+
kind: "failed",
|
|
790
|
+
detail: snapshot.previousRunning ? "the previous service did not restart" : "could not restore the previous stopped state"
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
return { kind: "ok" };
|
|
794
|
+
};
|
|
795
|
+
var recoverDaemonServiceTransaction = (definition, path, run) => {
|
|
796
|
+
const stored = readJsonSafe(path);
|
|
797
|
+
if (stored.kind === "missing") return { kind: "ok" };
|
|
798
|
+
if (stored.kind !== "ok") {
|
|
799
|
+
return { kind: "failed", detail: `transaction receipt is ${stored.kind}` };
|
|
800
|
+
}
|
|
801
|
+
const transaction = parseDaemonServiceTransaction(stored.value);
|
|
802
|
+
if (!transaction) return { kind: "failed", detail: "transaction receipt is invalid" };
|
|
803
|
+
if (transaction.definitionPath !== definition.definitionPath) {
|
|
804
|
+
return { kind: "failed", detail: "transaction receipt belongs to a different service definition" };
|
|
805
|
+
}
|
|
806
|
+
if (transaction.phase === "prepared") {
|
|
807
|
+
const restored = restoreDaemonServiceSnapshot(definition, transaction, run);
|
|
808
|
+
if (restored.kind !== "ok") return restored;
|
|
809
|
+
}
|
|
810
|
+
try {
|
|
811
|
+
safeUnlink(path);
|
|
812
|
+
} catch (err) {
|
|
813
|
+
return { kind: "failed", detail: `could not clear transaction receipt: ${err instanceof Error ? err.message : String(err)}` };
|
|
814
|
+
}
|
|
815
|
+
return { kind: "ok" };
|
|
816
|
+
};
|
|
577
817
|
var replaceDaemonService = async (options = {}) => {
|
|
818
|
+
const definition = resolveDaemonServiceDefinition(options);
|
|
819
|
+
if (!definition) {
|
|
820
|
+
return {
|
|
821
|
+
kind: "unavailable",
|
|
822
|
+
detail: "Could not find the globally installed daemon entry. Run Pushary setup again."
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
const run = options.run ?? runDaemonServiceCommand;
|
|
826
|
+
const transactionPath = join(options.stateDir ?? options.dir ?? pusharyDir(), "daemon-service-transaction.json");
|
|
827
|
+
const recovered = recoverDaemonServiceTransaction(definition, transactionPath, run);
|
|
828
|
+
if (recovered.kind !== "ok") {
|
|
829
|
+
return { kind: "failed", detail: `Could not recover interrupted service replacement: ${recovered.detail}` };
|
|
830
|
+
}
|
|
831
|
+
let previousContents = null;
|
|
832
|
+
try {
|
|
833
|
+
if (existsSync(definition.definitionPath)) {
|
|
834
|
+
previousContents = readFileSync(definition.definitionPath, "utf-8");
|
|
835
|
+
}
|
|
836
|
+
} catch (err) {
|
|
837
|
+
return { kind: "failed", detail: `Could not snapshot the existing service: ${err instanceof Error ? err.message : String(err)}` };
|
|
838
|
+
}
|
|
839
|
+
const previousState = probeDaemonServiceState(definition, run);
|
|
840
|
+
if (previousState.kind !== "ok") return { kind: "failed", detail: previousState.detail };
|
|
841
|
+
if ((previousState.registered || previousState.running) && previousContents === null) {
|
|
842
|
+
return { kind: "failed", detail: "Could not replace an active native service with no recoverable definition" };
|
|
843
|
+
}
|
|
844
|
+
const transaction = {
|
|
845
|
+
version: 1,
|
|
846
|
+
phase: "prepared",
|
|
847
|
+
definitionPath: definition.definitionPath,
|
|
848
|
+
previousContents,
|
|
849
|
+
previousRegistered: previousState.registered,
|
|
850
|
+
previousEnabled: previousState.enabled,
|
|
851
|
+
previousRunning: previousState.running
|
|
852
|
+
};
|
|
853
|
+
try {
|
|
854
|
+
writeJsonAtomicDurable(transactionPath, transaction, 384);
|
|
855
|
+
} catch (err) {
|
|
856
|
+
return { kind: "failed", detail: `Could not persist the service rollback snapshot: ${err instanceof Error ? err.message : String(err)}` };
|
|
857
|
+
}
|
|
578
858
|
const stopped = await stopDaemonRunning(options);
|
|
579
|
-
|
|
859
|
+
if (stopped.kind === "unavailable") {
|
|
860
|
+
try {
|
|
861
|
+
safeUnlink(transactionPath);
|
|
862
|
+
} catch (err) {
|
|
863
|
+
return {
|
|
864
|
+
kind: "failed",
|
|
865
|
+
detail: `${stopped.detail}; could not clear unused rollback evidence: ${err instanceof Error ? err.message : String(err)}`
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
return stopped;
|
|
869
|
+
}
|
|
870
|
+
let installed = applyDaemonService("install", definition, { run });
|
|
871
|
+
if (installed.kind === "ok") {
|
|
872
|
+
const state = probeDaemonServiceState(definition, run);
|
|
873
|
+
if (state.kind !== "ok") {
|
|
874
|
+
installed = { kind: "failed", detail: state.detail };
|
|
875
|
+
} else if (!state.registered || !state.enabled || !state.running) {
|
|
876
|
+
installed = {
|
|
877
|
+
kind: "failed",
|
|
878
|
+
detail: !state.registered ? "Service registration verification failed" : !state.enabled ? "Service enable verification failed" : "Service start verification failed"
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (installed.kind === "ok") {
|
|
883
|
+
try {
|
|
884
|
+
writeJsonAtomicDurable(transactionPath, { ...transaction, phase: "committed" }, 384);
|
|
885
|
+
} catch (err) {
|
|
886
|
+
const detail = `Could not commit the service replacement: ${err instanceof Error ? err.message : String(err)}`;
|
|
887
|
+
const recovered2 = recoverDaemonServiceTransaction(definition, transactionPath, run);
|
|
888
|
+
return recovered2.kind === "ok" ? { kind: "failed", detail } : {
|
|
889
|
+
kind: "failed",
|
|
890
|
+
detail: `${detail}; recovery failed: ${recovered2.detail}`
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
try {
|
|
894
|
+
safeUnlink(transactionPath);
|
|
895
|
+
return installed;
|
|
896
|
+
} catch (err) {
|
|
897
|
+
return {
|
|
898
|
+
kind: "failed",
|
|
899
|
+
detail: `Service replacement committed but its receipt could not be cleared: ${err instanceof Error ? err.message : String(err)}`
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const restored = restoreDaemonServiceSnapshot(definition, transaction, run);
|
|
904
|
+
if (restored.kind !== "ok") {
|
|
905
|
+
return { kind: "failed", detail: `${installed.detail}; rollback failed: ${restored.detail}` };
|
|
906
|
+
}
|
|
907
|
+
try {
|
|
908
|
+
safeUnlink(transactionPath);
|
|
909
|
+
} catch (err) {
|
|
910
|
+
return {
|
|
911
|
+
kind: "failed",
|
|
912
|
+
detail: `${installed.detail}; previous service restored but recovery evidence could not be cleared: ${err instanceof Error ? err.message : String(err)}`
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
kind: "failed",
|
|
917
|
+
detail: `${installed.detail}; ${transaction.previousRegistered ? "previous service restored" : "previous service state restored"}`
|
|
918
|
+
};
|
|
580
919
|
};
|
|
581
920
|
var launchInstalledDaemon = (options) => {
|
|
582
921
|
const entryExists = options.entryExists ?? existsSync;
|
|
@@ -633,8 +972,21 @@ var ensureDaemonRunning = async (options = {}) => {
|
|
|
633
972
|
}
|
|
634
973
|
return { kind: "unavailable", status, detail: `daemon did not become healthy (${status.kind})` };
|
|
635
974
|
};
|
|
975
|
+
var waitForDaemonRemoteProof = async (options = {}) => {
|
|
976
|
+
const sleep = options.sleep ?? wait;
|
|
977
|
+
const pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
978
|
+
const attempts = Math.max(1, Math.ceil((options.waitTimeoutMs ?? 5e3) / pollIntervalMs));
|
|
979
|
+
let status = readDaemonStatus(options);
|
|
980
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
981
|
+
status = readDaemonStatus(options);
|
|
982
|
+
if (daemonHasRemoteProof(status)) return status;
|
|
983
|
+
if (attempt + 1 < attempts) await sleep(pollIntervalMs);
|
|
984
|
+
}
|
|
985
|
+
return status;
|
|
986
|
+
};
|
|
636
987
|
|
|
637
988
|
export {
|
|
989
|
+
daemonHasRemoteProof,
|
|
638
990
|
daemonServiceManagerAvailable,
|
|
639
991
|
DAEMON_LEASE_RENEW_INTERVAL_MS,
|
|
640
992
|
parseDaemonAction,
|
|
@@ -647,5 +999,6 @@ export {
|
|
|
647
999
|
stopDaemonService,
|
|
648
1000
|
disableDaemonService,
|
|
649
1001
|
replaceDaemonService,
|
|
650
|
-
ensureDaemonRunning
|
|
1002
|
+
ensureDaemonRunning,
|
|
1003
|
+
waitForDaemonRemoteProof
|
|
651
1004
|
};
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
readLastUserPrompt,
|
|
18
18
|
repoKeyFor,
|
|
19
19
|
throttlePass
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-4XZCDSNG.js";
|
|
21
21
|
import {
|
|
22
22
|
DEFAULT_SESSION,
|
|
23
23
|
askUser,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "./chunk-KOPUTJQC.js";
|
|
37
37
|
import {
|
|
38
38
|
getMachineId
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-DKDDLF5P.js";
|
|
40
40
|
import {
|
|
41
41
|
getApiKey,
|
|
42
42
|
getBaseUrl
|