@pushary/agent-hooks 0.95.8 → 0.98.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.
- package/CHANGELOG.md +27 -0
- package/dist/bin/pushary-bell-hook.js +19 -21
- package/dist/bin/pushary-bell.js +6 -8
- package/dist/bin/pushary-claude.js +21 -23
- package/dist/bin/pushary-clean.js +19 -22
- package/dist/bin/pushary-codex-bridge.js +13 -14
- package/dist/bin/pushary-codex-hook.js +452 -104
- package/dist/bin/pushary-codex.js +8 -8
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-cowork.js +3 -3
- package/dist/bin/pushary-daemon.js +25 -27
- package/dist/bin/pushary-disconnect.js +6 -8
- package/dist/bin/pushary-doctor.js +28 -23
- package/dist/bin/pushary-elicitation-hook.js +19 -21
- package/dist/bin/pushary-gemini-bridge.js +11 -12
- package/dist/bin/pushary-gemini-hook.js +22 -24
- package/dist/bin/pushary-hook.js +24 -26
- package/dist/bin/pushary-login.js +7 -8
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +21 -23
- package/dist/bin/pushary-opencode-hook.js +22 -24
- package/dist/bin/pushary-permission-denied-hook.js +28 -27
- package/dist/bin/pushary-permission-hook.js +24 -26
- package/dist/bin/pushary-post-hook.js +21 -23
- package/dist/bin/pushary-prompt-hook.js +21 -23
- package/dist/bin/pushary-session-end-hook.js +21 -23
- package/dist/bin/pushary-session-start-hook.js +24 -26
- package/dist/bin/pushary-setup.js +45 -40
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +10 -11
- package/dist/bin/pushary-stop-hook.js +21 -23
- package/dist/bin/pushary-stopfailure-hook.js +21 -23
- package/dist/bin/pushary-transcript-register.js +17 -19
- package/dist/bin/pushary-transcripts.js +5 -5
- package/dist/bin/pushary-upgrade.js +17 -20
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-GOPIMNVC.js → chunk-2GLOHBYB.js} +1 -1
- package/dist/{chunk-BQL7UDIB.js → chunk-3RVCQDC4.js} +1 -1
- package/dist/chunk-53QCTSWY.js +202 -0
- package/dist/{chunk-3YFJGKJQ.js → chunk-5B5QV3KC.js} +8 -8
- package/dist/{chunk-FRXZMCVA.js → chunk-5KLAETSU.js} +1 -1
- package/dist/{chunk-SDNTRS76.js → chunk-6C6MS57P.js} +1 -1
- package/dist/{chunk-A22VQ5ZL.js → chunk-6CYB77VD.js} +111 -55
- package/dist/{chunk-QD73WATQ.js → chunk-6LRGZJFB.js} +21 -21
- package/dist/{chunk-Z4JVEL7R.js → chunk-A76GNRIK.js} +191 -2
- package/dist/{chunk-RE25RJ5A.js → chunk-B35OCFXH.js} +4 -4
- package/dist/{chunk-UKMLSQXA.js → chunk-CP34FI4B.js} +2 -2
- package/dist/{chunk-I7LKI4WO.js → chunk-DOD3A6EU.js} +6 -8
- package/dist/{chunk-N6FAXQDD.js → chunk-FAJ4R7IB.js} +1 -1
- package/dist/{chunk-U5RIXR7I.js → chunk-HB2KB7QL.js} +1 -1
- package/dist/{chunk-QV266EGS.js → chunk-HFQ7WSHZ.js} +11 -14
- package/dist/{chunk-QJPYJVL3.js → chunk-IRLTW6KF.js} +1 -1
- package/dist/{chunk-Y6SCMXR6.js → chunk-J44RPHUZ.js} +2 -2
- package/dist/{chunk-R6LBGKWQ.js → chunk-JQU6PMH4.js} +17 -15
- package/dist/{chunk-4DUEIUUO.js → chunk-K6CH5NVW.js} +1 -1
- package/dist/{chunk-XZWUOCJZ.js → chunk-LBNAGGLV.js} +1 -1
- package/dist/{chunk-XS33QBJA.js → chunk-LGXUGYUH.js} +50 -1
- package/dist/{chunk-XO3JR3OP.js → chunk-LX4NNYII.js} +2 -2
- package/dist/{chunk-XEVOPOKT.js → chunk-M4TVEMLH.js} +1 -1
- package/dist/{chunk-TY6IJY6I.js → chunk-NIXVMMJM.js} +1 -1
- package/dist/{chunk-VERXHIQF.js → chunk-NQGYPTJQ.js} +1 -1
- package/dist/{chunk-EMGGUO2Q.js → chunk-OBYC6SN4.js} +1 -1
- package/dist/{chunk-XUTSZYYJ.js → chunk-OPR2BPD4.js} +1 -4
- package/dist/{chunk-MC5WUOJJ.js → chunk-OUAE3GKO.js} +75 -1
- package/dist/{chunk-ATKGKAP6.js → chunk-QXIF6R4P.js} +16 -13
- package/dist/chunk-SI7GYBQP.js +15 -0
- package/dist/{chunk-VMQU7YEJ.js → chunk-SJLGOVC6.js} +1 -1
- package/dist/{chunk-54W7ELDQ.js → chunk-TZHDKRMG.js} +2 -2
- package/dist/{chunk-F4TMY6XI.js → chunk-V47GITZV.js} +3 -3
- package/dist/{chunk-L7NOG547.js → chunk-VZD2GEKB.js} +1 -1
- package/dist/{chunk-R3OZ5ISU.js → chunk-W2MG2P46.js} +4 -4
- package/dist/{chunk-E47WV7BJ.js → chunk-WU6LSXJ6.js} +2 -2
- package/dist/{chunk-NOVA7KZ2.js → chunk-XL3RKJYQ.js} +308 -35
- package/dist/{chunk-5GKNAQTG.js → chunk-XOOWC62T.js} +1 -1
- package/dist/{chunk-SKPXQRKN.js → chunk-XPPXOKIG.js} +1 -1
- package/dist/{chunk-HUGIKB5O.js → chunk-YJDRHQLG.js} +6 -6
- package/dist/src/index.js +11 -11
- package/package.json +1 -1
- package/dist/chunk-3AJIK5AO.js +0 -57
- package/dist/chunk-L4ZCFMWW.js +0 -177
- package/dist/chunk-VDI645ZX.js +0 -18
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
reapplyAllAgents
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-JQU6PMH4.js";
|
|
5
|
+
import "../chunk-K6CH5NVW.js";
|
|
6
6
|
import {
|
|
7
7
|
waitForMachineReadiness
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-B35OCFXH.js";
|
|
9
|
+
import "../chunk-QXIF6R4P.js";
|
|
10
10
|
import {
|
|
11
11
|
detectProviderLiveCapabilities,
|
|
12
12
|
hasSpawnProvider,
|
|
13
13
|
resolveControlPolicy
|
|
14
|
-
} from "../chunk-
|
|
15
|
-
import "../chunk-
|
|
14
|
+
} from "../chunk-TZHDKRMG.js";
|
|
15
|
+
import "../chunk-6LRGZJFB.js";
|
|
16
16
|
import {
|
|
17
17
|
daemonHasRemoteProof,
|
|
18
18
|
daemonServiceManagerAvailable,
|
|
@@ -23,26 +23,23 @@ import {
|
|
|
23
23
|
replaceDaemonService,
|
|
24
24
|
stopDaemonService,
|
|
25
25
|
waitForDaemonRemoteProof
|
|
26
|
-
} from "../chunk-
|
|
27
|
-
import "../chunk-
|
|
28
|
-
import "../chunk-3AJIK5AO.js";
|
|
26
|
+
} from "../chunk-DOD3A6EU.js";
|
|
27
|
+
import "../chunk-HFQ7WSHZ.js";
|
|
29
28
|
import {
|
|
30
29
|
needsShell
|
|
31
30
|
} from "../chunk-XHKBHWLX.js";
|
|
32
|
-
import
|
|
33
|
-
|
|
34
|
-
npmErrorMessage,
|
|
35
|
-
resolveGlobalBinary
|
|
36
|
-
} from "../chunk-L4ZCFMWW.js";
|
|
37
|
-
import "../chunk-XUTSZYYJ.js";
|
|
38
|
-
import "../chunk-E47WV7BJ.js";
|
|
31
|
+
import "../chunk-OPR2BPD4.js";
|
|
32
|
+
import "../chunk-WU6LSXJ6.js";
|
|
39
33
|
import {
|
|
40
34
|
claudeJson,
|
|
41
35
|
codexConfigToml,
|
|
42
36
|
cursorPluginDir,
|
|
37
|
+
execNpm,
|
|
43
38
|
geminiSettings,
|
|
39
|
+
npmErrorMessage,
|
|
40
|
+
resolveGlobalBinary,
|
|
44
41
|
vscodePluginDir
|
|
45
|
-
} from "../chunk-
|
|
42
|
+
} from "../chunk-XL3RKJYQ.js";
|
|
46
43
|
import {
|
|
47
44
|
exitOnHelpFlag
|
|
48
45
|
} from "../chunk-GOQHN2UH.js";
|
|
@@ -52,13 +49,13 @@ import {
|
|
|
52
49
|
} from "../chunk-DINDL2CF.js";
|
|
53
50
|
import {
|
|
54
51
|
readControlMode
|
|
55
|
-
} from "../chunk-
|
|
52
|
+
} from "../chunk-SJLGOVC6.js";
|
|
56
53
|
import "../chunk-KZERVKTD.js";
|
|
57
|
-
import "../chunk-
|
|
54
|
+
import "../chunk-VZD2GEKB.js";
|
|
58
55
|
import {
|
|
59
56
|
getApiKey
|
|
60
57
|
} from "../chunk-2UMNXADU.js";
|
|
61
|
-
import "../chunk-
|
|
58
|
+
import "../chunk-OUAE3GKO.js";
|
|
62
59
|
|
|
63
60
|
// bin/pushary-upgrade.ts
|
|
64
61
|
import { existsSync } from "fs";
|
package/dist/bin/pushary-wait.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
} from "../chunk-CVWG2N3Y.js";
|
|
5
5
|
import {
|
|
6
6
|
exitOnFailedResponse
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-6C6MS57P.js";
|
|
8
|
+
import "../chunk-3RVCQDC4.js";
|
|
9
9
|
import {
|
|
10
10
|
createIo
|
|
11
11
|
} from "../chunk-5RUIFDTP.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
getApiKey,
|
|
29
29
|
getBaseUrl
|
|
30
30
|
} from "../chunk-2UMNXADU.js";
|
|
31
|
-
import "../chunk-
|
|
31
|
+
import "../chunk-OUAE3GKO.js";
|
|
32
32
|
|
|
33
33
|
// bin/pushary-wait.ts
|
|
34
34
|
exitOnHelpFlag("wait");
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
isValidApiKey,
|
|
15
15
|
normalizeKeyScope
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-OUAE3GKO.js";
|
|
17
17
|
|
|
18
18
|
// src/session.ts
|
|
19
19
|
var samePusharyAccount = (left, right) => left.kind === "ok" && right.kind === "ok" && left.identity.workspaceId !== "" && left.identity.workspaceId === right.identity.workspaceId && left.identity.siteId === right.identity.siteId;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalGateInputs
|
|
3
|
+
} from "./chunk-A76GNRIK.js";
|
|
4
|
+
import {
|
|
5
|
+
canonicalJson
|
|
6
|
+
} from "./chunk-XL3RKJYQ.js";
|
|
7
|
+
import {
|
|
8
|
+
writeFileAtomic
|
|
9
|
+
} from "./chunk-VZD2GEKB.js";
|
|
10
|
+
import {
|
|
11
|
+
BLOCKED_ACTION_GRANT_TTL_SECONDS,
|
|
12
|
+
blockedActionIdentity,
|
|
13
|
+
shouldDeferToNativeMode
|
|
14
|
+
} from "./chunk-OUAE3GKO.js";
|
|
15
|
+
|
|
16
|
+
// src/native-defer.ts
|
|
17
|
+
var nativeDeferOptions = () => {
|
|
18
|
+
const respect = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
|
|
19
|
+
const bypass = process.env.PUSHARY_DEFER_BYPASS;
|
|
20
|
+
return {
|
|
21
|
+
respectPermissionMode: !(respect === "0" || respect === "false"),
|
|
22
|
+
deferBypass: bypass === "1" || bypass === "true"
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
var defersToNativeMode = (permissionMode, toolName) => shouldDeferToNativeMode(permissionMode, toolName, nativeDeferOptions());
|
|
26
|
+
|
|
27
|
+
// src/codex-call-decision.ts
|
|
28
|
+
import { createHash } from "crypto";
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
|
|
30
|
+
import { tmpdir, userInfo } from "os";
|
|
31
|
+
import { join } from "path";
|
|
32
|
+
var CODEX_DECISION_TTL_MS = 60 * 1e3;
|
|
33
|
+
var userScope = () => {
|
|
34
|
+
try {
|
|
35
|
+
const info = userInfo();
|
|
36
|
+
const id = typeof info.uid === "number" && info.uid >= 0 ? String(info.uid) : info.username;
|
|
37
|
+
return id.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || "default";
|
|
38
|
+
} catch {
|
|
39
|
+
return "default";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var decisionDir = () => join(tmpdir(), `pushary-codex-decisions-${userScope()}`);
|
|
43
|
+
var decisionFile = (key) => join(decisionDir(), key);
|
|
44
|
+
var PRESENTATION_ONLY_INPUT_KEYS = ["description"];
|
|
45
|
+
var identifyingToolInput = (toolInput) => {
|
|
46
|
+
if (!toolInput) return {};
|
|
47
|
+
const identifying = {};
|
|
48
|
+
for (const [key, value] of Object.entries(toolInput)) {
|
|
49
|
+
if (PRESENTATION_ONLY_INPUT_KEYS.includes(key)) continue;
|
|
50
|
+
identifying[key] = value;
|
|
51
|
+
}
|
|
52
|
+
return identifying;
|
|
53
|
+
};
|
|
54
|
+
var codexCallKey = (identity) => {
|
|
55
|
+
if (!identity.sessionId || !identity.turnId || !identity.toolName) return null;
|
|
56
|
+
const material = canonicalJson([
|
|
57
|
+
identity.sessionId,
|
|
58
|
+
identity.turnId,
|
|
59
|
+
identity.toolName,
|
|
60
|
+
identifyingToolInput(identity.toolInput)
|
|
61
|
+
]);
|
|
62
|
+
return createHash("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
|
|
63
|
+
};
|
|
64
|
+
var parseDecision = (raw) => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
if (parsed.kind === "approved") {
|
|
68
|
+
return typeof parsed.toolUseId === "string" && parsed.toolUseId ? { kind: "approved", toolUseId: parsed.toolUseId } : null;
|
|
69
|
+
}
|
|
70
|
+
if (parsed.kind === "handled") return { kind: "handled" };
|
|
71
|
+
if (parsed.kind === "denied") {
|
|
72
|
+
return { kind: "denied", reason: typeof parsed.reason === "string" ? parsed.reason : "" };
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var sweepExpired = (dir, nowMs) => {
|
|
80
|
+
try {
|
|
81
|
+
for (const name of readdirSync(dir)) {
|
|
82
|
+
const path = join(dir, name);
|
|
83
|
+
try {
|
|
84
|
+
if (nowMs - statSync(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync(path, { force: true });
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var recordCodexCallDecision = (key, decision) => {
|
|
94
|
+
if (!key) return;
|
|
95
|
+
try {
|
|
96
|
+
const dir = decisionDir();
|
|
97
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
98
|
+
else sweepExpired(dir, Date.now());
|
|
99
|
+
writeFileSync(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
|
|
100
|
+
} catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var consumeCodexCallDecision = (key) => {
|
|
105
|
+
if (!key) return null;
|
|
106
|
+
const path = decisionFile(key);
|
|
107
|
+
try {
|
|
108
|
+
const fresh = Date.now() - statSync(path).mtimeMs < CODEX_DECISION_TTL_MS;
|
|
109
|
+
const raw = readFileSync(path, "utf-8");
|
|
110
|
+
rmSync(path, { force: true });
|
|
111
|
+
return fresh ? parseDecision(raw) : null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/blocked-action-hint.ts
|
|
118
|
+
import { createHash as createHash2 } from "crypto";
|
|
119
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
120
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
121
|
+
import { join as join2 } from "path";
|
|
122
|
+
var BLOCKED_ACTION_HINT_TTL_MS = BLOCKED_ACTION_GRANT_TTL_SECONDS * 1e3;
|
|
123
|
+
var FINGERPRINT = /^[0-9a-f]{64}$/;
|
|
124
|
+
var isFingerprint = (value) => typeof value === "string" && FINGERPRINT.test(value);
|
|
125
|
+
var blockedActionSubject = (source, sessionId, toolName, toolInput, cwd) => {
|
|
126
|
+
if (source !== "claude") return { source, sessionId, toolName, toolInput };
|
|
127
|
+
return cwd ? { source, sessionId, cwd, toolName, toolInput } : null;
|
|
128
|
+
};
|
|
129
|
+
var blockedActionFingerprint = ({ source = "claude", sessionId, toolName, toolInput, cwd }) => {
|
|
130
|
+
if (!sessionId || !toolInput) return null;
|
|
131
|
+
const canonical = canonicalGateInputs(source, toolName, [toolInput], cwd);
|
|
132
|
+
if (!canonical || canonical.toolInputs.length !== 1) return null;
|
|
133
|
+
const [canonicalInput] = canonical.toolInputs;
|
|
134
|
+
if (!canonicalInput) return null;
|
|
135
|
+
const subject = blockedActionSubject(source, sessionId, canonical.toolName, canonicalInput, cwd);
|
|
136
|
+
return subject ? createHash2("sha256").update(blockedActionIdentity(subject), "utf8").digest("hex") : null;
|
|
137
|
+
};
|
|
138
|
+
var hintDir = () => join2(tmpdir2(), `pushary-blocked-action-hints-${userScope()}`);
|
|
139
|
+
var hintFile = (fingerprint) => join2(hintDir(), fingerprint);
|
|
140
|
+
var isExpired = (mtimeMs, nowMs) => nowMs - mtimeMs >= BLOCKED_ACTION_HINT_TTL_MS;
|
|
141
|
+
var sweepExpired2 = (dir, nowMs) => {
|
|
142
|
+
try {
|
|
143
|
+
for (const name of readdirSync2(dir)) {
|
|
144
|
+
const path = join2(dir, name);
|
|
145
|
+
try {
|
|
146
|
+
if (isExpired(statSync2(path).mtimeMs, nowMs)) rmSync2(path, { force: true });
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var recordBlockedActionHint = (fingerprint) => {
|
|
156
|
+
if (!isFingerprint(fingerprint)) return false;
|
|
157
|
+
try {
|
|
158
|
+
const dir = hintDir();
|
|
159
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
160
|
+
else sweepExpired2(dir, Date.now());
|
|
161
|
+
writeFileAtomic(hintFile(fingerprint), JSON.stringify({ fingerprint }), 384);
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var hasBlockedActionHint = (fingerprint) => {
|
|
168
|
+
if (!isFingerprint(fingerprint)) return false;
|
|
169
|
+
const path = hintFile(fingerprint);
|
|
170
|
+
try {
|
|
171
|
+
const stat = statSync2(path);
|
|
172
|
+
if (!stat.isFile()) return false;
|
|
173
|
+
if (isExpired(stat.mtimeMs, Date.now())) {
|
|
174
|
+
rmSync2(path, { force: true });
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
178
|
+
return parsed?.fingerprint === fingerprint;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var forgetBlockedActionHint = (fingerprint) => {
|
|
184
|
+
if (!isFingerprint(fingerprint)) return;
|
|
185
|
+
try {
|
|
186
|
+
rmSync2(hintFile(fingerprint), { force: true });
|
|
187
|
+
} catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export {
|
|
193
|
+
defersToNativeMode,
|
|
194
|
+
userScope,
|
|
195
|
+
codexCallKey,
|
|
196
|
+
recordCodexCallDecision,
|
|
197
|
+
consumeCodexCallDecision,
|
|
198
|
+
blockedActionFingerprint,
|
|
199
|
+
recordBlockedActionHint,
|
|
200
|
+
hasBlockedActionHint,
|
|
201
|
+
forgetBlockedActionHint
|
|
202
|
+
};
|
|
@@ -3,19 +3,19 @@ import {
|
|
|
3
3
|
loadPin,
|
|
4
4
|
savePin,
|
|
5
5
|
verifyRecipients
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OBYC6SN4.js";
|
|
7
7
|
import {
|
|
8
8
|
hookOwnerOf
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-W2MG2P46.js";
|
|
10
10
|
import {
|
|
11
11
|
ensureDaemonRunning
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-DOD3A6EU.js";
|
|
13
13
|
import {
|
|
14
14
|
buildTranscriptRecords,
|
|
15
15
|
decodePublicKey,
|
|
16
16
|
generateSessionKey,
|
|
17
17
|
wrapSessionKey
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-XPPXOKIG.js";
|
|
19
19
|
import {
|
|
20
20
|
fetchTranscriptRecipients,
|
|
21
21
|
uploadTranscriptRecords
|
|
@@ -23,17 +23,17 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
transcriptEnvironmentPreference,
|
|
25
25
|
transcriptsEnabled
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-NQGYPTJQ.js";
|
|
27
27
|
import {
|
|
28
28
|
codexHome,
|
|
29
29
|
pusharyDir
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-XL3RKJYQ.js";
|
|
31
31
|
import {
|
|
32
32
|
readTranscriptPreference
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-SJLGOVC6.js";
|
|
34
34
|
import {
|
|
35
35
|
writeJsonAtomic
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-VZD2GEKB.js";
|
|
37
37
|
import {
|
|
38
38
|
getApiKey,
|
|
39
39
|
getBaseUrl
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
blockedActionFingerprint,
|
|
3
|
+
defersToNativeMode,
|
|
4
|
+
forgetBlockedActionHint,
|
|
5
|
+
hasBlockedActionHint,
|
|
6
|
+
recordBlockedActionHint
|
|
7
|
+
} from "./chunk-53QCTSWY.js";
|
|
4
8
|
import {
|
|
5
9
|
isGatingMoment,
|
|
6
10
|
recordKeylessMoment
|
|
7
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XOOWC62T.js";
|
|
8
12
|
import {
|
|
9
13
|
denyReasonFrom,
|
|
10
14
|
isDeferAnswer
|
|
@@ -20,7 +24,7 @@ import {
|
|
|
20
24
|
reportEvent,
|
|
21
25
|
settleQuestionsForToolUse,
|
|
22
26
|
throttlePass
|
|
23
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-YJDRHQLG.js";
|
|
24
28
|
import {
|
|
25
29
|
UNNAMED_ACTION,
|
|
26
30
|
deriveAction,
|
|
@@ -30,37 +34,46 @@ import {
|
|
|
30
34
|
describeToolCall,
|
|
31
35
|
permissionDeniedAction,
|
|
32
36
|
scopePathFor
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-A76GNRIK.js";
|
|
34
38
|
import {
|
|
35
39
|
DECISION_STOP_REASON,
|
|
36
40
|
askUser,
|
|
41
|
+
cancelQuestion,
|
|
42
|
+
claimBlockedAction,
|
|
37
43
|
isDecisionStopped,
|
|
38
44
|
isLocalQuestionHandoff,
|
|
45
|
+
pollForAnswer,
|
|
46
|
+
requestGate,
|
|
39
47
|
sendNotification,
|
|
40
48
|
waitForQuestion
|
|
41
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-LGXUGYUH.js";
|
|
42
50
|
import {
|
|
43
51
|
getMachineId
|
|
44
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-WU6LSXJ6.js";
|
|
45
53
|
import {
|
|
46
54
|
getApiKey,
|
|
47
55
|
getBaseUrl
|
|
48
56
|
} from "./chunk-2UMNXADU.js";
|
|
49
57
|
import {
|
|
58
|
+
GATE_BLOCK_REASON_MAX_LENGTH,
|
|
59
|
+
GATE_WIRE,
|
|
50
60
|
buildDecisionEpisodeFeatures,
|
|
61
|
+
claudePermissionDeniedOutput,
|
|
51
62
|
claudePermissionRequestOutput,
|
|
52
63
|
claudePreToolUseOutput,
|
|
53
64
|
effectiveWaitSeconds,
|
|
65
|
+
hookHoldWindowMs,
|
|
54
66
|
hookWaitClamped,
|
|
55
67
|
hookWaitDeadline,
|
|
56
68
|
hookWritePath,
|
|
69
|
+
isBlockedActionRequest,
|
|
57
70
|
isPermissionRequestOwnedByPreToolUse,
|
|
58
71
|
isPusharyOwnTool,
|
|
59
72
|
parseAgentQuestionAnswers,
|
|
60
73
|
parseAgentQuestions,
|
|
61
74
|
resolveGate,
|
|
62
75
|
stoppedOutput
|
|
63
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-OUAE3GKO.js";
|
|
64
77
|
|
|
65
78
|
// src/decision-episode.ts
|
|
66
79
|
var EPISODE_PATH = "/api/agent/decision-episode";
|
|
@@ -89,7 +102,7 @@ import { basename, join } from "path";
|
|
|
89
102
|
import { tmpdir } from "os";
|
|
90
103
|
import { existsSync, writeFileSync } from "fs";
|
|
91
104
|
var START_MS = Date.now();
|
|
92
|
-
var allow = () => claudePreToolUseOutput("allow");
|
|
105
|
+
var allow = (reason) => claudePreToolUseOutput("allow", reason);
|
|
93
106
|
var deny = (reason) => claudePreToolUseOutput("deny", reason);
|
|
94
107
|
var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
|
|
95
108
|
var ask = (reason) => claudePreToolUseOutput("ask", reason);
|
|
@@ -449,6 +462,45 @@ var claudeGateInput = (modeState, config, input) => {
|
|
|
449
462
|
sessionId: input.session_id
|
|
450
463
|
};
|
|
451
464
|
};
|
|
465
|
+
var BLOCKED_ACTION_GATE_TIMEOUT_MS = 15e3;
|
|
466
|
+
var BLOCKED_ACTION_CLAIM_TIMEOUT_MS = 4e3;
|
|
467
|
+
var BLOCKED_ACTION_ALLOW_REASON = "Approved from Pushary after auto mode blocked it";
|
|
468
|
+
var blockReasonOf = (reason) => typeof reason === "string" ? reason.trim().slice(0, GATE_BLOCK_REASON_MAX_LENGTH).trim() || void 0 : void 0;
|
|
469
|
+
var blockedActionFingerprintFor = (input) => blockedActionFingerprint({
|
|
470
|
+
sessionId: input.session_id,
|
|
471
|
+
toolName: input.tool_name,
|
|
472
|
+
toolInput: input.tool_input,
|
|
473
|
+
cwd: input.cwd
|
|
474
|
+
});
|
|
475
|
+
var blockedActionGateRequest = (input, event) => ({
|
|
476
|
+
v: GATE_WIRE,
|
|
477
|
+
source: "claude",
|
|
478
|
+
event,
|
|
479
|
+
toolName: input.tool_name,
|
|
480
|
+
toolInputs: [input.tool_input ?? {}],
|
|
481
|
+
sessionId: input.session_id,
|
|
482
|
+
toolUseId: input.tool_use_id,
|
|
483
|
+
machineId: getMachineId(),
|
|
484
|
+
cwd: input.cwd,
|
|
485
|
+
repoKey: repoKeyFor(input.cwd),
|
|
486
|
+
permissionMode: input.permission_mode,
|
|
487
|
+
...event === "PermissionDenied" ? { createDecision: true, localSurfaceAvailable: false, blockReason: blockReasonOf(input.reason) } : {}
|
|
488
|
+
});
|
|
489
|
+
var claimedBlockedActionRetry = async (apiKey, input) => {
|
|
490
|
+
const fingerprint = blockedActionFingerprintFor(input);
|
|
491
|
+
if (!fingerprint || !hasBlockedActionHint(fingerprint)) return false;
|
|
492
|
+
try {
|
|
493
|
+
const granted = await claimBlockedAction(
|
|
494
|
+
apiKey,
|
|
495
|
+
blockedActionGateRequest(input, "PreToolUse"),
|
|
496
|
+
BLOCKED_ACTION_CLAIM_TIMEOUT_MS
|
|
497
|
+
);
|
|
498
|
+
if (granted !== null) forgetBlockedActionHint(fingerprint);
|
|
499
|
+
return granted === true;
|
|
500
|
+
} catch {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
452
504
|
var handlePreToolUse = async (input) => {
|
|
453
505
|
if (isPusharyOwnTool(input.tool_name)) return void 0;
|
|
454
506
|
let apiKey;
|
|
@@ -469,6 +521,7 @@ var handlePreToolUse = async (input) => {
|
|
|
469
521
|
if (input.tool_name === "ExitPlanMode") {
|
|
470
522
|
return handleExitPlanMode(apiKey, input);
|
|
471
523
|
}
|
|
524
|
+
if (await claimedBlockedActionRetry(apiKey, input)) return allow(BLOCKED_ACTION_ALLOW_REASON);
|
|
472
525
|
if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
|
|
473
526
|
return void 0;
|
|
474
527
|
}
|
|
@@ -531,12 +584,6 @@ var handlePermissionRequest = async (input) => {
|
|
|
531
584
|
return void 0;
|
|
532
585
|
}
|
|
533
586
|
};
|
|
534
|
-
var PERMISSION_DENIED_WINDOW_MS = 5 * 60 * 1e3;
|
|
535
|
-
var PERMISSION_DENIED_WAIT_SECONDS = 25;
|
|
536
|
-
var automodeRetryEnabled = () => {
|
|
537
|
-
const flag = process.env.PUSHARY_AUTOMODE_RETRY;
|
|
538
|
-
return flag === "1" || flag === "true";
|
|
539
|
-
};
|
|
540
587
|
var reportPermissionDenied = (input) => reportEvent({
|
|
541
588
|
event: "permission_denied",
|
|
542
589
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -548,9 +595,48 @@ var reportPermissionDenied = (input) => reportEvent({
|
|
|
548
595
|
// Side report, not a command carrier. Without timeoutMs this takes the phone's
|
|
549
596
|
// queued instruction and acknowledges it delivered, and this hook can print
|
|
550
597
|
// only {retry}. 4s rather than the Notification path's 2.5s because
|
|
551
|
-
// PermissionDenied
|
|
598
|
+
// PermissionDenied holds the agent for the full hook budget, not 10s.
|
|
552
599
|
}, { timeoutMs: 4e3 }).catch(() => void 0);
|
|
553
|
-
var
|
|
600
|
+
var isAffirmativeAnswer = (answer) => answer.answered && !isDecisionStopped(answer) && answer.value === "yes";
|
|
601
|
+
var PERMISSION_DENIED_NOTICE_WINDOW_MS = 5 * 60 * 1e3;
|
|
602
|
+
var blockedActionHoldEndsAt = (holdBudgetSeconds) => START_MS + hookHoldWindowMs("claude", holdBudgetSeconds);
|
|
603
|
+
var notifyPermissionDenied = async (apiKey, input) => {
|
|
604
|
+
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
605
|
+
if (!throttlePass(`permdenied:${sessionKey}:${input.tool_name}`, PERMISSION_DENIED_NOTICE_WINDOW_MS)) return;
|
|
606
|
+
const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
|
|
607
|
+
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
608
|
+
const projectName = basename(input.cwd ?? process.cwd());
|
|
609
|
+
await sendNotification(apiKey, {
|
|
610
|
+
title: `Auto mode blocked ${input.tool_name}`,
|
|
611
|
+
body: `Your agent was denied ${input.tool_name} in ${projectName}.`,
|
|
612
|
+
agentName: `Claude Code - ${projectName}`,
|
|
613
|
+
sessionId: input.session_id
|
|
614
|
+
}, 4e3).catch(() => void 0);
|
|
615
|
+
};
|
|
616
|
+
var isRetryOutput = (output) => {
|
|
617
|
+
if (!output) return false;
|
|
618
|
+
try {
|
|
619
|
+
const parsed = JSON.parse(output);
|
|
620
|
+
return parsed?.hookSpecificOutput?.hookEventName === "PermissionDenied" && parsed.hookSpecificOutput.retry === true;
|
|
621
|
+
} catch {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
var retryOnce = (fingerprint) => recordBlockedActionHint(fingerprint) ? claudePermissionDeniedOutput("allow") : void 0;
|
|
626
|
+
var askAboutBlockedAction = async (apiKey, input, fingerprint, holdEndsAt) => {
|
|
627
|
+
const reply = await requestGate(apiKey, blockedActionGateRequest(input, "PermissionDenied"), BLOCKED_ACTION_GATE_TIMEOUT_MS);
|
|
628
|
+
if (!reply) return void 0;
|
|
629
|
+
if (reply.kind === "no_opinion") return isRetryOutput(reply.output) ? retryOnce(fingerprint) : void 0;
|
|
630
|
+
if (reply.kind !== "ask" || !reply.correlationId || reply.deadlineSeconds === void 0) return void 0;
|
|
631
|
+
if (reply.noDevices) {
|
|
632
|
+
await cancelQuestion(apiKey, reply.correlationId, AbortSignal.timeout(4e3)).catch(() => void 0);
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
const deadline = Math.min(hookWaitDeadline(START_MS, reply.deadlineSeconds, "claude", Date.now()), holdEndsAt);
|
|
636
|
+
const answer = await pollForAnswer(apiKey, reply.correlationId, deadline);
|
|
637
|
+
return isAffirmativeAnswer(answer) ? retryOnce(fingerprint) : void 0;
|
|
638
|
+
};
|
|
639
|
+
var handlePermissionDenied = async (input, holdBudgetSeconds) => {
|
|
554
640
|
await reportPermissionDenied(input);
|
|
555
641
|
if (isPusharyOwnTool(input.tool_name)) return void 0;
|
|
556
642
|
let apiKey;
|
|
@@ -560,46 +646,16 @@ var handlePermissionDenied = async (input) => {
|
|
|
560
646
|
return void 0;
|
|
561
647
|
}
|
|
562
648
|
try {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (!
|
|
567
|
-
const
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
await sendNotification(apiKey, {
|
|
571
|
-
title: `Auto-mode blocked ${input.tool_name}`,
|
|
572
|
-
body: `Your agent was denied ${input.tool_name} in ${projectName}.`,
|
|
573
|
-
agentName: `Claude Code - ${projectName}`,
|
|
574
|
-
sessionId: input.session_id
|
|
575
|
-
}, 4e3).catch(() => {
|
|
576
|
-
});
|
|
649
|
+
await settleQuestionsForToolUse(input.session_id || DEFAULT_SESSION, input.tool_use_id);
|
|
650
|
+
if (!isBlockedActionRequest({ source: "claude", event: "PermissionDenied", toolName: input.tool_name })) return void 0;
|
|
651
|
+
const fingerprint = blockedActionFingerprintFor(input);
|
|
652
|
+
if (!fingerprint) return void 0;
|
|
653
|
+
const holdEndsAt = blockedActionHoldEndsAt(holdBudgetSeconds);
|
|
654
|
+
if (holdEndsAt - Date.now() <= BLOCKED_ACTION_GATE_TIMEOUT_MS) {
|
|
655
|
+
await notifyPermissionDenied(apiKey, input);
|
|
577
656
|
return void 0;
|
|
578
657
|
}
|
|
579
|
-
|
|
580
|
-
try {
|
|
581
|
-
result = await askUser(apiKey, {
|
|
582
|
-
question: `Auto-mode blocked ${input.tool_name} in ${projectName}. Let the agent retry it?`,
|
|
583
|
-
type: "confirm",
|
|
584
|
-
context: "Your agent hit a permission wall in auto mode",
|
|
585
|
-
agentName: `Claude Code - ${projectName}`,
|
|
586
|
-
sessionId: input.session_id,
|
|
587
|
-
machineId: getMachineId(),
|
|
588
|
-
toolName: input.tool_name,
|
|
589
|
-
// Suffixed because the PreToolUse gate may already have asked about this
|
|
590
|
-
// same tool call. Without it, the retry prompt would collide with that
|
|
591
|
-
// decision and hand back its answer instead of asking a new question.
|
|
592
|
-
toolUseId: input.tool_use_id ? `${input.tool_use_id}#retry` : void 0
|
|
593
|
-
});
|
|
594
|
-
} catch {
|
|
595
|
-
return void 0;
|
|
596
|
-
}
|
|
597
|
-
const deadline = hookWaitDeadline(START_MS, PERMISSION_DENIED_WAIT_SECONDS, "claude", Date.now());
|
|
598
|
-
const answer = await waitForQuestion(apiKey, result, deadline);
|
|
599
|
-
if (answer.answered && answer.value === "yes") {
|
|
600
|
-
return { hookSpecificOutput: { hookEventName: "PermissionDenied", retry: true } };
|
|
601
|
-
}
|
|
602
|
-
return void 0;
|
|
658
|
+
return await askAboutBlockedAction(apiKey, input, fingerprint, holdEndsAt);
|
|
603
659
|
} catch {
|
|
604
660
|
return void 0;
|
|
605
661
|
}
|