@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/bin/pushary-bell-hook.js +3 -3
  3. package/dist/bin/pushary-bell.js +3 -3
  4. package/dist/bin/pushary-claude.js +5 -5
  5. package/dist/bin/pushary-clean.js +13 -10
  6. package/dist/bin/pushary-codex-bridge.js +3 -3
  7. package/dist/bin/pushary-codex-hook.js +3 -3
  8. package/dist/bin/pushary-codex.js +3 -3
  9. package/dist/bin/pushary-connect.js +2 -2
  10. package/dist/bin/pushary-daemon.js +49 -14
  11. package/dist/bin/pushary-doctor.js +29 -8
  12. package/dist/bin/pushary-elicitation-hook.js +2 -2
  13. package/dist/bin/pushary-gemini-bridge.js +3 -3
  14. package/dist/bin/pushary-gemini-hook.js +3 -3
  15. package/dist/bin/pushary-hook.js +4 -4
  16. package/dist/bin/pushary-login.js +2 -2
  17. package/dist/bin/pushary-logout.js +2 -2
  18. package/dist/bin/pushary-notification-hook.js +3 -3
  19. package/dist/bin/pushary-opencode-hook.js +3 -3
  20. package/dist/bin/pushary-permission-denied-hook.js +4 -4
  21. package/dist/bin/pushary-permission-hook.js +4 -4
  22. package/dist/bin/pushary-post-hook.js +3 -3
  23. package/dist/bin/pushary-prompt-hook.js +3 -3
  24. package/dist/bin/pushary-session-end-hook.js +3 -3
  25. package/dist/bin/pushary-session-start-hook.js +3 -3
  26. package/dist/bin/pushary-setup.js +20 -7
  27. package/dist/bin/pushary-status.js +4 -4
  28. package/dist/bin/pushary-stop-hook.js +3 -3
  29. package/dist/bin/pushary-stopfailure-hook.js +3 -3
  30. package/dist/bin/pushary-upgrade.js +16 -8
  31. package/dist/{chunk-BPKMGE7L.js → chunk-4XZCDSNG.js} +1 -1
  32. package/dist/{chunk-ZH7TWAYK.js → chunk-543KNJKC.js} +390 -37
  33. package/dist/{chunk-2M66JVZM.js → chunk-DKDDLF5P.js} +1 -1
  34. package/dist/{chunk-2MEJBUOI.js → chunk-EA3E74P2.js} +1 -1
  35. package/dist/{chunk-5YUVKPTS.js → chunk-EJT2ER6G.js} +2 -2
  36. package/dist/{chunk-WQGMUIDW.js → chunk-FX5QSMPY.js} +1 -1
  37. package/dist/{chunk-42TTFZLV.js → chunk-TF247BET.js} +1 -1
  38. package/dist/{chunk-TESGYWEM.js → chunk-U5TQCQ25.js} +32 -1
  39. package/dist/{chunk-L3YUHEB6.js → chunk-V6N4Q223.js} +1 -1
  40. package/dist/{chunk-6MTNS63X.js → chunk-XX2V4QS5.js} +12 -1
  41. package/dist/src/index.js +4 -4
  42. 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-2M66JVZM.js";
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
- } from "./chunk-6MTNS63X.js";
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 ?? ((command, args) => {
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: { command: "launchctl", args: ["print", target] }
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: { command: "systemctl", args: ["--user", "is-enabled", unit] },
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: { command: "schtasks.exe", args: ["/Query", "/TN", taskName] }
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 ?? ((command, args) => {
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
- for (const step of steps) {
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
- let registered = false;
238
- try {
239
- run(definition.registrationProbe.command, definition.registrationProbe.args);
240
- registered = true;
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 definitionPresent = supported && hasDaemonServiceDefinition(options);
567
- const service = definitionPresent && !managerAvailable ? { kind: "failed", detail: "Native background-service manager is unavailable" } : managerAvailable ? manageDaemonService(action, options) : { kind: "ok" };
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
- return stopped.kind === "unavailable" ? stopped : manageDaemonService("install", options);
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
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GMI5QZLG.js";
4
4
  import {
5
5
  writeFileAtomic
6
- } from "./chunk-6MTNS63X.js";
6
+ } from "./chunk-XX2V4QS5.js";
7
7
 
8
8
  // src/identity.ts
9
9
  import { randomUUID } from "crypto";
@@ -35,7 +35,7 @@ import {
35
35
  } from "./chunk-GMI5QZLG.js";
36
36
  import {
37
37
  readJsonSafe
38
- } from "./chunk-6MTNS63X.js";
38
+ } from "./chunk-XX2V4QS5.js";
39
39
 
40
40
  // src/reapply.ts
41
41
  import { join, dirname } from "path";
@@ -17,7 +17,7 @@ import {
17
17
  readLastUserPrompt,
18
18
  repoKeyFor,
19
19
  throttlePass
20
- } from "./chunk-BPKMGE7L.js";
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-2M66JVZM.js";
39
+ } from "./chunk-DKDDLF5P.js";
40
40
  import {
41
41
  getApiKey,
42
42
  getBaseUrl
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-L3YUHEB6.js";
3
+ } from "./chunk-V6N4Q223.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GMI5QZLG.js";
4
4
  import {
5
5
  readJsonSafe
6
- } from "./chunk-6MTNS63X.js";
6
+ } from "./chunk-XX2V4QS5.js";
7
7
 
8
8
  // src/process-ownership.ts
9
9
  import { createHash, randomUUID } from "crypto";