@pushary/agent-hooks 0.85.0 → 0.85.2
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 +29 -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 +17 -12
- 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-ADISVXZL.js → chunk-4U2YRHNF.js} +1 -1
- package/dist/{chunk-WEAQQSHQ.js → chunk-4YMWLMAP.js} +1 -1
- package/dist/{chunk-437T77TY.js → chunk-CWV4CMB6.js} +1 -1
- package/dist/{chunk-EXMUM226.js → chunk-DPDUB4XF.js} +62 -156
- package/dist/{chunk-7PZPJSWI.js → chunk-GM74DUMS.js} +1 -1
- package/dist/{chunk-DOXSWT4Q.js → chunk-GY3I353G.js} +6 -6
- package/dist/chunk-HSGEEBBL.js +37 -0
- package/dist/{chunk-ERYKGH4J.js → chunk-JBLRZGTT.js} +1 -1
- package/dist/{chunk-4QPOMJUB.js → chunk-JT5NQQCE.js} +2 -2
- package/dist/{chunk-MQMPG5X4.js → chunk-K337LIBD.js} +260 -10
- package/dist/{chunk-HVZIHTOO.js → chunk-KJW6NKF7.js} +7 -7
- package/dist/{chunk-K7CSRGKZ.js → chunk-KZIGJLOX.js} +1 -1
- package/dist/{chunk-6LC4WSCL.js → chunk-LCA2EQTK.js} +1 -1
- package/dist/{chunk-F52SIIDG.js → chunk-LSB6PFLN.js} +1 -1
- package/dist/{chunk-YKW6JCWI.js → chunk-LXWISFF3.js} +1 -1
- package/dist/{chunk-FBGBJXVC.js → chunk-NTIUQB3E.js} +1 -1
- package/dist/{chunk-NJ7JT26G.js → chunk-PKUNB3SN.js} +1 -1
- package/dist/{chunk-AXRATIS6.js → chunk-Q4URN3E7.js} +1 -1
- package/dist/{chunk-GCKAWHFP.js → chunk-T54OT7IY.js} +1 -1
- package/dist/{chunk-64PXDATC.js → chunk-XJDL3MUE.js} +17 -17
- package/dist/{chunk-GFM3STWO.js → chunk-Y7E2MC3G.js} +20 -49
- package/dist/chunk-YHG74UFF.js +12 -0
- package/dist/src/index.d.ts +7 -29
- 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,177 @@ 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
|
+
};
|
|
1038
|
+
var claudePreToolUseOutput = (decision, reason, updatedInput) => ({
|
|
1039
|
+
hookSpecificOutput: {
|
|
1040
|
+
hookEventName: "PreToolUse",
|
|
1041
|
+
permissionDecision: decision,
|
|
1042
|
+
...reason ? { permissionDecisionReason: reason } : {},
|
|
1043
|
+
...updatedInput ? { updatedInput } : {}
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
var claudePermissionRequestOutput = (decision, reason, updatedInput) => ({
|
|
1047
|
+
hookSpecificOutput: {
|
|
1048
|
+
hookEventName: "PermissionRequest",
|
|
1049
|
+
decision: decision === "allow" ? { behavior: "allow", ...updatedInput ? { updatedInput } : {} } : { behavior: "deny", ...reason ? { message: reason } : {} }
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
var gateOutputFor = (source, event, decision, reason) => {
|
|
1053
|
+
let output;
|
|
1054
|
+
if (source === "claude") {
|
|
1055
|
+
if (event === "PreToolUse") {
|
|
1056
|
+
output = claudePreToolUseOutput(decision, reason);
|
|
1057
|
+
} else if (event === "PermissionRequest" && decision !== "ask") {
|
|
1058
|
+
output = claudePermissionRequestOutput(decision, reason);
|
|
1059
|
+
}
|
|
1060
|
+
} else if (source === "codex") {
|
|
1061
|
+
if (event === "PermissionRequest" && decision !== "ask") {
|
|
1062
|
+
output = {
|
|
1063
|
+
hookSpecificOutput: {
|
|
1064
|
+
hookEventName: "PermissionRequest",
|
|
1065
|
+
decision: decision === "allow" ? { behavior: "allow" } : { behavior: "deny", message: reason ?? "Denied from Pushary" }
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
} else if (event === "PreToolUse" && decision === "deny") {
|
|
1069
|
+
output = {
|
|
1070
|
+
hookSpecificOutput: {
|
|
1071
|
+
hookEventName: "PreToolUse",
|
|
1072
|
+
permissionDecision: "deny",
|
|
1073
|
+
permissionDecisionReason: reason ?? "Denied from Pushary"
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
} else if (source === "gemini" && event === "BeforeTool" && decision !== "ask") {
|
|
1078
|
+
output = decision === "allow" ? { decision: "allow" } : { decision: "deny", reason: reason ?? "Denied from Pushary" };
|
|
1079
|
+
} else if (source === "cursor" && event === "beforeShellExecution") {
|
|
1080
|
+
output = decision === "allow" ? { permission: "allow" } : decision === "ask" ? { permission: "ask" } : {
|
|
1081
|
+
permission: "deny",
|
|
1082
|
+
user_message: "Command denied via Pushary.",
|
|
1083
|
+
agent_message: reason ?? "Denied from Pushary"
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
return output;
|
|
1087
|
+
};
|
|
836
1088
|
|
|
837
1089
|
export {
|
|
838
|
-
isApprovalMode,
|
|
839
1090
|
parseAgentQuestionAnswers,
|
|
840
1091
|
normalizeInstallSource,
|
|
841
1092
|
HOOK_BUDGETS,
|
|
@@ -844,21 +1095,20 @@ export {
|
|
|
844
1095
|
effectiveWaitSeconds,
|
|
845
1096
|
normalizeRepoRemote,
|
|
846
1097
|
localRepoKey,
|
|
847
|
-
policyArgForms,
|
|
848
1098
|
isSafeReadOnlyCommand,
|
|
849
1099
|
isAutoApprove,
|
|
850
|
-
isAutoDeny,
|
|
851
|
-
verdictForPolicy,
|
|
852
|
-
selectGoverningRule,
|
|
853
1100
|
isValidApiKey,
|
|
854
1101
|
ACTION_BODY_MAX,
|
|
855
1102
|
DECISION_LINE_MAX,
|
|
856
1103
|
redactSecrets,
|
|
857
1104
|
redactSecretsDeep,
|
|
858
|
-
classifyDecisionRisk,
|
|
859
1105
|
buildDecisionEpisodeFeatures,
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1106
|
+
resolveAutoResolveOrigin,
|
|
1107
|
+
resolvePolicy,
|
|
1108
|
+
KILL_REASON,
|
|
1109
|
+
resolveGate,
|
|
1110
|
+
modeStateFromResponse,
|
|
1111
|
+
claudePreToolUseOutput,
|
|
1112
|
+
claudePermissionRequestOutput,
|
|
1113
|
+
gateOutputFor
|
|
864
1114
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HOOK_BUDGETS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-K337LIBD.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
|
guardBinary
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HSGEEBBL.js";
|
|
4
4
|
import {
|
|
5
5
|
HOOK_BUDGETS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-K337LIBD.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
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRETOOLUSE_HANDLED_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XJDL3MUE.js";
|
|
4
4
|
import {
|
|
5
5
|
isGatingMoment,
|
|
6
6
|
recordKeylessMoment
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-PKUNB3SN.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-DPDUB4XF.js";
|
|
33
32
|
import {
|
|
34
33
|
getMachineId
|
|
35
34
|
} from "./chunk-RN3NOEJF.js";
|
|
@@ -39,11 +38,14 @@ import {
|
|
|
39
38
|
} from "./chunk-2UMNXADU.js";
|
|
40
39
|
import {
|
|
41
40
|
buildDecisionEpisodeFeatures,
|
|
41
|
+
claudePermissionRequestOutput,
|
|
42
|
+
claudePreToolUseOutput,
|
|
42
43
|
effectiveWaitSeconds,
|
|
43
44
|
hookWaitClamped,
|
|
44
45
|
hookWaitDeadline,
|
|
45
|
-
parseAgentQuestionAnswers
|
|
46
|
-
|
|
46
|
+
parseAgentQuestionAnswers,
|
|
47
|
+
resolveGate
|
|
48
|
+
} from "./chunk-K337LIBD.js";
|
|
47
49
|
|
|
48
50
|
// src/decision-episode.ts
|
|
49
51
|
var EPISODE_PATH = "/api/agent/decision-episode";
|
|
@@ -73,33 +75,10 @@ import { tmpdir } from "os";
|
|
|
73
75
|
import { existsSync, writeFileSync } from "fs";
|
|
74
76
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
75
77
|
var START_MS = Date.now();
|
|
76
|
-
var allow = () => (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
var deny = (reason) => ({
|
|
83
|
-
hookSpecificOutput: {
|
|
84
|
-
hookEventName: "PreToolUse",
|
|
85
|
-
permissionDecision: "deny",
|
|
86
|
-
permissionDecisionReason: reason
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
var allowWithInput = (updatedInput) => ({
|
|
90
|
-
hookSpecificOutput: {
|
|
91
|
-
hookEventName: "PreToolUse",
|
|
92
|
-
permissionDecision: "allow",
|
|
93
|
-
updatedInput
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
var ask = (reason) => ({
|
|
97
|
-
hookSpecificOutput: {
|
|
98
|
-
hookEventName: "PreToolUse",
|
|
99
|
-
permissionDecision: "ask",
|
|
100
|
-
...reason ? { permissionDecisionReason: reason } : {}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
78
|
+
var allow = () => claudePreToolUseOutput("allow");
|
|
79
|
+
var deny = (reason) => claudePreToolUseOutput("deny", reason);
|
|
80
|
+
var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
|
|
81
|
+
var ask = (reason) => claudePreToolUseOutput("ask", reason);
|
|
103
82
|
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
104
83
|
while (Date.now() < deadlineMs) {
|
|
105
84
|
const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
|
|
@@ -513,14 +492,16 @@ var handlePreToolUse = async (input) => {
|
|
|
513
492
|
if (input.tool_name === "AskUserQuestion") {
|
|
514
493
|
return handleAskUserQuestion(apiKey, input);
|
|
515
494
|
}
|
|
495
|
+
if (verdict.kind === "deny") return deny(verdict.reason);
|
|
516
496
|
if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
|
|
517
497
|
return void 0;
|
|
518
498
|
}
|
|
519
499
|
switch (verdict.kind) {
|
|
520
500
|
case "allow":
|
|
521
501
|
return allow();
|
|
522
|
-
|
|
523
|
-
|
|
502
|
+
// No `deny` case: it is handled above the defer, and the compiler enforces
|
|
503
|
+
// that it stays there. Restoring one here would type-error as unreachable.
|
|
504
|
+
//
|
|
524
505
|
// State we could not establish, with an auto-approve on the table. No
|
|
525
506
|
// opinion, so Claude Code's own flow runs as if the hook were absent.
|
|
526
507
|
case "defer":
|
|
@@ -532,18 +513,8 @@ var handlePreToolUse = async (input) => {
|
|
|
532
513
|
return void 0;
|
|
533
514
|
}
|
|
534
515
|
};
|
|
535
|
-
var permReqAllow = (updatedInput) => (
|
|
536
|
-
|
|
537
|
-
hookEventName: "PermissionRequest",
|
|
538
|
-
decision: { behavior: "allow", ...updatedInput ? { updatedInput } : {} }
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
var permReqDeny = (message) => ({
|
|
542
|
-
hookSpecificOutput: {
|
|
543
|
-
hookEventName: "PermissionRequest",
|
|
544
|
-
decision: { behavior: "deny", ...message ? { message } : {} }
|
|
545
|
-
}
|
|
546
|
-
});
|
|
516
|
+
var permReqAllow = (updatedInput) => claudePermissionRequestOutput("allow", void 0, updatedInput);
|
|
517
|
+
var permReqDeny = (message) => claudePermissionRequestOutput("deny", message);
|
|
547
518
|
var toPermissionRequestOutput = (hookOutput) => {
|
|
548
519
|
if (!hookOutput) return void 0;
|
|
549
520
|
const d = hookOutput.hookSpecificOutput;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/answer.ts
|
|
2
|
+
var denyReasonFrom = (value, note) => {
|
|
3
|
+
const trimmed = note?.trim();
|
|
4
|
+
if (trimmed) return `Denied from your phone: ${trimmed}`;
|
|
5
|
+
return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
|
|
6
|
+
};
|
|
7
|
+
var isDeferAnswer = (value) => value === "defer";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
denyReasonFrom,
|
|
11
|
+
isDeferAnswer
|
|
12
|
+
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { PolicyConfig, ApprovalMode,
|
|
2
|
-
export { ApprovalMode, PolicyConfig, ToolPolicy } from '@pushary/contracts';
|
|
1
|
+
import { ClaudePreToolUseOutput, PolicyConfig, ApprovalMode, ModeState, AutoResolveOrigin, AgentQuestion } from '@pushary/contracts';
|
|
2
|
+
export { ApprovalMode, ModeState, PolicyConfig, ToolPolicy, resolvePolicy } from '@pushary/contracts';
|
|
3
|
+
import { ReceiptMeta } from '@pushary/hook-mapping';
|
|
3
4
|
|
|
4
5
|
interface ToolInput {
|
|
5
6
|
tool_name: string;
|
|
@@ -10,35 +11,11 @@ interface ToolInput {
|
|
|
10
11
|
tool_use_id?: string;
|
|
11
12
|
permission_mode?: string;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
hookSpecificOutput: {
|
|
15
|
-
hookEventName: 'PreToolUse';
|
|
16
|
-
permissionDecision: 'allow' | 'deny' | 'ask';
|
|
17
|
-
permissionDecisionReason?: string;
|
|
18
|
-
updatedInput?: Record<string, unknown>;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
14
|
+
type HookOutput = ClaudePreToolUseOutput;
|
|
21
15
|
declare const handlePreToolUse: (input: ToolInput) => Promise<HookOutput | undefined>;
|
|
22
16
|
|
|
23
|
-
type ReceiptKind = 'edit' | 'write' | 'bash' | 'commit';
|
|
24
|
-
interface ReceiptMeta {
|
|
25
|
-
kind: ReceiptKind;
|
|
26
|
-
target: string;
|
|
27
|
-
ok: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
17
|
declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string, repoAware?: boolean, sessionId?: string) => Promise<PolicyConfig>;
|
|
31
|
-
|
|
32
|
-
declare const resolvePolicy: (config: PolicyConfig, toolName: string, modeOverride?: ApprovalMode | null, toolInput?: Record<string, unknown>, cwd?: string, repoKey?: string, sessionId?: string) => ToolPolicy;
|
|
33
|
-
interface ModeState {
|
|
34
|
-
readonly mode: ApprovalMode | null;
|
|
35
|
-
readonly kill: boolean;
|
|
36
|
-
readonly policyVersion: string | null;
|
|
37
|
-
readonly relayUrl: string | null;
|
|
38
|
-
readonly trainingConsent?: boolean;
|
|
39
|
-
readonly scope?: ScopeContract | null;
|
|
40
|
-
readonly degraded?: boolean;
|
|
41
|
-
}
|
|
18
|
+
|
|
42
19
|
declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
|
|
43
20
|
declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
|
|
44
21
|
|
|
@@ -142,6 +119,7 @@ interface AskUserParams {
|
|
|
142
119
|
actionBody?: string;
|
|
143
120
|
scopePath?: string;
|
|
144
121
|
toolUseId?: string;
|
|
122
|
+
requestId?: string;
|
|
145
123
|
}
|
|
146
124
|
interface AskUserResponse {
|
|
147
125
|
correlationId: string;
|
|
@@ -166,4 +144,4 @@ declare const cancelQuestion: (apiKey: string, correlationId: string) => Promise
|
|
|
166
144
|
declare const getApiKey: () => string;
|
|
167
145
|
declare const getBaseUrl: () => string;
|
|
168
146
|
|
|
169
|
-
export {
|
|
147
|
+
export { askUser, cancelQuestion, fetchModeOverride, fetchModeState, getApiKey, getBaseUrl, getPolicy, handleNotification, handlePostToolUse, handlePreToolUse, handleStop, reportEvent, waitForAnswer };
|