@pushary/agent-hooks 0.89.12 → 0.90.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +1 -0
  3. package/data/SKILL.md +1 -1
  4. package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
  5. package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
  6. package/dist/bin/pushary-bell-hook.js +4 -4
  7. package/dist/bin/pushary-bell.js +8 -8
  8. package/dist/bin/pushary-claude.js +194 -38
  9. package/dist/bin/pushary-clean.js +15 -32
  10. package/dist/bin/pushary-codex-bridge.js +37 -12
  11. package/dist/bin/pushary-codex-hook.js +45 -12
  12. package/dist/bin/pushary-codex.js +7 -7
  13. package/dist/bin/pushary-connect.js +11 -10
  14. package/dist/bin/pushary-daemon.js +186 -62
  15. package/dist/bin/pushary-disconnect.js +8 -8
  16. package/dist/bin/pushary-doctor.js +108 -25
  17. package/dist/bin/pushary-elicitation-hook.js +4 -4
  18. package/dist/bin/pushary-gemini-bridge.js +33 -11
  19. package/dist/bin/pushary-gemini-hook.js +21 -10
  20. package/dist/bin/pushary-hook.js +13 -13
  21. package/dist/bin/pushary-login.js +18 -7
  22. package/dist/bin/pushary-logout.js +6 -6
  23. package/dist/bin/pushary-mode.js +28 -21
  24. package/dist/bin/pushary-notification-hook.js +7 -7
  25. package/dist/bin/pushary-opencode-hook.js +8 -8
  26. package/dist/bin/pushary-permission-denied-hook.js +11 -11
  27. package/dist/bin/pushary-permission-hook.js +11 -11
  28. package/dist/bin/pushary-post-hook.js +14 -8
  29. package/dist/bin/pushary-prompt-hook.js +7 -7
  30. package/dist/bin/pushary-session-end-hook.js +7 -7
  31. package/dist/bin/pushary-session-start-hook.js +7 -7
  32. package/dist/bin/pushary-setup.js +168 -45
  33. package/dist/bin/pushary-stats.js +4 -4
  34. package/dist/bin/pushary-status.js +11 -10
  35. package/dist/bin/pushary-stop-hook.js +14 -8
  36. package/dist/bin/pushary-stopfailure-hook.js +7 -7
  37. package/dist/bin/pushary-suggestions.js +2 -2
  38. package/dist/bin/pushary-transcripts.d.ts +1 -0
  39. package/dist/bin/pushary-transcripts.js +79 -0
  40. package/dist/bin/pushary-upgrade.js +15 -15
  41. package/dist/bin/pushary-wait.js +5 -5
  42. package/dist/bin/pushary.js +3 -2
  43. package/dist/{chunk-KVHP5ZME.js → chunk-2LAHXHBR.js} +1 -1
  44. package/dist/{chunk-RLQWNEPM.js → chunk-43GMYSS4.js} +15 -0
  45. package/dist/{chunk-5NDB3YOE.js → chunk-54ELC7D7.js} +1 -1
  46. package/dist/{chunk-VPLQFXCH.js → chunk-5NAZ7SFS.js} +1 -1
  47. package/dist/chunk-7FE2KABY.js +156 -0
  48. package/dist/{chunk-DHR4F2CR.js → chunk-7TPQEWUZ.js} +1 -1
  49. package/dist/{chunk-Y4WVZIEI.js → chunk-AJYV3VGU.js} +1 -1
  50. package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
  51. package/dist/{chunk-OCMOTJTW.js → chunk-B7UZHEBL.js} +29 -22
  52. package/dist/{chunk-7HYJPOGG.js → chunk-BQBEH6TV.js} +5 -5
  53. package/dist/{chunk-OTPJ4FUR.js → chunk-BVBEURWJ.js} +1 -1
  54. package/dist/{chunk-7DK3ZH3N.js → chunk-CELX75MW.js} +1 -1
  55. package/dist/{chunk-6VXNA5JB.js → chunk-CNPAT3HL.js} +3 -2
  56. package/dist/{chunk-327YMKP5.js → chunk-DYP6SMOR.js} +1 -1
  57. package/dist/chunk-DZDI62LY.js +145 -0
  58. package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
  59. package/dist/{chunk-PS7DQ2SU.js → chunk-F2ICEWZL.js} +101 -19
  60. package/dist/{chunk-3XSP5SML.js → chunk-FQL5E6NG.js} +2 -2
  61. package/dist/{chunk-JHJHCMAF.js → chunk-FXEVRLHC.js} +20 -2
  62. package/dist/{chunk-6V7ZDH3A.js → chunk-GL3FFM7A.js} +7 -4
  63. package/dist/{chunk-NBRM3FMD.js → chunk-H2QZRQR2.js} +1 -1
  64. package/dist/{chunk-GKPURU7P.js → chunk-I463AEG3.js} +1 -1
  65. package/dist/{chunk-XREW7RQ3.js → chunk-JW4KO5AF.js} +1 -1
  66. package/dist/{chunk-OVEGEQSB.js → chunk-KUZWXPUS.js} +3 -2
  67. package/dist/{chunk-JVZX5YRJ.js → chunk-L4E2BIJ2.js} +80 -7
  68. package/dist/{chunk-RNKPLUWC.js → chunk-OSALSIUF.js} +1 -1
  69. package/dist/{chunk-MFNQ5IMW.js → chunk-PYA4FLKI.js} +32 -21
  70. package/dist/chunk-QCGNGEGE.js +15 -0
  71. package/dist/chunk-S6LYP7QA.js +127 -0
  72. package/dist/{chunk-OADGBOP7.js → chunk-TDZFPMSZ.js} +70 -11
  73. package/dist/{chunk-JJV5Q4KG.js → chunk-UZNKVRLE.js} +1 -1
  74. package/dist/{chunk-FCUUVHTS.js → chunk-VHWCVD42.js} +1 -1
  75. package/dist/{chunk-3MCEES6Z.js → chunk-VNMKIJZB.js} +15 -2
  76. package/dist/{chunk-6GU55AQQ.js → chunk-Y6BWPERV.js} +90 -7
  77. package/dist/chunk-YKLCWWEI.js +76 -0
  78. package/dist/src/index.d.ts +3 -0
  79. package/dist/src/index.js +11 -11
  80. package/package.json +3 -2
  81. package/dist/chunk-NESKLV6R.js +0 -72
  82. package/dist/chunk-WDVQ6O4C.js +0 -84
@@ -5,7 +5,7 @@ import {
5
5
  FILE_TARGET_TOOLS,
6
6
  redactSecrets,
7
7
  redactSecretsDeep
8
- } from "./chunk-3MCEES6Z.js";
8
+ } from "./chunk-VNMKIJZB.js";
9
9
 
10
10
  // ../hook-mapping/src/describe.ts
11
11
  import { isAbsolute, relative, resolve } from "path";
@@ -44,6 +44,48 @@ var eventPrefixes = {
44
44
  Read: (input) => `read: ${input.file_path ?? "unknown"}`,
45
45
  TodoWrite: (input) => describeTodoProgress(input.todos)
46
46
  };
47
+ var MCP_TOOL_PREFIX = "mcp__";
48
+ var parseMcpToolName = (toolName) => {
49
+ if (!toolName.startsWith(MCP_TOOL_PREFIX)) return void 0;
50
+ const rest = toolName.slice(MCP_TOOL_PREFIX.length);
51
+ const split = rest.indexOf("__");
52
+ if (split <= 0 || split + 2 >= rest.length) return void 0;
53
+ return { server: rest.slice(0, split), tool: rest.slice(split + 2) };
54
+ };
55
+ var NETWORK_TARGET_KEYS = ["url", "urls", "href", "host", "hosts", "hostname", "domain", "domains"];
56
+ var ABSOLUTE_HTTP_URL = /^https?:\/\//i;
57
+ var HOSTNAME = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+(?::\d{1,5})?$/i;
58
+ var hostOfUrl = (value) => {
59
+ try {
60
+ return new URL(value).host || void 0;
61
+ } catch {
62
+ return void 0;
63
+ }
64
+ };
65
+ var hostOf = (value, bareHostnames) => {
66
+ if (typeof value !== "string") return void 0;
67
+ const trimmed = value.trim();
68
+ if (ABSOLUTE_HTTP_URL.test(trimmed)) return hostOfUrl(trimmed);
69
+ return bareHostnames && HOSTNAME.test(trimmed) ? trimmed : void 0;
70
+ };
71
+ var firstHost = (value, bareHostnames) => Array.isArray(value) ? value.map((entry) => hostOf(entry, bareHostnames)).find((host) => host !== void 0) : hostOf(value, bareHostnames);
72
+ var deriveNetworkTarget = (toolInput) => {
73
+ for (const key of NETWORK_TARGET_KEYS) {
74
+ const host = firstHost(toolInput[key], true);
75
+ if (host) return host;
76
+ }
77
+ return Object.values(toolInput).map((value) => firstHost(value, false)).find((host) => host !== void 0);
78
+ };
79
+ var describeUnbuiltCall = (toolName, toolInput, format) => {
80
+ const mcp = parseMcpToolName(toolName);
81
+ const host = deriveNetworkTarget(toolInput);
82
+ if (mcp) {
83
+ if (format === "event") return `called ${mcp.tool} on ${mcp.server}`;
84
+ return host ? `call ${mcp.tool} on ${mcp.server} to reach ${host}` : `call ${mcp.tool} on ${mcp.server}`;
85
+ }
86
+ if (host) return format === "event" ? `accessed ${host}` : `network access to ${host}`;
87
+ return void 0;
88
+ };
47
89
  var EVENT_ACTION_MAX = 120;
48
90
  var HOOK_FALLBACK_MAX = 200;
49
91
  var UNNAMED_ACTION = "this request";
@@ -51,11 +93,12 @@ var describeToolCall = (toolName, toolInput, format = "hook") => {
51
93
  if (!toolName) return "";
52
94
  const prefixes = format === "hook" ? hookPrefixes : eventPrefixes;
53
95
  const builder = prefixes[toolName];
96
+ const unbuilt = builder ? void 0 : describeUnbuiltCall(toolName, toolInput ?? {}, format);
54
97
  const detail = JSON.stringify(toolInput ?? {});
55
- const raw = builder ? builder(toolInput) : format === "hook" ? detail === "{}" || detail === void 0 ? toolName : `${toolName}: ${detail}` : `${toolName}: done`;
98
+ const raw = builder ? builder(toolInput) : unbuilt ?? (format === "hook" ? detail === "{}" || detail === void 0 ? toolName : `${toolName}: ${detail}` : `${toolName}: done`);
56
99
  const redacted = redactSecrets(raw);
57
100
  if (format === "event") return redacted.slice(0, EVENT_ACTION_MAX);
58
- return builder ? redacted : redacted.slice(0, HOOK_FALLBACK_MAX);
101
+ return builder || unbuilt ? redacted : redacted.slice(0, HOOK_FALLBACK_MAX);
59
102
  };
60
103
  var TOOL_TARGET_MAX_LENGTH = 80;
61
104
  var deriveCommandHead = (command) => {
@@ -76,7 +119,9 @@ var deriveToolTarget = (toolName, toolInput) => {
76
119
  if (dot <= 0) return void 0;
77
120
  return base.slice(dot).slice(0, TOOL_TARGET_MAX_LENGTH);
78
121
  }
79
- return void 0;
122
+ const mcp = parseMcpToolName(toolName);
123
+ if (mcp) return mcp.server.slice(0, TOOL_TARGET_MAX_LENGTH);
124
+ return deriveNetworkTarget(toolInput)?.slice(0, TOOL_TARGET_MAX_LENGTH);
80
125
  };
81
126
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun", "npx", "deno", "rake", "make", "just", "task", "turbo"]);
82
127
  var RUN_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "run-script", "exec", "task"]);
@@ -210,7 +255,7 @@ var rawActionBody = (toolName, toolInput) => {
210
255
  if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor" || toolName === "run_shell_command" || toolName === "shell" || toolName === "apply_patch") {
211
256
  return firstString(toolInput.command);
212
257
  }
213
- return void 0;
258
+ return firstString(toolInput.url, toolInput.href);
214
259
  };
215
260
  var deriveActionBody = (toolName, toolInput) => {
216
261
  try {
@@ -264,11 +309,36 @@ var summarizeStop = (input) => {
264
309
  if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
265
310
  const crons = countArray(input.session_crons);
266
311
  if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
267
- return summarizeFinalMessage(input.last_assistant_message);
312
+ return summarizeFinalMessage(input.last_assistant_message ?? input.prompt_response);
313
+ };
314
+ var subagentStopAction = (agentType) => {
315
+ const name = typeof agentType === "string" ? agentType.replace(/\s+/g, " ").trim() : "";
316
+ return name ? `${name} subagent finished` : "Subagent finished";
317
+ };
318
+ var subagentStopSummary = (input) => {
319
+ const message = input.last_assistant_message ?? input.prompt_response;
320
+ if (typeof message === "string" && message.trim()) return summarizeStop(input);
321
+ return subagentStopAction(input.agent_type);
322
+ };
323
+ var COMPACTION_TRIGGERS = {
324
+ manual: "on request",
325
+ auto: "automatically"
326
+ };
327
+ var compactionAction = (hookEvent, trigger) => {
328
+ const how = typeof trigger === "string" ? COMPACTION_TRIGGERS[trigger] : void 0;
329
+ const base = hookEvent === "PostCompact" ? "Compacted context" : "Compacting context";
330
+ return how ? `${base} ${how}` : base;
268
331
  };
269
332
  var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "Session started";
270
333
  var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
271
334
  var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
335
+ var asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
336
+ var sessionIdentityOf = (payload) => {
337
+ const sessionId = asString(payload.session_id);
338
+ const agentId = asString(payload.agent_id);
339
+ if (!agentId) return { sessionId };
340
+ return { sessionId: agentId, parentSessionId: sessionId };
341
+ };
272
342
 
273
343
  export {
274
344
  HOOK_FALLBACK_MAX,
@@ -287,7 +357,10 @@ export {
287
357
  deriveBlocker,
288
358
  AGENT_IDENTITIES,
289
359
  summarizeStop,
360
+ subagentStopSummary,
361
+ compactionAction,
290
362
  sessionStartAction,
291
363
  sessionEndAction,
292
- taskTitleFrom
364
+ taskTitleFrom,
365
+ sessionIdentityOf
293
366
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-BC3VCZ3E.js";
4
4
  import {
5
5
  resolveInstallSourceFor
6
- } from "./chunk-XREW7RQ3.js";
6
+ } from "./chunk-JW4KO5AF.js";
7
7
  import {
8
8
  backupFile,
9
9
  readJsonSafe,
@@ -3,6 +3,10 @@ import {
3
3
  openSealedKey,
4
4
  publicKeyFingerprint
5
5
  } from "./chunk-3EGEA4KH.js";
6
+ import {
7
+ humanIsTty,
8
+ writeHuman
9
+ } from "./chunk-QCGNGEGE.js";
6
10
  import {
7
11
  useColor
8
12
  } from "./chunk-5RUIFDTP.js";
@@ -11,7 +15,7 @@ import {
11
15
  } from "./chunk-BSZYIAZL.js";
12
16
  import {
13
17
  reportedInstallSource
14
- } from "./chunk-XREW7RQ3.js";
18
+ } from "./chunk-JW4KO5AF.js";
15
19
 
16
20
  // src/reach.ts
17
21
  var reachVerdict = (channels) => {
@@ -263,16 +267,6 @@ var describeRoundTrip = (outcome) => {
263
267
  }
264
268
  };
265
269
 
266
- // src/cli/human.ts
267
- var stream = process.stdout;
268
- var setHumanStream = (target) => {
269
- stream = target;
270
- };
271
- var writeHuman = (text) => {
272
- stream.write(text);
273
- };
274
- var humanIsTty = () => stream === process.stdout && process.stdout.isTTY === true;
275
-
276
270
  // src/onboarding.ts
277
271
  import qrcodeTerminal from "qrcode-terminal";
278
272
  var paint = (open) => (s) => useColor() ? `${open}${s}\x1B[0m` : s;
@@ -650,6 +644,20 @@ var confirmAppConnection = async (apiKey) => {
650
644
  }
651
645
  return sendAndConfirm(apiKey, channels);
652
646
  };
647
+ var planAppConnect = (channels) => channels.precise && ownedApp(channels) === 0 ? { kind: "unpaired", repair: describeAppRepair(reachVerdict(channels)) ?? [] } : { kind: "send" };
648
+ var connectExistingApp = async (apiKey) => {
649
+ const probe = await fetchChannels(apiKey);
650
+ if (probe.kind !== "ok") return describeProbeFailure(probe);
651
+ const plan = planAppConnect(probe.status);
652
+ if (plan.kind === "send") {
653
+ console.log(` ${check} ${describeExistingConnection(probe.status)}`);
654
+ return sendAndConfirm(apiKey, probe.status);
655
+ }
656
+ console.log(` ${yellow("!")} No Pushary app of yours is connected to this site, and nothing was paired just now.`);
657
+ for (const line of plan.repair) console.log(` ${dim(line)}`);
658
+ console.log(` ${dim("No app yet? Get it at")} ${cyan("https://pushary.com/download")}`);
659
+ return phoneFailure("app_not_paired", plan.repair);
660
+ };
653
661
  var printConnectInstructions = async (apiKey) => {
654
662
  const probe = await fetchChannels(apiKey);
655
663
  if (probe.kind !== "ok") return describeProbeFailure(probe);
@@ -737,6 +745,8 @@ var ackPairing = async (pairId) => {
737
745
  } catch {
738
746
  }
739
747
  };
748
+ var pairFailed = (reason) => ({ kind: "failed", reason });
749
+ var pairingEscapeApplies = (outcome) => outcome.reason === "timeout" || outcome.reason === "expired" || outcome.reason === "cancelled";
740
750
  var buildPairLinks = (pairId, publicKeyB64, baseUrl = apiBase(), code = null) => {
741
751
  const id = encodeURIComponent(pairId);
742
752
  const pk = encodeURIComponent(publicKeyB64);
@@ -760,7 +770,7 @@ var connectViaAppPairing = async (options = {}) => {
760
770
  const started = await startPairing(keypair.publicKeyB64);
761
771
  if (!started) {
762
772
  console.log(` ${yellow("!")} Couldn't start pairing. Check your connection and try again.`);
763
- return null;
773
+ return pairFailed("unreachable");
764
774
  }
765
775
  const { pairId, code } = started;
766
776
  const { qrLink, universalLink, deepLink } = buildPairLinks(pairId, keypair.publicKeyB64, apiBase(), code);
@@ -800,14 +810,14 @@ var connectViaAppPairing = async (options = {}) => {
800
810
  let consecutiveFailures = 0;
801
811
  try {
802
812
  while (Date.now() < deadline) {
803
- if (cancelled) return null;
813
+ if (cancelled) return pairFailed("cancelled");
804
814
  const claim = await claimPairing(pairId);
805
815
  if (claim === null) {
806
816
  consecutiveFailures += 1;
807
817
  if (consecutiveFailures >= PAIR_MAX_CONSECUTIVE_FAILURES) {
808
818
  stop(yellow("!"), "Can't reach pushary.com to finish pairing. Check your connection, then re-run setup.");
809
819
  await cancelPairing(pairId);
810
- return null;
820
+ return pairFailed("unreachable");
811
821
  }
812
822
  await sleep(PAIR_POLL_INTERVAL_MS);
813
823
  continue;
@@ -820,29 +830,29 @@ var connectViaAppPairing = async (options = {}) => {
820
830
  } catch {
821
831
  stop(yellow("!"), "Pairing failed to decrypt \u2014 re-run setup to try again");
822
832
  await cancelPairing(pairId);
823
- return null;
833
+ return pairFailed("decrypt");
824
834
  }
825
835
  await ackPairing(pairId);
826
836
  stop(check, "App connected");
827
- return { apiKey, handle: claim.siteSlug };
837
+ return { kind: "paired", apiKey, handle: claim.siteSlug };
828
838
  }
829
839
  if (claim?.status === "expired") {
830
840
  stop(yellow("!"), "Pairing expired \u2014 re-run setup to try again");
831
- return null;
841
+ return pairFailed("expired");
832
842
  }
833
843
  if (claim?.status === "failed") {
834
844
  stop(yellow("!"), describePairFailure(claim.reason));
835
- return null;
845
+ return pairFailed("rejected");
836
846
  }
837
847
  await sleep(PAIR_POLL_INTERVAL_MS);
838
848
  }
839
849
  stop(yellow("!"), "Timed out waiting for the app");
840
850
  await cancelPairing(pairId);
841
- return null;
851
+ return pairFailed("timeout");
842
852
  } catch (err) {
843
853
  stop(yellow("!"), `Pairing error ${dim(`(${err instanceof Error ? err.message : "network"})`)}`);
844
854
  await cancelPairing(pairId);
845
- return null;
855
+ return pairFailed("unreachable");
846
856
  } finally {
847
857
  process.off("SIGINT", onInterrupt);
848
858
  }
@@ -853,10 +863,11 @@ export {
853
863
  describeReach,
854
864
  verifyRoundTrip,
855
865
  describeRoundTrip,
856
- setHumanStream,
857
866
  fetchChannels,
858
867
  connectDevice,
859
868
  confirmAppConnection,
869
+ connectExistingApp,
860
870
  printConnectInstructions,
871
+ pairingEscapeApplies,
861
872
  connectViaAppPairing
862
873
  };
@@ -0,0 +1,15 @@
1
+ // src/cli/human.ts
2
+ var stream = process.stdout;
3
+ var setHumanStream = (target) => {
4
+ stream = target;
5
+ };
6
+ var writeHuman = (text) => {
7
+ stream.write(text);
8
+ };
9
+ var humanIsTty = () => stream === process.stdout && process.stdout.isTTY === true;
10
+
11
+ export {
12
+ setHumanStream,
13
+ writeHuman,
14
+ humanIsTty
15
+ };
@@ -0,0 +1,127 @@
1
+ import {
2
+ HOOK_LOCATOR_BINARY
3
+ } from "./chunk-VNMKIJZB.js";
4
+
5
+ // src/diagnostics/bridge.ts
6
+ import { execFileSync } from "child_process";
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { dirname, join } from "path";
10
+ var BRIDGE_BUNDLE_IDENTIFIER = "com.pushary.app";
11
+ var BRIDGE_HELPER_SUFFIX = "/Contents/Helpers/pushary-bridge";
12
+ var BRIDGE_BUNDLE_ROOTS = ["/Applications/Pushary.app", "$HOME/Applications/Pushary.app"];
13
+ var expandHome = (path, home) => path.replaceAll("$HOME", home);
14
+ var parseBridgeStub = (contents, stubPath) => {
15
+ const helperSuffix = contents.match(/^H=(\S+)$/m)?.[1] ?? BRIDGE_HELPER_SUFFIX;
16
+ const cachePath = contents.match(/^C="([^"\n]+)"$/m)?.[1] ?? join(dirname(stubPath), ".bridge-cache");
17
+ const rootsLine = contents.match(/^for P in ((?:"[^"\n]*"\s*)+); do$/m)?.[1];
18
+ const bundleRoots = rootsLine ? [...rootsLine.matchAll(/"([^"]*)"/g)].map((match) => match[1]) : BRIDGE_BUNDLE_ROOTS;
19
+ const bundleIdentifier = contents.match(/kMDItemCFBundleIdentifier == "([^"]+)"/)?.[1] ?? BRIDGE_BUNDLE_IDENTIFIER;
20
+ return { helperSuffix, cachePath, bundleRoots, bundleIdentifier };
21
+ };
22
+ var resolveBridgeStub = (stubPath, probe) => {
23
+ const contents = probe.readFile(stubPath);
24
+ if (contents === null) return { kind: "no-stub", stub: stubPath };
25
+ const shape = parseBridgeStub(contents, stubPath);
26
+ const helperIn = (bundle) => `${bundle}${shape.helperSuffix}`;
27
+ const looked = [];
28
+ for (const root of shape.bundleRoots.map((root2) => expandHome(root2, probe.home))) {
29
+ looked.push(root);
30
+ if (probe.exists(helperIn(root))) return { kind: "bundle", helper: helperIn(root), via: "root" };
31
+ }
32
+ const cached = shape.cachePath ? probe.readFile(shape.cachePath)?.split("\n")[0]?.trim() : void 0;
33
+ if (cached) {
34
+ looked.push(cached);
35
+ if (probe.exists(helperIn(cached))) return { kind: "bundle", helper: helperIn(cached), via: "cache" };
36
+ }
37
+ for (const found of probe.mdfind(shape.bundleIdentifier)) {
38
+ looked.push(found);
39
+ if (probe.exists(helperIn(found))) return { kind: "bundle", helper: helperIn(found), via: "spotlight" };
40
+ }
41
+ return { kind: "missing", stub: stubPath, looked };
42
+ };
43
+ var BRIDGE_GONE_MESSAGE = "the Mac app is gone but its hooks remain; run pushary setup or reinstall the app";
44
+ var describeBridgeResolution = (resolution) => {
45
+ switch (resolution.kind) {
46
+ case "bundle":
47
+ return `${resolution.helper} (${resolution.via === "root" ? "installed" : resolution.via === "cache" ? "cached path" : "found by Spotlight"})`;
48
+ case "missing":
49
+ return BRIDGE_GONE_MESSAGE;
50
+ case "no-stub":
51
+ return `${resolution.stub} is not there; ${BRIDGE_GONE_MESSAGE}`;
52
+ }
53
+ };
54
+ var isBridgeLocator = (executable) => executable !== null && executable.split(/[/\\]/).pop() === HOOK_LOCATOR_BINARY;
55
+ var spotlight = (bundleIdentifier) => {
56
+ if (process.platform !== "darwin") return [];
57
+ try {
58
+ return execFileSync("/usr/bin/mdfind", [`kMDItemCFBundleIdentifier == "${bundleIdentifier}"`], {
59
+ encoding: "utf-8",
60
+ timeout: 5e3,
61
+ stdio: ["ignore", "pipe", "ignore"]
62
+ }).split("\n").map((line) => line.trim()).filter((line) => line !== "");
63
+ } catch {
64
+ return [];
65
+ }
66
+ };
67
+ var readOrNull = (path) => {
68
+ try {
69
+ return readFileSync(path, "utf-8");
70
+ } catch {
71
+ return null;
72
+ }
73
+ };
74
+ var liveBridgeProbe = () => ({
75
+ exists: existsSync,
76
+ readFile: readOrNull,
77
+ mdfind: spotlight,
78
+ home: homedir()
79
+ });
80
+
81
+ // src/diagnostics/service-runtime.ts
82
+ var unescapeXml = (value) => value.replaceAll("&quot;", '"').replaceAll("&apos;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
83
+ var unquoteSystemd = (value) => value.replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("%%", "%");
84
+ var readServiceExecPath = (contents, platform) => {
85
+ if (platform === "darwin") {
86
+ const program = contents.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];
87
+ return program ? unescapeXml(program) : null;
88
+ }
89
+ if (platform === "linux") {
90
+ const exec = contents.match(/^ExecStart="((?:[^"\\]|\\.)*)"/m)?.[1];
91
+ return exec ? unquoteSystemd(exec) : null;
92
+ }
93
+ if (platform === "win32") {
94
+ const command = contents.match(/<Command>([^<]*)<\/Command>/)?.[1];
95
+ return command ? unescapeXml(command) : null;
96
+ }
97
+ return null;
98
+ };
99
+ var MANAGER_SEGMENTS = [
100
+ [".nvm", "nvm"],
101
+ ["nvm", "nvm"],
102
+ ["fnm", "fnm"],
103
+ [".fnm", "fnm"],
104
+ [".volta", "volta"],
105
+ ["volta", "volta"],
106
+ [".asdf", "asdf"],
107
+ ["asdf", "asdf"]
108
+ ];
109
+ var versionManagerOf = (execPath) => {
110
+ const segments = execPath.split(/[/\\]/);
111
+ for (const segment of segments) {
112
+ const hit = MANAGER_SEGMENTS.find(([name]) => name === segment);
113
+ if (hit) return hit[1];
114
+ }
115
+ return null;
116
+ };
117
+ var describeVersionManagerPin = (execPath, manager) => `The background service is pinned to ${execPath}, which ${manager} manages. Switching Node versions strands it; run pushary daemon install afterwards.`;
118
+
119
+ export {
120
+ resolveBridgeStub,
121
+ describeBridgeResolution,
122
+ isBridgeLocator,
123
+ liveBridgeProbe,
124
+ readServiceExecPath,
125
+ versionManagerOf,
126
+ describeVersionManagerPin
127
+ };
@@ -4,6 +4,7 @@ import {
4
4
  isDefaultSession,
5
5
  isPolicyConfig,
6
6
  listPendingQuestions,
7
+ listPendingQuestionsForToolUse,
7
8
  markPendingCommandDelivered,
8
9
  readPendingCommand,
9
10
  removePendingCommand,
@@ -12,9 +13,10 @@ import {
12
13
  savePendingCommand,
13
14
  sendNotification,
14
15
  waitForAnswer
15
- } from "./chunk-JHJHCMAF.js";
16
+ } from "./chunk-FXEVRLHC.js";
16
17
  import {
17
18
  AGENT_IDENTITIES,
19
+ compactionAction,
18
20
  deriveReceiptMeta,
19
21
  deriveToolTarget,
20
22
  describeToolCall,
@@ -22,20 +24,22 @@ import {
22
24
  parseApplyPatchFiles,
23
25
  parseApplyPatchOps,
24
26
  sessionEndAction,
27
+ sessionIdentityOf,
25
28
  sessionStartAction,
29
+ subagentStopSummary,
26
30
  summarizeStop,
27
31
  taskTitleFrom,
28
32
  toolResultErrorText
29
- } from "./chunk-JVZX5YRJ.js";
33
+ } from "./chunk-L4E2BIJ2.js";
30
34
  import {
31
35
  getMachineId
32
- } from "./chunk-5NDB3YOE.js";
36
+ } from "./chunk-54ELC7D7.js";
33
37
  import {
34
38
  withRetry
35
39
  } from "./chunk-BSZYIAZL.js";
36
40
  import {
37
41
  reportedInstallSource
38
- } from "./chunk-XREW7RQ3.js";
42
+ } from "./chunk-JW4KO5AF.js";
39
43
  import {
40
44
  getApiKey,
41
45
  getBaseUrl
@@ -51,7 +55,7 @@ import {
51
55
  pendingCommandEvents,
52
56
  resolveAutoResolveOrigin,
53
57
  resolvePolicy
54
- } from "./chunk-3MCEES6Z.js";
58
+ } from "./chunk-VNMKIJZB.js";
55
59
 
56
60
  // src/policy.ts
57
61
  import { createHash } from "crypto";
@@ -505,6 +509,15 @@ var permissionTimeoutDecision = (timeoutAction) => {
505
509
  };
506
510
  var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
507
511
 
512
+ // src/driver.ts
513
+ var DRIVER_ENV = "PUSHARY_DRIVER";
514
+ var PHONE_DRIVER = "phone";
515
+ var driverFromEnv = (env) => env[DRIVER_ENV] === PHONE_DRIVER ? PHONE_DRIVER : void 0;
516
+ var phoneDrivenEnv = (base) => ({
517
+ ...base,
518
+ [DRIVER_ENV]: PHONE_DRIVER
519
+ });
520
+
508
521
  // src/throttle.ts
509
522
  import { join as join4 } from "path";
510
523
  import { tmpdir as tmpdir3 } from "os";
@@ -552,10 +565,13 @@ var readLastPrompt = (sessionId) => {
552
565
  return void 0;
553
566
  }
554
567
  };
555
- var reconcilePendingQuestions = async (sessionId) => {
568
+ var pendingQuestionsToSettle = (sessionId, toolUseId) => {
569
+ const settledElsewhere = toolUseId ? listPendingQuestionsForToolUse(sessionId, toolUseId) : [];
570
+ return [.../* @__PURE__ */ new Set([...settledElsewhere, ...listPendingQuestions(sessionId)])];
571
+ };
572
+ var reconcileQuestions = async (sessionId, files) => {
556
573
  const late = [];
557
574
  try {
558
- const files = listPendingQuestions(sessionId);
559
575
  if (files.length === 0) return late;
560
576
  const apiKey = getApiKey();
561
577
  for (const correlationId of files) {
@@ -576,6 +592,7 @@ var reconcilePendingQuestions = async (sessionId) => {
576
592
  }
577
593
  return late;
578
594
  };
595
+ var reconcilePendingQuestions = (sessionId, toolUseId) => reconcileQuestions(sessionId, pendingQuestionsToSettle(sessionId, toolUseId));
579
596
  var describeLateAnswer = (value) => {
580
597
  const v = (value ?? "").trim();
581
598
  if (v === "yes") return "approved it";
@@ -724,6 +741,7 @@ var reportEvent = async (event, options = {}) => {
724
741
  // Link-back for a phone-spawned session: forward the spawn id the daemon
725
742
  // set in the environment so the server can map spawnId -> sessionId.
726
743
  ...process.env.PUSHARY_SPAWN_ID ? { spawnId: process.env.PUSHARY_SPAWN_ID } : {},
744
+ ...driverFromEnv(process.env) ? { driver: driverFromEnv(process.env) } : {},
727
745
  canDrainCommand: clientCanDrain && !storedCommand,
728
746
  ackMode: clientCanDrain && !storedCommand,
729
747
  skipCommandDrain: storedCommand != null
@@ -780,6 +798,11 @@ var reportEvent = async (event, options = {}) => {
780
798
  }, { maxAttempts: options.maxAttempts ?? 2, baseDelayMs: 300 });
781
799
  };
782
800
  var NON_GATED_MIRROR_TOOLS = /* @__PURE__ */ new Set(["TodoWrite"]);
801
+ var settleQuestionsForToolUse = async (sessionId, toolUseId) => {
802
+ if (!toolUseId) return;
803
+ const late = await reconcileQuestions(sessionId, listPendingQuestionsForToolUse(sessionId, toolUseId));
804
+ for (const answer of late) removePendingQuestion(sessionId, answer.correlationId);
805
+ };
783
806
  var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
784
807
  try {
785
808
  const projectName = basename2(input.cwd ?? process.cwd());
@@ -790,7 +813,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
790
813
  const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
791
814
  const sessionKey = input.session_id || DEFAULT_SESSION;
792
815
  const liveMode = await fetchModeState(getApiKey(), input.session_id, MODE_REUSE_MS);
793
- const late = await reconcilePendingQuestions(sessionKey);
816
+ const late = await reconcilePendingQuestions(sessionKey, input.tool_use_id);
794
817
  let additionalContext;
795
818
  if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
796
819
  additionalContext = buildLateAnswerContext(late);
@@ -875,7 +898,7 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
875
898
  agentType: agent.type,
876
899
  agentName: `${agent.label} - ${projectName}`,
877
900
  action: summarizeStop(input),
878
- sessionId: input.session_id,
901
+ ...sessionIdentityOf(input),
879
902
  usage: deriveUsage(input.transcript_path, input.session_id)
880
903
  }, { maxAttempts: 1 })
881
904
  ];
@@ -897,16 +920,46 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
897
920
  return void 0;
898
921
  }
899
922
  };
923
+ var isSubagentStop = (input) => input.hook_event_name === "SubagentStop" || typeof input.agent_id === "string" && input.agent_id.length > 0;
924
+ var SUBAGENT_STOP_REPORT_TIMEOUT_MS = 4e3;
925
+ var handleSubagentStop = async (input, agent = CLAUDE_CODE_AGENT) => {
926
+ try {
927
+ const projectName = basename2(input.cwd ?? process.cwd());
928
+ await reportEvent({
929
+ event: "session_end",
930
+ agentType: agent.type,
931
+ agentName: `${agent.label} - ${projectName}`,
932
+ action: subagentStopSummary(input),
933
+ ...sessionIdentityOf(input)
934
+ }, { maxAttempts: 1, timeoutMs: SUBAGENT_STOP_REPORT_TIMEOUT_MS });
935
+ } catch {
936
+ }
937
+ };
938
+ var isCompactionEvent = (hookEventName) => hookEventName === "PreCompact" || hookEventName === "PostCompact";
939
+ var COMPACTION_REPORT_TIMEOUT_MS = 4e3;
940
+ var handleCompaction = async (input, agent = CLAUDE_CODE_AGENT) => {
941
+ try {
942
+ const projectName = basename2(input.cwd ?? process.cwd());
943
+ await reportEvent({
944
+ event: "compaction",
945
+ agentType: agent.type,
946
+ agentName: `${agent.label} - ${projectName}`,
947
+ action: compactionAction(input.hook_event_name, input.trigger),
948
+ sessionId: input.session_id
949
+ }, { maxAttempts: 1, timeoutMs: COMPACTION_REPORT_TIMEOUT_MS });
950
+ } catch {
951
+ }
952
+ };
900
953
  var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
901
954
  try {
902
955
  const projectName = basename2(input.cwd ?? process.cwd());
903
- const action = sessionStartAction(input.source);
956
+ const action = input.agent_id ? "Subagent started" : sessionStartAction(input.source);
904
957
  await reportEvent({
905
958
  event: "session_start",
906
959
  agentType: agent.type,
907
960
  agentName: `${agent.label} - ${projectName}`,
908
961
  action,
909
- sessionId: input.session_id
962
+ ...sessionIdentityOf(input)
910
963
  }, { maxAttempts: 2, timeoutMs: 4e3 });
911
964
  } catch {
912
965
  }
@@ -1033,6 +1086,7 @@ export {
1033
1086
  getPolicy,
1034
1087
  fetchModeState,
1035
1088
  fetchModeOverride,
1089
+ phoneDrivenEnv,
1036
1090
  repoKeyFor,
1037
1091
  throttlePass,
1038
1092
  readLastUserPrompt,
@@ -1048,9 +1102,14 @@ export {
1048
1102
  preToolUseTimeoutDecision,
1049
1103
  readLastPrompt,
1050
1104
  reportEvent,
1105
+ settleQuestionsForToolUse,
1051
1106
  handlePostToolUse,
1052
1107
  handleUserPrompt,
1053
1108
  handleStop,
1109
+ isSubagentStop,
1110
+ handleSubagentStop,
1111
+ isCompactionEvent,
1112
+ handleCompaction,
1054
1113
  handleSessionStart,
1055
1114
  handleSessionEnd,
1056
1115
  handleNotification,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readTranscriptPreference
3
- } from "./chunk-RNKPLUWC.js";
3
+ } from "./chunk-OSALSIUF.js";
4
4
 
5
5
  // src/transcript-setting.ts
6
6
  var fromEnv = () => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-3MCEES6Z.js";
3
+ } from "./chunk-VNMKIJZB.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";