@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
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "./chunk-SAF6HGAA.js";
|
|
11
11
|
import {
|
|
12
12
|
reportedInstallSource
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ES3FAX5X.js";
|
|
14
14
|
import {
|
|
15
15
|
getApiKey,
|
|
16
16
|
getBaseUrl
|
|
@@ -18,19 +18,15 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
ACTION_BODY_MAX,
|
|
20
20
|
DECISION_LINE_MAX,
|
|
21
|
-
classifyDecisionRisk,
|
|
22
|
-
isApprovalMode,
|
|
23
21
|
isAutoApprove,
|
|
24
|
-
isAutoDeny,
|
|
25
|
-
isSafeReadOnlyCommand,
|
|
26
|
-
isScopeContract,
|
|
27
22
|
localRepoKey,
|
|
23
|
+
modeStateFromResponse,
|
|
28
24
|
normalizeRepoRemote,
|
|
29
|
-
policyArgForms,
|
|
30
25
|
redactSecrets,
|
|
31
26
|
redactSecretsDeep,
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
resolveAutoResolveOrigin,
|
|
28
|
+
resolvePolicy
|
|
29
|
+
} from "./chunk-G3LXA2ZP.js";
|
|
34
30
|
|
|
35
31
|
// src/policy.ts
|
|
36
32
|
import { createHash } from "crypto";
|
|
@@ -120,66 +116,6 @@ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessio
|
|
|
120
116
|
throw new Error("Failed to fetch policy and no cached policy available");
|
|
121
117
|
}
|
|
122
118
|
};
|
|
123
|
-
var autoApprove = (tool) => ({
|
|
124
|
-
tool,
|
|
125
|
-
timeoutSeconds: 0,
|
|
126
|
-
timeoutAction: "approve",
|
|
127
|
-
mode: "terminal_only",
|
|
128
|
-
pushFirstSeconds: 0
|
|
129
|
-
});
|
|
130
|
-
var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
|
|
131
|
-
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
132
|
-
const arg = args[0];
|
|
133
|
-
const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
|
|
134
|
-
const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
|
|
135
|
-
if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
|
|
136
|
-
return "safe_readonly";
|
|
137
|
-
}
|
|
138
|
-
return "policy_timeout";
|
|
139
|
-
};
|
|
140
|
-
var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
|
|
141
|
-
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
142
|
-
const arg = args[0];
|
|
143
|
-
const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
|
|
144
|
-
let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
|
|
145
|
-
tool: toolName,
|
|
146
|
-
timeoutSeconds: config.defaultTimeoutSeconds,
|
|
147
|
-
timeoutAction: config.defaultTimeoutAction,
|
|
148
|
-
mode: config.defaultMode ?? "push_first",
|
|
149
|
-
pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
|
|
150
|
-
};
|
|
151
|
-
const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
|
|
152
|
-
if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
|
|
153
|
-
base = autoApprove(base.tool);
|
|
154
|
-
}
|
|
155
|
-
const wouldAutoApprove = isAutoApprove(base);
|
|
156
|
-
if (!governedBySpecificRule && wouldAutoApprove && toolName === "Bash" && typeof arg === "string" && classifyDecisionRisk(toolName, arg) === "destructive") {
|
|
157
|
-
base = {
|
|
158
|
-
tool: base.tool,
|
|
159
|
-
timeoutSeconds: config.defaultTimeoutSeconds,
|
|
160
|
-
timeoutAction: "wait",
|
|
161
|
-
mode: "push_first",
|
|
162
|
-
pushFirstSeconds: config.defaultPushFirstSeconds ?? 20
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const effectiveOverride = modeOverride ?? config.modeOverride;
|
|
166
|
-
if (effectiveOverride) {
|
|
167
|
-
return { ...base, mode: effectiveOverride };
|
|
168
|
-
}
|
|
169
|
-
return base;
|
|
170
|
-
};
|
|
171
|
-
var restraintRank = (policy) => {
|
|
172
|
-
if (isAutoDeny(policy)) return 4;
|
|
173
|
-
if (isAutoApprove(policy)) return 0;
|
|
174
|
-
if (policy.mode === "push_only") return 3;
|
|
175
|
-
return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
|
|
176
|
-
};
|
|
177
|
-
var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
|
|
178
|
-
var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey, sessionId) => {
|
|
179
|
-
if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey, sessionId);
|
|
180
|
-
return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey, sessionId)).reduce(strictestPolicy);
|
|
181
|
-
};
|
|
182
|
-
var isModeStateDegraded = (state) => state?.degraded === true;
|
|
183
119
|
var MODE_GRACE_MS = 60 * 1e3;
|
|
184
120
|
var modeStateCacheFile = (apiKey) => {
|
|
185
121
|
const hash = createHash("sha256").update(`mode:${apiKey}`).digest("hex").slice(0, 12);
|
|
@@ -220,7 +156,6 @@ var degradedFallback = (apiKey, authoritative = false) => {
|
|
|
220
156
|
}
|
|
221
157
|
return unknownMode();
|
|
222
158
|
};
|
|
223
|
-
var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
|
|
224
159
|
var MODE_REUSE_MS = CACHE_TTL_MS;
|
|
225
160
|
var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
|
|
226
161
|
const last = readLastGoodMode(apiKey);
|
|
@@ -243,19 +178,7 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
|
243
178
|
if (!response.ok) {
|
|
244
179
|
return degradedFallback(apiKey, response.status === 401 || response.status === 403);
|
|
245
180
|
}
|
|
246
|
-
const
|
|
247
|
-
const mode = data.override?.mode;
|
|
248
|
-
const state = {
|
|
249
|
-
mode: isApprovalMode(mode) ? mode : null,
|
|
250
|
-
kill: data.kill === true,
|
|
251
|
-
policyVersion: toPolicyVersion(data.policyVersion),
|
|
252
|
-
relayUrl: typeof data.relayUrl === "string" && data.relayUrl.length > 0 ? data.relayUrl : null,
|
|
253
|
-
trainingConsent: data.trainingConsent === true,
|
|
254
|
-
// Validated rather than cast: a malformed contract must read as no contract,
|
|
255
|
-
// never as one that silently matches nothing.
|
|
256
|
-
scope: isScopeContract(data.scope) ? data.scope : null,
|
|
257
|
-
degraded: false
|
|
258
|
-
};
|
|
181
|
+
const state = modeStateFromResponse(await response.json());
|
|
259
182
|
writeLastGoodMode(apiKey, state, sessionId);
|
|
260
183
|
return state;
|
|
261
184
|
} catch {
|
|
@@ -265,9 +188,11 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
|
265
188
|
var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
|
|
266
189
|
|
|
267
190
|
// src/api.ts
|
|
191
|
+
import { randomUUID } from "crypto";
|
|
268
192
|
installProxyDispatcher();
|
|
269
193
|
var askUser = async (apiKey, params, timeoutMs = 15e3) => {
|
|
270
|
-
const
|
|
194
|
+
const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
|
|
195
|
+
const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
|
|
271
196
|
if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
|
|
272
197
|
return result;
|
|
273
198
|
};
|
|
@@ -286,7 +211,7 @@ var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
|
|
|
286
211
|
await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: 3, timeoutMs });
|
|
287
212
|
};
|
|
288
213
|
|
|
289
|
-
// src/describe.ts
|
|
214
|
+
// ../hook-mapping/src/describe.ts
|
|
290
215
|
import { isAbsolute, relative } from "path";
|
|
291
216
|
var hookPrefixes = {
|
|
292
217
|
Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
|
|
@@ -455,6 +380,36 @@ var deriveBlocker = (mode, reason) => {
|
|
|
455
380
|
return text.slice(0, DECISION_LINE_MAX);
|
|
456
381
|
};
|
|
457
382
|
|
|
383
|
+
// ../hook-mapping/src/map.ts
|
|
384
|
+
import { basename } from "path";
|
|
385
|
+
var TASK_TITLE_MAX_LENGTH = 120;
|
|
386
|
+
var STOP_SUMMARY_MAX_LENGTH = 200;
|
|
387
|
+
var SESSION_END_ACTIONS = {
|
|
388
|
+
clear: "Session cleared",
|
|
389
|
+
logout: "Logged out",
|
|
390
|
+
resume: "Session suspended",
|
|
391
|
+
prompt_input_exit: "Session ended",
|
|
392
|
+
bypass_permissions_disabled: "Session ended",
|
|
393
|
+
other: "Session ended"
|
|
394
|
+
};
|
|
395
|
+
var countArray = (value) => Array.isArray(value) ? value.length : 0;
|
|
396
|
+
var summarizeFinalMessage = (message) => {
|
|
397
|
+
if (typeof message !== "string") return "Session ended";
|
|
398
|
+
const collapsed = message.replace(/\s+/g, " ").trim();
|
|
399
|
+
if (!collapsed) return "Session ended";
|
|
400
|
+
return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
|
|
401
|
+
};
|
|
402
|
+
var summarizeStop = (input) => {
|
|
403
|
+
const running = countArray(input.background_tasks);
|
|
404
|
+
if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
|
|
405
|
+
const crons = countArray(input.session_crons);
|
|
406
|
+
if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
|
|
407
|
+
return summarizeFinalMessage(input.last_assistant_message);
|
|
408
|
+
};
|
|
409
|
+
var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "Session started";
|
|
410
|
+
var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
|
|
411
|
+
var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
|
|
412
|
+
|
|
458
413
|
// src/pending.ts
|
|
459
414
|
import { join as join2 } from "path";
|
|
460
415
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -503,7 +458,7 @@ var removePendingSession = (sessionId) => {
|
|
|
503
458
|
|
|
504
459
|
// src/repo.ts
|
|
505
460
|
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
506
|
-
import { basename, dirname, isAbsolute as isAbsolute2, join as join3, resolve } from "path";
|
|
461
|
+
import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join3, resolve } from "path";
|
|
507
462
|
var MAX_PARENT_WALK = 64;
|
|
508
463
|
var GIT_FILE_PREFIX = "gitdir:";
|
|
509
464
|
var findGitDir = (startDir) => {
|
|
@@ -562,9 +517,9 @@ var deriveRepoKey = (cwd) => {
|
|
|
562
517
|
const normalized = remote ? normalizeRepoRemote(remote) : void 0;
|
|
563
518
|
if (normalized) return normalized;
|
|
564
519
|
const root = gitDir.endsWith(".git") ? dirname(gitDir) : dir;
|
|
565
|
-
return localRepoKey(
|
|
520
|
+
return localRepoKey(basename2(root));
|
|
566
521
|
}
|
|
567
|
-
return localRepoKey(
|
|
522
|
+
return localRepoKey(basename2(resolve(dir)));
|
|
568
523
|
} catch {
|
|
569
524
|
return void 0;
|
|
570
525
|
}
|
|
@@ -869,7 +824,7 @@ var throttlePass = (key, windowMs) => {
|
|
|
869
824
|
};
|
|
870
825
|
|
|
871
826
|
// src/events.ts
|
|
872
|
-
import { basename as
|
|
827
|
+
import { basename as basename3, join as join6 } from "path";
|
|
873
828
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
874
829
|
import { tmpdir as tmpdir5 } from "os";
|
|
875
830
|
var INTENT_DIR = join6(tmpdir5(), "pushary-intent");
|
|
@@ -1058,7 +1013,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1058
1013
|
var NON_GATED_MIRROR_TOOLS = /* @__PURE__ */ new Set(["TodoWrite"]);
|
|
1059
1014
|
var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1060
1015
|
try {
|
|
1061
|
-
const projectName =
|
|
1016
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1062
1017
|
const action = describeToolCall(input.tool_name, input.tool_input, "event");
|
|
1063
1018
|
const lookup = toPolicyLookup(input.tool_name, input.tool_input);
|
|
1064
1019
|
const toolResult = input.tool_response ?? input.tool_result;
|
|
@@ -1110,13 +1065,12 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1110
1065
|
return void 0;
|
|
1111
1066
|
}
|
|
1112
1067
|
};
|
|
1113
|
-
var TASK_TITLE_MAX_LENGTH = 120;
|
|
1114
1068
|
var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1115
1069
|
try {
|
|
1116
|
-
const projectName =
|
|
1070
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1117
1071
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1118
1072
|
const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
|
|
1119
|
-
const taskTitle = titlesEnabled ? input.prompt
|
|
1073
|
+
const taskTitle = titlesEnabled ? taskTitleFrom(input.prompt) : void 0;
|
|
1120
1074
|
if (input.prompt) saveLastPrompt(sessionKey, input.prompt);
|
|
1121
1075
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1122
1076
|
let additionalContext;
|
|
@@ -1141,24 +1095,9 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1141
1095
|
return void 0;
|
|
1142
1096
|
}
|
|
1143
1097
|
};
|
|
1144
|
-
var STOP_SUMMARY_MAX_LENGTH = 200;
|
|
1145
|
-
var summarizeFinalMessage = (message) => {
|
|
1146
|
-
if (typeof message !== "string") return "Session ended";
|
|
1147
|
-
const collapsed = message.replace(/\s+/g, " ").trim();
|
|
1148
|
-
if (!collapsed) return "Session ended";
|
|
1149
|
-
return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
|
|
1150
|
-
};
|
|
1151
|
-
var countArray = (value) => Array.isArray(value) ? value.length : 0;
|
|
1152
|
-
var summarizeStop = (input) => {
|
|
1153
|
-
const running = countArray(input.background_tasks);
|
|
1154
|
-
if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
|
|
1155
|
-
const crons = countArray(input.session_crons);
|
|
1156
|
-
if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
|
|
1157
|
-
return summarizeFinalMessage(input.last_assistant_message);
|
|
1158
|
-
};
|
|
1159
1098
|
var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1160
1099
|
try {
|
|
1161
|
-
const projectName =
|
|
1100
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1162
1101
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1163
1102
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1164
1103
|
const tasks = [
|
|
@@ -1194,8 +1133,8 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1194
1133
|
};
|
|
1195
1134
|
var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1196
1135
|
try {
|
|
1197
|
-
const projectName =
|
|
1198
|
-
const action = input.source
|
|
1136
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1137
|
+
const action = sessionStartAction(input.source);
|
|
1199
1138
|
await reportEvent({
|
|
1200
1139
|
event: "session_start",
|
|
1201
1140
|
agentType: agent.type,
|
|
@@ -1206,18 +1145,10 @@ var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1206
1145
|
} catch {
|
|
1207
1146
|
}
|
|
1208
1147
|
};
|
|
1209
|
-
var SESSION_END_ACTIONS = {
|
|
1210
|
-
clear: "Session cleared",
|
|
1211
|
-
logout: "Logged out",
|
|
1212
|
-
resume: "Session suspended",
|
|
1213
|
-
prompt_input_exit: "Session ended",
|
|
1214
|
-
bypass_permissions_disabled: "Session ended",
|
|
1215
|
-
other: "Session ended"
|
|
1216
|
-
};
|
|
1217
1148
|
var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1218
1149
|
try {
|
|
1219
|
-
const projectName =
|
|
1220
|
-
const action =
|
|
1150
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1151
|
+
const action = sessionEndAction(input.reason);
|
|
1221
1152
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1222
1153
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1223
1154
|
if (late.length > 0) {
|
|
@@ -1240,7 +1171,7 @@ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set(["idle_prompt", "agent_nee
|
|
|
1240
1171
|
var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
|
|
1241
1172
|
var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1242
1173
|
try {
|
|
1243
|
-
const projectName =
|
|
1174
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1244
1175
|
const notifType = input.notification_type ?? input.type ?? "";
|
|
1245
1176
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1246
1177
|
if (NOTIFICATION_PUSH_TYPES.has(notifType)) {
|
|
@@ -1313,7 +1244,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1313
1244
|
} catch {
|
|
1314
1245
|
return;
|
|
1315
1246
|
}
|
|
1316
|
-
const projectName =
|
|
1247
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1317
1248
|
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1318
1249
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1319
1250
|
const reason = describeStopFailure(input.error);
|
|
@@ -1332,10 +1263,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1332
1263
|
export {
|
|
1333
1264
|
policyRequestFor,
|
|
1334
1265
|
getPolicy,
|
|
1335
|
-
resolveAutoResolveOrigin,
|
|
1336
|
-
resolvePolicy,
|
|
1337
|
-
resolvePolicyAcross,
|
|
1338
|
-
isModeStateDegraded,
|
|
1339
1266
|
fetchModeState,
|
|
1340
1267
|
fetchModeOverride,
|
|
1341
1268
|
askUser,
|
|
@@ -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 {
|
|
2
|
-
|
|
1
|
+
import { ReceiptMeta } from '@pushary/hook-mapping';
|
|
2
|
+
import { PolicyConfig, ApprovalMode, ModeState, AutoResolveOrigin, AgentQuestion } from '@pushary/contracts';
|
|
3
|
+
export { ApprovalMode, ModeState, PolicyConfig, ToolPolicy, resolvePolicy } from '@pushary/contracts';
|
|
3
4
|
|
|
4
5
|
interface ToolInput {
|
|
5
6
|
tool_name: string;
|
|
@@ -20,25 +21,8 @@ interface HookOutput {
|
|
|
20
21
|
}
|
|
21
22
|
declare const handlePreToolUse: (input: ToolInput) => Promise<HookOutput | undefined>;
|
|
22
23
|
|
|
23
|
-
type ReceiptKind = 'edit' | 'write' | 'bash' | 'commit';
|
|
24
|
-
interface ReceiptMeta {
|
|
25
|
-
kind: ReceiptKind;
|
|
26
|
-
target: string;
|
|
27
|
-
ok: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
24
|
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
|
-
}
|
|
25
|
+
|
|
42
26
|
declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
|
|
43
27
|
declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
|
|
44
28
|
|
|
@@ -142,6 +126,7 @@ interface AskUserParams {
|
|
|
142
126
|
actionBody?: string;
|
|
143
127
|
scopePath?: string;
|
|
144
128
|
toolUseId?: string;
|
|
129
|
+
requestId?: string;
|
|
145
130
|
}
|
|
146
131
|
interface AskUserResponse {
|
|
147
132
|
correlationId: string;
|
|
@@ -166,4 +151,4 @@ declare const cancelQuestion: (apiKey: string, correlationId: string) => Promise
|
|
|
166
151
|
declare const getApiKey: () => string;
|
|
167
152
|
declare const getBaseUrl: () => string;
|
|
168
153
|
|
|
169
|
-
export {
|
|
154
|
+
export { askUser, cancelQuestion, fetchModeOverride, fetchModeState, getApiKey, getBaseUrl, getPolicy, handleNotification, handlePostToolUse, handlePreToolUse, handleStop, reportEvent, waitForAnswer };
|
package/dist/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-LB6RSCLL.js";
|
|
4
|
+
import "../chunk-GM54KC62.js";
|
|
5
|
+
import "../chunk-HSGEEBBL.js";
|
|
6
6
|
import "../chunk-VT4IVERX.js";
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-VGADRFG6.js";
|
|
8
|
+
import "../chunk-YHG74UFF.js";
|
|
9
9
|
import {
|
|
10
10
|
askUser,
|
|
11
11
|
cancelQuestion,
|
|
@@ -16,20 +16,21 @@ import {
|
|
|
16
16
|
handlePostToolUse,
|
|
17
17
|
handleStop,
|
|
18
18
|
reportEvent,
|
|
19
|
-
resolvePolicy,
|
|
20
19
|
waitForAnswer
|
|
21
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-Y7BALRVB.js";
|
|
22
21
|
import "../chunk-RN3NOEJF.js";
|
|
23
22
|
import "../chunk-BSZYIAZL.js";
|
|
24
23
|
import "../chunk-SAF6HGAA.js";
|
|
25
|
-
import "../chunk-
|
|
24
|
+
import "../chunk-ES3FAX5X.js";
|
|
26
25
|
import "../chunk-7QLSKOSU.js";
|
|
27
26
|
import "../chunk-KZERVKTD.js";
|
|
28
27
|
import {
|
|
29
28
|
getApiKey,
|
|
30
29
|
getBaseUrl
|
|
31
30
|
} from "../chunk-2UMNXADU.js";
|
|
32
|
-
import
|
|
31
|
+
import {
|
|
32
|
+
resolvePolicy
|
|
33
|
+
} from "../chunk-G3LXA2ZP.js";
|
|
33
34
|
export {
|
|
34
35
|
askUser,
|
|
35
36
|
cancelQuestion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.85.
|
|
3
|
+
"version": "0.85.1",
|
|
4
4
|
"description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
"build": "node scripts/bundle-plugin.mjs && tsup",
|
|
81
81
|
"dev": "tsup --watch",
|
|
82
82
|
"test": "node ../../scripts/ci/run-tests.mjs src bin",
|
|
83
|
-
"typecheck": "tsc --noEmit"
|
|
83
|
+
"typecheck": "tsc --noEmit",
|
|
84
|
+
"gen:hooks-spec": "bun scripts/generate-hooks-spec.ts"
|
|
84
85
|
},
|
|
85
86
|
"dependencies": {
|
|
86
87
|
"@inquirer/prompts": "^8.4.2",
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
},
|
|
92
93
|
"devDependencies": {
|
|
93
94
|
"@pushary/contracts": "workspace:*",
|
|
95
|
+
"@pushary/hook-mapping": "workspace:*",
|
|
94
96
|
"tsup": "^8.0.0",
|
|
95
97
|
"typescript": "^5.0.0"
|
|
96
98
|
}
|
package/dist/chunk-GXIFADGD.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isModeStateDegraded,
|
|
3
|
-
resolveAutoResolveOrigin,
|
|
4
|
-
resolvePolicyAcross
|
|
5
|
-
} from "./chunk-EXMUM226.js";
|
|
6
|
-
import {
|
|
7
|
-
evaluateScope,
|
|
8
|
-
scopeChangeReason,
|
|
9
|
-
scopeRequiresHuman,
|
|
10
|
-
verdictForPolicy
|
|
11
|
-
} from "./chunk-MQMPG5X4.js";
|
|
12
|
-
|
|
13
|
-
// src/answer.ts
|
|
14
|
-
var denyReasonFrom = (value, note) => {
|
|
15
|
-
const trimmed = note?.trim();
|
|
16
|
-
if (trimmed) return `Denied from your phone: ${trimmed}`;
|
|
17
|
-
return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
|
|
18
|
-
};
|
|
19
|
-
var isDeferAnswer = (value) => value === "defer";
|
|
20
|
-
|
|
21
|
-
// src/gate.ts
|
|
22
|
-
var KILL_REASON = "Stopped by user, this agent was halted from Pushary";
|
|
23
|
-
var strictestScopeVerdict = (scope, paths) => {
|
|
24
|
-
if (paths.length === 0) return evaluateScope(scope, void 0);
|
|
25
|
-
let worst = { outcome: "in_scope" };
|
|
26
|
-
for (const path of paths) {
|
|
27
|
-
const verdict = evaluateScope(scope, path);
|
|
28
|
-
if (verdict.outcome === "off_limits") return verdict;
|
|
29
|
-
if (verdict.outcome === "out_of_scope") worst = verdict;
|
|
30
|
-
}
|
|
31
|
-
return worst;
|
|
32
|
-
};
|
|
33
|
-
var survivesDegraded = (config, toolName, toolInputs, cwd, repoKey, sessionId) => toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly";
|
|
34
|
-
var resolveGate = (input) => {
|
|
35
|
-
const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
|
|
36
|
-
if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
|
|
37
|
-
const policy = resolvePolicyAcross(config, toolName, modeState.mode, toolInputs, cwd, repoKey, sessionId);
|
|
38
|
-
const verdict = verdictForPolicy(policy);
|
|
39
|
-
const autoApproves = verdict === "allow";
|
|
40
|
-
const autoDenies = verdict === "deny";
|
|
41
|
-
if (autoDenies) return { kind: "deny", policy, reason: `Denied by policy for ${policy.tool}` };
|
|
42
|
-
if (isModeStateDegraded(modeState)) {
|
|
43
|
-
if (autoApproves) {
|
|
44
|
-
return survivesDegraded(config, toolName, toolInputs, cwd, repoKey, sessionId) ? { kind: "allow", policy } : { kind: "defer", policy };
|
|
45
|
-
}
|
|
46
|
-
return { kind: "gate", policy };
|
|
47
|
-
}
|
|
48
|
-
const scopeVerdict = modeState.scope ? strictestScopeVerdict(modeState.scope, input.scopePaths ?? []) : { outcome: "no_contract" };
|
|
49
|
-
if (autoApproves) {
|
|
50
|
-
if (!scopeRequiresHuman(scopeVerdict)) return { kind: "allow", policy };
|
|
51
|
-
}
|
|
52
|
-
const scopeReason = scopeChangeReason(scopeVerdict);
|
|
53
|
-
return {
|
|
54
|
-
kind: "gate",
|
|
55
|
-
policy,
|
|
56
|
-
scopeReason,
|
|
57
|
-
scopePath: scopeReason && "path" in scopeVerdict ? scopeVerdict.path : void 0
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export {
|
|
62
|
-
denyReasonFrom,
|
|
63
|
-
isDeferAnswer,
|
|
64
|
-
KILL_REASON,
|
|
65
|
-
resolveGate
|
|
66
|
-
};
|
package/dist/chunk-PWTKKQQI.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
quoteIfNeeded
|
|
3
|
-
} from "./chunk-VT4IVERX.js";
|
|
4
|
-
|
|
5
|
-
// src/hook-command.ts
|
|
6
|
-
var 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 RESOLVE = {
|
|
10
|
-
native: `${LOCATE}; exec "$B"`,
|
|
11
|
-
node: `${LOCATE}; ${NODE_PRESENT}; exec "$B"`
|
|
12
|
-
};
|
|
13
|
-
var guardHookCommand = (target, platform = process.platform) => {
|
|
14
|
-
if (target.kind === "node") {
|
|
15
|
-
const script = quoteIfNeeded(target.script);
|
|
16
|
-
return platform === "win32" ? `node ${script}` : `${SHELL} '[ -f "$0" ] || exit 0; command -v node >/dev/null 2>&1 || exit 0; exec node "$0"' ${script}`;
|
|
17
|
-
}
|
|
18
|
-
const path = quoteIfNeeded(target.path);
|
|
19
|
-
return platform === "win32" ? path : `${SHELL} '${RESOLVE[target.runtime]}' ${path}`;
|
|
20
|
-
};
|
|
21
|
-
var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
|
|
22
|
-
var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
guardBinary,
|
|
26
|
-
guardNodeScript
|
|
27
|
-
};
|