@pushary/agent-hooks 0.89.7 → 0.89.8

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 +17 -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 +12 -9
  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 +7 -7
  11. package/dist/bin/pushary-doctor.js +4 -4
  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 +5 -5
  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 +5 -5
  31. package/dist/{chunk-BPKMGE7L.js → chunk-4XZCDSNG.js} +1 -1
  32. package/dist/{chunk-2M66JVZM.js → chunk-DKDDLF5P.js} +1 -1
  33. package/dist/{chunk-2MEJBUOI.js → chunk-EA3E74P2.js} +1 -1
  34. package/dist/{chunk-5YUVKPTS.js → chunk-EJT2ER6G.js} +2 -2
  35. package/dist/{chunk-WQGMUIDW.js → chunk-FX5QSMPY.js} +1 -1
  36. package/dist/{chunk-ZH7TWAYK.js → chunk-R2AGKCXL.js} +349 -26
  37. package/dist/{chunk-42TTFZLV.js → chunk-TF247BET.js} +1 -1
  38. package/dist/{chunk-L3YUHEB6.js → chunk-V6N4Q223.js} +1 -1
  39. package/dist/{chunk-6MTNS63X.js → chunk-XX2V4QS5.js} +12 -1
  40. package/dist/{chunk-TESGYWEM.js → chunk-ZL7HEF2T.js} +1 -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,39 @@ 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 runDaemonServiceCommand = (command, args) => execFileSync(command, [...args], {
35
+ encoding: "utf-8",
36
+ stdio: ["ignore", "pipe", "ignore"],
37
+ windowsHide: true
38
+ });
39
+ var commandFailureOutput = (error) => {
40
+ const failure = error;
41
+ return [failure.stdout, failure.stderr, failure.message].map((value) => Buffer.isBuffer(value) ? value.toString("utf-8") : typeof value === "string" ? value : "").filter(Boolean).join("\n");
42
+ };
43
+ var isExpectedProbeFailure = (probe, error) => {
44
+ const failure = error;
45
+ const status = typeof failure.status === "number" ? failure.status : typeof failure.code === "number" ? failure.code : null;
46
+ const exitMatches = probe.negativeExitCodes === void 0 || status !== null && probe.negativeExitCodes.includes(status);
47
+ const outputMatches = probe.negativeOutputPattern === void 0 || probe.negativeOutputPattern.test(commandFailureOutput(error));
48
+ return (probe.negativeExitCodes !== void 0 || probe.negativeOutputPattern !== void 0) && exitMatches && outputMatches;
49
+ };
33
50
  var daemonServiceManagerAvailable = (options = {}) => {
34
51
  const platform = options.platform ?? process.platform;
35
- const run = options.run ?? ((command, args) => {
36
- execFileSync(command, [...args], { stdio: "ignore", windowsHide: true });
37
- });
52
+ const run = options.run ?? runDaemonServiceCommand;
38
53
  try {
39
54
  if (platform === "darwin") {
40
55
  const uid = options.uid ?? process.getuid?.();
@@ -48,6 +63,12 @@ var daemonServiceManagerAvailable = (options = {}) => {
48
63
  }
49
64
  if (platform === "win32") {
50
65
  run("sc.exe", ["query", "Schedule"]);
66
+ run("powershell.exe", [
67
+ "-NoProfile",
68
+ "-NonInteractive",
69
+ "-Command",
70
+ "Get-Command Get-ScheduledTask -ErrorAction Stop | Out-Null"
71
+ ]);
51
72
  return true;
52
73
  }
53
74
  return false;
@@ -103,8 +124,25 @@ var buildDaemonService = (input) => {
103
124
  ],
104
125
  start: [{ command: "launchctl", args: ["kickstart", "-k", target] }],
105
126
  stop: [{ command: "launchctl", args: ["kill", "SIGTERM", target], ignoreFailure: true }],
127
+ disable: [{ command: "launchctl", args: ["disable", target] }],
128
+ canDisableWhenUnregistered: true,
106
129
  uninstall: [{ command: "launchctl", args: ["bootout", domain, definitionPath], ignoreFailure: true }],
107
- registrationProbe: { command: "launchctl", args: ["print", target] }
130
+ registrationProbe: {
131
+ command: "launchctl",
132
+ args: ["print", target],
133
+ negativeExitCodes: [113]
134
+ },
135
+ enabledProbe: {
136
+ command: "launchctl",
137
+ args: ["print-disabled", domain],
138
+ disabledOutputPattern: new RegExp(`"${label}"\\s*=>\\s*disabled`)
139
+ },
140
+ runningProbe: {
141
+ command: "launchctl",
142
+ args: ["print", target],
143
+ outputPattern: /\bstate = running\b/,
144
+ negativeExitCodes: [113]
145
+ }
108
146
  };
109
147
  }
110
148
  if (input.platform === "linux") {
@@ -134,10 +172,20 @@ WantedBy=default.target
134
172
  ],
135
173
  start: [{ command: "systemctl", args: ["--user", "start", unit] }],
136
174
  stop: [{ command: "systemctl", args: ["--user", "stop", unit], ignoreFailure: true }],
175
+ disable: [{ command: "systemctl", args: ["--user", "disable", unit] }],
137
176
  uninstall: [
138
177
  { command: "systemctl", args: ["--user", "disable", "--now", unit], ignoreFailure: true }
139
178
  ],
140
- registrationProbe: { command: "systemctl", args: ["--user", "is-enabled", unit] },
179
+ registrationProbe: {
180
+ command: "systemctl",
181
+ args: ["--user", "is-enabled", unit],
182
+ negativeOutputPattern: /^(?:disabled|not-found)\s*$/m
183
+ },
184
+ runningProbe: {
185
+ command: "systemctl",
186
+ args: ["--user", "is-active", unit],
187
+ negativeOutputPattern: /^(?:inactive|failed|unknown)\s*$/m
188
+ },
141
189
  reloadAfterUninstall: { command: "systemctl", args: ["--user", "daemon-reload"] }
142
190
  };
143
191
  }
@@ -175,8 +223,38 @@ WantedBy=default.target
175
223
  ],
176
224
  start: [{ command: "schtasks.exe", args: ["/Run", "/TN", taskName] }],
177
225
  stop: [{ command: "schtasks.exe", args: ["/End", "/TN", taskName], ignoreFailure: true }],
226
+ disable: [{ command: "schtasks.exe", args: ["/Change", "/TN", taskName, "/Disable"] }],
178
227
  uninstall: [{ command: "schtasks.exe", args: ["/Delete", "/TN", taskName, "/F"], ignoreFailure: true }],
179
- registrationProbe: { command: "schtasks.exe", args: ["/Query", "/TN", taskName] }
228
+ registrationProbe: {
229
+ command: "powershell.exe",
230
+ args: [
231
+ "-NoProfile",
232
+ "-NonInteractive",
233
+ "-Command",
234
+ "$task = @(Get-ScheduledTask -ErrorAction Stop) | Where-Object { $_.TaskName -eq 'Pushary Daemon' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { exit 3 }"
235
+ ],
236
+ negativeExitCodes: [3]
237
+ },
238
+ enabledProbe: {
239
+ command: "powershell.exe",
240
+ args: [
241
+ "-NoProfile",
242
+ "-NonInteractive",
243
+ "-Command",
244
+ "$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 -eq 'Disabled') { exit 4 }"
245
+ ],
246
+ negativeExitCodes: [3, 4]
247
+ },
248
+ runningProbe: {
249
+ command: "powershell.exe",
250
+ args: [
251
+ "-NoProfile",
252
+ "-NonInteractive",
253
+ "-Command",
254
+ "$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 }"
255
+ ],
256
+ negativeExitCodes: [3, 4]
257
+ }
180
258
  };
181
259
  }
182
260
  return null;
@@ -219,26 +297,30 @@ var resolveDaemonServiceDefinition = (options = {}) => {
219
297
  uid: options.uid ?? process.getuid?.()
220
298
  });
221
299
  };
300
+ var runDaemonServiceSteps = (steps, run) => {
301
+ for (const step of steps) {
302
+ try {
303
+ run(step.command, step.args);
304
+ } catch (err) {
305
+ if (!step.ignoreFailure) throw err;
306
+ }
307
+ }
308
+ };
222
309
  var applyDaemonService = (action, definition, options = {}) => {
223
- const run = options.run ?? ((command, args) => {
224
- execFileSync(command, [...args], { stdio: "ignore", windowsHide: true });
225
- });
310
+ const run = options.run ?? runDaemonServiceCommand;
226
311
  try {
227
312
  if (action === "install") writeFileAtomic(definition.definitionPath, definition.contents, 384);
228
313
  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
- }
314
+ runDaemonServiceSteps(steps, run);
236
315
  if (action === "uninstall") {
237
- let registered = false;
316
+ let registered = true;
238
317
  try {
239
318
  run(definition.registrationProbe.command, definition.registrationProbe.args);
240
- registered = true;
241
- } catch {
319
+ } catch (err) {
320
+ if (!isExpectedProbeFailure(definition.registrationProbe, err)) {
321
+ throw new Error(`Could not verify native background service is absent: ${commandFailureOutput(err) || String(err)}`);
322
+ }
323
+ registered = false;
242
324
  }
243
325
  if (registered) throw new Error("Native background service remains registered");
244
326
  safeUnlink(definition.definitionPath);
@@ -563,8 +645,7 @@ var deactivateDaemonService = async (action, options) => {
563
645
  const platform = options.platform ?? process.platform;
564
646
  const supported = ["darwin", "linux", "win32"].includes(platform);
565
647
  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" };
648
+ const service = supported && !managerAvailable ? { kind: "failed", detail: "Native background-service manager is unavailable" } : managerAvailable ? manageDaemonService(action, options) : { kind: "ok" };
568
649
  const stopped = await stopDaemonRunning(options);
569
650
  const failures = [
570
651
  service.kind === "ok" ? null : service.detail,
@@ -574,9 +655,251 @@ var deactivateDaemonService = async (action, options) => {
574
655
  };
575
656
  var stopDaemonService = async (options = {}) => deactivateDaemonService("stop", options);
576
657
  var disableDaemonService = async (options = {}) => deactivateDaemonService("uninstall", options);
658
+ var parseDaemonServiceTransaction = (value) => {
659
+ if (typeof value !== "object" || value === null) return null;
660
+ const transaction = value;
661
+ 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;
662
+ return {
663
+ ...transaction,
664
+ previousEnabled: typeof transaction.previousEnabled === "boolean" ? transaction.previousEnabled : transaction.previousRegistered
665
+ };
666
+ };
667
+ var probeDaemonServiceState = (definition, run) => {
668
+ let registered = true;
669
+ try {
670
+ run(definition.registrationProbe.command, definition.registrationProbe.args);
671
+ } catch (err) {
672
+ if (!isExpectedProbeFailure(definition.registrationProbe, err)) {
673
+ return {
674
+ kind: "failed",
675
+ detail: `Could not inspect native service registration: ${commandFailureOutput(err) || String(err)}`
676
+ };
677
+ }
678
+ registered = false;
679
+ }
680
+ let enabled = registered;
681
+ const enabledProbe = definition.enabledProbe;
682
+ if (enabledProbe) {
683
+ try {
684
+ const output = run(enabledProbe.command, enabledProbe.args);
685
+ enabled = !(enabledProbe.disabledOutputPattern && typeof output === "string" && enabledProbe.disabledOutputPattern.test(output));
686
+ } catch (err) {
687
+ if (!isExpectedProbeFailure(enabledProbe, err)) {
688
+ return {
689
+ kind: "failed",
690
+ detail: `Could not inspect whether the native service is enabled: ${commandFailureOutput(err) || String(err)}`
691
+ };
692
+ }
693
+ enabled = false;
694
+ }
695
+ }
696
+ try {
697
+ const output = run(definition.runningProbe.command, definition.runningProbe.args);
698
+ const running = definition.runningProbe.outputPattern ? typeof output === "string" && definition.runningProbe.outputPattern.test(output) : true;
699
+ return { kind: "ok", registered, enabled, running };
700
+ } catch (err) {
701
+ if (!isExpectedProbeFailure(definition.runningProbe, err)) {
702
+ return {
703
+ kind: "failed",
704
+ detail: `Could not inspect whether the native service is running: ${commandFailureOutput(err) || String(err)}`
705
+ };
706
+ }
707
+ return { kind: "ok", registered, enabled, running: false };
708
+ }
709
+ };
710
+ var restoreDaemonServiceSnapshot = (definition, snapshot, run) => {
711
+ const removed = applyDaemonService("uninstall", definition, { run });
712
+ if (removed.kind !== "ok") return removed;
713
+ try {
714
+ if (snapshot.previousContents === null) safeUnlink(definition.definitionPath);
715
+ else writeFileAtomic(definition.definitionPath, snapshot.previousContents, 384);
716
+ } catch (err) {
717
+ return {
718
+ kind: "failed",
719
+ detail: `could not restore the previous definition: ${err instanceof Error ? err.message : String(err)}`
720
+ };
721
+ }
722
+ if (!snapshot.previousRegistered) {
723
+ const reload = definition.reloadAfterUninstall;
724
+ if (snapshot.previousContents !== null && reload) {
725
+ try {
726
+ run(reload.command, reload.args);
727
+ } catch (err) {
728
+ return { kind: "failed", detail: `could not reload the previous definition: ${commandFailureOutput(err) || String(err)}` };
729
+ }
730
+ }
731
+ if (snapshot.previousRunning) {
732
+ const started = applyDaemonService("start", definition, { run });
733
+ if (started.kind !== "ok") return started;
734
+ }
735
+ if (!snapshot.previousEnabled && definition.canDisableWhenUnregistered) {
736
+ try {
737
+ runDaemonServiceSteps(definition.disable, run);
738
+ } catch (err) {
739
+ return { kind: "failed", detail: `could not restore the previous disabled state: ${commandFailureOutput(err) || String(err)}` };
740
+ }
741
+ }
742
+ const state2 = probeDaemonServiceState(definition, run);
743
+ if (state2.kind !== "ok") return { kind: "failed", detail: state2.detail };
744
+ if (state2.registered || state2.enabled !== snapshot.previousEnabled || state2.running !== snapshot.previousRunning) {
745
+ return { kind: "failed", detail: "could not restore the previous unregistered service state" };
746
+ }
747
+ return { kind: "ok" };
748
+ }
749
+ if (snapshot.previousContents === null) {
750
+ return { kind: "failed", detail: "the previous registered service had no recoverable definition" };
751
+ }
752
+ const restored = applyDaemonService("install", {
753
+ ...definition,
754
+ contents: snapshot.previousContents
755
+ }, { run });
756
+ if (restored.kind !== "ok") return restored;
757
+ if (!snapshot.previousRunning) applyDaemonService("stop", definition, { run });
758
+ if (!snapshot.previousEnabled) {
759
+ try {
760
+ runDaemonServiceSteps(definition.disable, run);
761
+ } catch (err) {
762
+ return { kind: "failed", detail: `could not restore the previous disabled state: ${commandFailureOutput(err) || String(err)}` };
763
+ }
764
+ }
765
+ const state = probeDaemonServiceState(definition, run);
766
+ if (state.kind !== "ok") return { kind: "failed", detail: state.detail };
767
+ if (!state.registered) return { kind: "failed", detail: "the previous service is no longer registered" };
768
+ if (state.enabled !== snapshot.previousEnabled) {
769
+ return { kind: "failed", detail: "could not restore whether the previous service was enabled" };
770
+ }
771
+ if (state.running !== snapshot.previousRunning) {
772
+ return {
773
+ kind: "failed",
774
+ detail: snapshot.previousRunning ? "the previous service did not restart" : "could not restore the previous stopped state"
775
+ };
776
+ }
777
+ return { kind: "ok" };
778
+ };
779
+ var recoverDaemonServiceTransaction = (definition, path, run) => {
780
+ const stored = readJsonSafe(path);
781
+ if (stored.kind === "missing") return { kind: "ok" };
782
+ if (stored.kind !== "ok") {
783
+ return { kind: "failed", detail: `transaction receipt is ${stored.kind}` };
784
+ }
785
+ const transaction = parseDaemonServiceTransaction(stored.value);
786
+ if (!transaction) return { kind: "failed", detail: "transaction receipt is invalid" };
787
+ if (transaction.definitionPath !== definition.definitionPath) {
788
+ return { kind: "failed", detail: "transaction receipt belongs to a different service definition" };
789
+ }
790
+ if (transaction.phase === "prepared") {
791
+ const restored = restoreDaemonServiceSnapshot(definition, transaction, run);
792
+ if (restored.kind !== "ok") return restored;
793
+ }
794
+ try {
795
+ safeUnlink(path);
796
+ } catch (err) {
797
+ return { kind: "failed", detail: `could not clear transaction receipt: ${err instanceof Error ? err.message : String(err)}` };
798
+ }
799
+ return { kind: "ok" };
800
+ };
577
801
  var replaceDaemonService = async (options = {}) => {
802
+ const definition = resolveDaemonServiceDefinition(options);
803
+ if (!definition) {
804
+ return {
805
+ kind: "unavailable",
806
+ detail: "Could not find the globally installed daemon entry. Run Pushary setup again."
807
+ };
808
+ }
809
+ const run = options.run ?? runDaemonServiceCommand;
810
+ const transactionPath = join(options.stateDir ?? options.dir ?? pusharyDir(), "daemon-service-transaction.json");
811
+ const recovered = recoverDaemonServiceTransaction(definition, transactionPath, run);
812
+ if (recovered.kind !== "ok") {
813
+ return { kind: "failed", detail: `Could not recover interrupted service replacement: ${recovered.detail}` };
814
+ }
815
+ let previousContents = null;
816
+ try {
817
+ if (existsSync(definition.definitionPath)) {
818
+ previousContents = readFileSync(definition.definitionPath, "utf-8");
819
+ }
820
+ } catch (err) {
821
+ return { kind: "failed", detail: `Could not snapshot the existing service: ${err instanceof Error ? err.message : String(err)}` };
822
+ }
823
+ const previousState = probeDaemonServiceState(definition, run);
824
+ if (previousState.kind !== "ok") return { kind: "failed", detail: previousState.detail };
825
+ if ((previousState.registered || previousState.running) && previousContents === null) {
826
+ return { kind: "failed", detail: "Could not replace an active native service with no recoverable definition" };
827
+ }
828
+ const transaction = {
829
+ version: 1,
830
+ phase: "prepared",
831
+ definitionPath: definition.definitionPath,
832
+ previousContents,
833
+ previousRegistered: previousState.registered,
834
+ previousEnabled: previousState.enabled,
835
+ previousRunning: previousState.running
836
+ };
837
+ try {
838
+ writeJsonAtomicDurable(transactionPath, transaction, 384);
839
+ } catch (err) {
840
+ return { kind: "failed", detail: `Could not persist the service rollback snapshot: ${err instanceof Error ? err.message : String(err)}` };
841
+ }
578
842
  const stopped = await stopDaemonRunning(options);
579
- return stopped.kind === "unavailable" ? stopped : manageDaemonService("install", options);
843
+ if (stopped.kind === "unavailable") {
844
+ try {
845
+ safeUnlink(transactionPath);
846
+ } catch (err) {
847
+ return {
848
+ kind: "failed",
849
+ detail: `${stopped.detail}; could not clear unused rollback evidence: ${err instanceof Error ? err.message : String(err)}`
850
+ };
851
+ }
852
+ return stopped;
853
+ }
854
+ let installed = applyDaemonService("install", definition, { run });
855
+ if (installed.kind === "ok") {
856
+ const state = probeDaemonServiceState(definition, run);
857
+ if (state.kind !== "ok") {
858
+ installed = { kind: "failed", detail: state.detail };
859
+ } else if (!state.registered || !state.enabled || !state.running) {
860
+ installed = {
861
+ kind: "failed",
862
+ detail: !state.registered ? "Service registration verification failed" : !state.enabled ? "Service enable verification failed" : "Service start verification failed"
863
+ };
864
+ }
865
+ }
866
+ if (installed.kind === "ok") {
867
+ try {
868
+ writeJsonAtomicDurable(transactionPath, { ...transaction, phase: "committed" }, 384);
869
+ } catch (err) {
870
+ const detail = `Could not commit the service replacement: ${err instanceof Error ? err.message : String(err)}`;
871
+ const recovered2 = recoverDaemonServiceTransaction(definition, transactionPath, run);
872
+ return recovered2.kind === "ok" ? { kind: "failed", detail } : {
873
+ kind: "failed",
874
+ detail: `${detail}; recovery failed: ${recovered2.detail}`
875
+ };
876
+ }
877
+ try {
878
+ safeUnlink(transactionPath);
879
+ return installed;
880
+ } catch (err) {
881
+ return {
882
+ kind: "failed",
883
+ detail: `Service replacement committed but its receipt could not be cleared: ${err instanceof Error ? err.message : String(err)}`
884
+ };
885
+ }
886
+ }
887
+ const restored = restoreDaemonServiceSnapshot(definition, transaction, run);
888
+ if (restored.kind !== "ok") {
889
+ return { kind: "failed", detail: `${installed.detail}; rollback failed: ${restored.detail}` };
890
+ }
891
+ try {
892
+ safeUnlink(transactionPath);
893
+ } catch (err) {
894
+ return {
895
+ kind: "failed",
896
+ detail: `${installed.detail}; previous service restored but recovery evidence could not be cleared: ${err instanceof Error ? err.message : String(err)}`
897
+ };
898
+ }
899
+ return {
900
+ kind: "failed",
901
+ detail: `${installed.detail}; ${transaction.previousRegistered ? "previous service restored" : "previous service state restored"}`
902
+ };
580
903
  };
581
904
  var launchInstalledDaemon = (options) => {
582
905
  const entryExists = options.entryExists ?? existsSync;
@@ -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";
@@ -9,7 +9,7 @@ import {
9
9
  readJsonSafe,
10
10
  writeFileAtomic,
11
11
  writeJsonAtomic
12
- } from "./chunk-6MTNS63X.js";
12
+ } from "./chunk-XX2V4QS5.js";
13
13
  import {
14
14
  configFilePath
15
15
  } from "./chunk-2UMNXADU.js";
@@ -46,10 +46,21 @@ var writeFileAtomic = (filePath, contents, mode) => {
46
46
  };
47
47
  var writeJsonAtomic = (filePath, data, mode) => writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}
48
48
  `, mode);
49
+ var writeJsonAtomicDurable = (filePath, data, mode) => {
50
+ writeJsonAtomic(filePath, data, mode);
51
+ if (process.platform === "win32") return;
52
+ const directory = openSync(dirname(filePath), "r");
53
+ try {
54
+ fsyncSync(directory);
55
+ } finally {
56
+ closeSync(directory);
57
+ }
58
+ };
49
59
 
50
60
  export {
51
61
  readJsonSafe,
52
62
  backupFile,
53
63
  writeFileAtomic,
54
- writeJsonAtomic
64
+ writeJsonAtomic,
65
+ writeJsonAtomicDurable
55
66
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getMachineId
3
- } from "./chunk-2M66JVZM.js";
3
+ } from "./chunk-DKDDLF5P.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-5YUVKPTS.js";
3
+ } from "../chunk-EJT2ER6G.js";
4
4
  import "../chunk-ATBKJNWS.js";
5
5
  import "../chunk-VZKXBFUA.js";
6
6
  import "../chunk-YHG74UFF.js";
@@ -12,21 +12,21 @@ import {
12
12
  handlePostToolUse,
13
13
  handleStop,
14
14
  reportEvent
15
- } from "../chunk-BPKMGE7L.js";
15
+ } from "../chunk-4XZCDSNG.js";
16
16
  import {
17
17
  askUser,
18
18
  cancelQuestion,
19
19
  waitForAnswer
20
20
  } from "../chunk-GKAKAOKC.js";
21
21
  import "../chunk-KOPUTJQC.js";
22
- import "../chunk-2M66JVZM.js";
22
+ import "../chunk-DKDDLF5P.js";
23
23
  import "../chunk-GMI5QZLG.js";
24
24
  import "../chunk-BSZYIAZL.js";
25
25
  import "../chunk-SAF6HGAA.js";
26
26
  import "../chunk-OFRNZWC4.js";
27
27
  import "../chunk-7QLSKOSU.js";
28
28
  import "../chunk-KZERVKTD.js";
29
- import "../chunk-6MTNS63X.js";
29
+ import "../chunk-XX2V4QS5.js";
30
30
  import {
31
31
  getApiKey,
32
32
  getBaseUrl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.89.7",
3
+ "version": "0.89.8",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",