@pushary/agent-hooks 0.85.0 → 0.85.1

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 (64) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/data/cursor-plugin/CHANGELOG.md +21 -0
  3. package/data/cursor-plugin/scripts/pushary-gate.mjs +54 -222
  4. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +11 -106
  5. package/data/vscode-plugin/CHANGELOG.md +26 -0
  6. package/data/vscode-plugin/scripts/pushary-gate.mjs +66 -74
  7. package/dist/bin/pushary-bell-hook.js +4 -4
  8. package/dist/bin/pushary-bell.js +6 -6
  9. package/dist/bin/pushary-claude.js +12 -11
  10. package/dist/bin/pushary-clean.js +6 -6
  11. package/dist/bin/pushary-codex-bridge.js +5 -5
  12. package/dist/bin/pushary-codex-hook.js +9 -9
  13. package/dist/bin/pushary-codex.js +3 -3
  14. package/dist/bin/pushary-connect.js +6 -6
  15. package/dist/bin/pushary-daemon.js +4 -4
  16. package/dist/bin/pushary-disconnect.js +5 -5
  17. package/dist/bin/pushary-doctor.js +11 -11
  18. package/dist/bin/pushary-gemini-bridge.js +5 -5
  19. package/dist/bin/pushary-gemini-hook.js +9 -9
  20. package/dist/bin/pushary-hook.js +8 -8
  21. package/dist/bin/pushary-login.js +4 -4
  22. package/dist/bin/pushary-logout.js +4 -4
  23. package/dist/bin/pushary-mode.js +3 -3
  24. package/dist/bin/pushary-notification-hook.js +3 -3
  25. package/dist/bin/pushary-permission-denied-hook.js +8 -8
  26. package/dist/bin/pushary-permission-hook.js +8 -8
  27. package/dist/bin/pushary-post-hook.js +3 -3
  28. package/dist/bin/pushary-prompt-hook.js +3 -3
  29. package/dist/bin/pushary-session-end-hook.js +3 -3
  30. package/dist/bin/pushary-session-start-hook.js +3 -3
  31. package/dist/bin/pushary-setup.js +14 -14
  32. package/dist/bin/pushary-stats.js +2 -2
  33. package/dist/bin/pushary-status.js +5 -5
  34. package/dist/bin/pushary-stop-hook.js +3 -3
  35. package/dist/bin/pushary-stopfailure-hook.js +3 -3
  36. package/dist/bin/pushary-upgrade.js +6 -6
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-YKW6JCWI.js → chunk-55656H4Q.js} +1 -1
  39. package/dist/{chunk-6LC4WSCL.js → chunk-BZKTCOZQ.js} +1 -1
  40. package/dist/{chunk-K7CSRGKZ.js → chunk-CIVG746V.js} +1 -1
  41. package/dist/{chunk-FBGBJXVC.js → chunk-DNSIVHIA.js} +1 -1
  42. package/dist/{chunk-437T77TY.js → chunk-ES3FAX5X.js} +1 -1
  43. package/dist/{chunk-AXRATIS6.js → chunk-EZPRY2ZY.js} +1 -1
  44. package/dist/{chunk-MQMPG5X4.js → chunk-G3LXA2ZP.js} +207 -10
  45. package/dist/{chunk-64PXDATC.js → chunk-GM54KC62.js} +17 -17
  46. package/dist/chunk-HSGEEBBL.js +37 -0
  47. package/dist/{chunk-ADISVXZL.js → chunk-IGV4KL7B.js} +1 -1
  48. package/dist/{chunk-GCKAWHFP.js → chunk-JDQY47OW.js} +1 -1
  49. package/dist/{chunk-7PZPJSWI.js → chunk-KTKIZEFT.js} +1 -1
  50. package/dist/{chunk-GFM3STWO.js → chunk-LB6RSCLL.js} +12 -10
  51. package/dist/{chunk-DOXSWT4Q.js → chunk-OZKRF7MS.js} +6 -6
  52. package/dist/{chunk-HVZIHTOO.js → chunk-PHXEADSL.js} +7 -7
  53. package/dist/{chunk-F52SIIDG.js → chunk-QDKJTPAT.js} +1 -1
  54. package/dist/{chunk-NJ7JT26G.js → chunk-VGADRFG6.js} +1 -1
  55. package/dist/{chunk-4QPOMJUB.js → chunk-VKJUVPTJ.js} +2 -2
  56. package/dist/{chunk-ERYKGH4J.js → chunk-XIR6N66V.js} +1 -1
  57. package/dist/{chunk-WEAQQSHQ.js → chunk-XLM6COJA.js} +1 -1
  58. package/dist/{chunk-EXMUM226.js → chunk-Y7BALRVB.js} +54 -127
  59. package/dist/chunk-YHG74UFF.js +12 -0
  60. package/dist/src/index.d.ts +6 -21
  61. package/dist/src/index.js +10 -9
  62. package/package.json +4 -2
  63. package/dist/chunk-GXIFADGD.js +0 -66
  64. package/dist/chunk-PWTKKQQI.js +0 -27
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-6MTNS63X.js";
10
10
  import {
11
11
  resolveInstallSourceFor
12
- } from "./chunk-437T77TY.js";
12
+ } from "./chunk-ES3FAX5X.js";
13
13
  import {
14
14
  configFilePath
15
15
  } from "./chunk-2UMNXADU.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-F52SIIDG.js";
4
+ } from "./chunk-QDKJTPAT.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-MQMPG5X4.js";
6
+ } from "./chunk-G3LXA2ZP.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-64PXDATC.js";
3
+ } from "./chunk-GM54KC62.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -65,6 +65,7 @@ var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
65
65
  };
66
66
  var hookWaitClamped = (startMs, policyWaitSeconds, agent, nowMs) => startMs + hookMaxWaitSeconds(agent) * 1e3 < nowMs + Math.max(policyWaitSeconds, 0) * 1e3;
67
67
  var effectiveWaitSeconds = (timeoutAction, policySeconds, agent) => timeoutAction === "wait" ? hookMaxWaitSeconds(agent) : policySeconds;
68
+ var HOOK_BATCH_MAX_BYTES = 4 * 1024 * 1024;
68
69
  var REPO_KEY_MAX_LENGTH = 200;
69
70
  var isRepoKeyMatch = (ruleRepoKey, currentRepoKey) => {
70
71
  if (!ruleRepoKey) return true;
@@ -635,6 +636,88 @@ var SECRET_REDACTION_RULES = [
635
636
  var HIGH_ENTROPY_RULE = { pattern: /[A-Za-z0-9+/]{40,}={0,2}/g, replacement: "[redacted]" };
636
637
  var redactSecrets = (text) => SECRET_REDACTION_RULES.reduce((acc, rule) => acc.replace(rule.pattern, rule.replacement), text);
637
638
  var redactSecretsDeep = (text) => redactSecrets(text).replace(HIGH_ENTROPY_RULE.pattern, HIGH_ENTROPY_RULE.replacement);
639
+ var AUTHORIZATION_CANONICAL_VERSION = "pushary.authz.v1";
640
+ var AUTHORIZATION_CANONICAL_FIXTURE = [
641
+ {
642
+ subject: { toolName: "refund.create" },
643
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
644
+ action:13:refund.create
645
+ target:0:
646
+ actor:0:
647
+ environment:0:
648
+ externalId:0:`
649
+ },
650
+ {
651
+ subject: {
652
+ toolName: "refund.create",
653
+ toolTarget: "ord_991",
654
+ actor: "user:u_44",
655
+ environment: "production",
656
+ externalId: "cust_7",
657
+ parameters: { currency: "EUR", amount: 4800, expedited: true, rate: 1.5 }
658
+ },
659
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
660
+ action:13:refund.create
661
+ target:7:ord_991
662
+ actor:9:user:u_44
663
+ environment:10:production
664
+ externalId:6:cust_7
665
+ p:13:amount=n:4800
666
+ p:14:currency=s:EUR
667
+ p:16:expedited=b:true
668
+ p:10:rate=n:1.5`
669
+ },
670
+ {
671
+ // Parameter order is not part of the subject. The same facts written in a
672
+ // different order are the same authorization.
673
+ subject: { toolName: "refund.create", parameters: { b: 2, a: 1 } },
674
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
675
+ action:13:refund.create
676
+ target:0:
677
+ actor:0:
678
+ environment:0:
679
+ externalId:0:
680
+ p:5:a=n:1
681
+ p:5:b=n:2`
682
+ },
683
+ {
684
+ // An integral float is the same number as the integer.
685
+ subject: { toolName: "a", parameters: { n: 49 } },
686
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
687
+ action:1:a
688
+ target:0:
689
+ actor:0:
690
+ environment:0:
691
+ externalId:0:
692
+ p:6:n=n:49`
693
+ },
694
+ {
695
+ // Delimiters inside a value cannot forge another subject.
696
+ subject: { toolName: "a", parameters: { "x\ny": "1" } },
697
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
698
+ action:1:a
699
+ target:0:
700
+ actor:0:
701
+ environment:0:
702
+ externalId:0:
703
+ p:7:x
704
+ y=s:1`
705
+ },
706
+ {
707
+ // Every finite number, including the ones another language would render
708
+ // differently. One runtime computes both sides of the comparison.
709
+ subject: { toolName: "a", parameters: { small: 1e-7, big: 1e21 } },
710
+ canonical: `${AUTHORIZATION_CANONICAL_VERSION}
711
+ action:1:a
712
+ target:0:
713
+ actor:0:
714
+ environment:0:
715
+ externalId:0:
716
+ p:11:big=n:1e+21
717
+ p:12:small=n:1e-7`
718
+ }
719
+ ];
720
+ var AUTHORIZATION_PERMIT_TTL_SECONDS = 60 * 60;
638
721
  var classifyDecisionRisk = (toolName, toolTarget) => {
639
722
  const parts = [toolName, toolTarget ?? ""];
640
723
  const matchesAny = (patterns) => parts.some((part) => patterns.some((re) => re.test(part)));
@@ -833,9 +916,127 @@ var isScopeContract = (value) => {
833
916
  const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
834
917
  return strings(c.allowedPaths) && strings(c.offLimitsPaths) && typeof c.doneWhen === "string";
835
918
  };
919
+ var autoApprove = (tool) => ({
920
+ tool,
921
+ timeoutSeconds: 0,
922
+ timeoutAction: "approve",
923
+ mode: "terminal_only",
924
+ pushFirstSeconds: 0
925
+ });
926
+ var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
927
+ const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
928
+ const arg = args[0];
929
+ const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
930
+ const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
931
+ if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
932
+ return "safe_readonly";
933
+ }
934
+ return "policy_timeout";
935
+ };
936
+ var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
937
+ const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
938
+ const arg = args[0];
939
+ const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
940
+ let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
941
+ tool: toolName,
942
+ timeoutSeconds: config.defaultTimeoutSeconds,
943
+ timeoutAction: config.defaultTimeoutAction,
944
+ mode: config.defaultMode ?? "push_first",
945
+ pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
946
+ };
947
+ const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
948
+ if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
949
+ base = autoApprove(base.tool);
950
+ }
951
+ const wouldAutoApprove = isAutoApprove(base);
952
+ if (!governedBySpecificRule && wouldAutoApprove && toolName === "Bash" && typeof arg === "string" && classifyDecisionRisk(toolName, arg) === "destructive") {
953
+ base = {
954
+ tool: base.tool,
955
+ timeoutSeconds: config.defaultTimeoutSeconds,
956
+ timeoutAction: "wait",
957
+ mode: "push_first",
958
+ pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
959
+ };
960
+ }
961
+ const effectiveOverride = modeOverride ?? config.modeOverride;
962
+ if (effectiveOverride) {
963
+ return { ...base, mode: effectiveOverride };
964
+ }
965
+ return base;
966
+ };
967
+ var restraintRank = (policy) => {
968
+ if (isAutoDeny(policy)) return 4;
969
+ if (isAutoApprove(policy)) return 0;
970
+ if (policy.mode === "push_only") return 3;
971
+ return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
972
+ };
973
+ var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
974
+ var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey, sessionId) => {
975
+ if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey, sessionId);
976
+ return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey, sessionId)).reduce(strictestPolicy);
977
+ };
978
+ var isModeStateDegraded = (state) => state?.degraded === true;
979
+ var KILL_REASON = "Stopped by user, this agent was halted from Pushary";
980
+ var strictestScopeVerdict = (scope, paths) => {
981
+ if (paths.length === 0) return evaluateScope(scope, void 0);
982
+ let worst = { outcome: "in_scope" };
983
+ for (const path of paths) {
984
+ const verdict = evaluateScope(scope, path);
985
+ if (verdict.outcome === "off_limits") return verdict;
986
+ if (verdict.outcome === "out_of_scope") worst = verdict;
987
+ }
988
+ return worst;
989
+ };
990
+ var survivesDegraded = (config, toolName, toolInputs, cwd, repoKey, sessionId) => toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly";
991
+ var resolveGate = (input) => {
992
+ const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
993
+ if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
994
+ const policy = resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId);
995
+ const verdict = verdictForPolicy(policy);
996
+ const autoApproves = verdict === "allow";
997
+ const autoDenies = verdict === "deny";
998
+ if (autoDenies) return { kind: "deny", policy, reason: `Denied by policy for ${policy.tool}` };
999
+ if (isModeStateDegraded(modeState)) {
1000
+ if (autoApproves) {
1001
+ return survivesDegraded(config, toolName, toolInputs, cwd, repoKey, sessionId) ? { kind: "allow", policy, source: "allowlist" } : { kind: "defer", policy };
1002
+ }
1003
+ return { kind: "gate", policy };
1004
+ }
1005
+ const scopeVerdict = modeState.scope ? strictestScopeVerdict(modeState.scope, input.scopePaths ?? []) : { outcome: "no_contract" };
1006
+ if (autoApproves) {
1007
+ if (!scopeRequiresHuman(scopeVerdict)) {
1008
+ return {
1009
+ kind: "allow",
1010
+ policy,
1011
+ source: toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly" ? "allowlist" : "policy"
1012
+ };
1013
+ }
1014
+ }
1015
+ const scopeReason = scopeChangeReason(scopeVerdict);
1016
+ return {
1017
+ kind: "gate",
1018
+ policy,
1019
+ scopeReason,
1020
+ scopePath: scopeReason && "path" in scopeVerdict ? scopeVerdict.path : void 0
1021
+ };
1022
+ };
1023
+ var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
1024
+ var modeStateFromResponse = (data) => {
1025
+ const mode = data.override?.mode;
1026
+ return {
1027
+ mode: isApprovalMode(mode) ? mode : null,
1028
+ kill: data.kill === true,
1029
+ policyVersion: toPolicyVersion(data.policyVersion),
1030
+ relayUrl: typeof data.relayUrl === "string" && data.relayUrl.length > 0 ? data.relayUrl : null,
1031
+ trainingConsent: data.trainingConsent === true,
1032
+ // Validated rather than cast: a malformed contract must read as no contract,
1033
+ // never as one that silently matches nothing.
1034
+ scope: isScopeContract(data.scope) ? data.scope : null,
1035
+ degraded: false
1036
+ };
1037
+ };
836
1038
 
837
1039
  export {
838
- isApprovalMode,
839
1040
  parseAgentQuestionAnswers,
840
1041
  normalizeInstallSource,
841
1042
  HOOK_BUDGETS,
@@ -844,21 +1045,17 @@ export {
844
1045
  effectiveWaitSeconds,
845
1046
  normalizeRepoRemote,
846
1047
  localRepoKey,
847
- policyArgForms,
848
1048
  isSafeReadOnlyCommand,
849
1049
  isAutoApprove,
850
- isAutoDeny,
851
- verdictForPolicy,
852
- selectGoverningRule,
853
1050
  isValidApiKey,
854
1051
  ACTION_BODY_MAX,
855
1052
  DECISION_LINE_MAX,
856
1053
  redactSecrets,
857
1054
  redactSecretsDeep,
858
- classifyDecisionRisk,
859
1055
  buildDecisionEpisodeFeatures,
860
- evaluateScope,
861
- scopeRequiresHuman,
862
- scopeChangeReason,
863
- isScopeContract
1056
+ resolveAutoResolveOrigin,
1057
+ resolvePolicy,
1058
+ KILL_REASON,
1059
+ resolveGate,
1060
+ modeStateFromResponse
864
1061
  };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  guardBinary
3
- } from "./chunk-PWTKKQQI.js";
3
+ } from "./chunk-HSGEEBBL.js";
4
4
  import {
5
5
  HOOK_BUDGETS
6
- } from "./chunk-MQMPG5X4.js";
6
+ } from "./chunk-G3LXA2ZP.js";
7
7
 
8
8
  // src/claude-config.ts
9
9
  import { join } from "path";
@@ -40,23 +40,23 @@ var MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"].join("|");
40
40
  var ESTABLISHED = [1, 0, 0];
41
41
  var OBSERVED = [2, 0, 0];
42
42
  var CLAUDE_HOOK_EVENTS = [
43
- { event: "PreToolUse", binary: "pushary-hook", matcher: GATED_TOOLS, timeout: HOOK_BUDGETS.claude.budgetSeconds, since: ESTABLISHED },
44
- { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, since: ESTABLISHED },
45
- { event: "Stop", binary: "pushary-stop-hook", timeout: 10, since: ESTABLISHED },
46
- { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, since: ESTABLISHED },
47
- { event: "Notification", binary: "pushary-notification-hook", matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed", timeout: 10, since: ESTABLISHED },
48
- { event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, since: ESTABLISHED },
49
- { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, since: ESTABLISHED },
50
- { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, since: ESTABLISHED },
51
- { event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, since: ESTABLISHED },
52
- { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: 60, since: ESTABLISHED },
43
+ { event: "PreToolUse", binary: "pushary-hook", matcher: GATED_TOOLS, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
44
+ { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
45
+ { event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
46
+ { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
47
+ { event: "Notification", binary: "pushary-notification-hook", matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
48
+ { event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
49
+ { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
50
+ { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
51
+ { event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
52
+ { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: 60, delivery: "telemetry", since: ESTABLISHED },
53
53
  // Observability only. Each carries session shape the island cannot get any other
54
54
  // way, and none of them decides anything.
55
- { event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, since: OBSERVED },
56
- { event: "SubagentStop", binary: "pushary-stop-hook", timeout: 10, since: OBSERVED },
57
- { event: "PreCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, since: OBSERVED },
58
- { event: "PostCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, since: OBSERVED },
59
- { event: "TeammateIdle", binary: "pushary-notification-hook", timeout: 10, since: OBSERVED }
55
+ { event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
56
+ { event: "SubagentStop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
57
+ { event: "PreCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
58
+ { event: "PostCompact", binary: "pushary-post-hook", matcher: "manual|auto", timeout: 10, delivery: "telemetry", since: OBSERVED },
59
+ { event: "TeammateIdle", binary: "pushary-notification-hook", timeout: 10, delivery: "telemetry", since: OBSERVED }
60
60
  ];
61
61
  var supportedClaudeEvents = (version) => CLAUDE_HOOK_EVENTS.filter((hook) => version ? atLeast(version, hook.since) : hook.since === ESTABLISHED);
62
62
 
@@ -0,0 +1,37 @@
1
+ import {
2
+ quoteIfNeeded
3
+ } from "./chunk-VT4IVERX.js";
4
+
5
+ // src/hook-command.ts
6
+ var HOOK_SHELL = "/bin/sh -c";
7
+ var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
8
+ var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
9
+ var HOOK_GUARD_SCRIPTS = {
10
+ native_binary: `${LOCATE}; exec "$B"`,
11
+ node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B"`,
12
+ node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0"`
13
+ };
14
+ var BINARY_GUARDS = {
15
+ native: "native_binary",
16
+ node: "node_binary"
17
+ };
18
+ var guardKindOf = (target) => {
19
+ switch (target.kind) {
20
+ case "node":
21
+ return "node_script";
22
+ case "binary":
23
+ return BINARY_GUARDS[target.runtime];
24
+ }
25
+ };
26
+ var guardHookCommand = (target, platform = process.platform) => {
27
+ const path = quoteIfNeeded(target.kind === "node" ? target.script : target.path);
28
+ if (platform === "win32") return target.kind === "node" ? `node ${path}` : path;
29
+ return `${HOOK_SHELL} '${HOOK_GUARD_SCRIPTS[guardKindOf(target)]}' ${path}`;
30
+ };
31
+ var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
32
+ var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
33
+
34
+ export {
35
+ guardBinary,
36
+ guardNodeScript
37
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  codexHomeFrom
3
- } from "./chunk-HVZIHTOO.js";
3
+ } from "./chunk-PHXEADSL.js";
4
4
 
5
5
  // src/vscode-config.ts
6
6
  import { homedir } from "os";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  redactSecrets
6
- } from "./chunk-MQMPG5X4.js";
6
+ } from "./chunk-G3LXA2ZP.js";
7
7
 
8
8
  // src/crypto.ts
9
9
  import nacl from "tweetnacl";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-K7CSRGKZ.js";
3
+ } from "./chunk-CIVG746V.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -1,15 +1,14 @@
1
1
  import {
2
2
  PRETOOLUSE_HANDLED_TOOLS
3
- } from "./chunk-64PXDATC.js";
3
+ } from "./chunk-GM54KC62.js";
4
4
  import {
5
5
  isGatingMoment,
6
6
  recordKeylessMoment
7
- } from "./chunk-NJ7JT26G.js";
7
+ } from "./chunk-VGADRFG6.js";
8
8
  import {
9
9
  denyReasonFrom,
10
- isDeferAnswer,
11
- resolveGate
12
- } from "./chunk-GXIFADGD.js";
10
+ isDeferAnswer
11
+ } from "./chunk-YHG74UFF.js";
13
12
  import {
14
13
  DEFAULT_SESSION,
15
14
  askUser,
@@ -29,7 +28,7 @@ import {
29
28
  sendNotification,
30
29
  throttlePass,
31
30
  waitForAnswer
32
- } from "./chunk-EXMUM226.js";
31
+ } from "./chunk-Y7BALRVB.js";
33
32
  import {
34
33
  getMachineId
35
34
  } from "./chunk-RN3NOEJF.js";
@@ -42,8 +41,9 @@ import {
42
41
  effectiveWaitSeconds,
43
42
  hookWaitClamped,
44
43
  hookWaitDeadline,
45
- parseAgentQuestionAnswers
46
- } from "./chunk-MQMPG5X4.js";
44
+ parseAgentQuestionAnswers,
45
+ resolveGate
46
+ } from "./chunk-G3LXA2ZP.js";
47
47
 
48
48
  // src/decision-episode.ts
49
49
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -513,14 +513,16 @@ var handlePreToolUse = async (input) => {
513
513
  if (input.tool_name === "AskUserQuestion") {
514
514
  return handleAskUserQuestion(apiKey, input);
515
515
  }
516
+ if (verdict.kind === "deny") return deny(verdict.reason);
516
517
  if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
517
518
  return void 0;
518
519
  }
519
520
  switch (verdict.kind) {
520
521
  case "allow":
521
522
  return allow();
522
- case "deny":
523
- return deny(verdict.reason);
523
+ // No `deny` case: it is handled above the defer, and the compiler enforces
524
+ // that it stays there. Restoring one here would type-error as unreachable.
525
+ //
524
526
  // State we could not establish, with an auto-approve on the table. No
525
527
  // opinion, so Claude Code's own flow runs as if the hook were absent.
526
528
  case "defer":
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  HOOK_BUDGETS
3
- } from "./chunk-MQMPG5X4.js";
3
+ } from "./chunk-G3LXA2ZP.js";
4
4
 
5
5
  // src/gemini-config.ts
6
6
  var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
7
7
  var GEMINI_MCP_URL = "https://pushary.com/api/mcp/mcp";
8
8
  var GEMINI_HOOK_EVENTS = [
9
- { event: "BeforeTool", matcher: "run_shell_command|write_file|replace", timeoutMs: HOOK_BUDGETS.gemini.budgetSeconds * 1e3 },
10
- { event: "AfterTool", matcher: "run_shell_command|write_file|replace", timeoutMs: 1e4 },
11
- { event: "BeforeAgent", timeoutMs: 1e4 },
12
- { event: "SessionStart", timeoutMs: 1e4 },
13
- { event: "SessionEnd", timeoutMs: 1e4 }
9
+ { event: "BeforeTool", matcher: "run_shell_command|write_file|replace", delivery: "approval", timeoutMs: HOOK_BUDGETS.gemini.budgetSeconds * 1e3 },
10
+ { event: "AfterTool", matcher: "run_shell_command|write_file|replace", delivery: "telemetry", timeoutMs: 1e4 },
11
+ { event: "BeforeAgent", delivery: "telemetry", timeoutMs: 1e4 },
12
+ { event: "SessionStart", delivery: "telemetry", timeoutMs: 1e4 },
13
+ { event: "SessionEnd", delivery: "telemetry", timeoutMs: 1e4 }
14
14
  ];
15
15
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
16
16
  var ensureRecord = (target, key) => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HOOK_BUDGETS
3
- } from "./chunk-MQMPG5X4.js";
3
+ } from "./chunk-G3LXA2ZP.js";
4
4
 
5
5
  // src/codex-config.ts
6
6
  import { createHash } from "crypto";
@@ -14,12 +14,12 @@ var codexHooksJson = () => join(codexHome(), "hooks.json");
14
14
  var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
15
15
  var codexSkillDir = () => join(codexHome(), "skills", "pushary");
16
16
  var CODEX_HOOK_EVENTS = [
17
- { event: "PermissionRequest", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
18
- { event: "PreToolUse", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
19
- { event: "PostToolUse", matcher: "Bash|apply_patch", timeout: 10 },
20
- { event: "UserPromptSubmit", timeout: 10 },
21
- { event: "Stop", timeout: 10 },
22
- { event: "SessionStart", matcher: "startup|resume", timeout: 10 }
17
+ { event: "PermissionRequest", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
18
+ { event: "PreToolUse", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
19
+ { event: "PostToolUse", matcher: "Bash|apply_patch", delivery: "telemetry", timeout: 10 },
20
+ { event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
21
+ { event: "Stop", delivery: "telemetry", timeout: 10 },
22
+ { event: "SessionStart", matcher: "startup|resume", delivery: "telemetry", timeout: 10 }
23
23
  ];
24
24
  var CODEX_EVENT_KEY = {
25
25
  PermissionRequest: "permission_request",
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-2UMNXADU.js";
13
13
  import {
14
14
  isValidApiKey
15
- } from "./chunk-MQMPG5X4.js";
15
+ } from "./chunk-G3LXA2ZP.js";
16
16
 
17
17
  // src/session.ts
18
18
  var IDENTITY_PATH = "/api/v1/server/identity";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-MQMPG5X4.js";
3
+ } from "./chunk-G3LXA2ZP.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  hasGeminiHooks
3
- } from "./chunk-DOXSWT4Q.js";
3
+ } from "./chunk-OZKRF7MS.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-HVZIHTOO.js";
6
+ } from "./chunk-PHXEADSL.js";
7
7
 
8
8
  // src/diagnostics/wiring.ts
9
9
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -5,7 +5,7 @@ import {
5
5
  generateSessionKey,
6
6
  uploadTranscriptRecords,
7
7
  wrapSessionKey
8
- } from "./chunk-GCKAWHFP.js";
8
+ } from "./chunk-JDQY47OW.js";
9
9
 
10
10
  // src/live-session.ts
11
11
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-HVZIHTOO.js";
10
+ } from "./chunk-PHXEADSL.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";