@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.
- package/CHANGELOG.md +20 -0
- package/data/cursor-plugin/CHANGELOG.md +21 -0
- package/data/cursor-plugin/scripts/pushary-gate.mjs +54 -222
- package/data/cursor-plugin/scripts/pushary-gate.test.mjs +11 -106
- package/data/vscode-plugin/CHANGELOG.md +26 -0
- package/data/vscode-plugin/scripts/pushary-gate.mjs +66 -74
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +6 -6
- package/dist/bin/pushary-claude.js +12 -11
- package/dist/bin/pushary-clean.js +6 -6
- package/dist/bin/pushary-codex-bridge.js +5 -5
- package/dist/bin/pushary-codex-hook.js +9 -9
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-daemon.js +4 -4
- package/dist/bin/pushary-disconnect.js +5 -5
- package/dist/bin/pushary-doctor.js +11 -11
- package/dist/bin/pushary-gemini-bridge.js +5 -5
- package/dist/bin/pushary-gemini-hook.js +9 -9
- package/dist/bin/pushary-hook.js +8 -8
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +3 -3
- package/dist/bin/pushary-permission-denied-hook.js +8 -8
- package/dist/bin/pushary-permission-hook.js +8 -8
- package/dist/bin/pushary-post-hook.js +3 -3
- package/dist/bin/pushary-prompt-hook.js +3 -3
- package/dist/bin/pushary-session-end-hook.js +3 -3
- package/dist/bin/pushary-session-start-hook.js +3 -3
- package/dist/bin/pushary-setup.js +14 -14
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +5 -5
- package/dist/bin/pushary-stop-hook.js +3 -3
- package/dist/bin/pushary-stopfailure-hook.js +3 -3
- package/dist/bin/pushary-upgrade.js +6 -6
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-YKW6JCWI.js → chunk-55656H4Q.js} +1 -1
- package/dist/{chunk-6LC4WSCL.js → chunk-BZKTCOZQ.js} +1 -1
- package/dist/{chunk-K7CSRGKZ.js → chunk-CIVG746V.js} +1 -1
- package/dist/{chunk-FBGBJXVC.js → chunk-DNSIVHIA.js} +1 -1
- package/dist/{chunk-437T77TY.js → chunk-ES3FAX5X.js} +1 -1
- package/dist/{chunk-AXRATIS6.js → chunk-EZPRY2ZY.js} +1 -1
- package/dist/{chunk-MQMPG5X4.js → chunk-G3LXA2ZP.js} +207 -10
- package/dist/{chunk-64PXDATC.js → chunk-GM54KC62.js} +17 -17
- package/dist/chunk-HSGEEBBL.js +37 -0
- package/dist/{chunk-ADISVXZL.js → chunk-IGV4KL7B.js} +1 -1
- package/dist/{chunk-GCKAWHFP.js → chunk-JDQY47OW.js} +1 -1
- package/dist/{chunk-7PZPJSWI.js → chunk-KTKIZEFT.js} +1 -1
- package/dist/{chunk-GFM3STWO.js → chunk-LB6RSCLL.js} +12 -10
- package/dist/{chunk-DOXSWT4Q.js → chunk-OZKRF7MS.js} +6 -6
- package/dist/{chunk-HVZIHTOO.js → chunk-PHXEADSL.js} +7 -7
- package/dist/{chunk-F52SIIDG.js → chunk-QDKJTPAT.js} +1 -1
- package/dist/{chunk-NJ7JT26G.js → chunk-VGADRFG6.js} +1 -1
- package/dist/{chunk-4QPOMJUB.js → chunk-VKJUVPTJ.js} +2 -2
- package/dist/{chunk-ERYKGH4J.js → chunk-XIR6N66V.js} +1 -1
- package/dist/{chunk-WEAQQSHQ.js → chunk-XLM6COJA.js} +1 -1
- package/dist/{chunk-EXMUM226.js → chunk-Y7BALRVB.js} +54 -127
- package/dist/chunk-YHG74UFF.js +12 -0
- package/dist/src/index.d.ts +6 -21
- package/dist/src/index.js +10 -9
- package/package.json +4 -2
- package/dist/chunk-GXIFADGD.js +0 -66
- package/dist/chunk-PWTKKQQI.js +0 -27
|
@@ -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
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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-
|
|
3
|
+
} from "./chunk-HSGEEBBL.js";
|
|
4
4
|
import {
|
|
5
5
|
HOOK_BUDGETS
|
|
6
|
-
} from "./chunk-
|
|
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,15 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRETOOLUSE_HANDLED_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GM54KC62.js";
|
|
4
4
|
import {
|
|
5
5
|
isGatingMoment,
|
|
6
6
|
recordKeylessMoment
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-VGADRFG6.js";
|
|
8
8
|
import {
|
|
9
9
|
denyReasonFrom,
|
|
10
|
-
isDeferAnswer
|
|
11
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
|
|
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-
|
|
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-
|
|
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",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasGeminiHooks
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OZKRF7MS.js";
|
|
4
4
|
import {
|
|
5
5
|
hasCodexHooks
|
|
6
|
-
} from "./chunk-
|
|
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;
|