@pushary/agent-hooks 0.84.1 → 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 +64 -0
- package/README.md +24 -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 +9 -7
- package/dist/bin/pushary-bell.js +12 -11
- package/dist/bin/pushary-claude.js +12 -11
- package/dist/bin/pushary-clean.js +8 -7
- package/dist/bin/pushary-codex-bridge.js +5 -5
- package/dist/bin/pushary-codex-hook.js +14 -20
- package/dist/bin/pushary-codex.js +3 -3
- package/dist/bin/pushary-connect.js +10 -10
- package/dist/bin/pushary-daemon.js +6 -6
- package/dist/bin/pushary-disconnect.d.ts +1 -0
- package/dist/bin/pushary-disconnect.js +125 -0
- package/dist/bin/pushary-doctor.js +16 -16
- package/dist/bin/pushary-gemini-bridge.js +5 -5
- package/dist/bin/pushary-gemini-hook.js +14 -20
- package/dist/bin/pushary-hook.js +16 -23
- package/dist/bin/pushary-login.js +6 -6
- package/dist/bin/pushary-logout.js +6 -6
- package/dist/bin/pushary-mode.js +5 -5
- package/dist/bin/pushary-notification-hook.js +8 -11
- package/dist/bin/pushary-permission-denied-hook.js +14 -12
- package/dist/bin/pushary-permission-hook.js +14 -12
- package/dist/bin/pushary-post-hook.js +8 -11
- package/dist/bin/pushary-prompt-hook.js +8 -11
- package/dist/bin/pushary-session-end-hook.js +8 -8
- package/dist/bin/pushary-session-start-hook.js +8 -8
- package/dist/bin/pushary-setup.js +29 -30
- package/dist/bin/pushary-stats.js +4 -4
- package/dist/bin/pushary-status.js +7 -7
- package/dist/bin/pushary-stop-hook.js +8 -8
- package/dist/bin/pushary-stopfailure-hook.js +8 -8
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-upgrade.js +15 -10
- package/dist/bin/pushary-wait.js +5 -5
- package/dist/bin/pushary.js +2 -1
- package/dist/{chunk-ILB6UOAF.js → chunk-55656H4Q.js} +1 -1
- package/dist/chunk-6CUUA7HO.js +59 -0
- 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-EZPRY2ZY.js +70 -0
- package/dist/{chunk-MQMPG5X4.js → chunk-G3LXA2ZP.js} +207 -10
- package/dist/chunk-GM54KC62.js +187 -0
- package/dist/{chunk-MGJBC6DD.js → chunk-GOEXZ5QJ.js} +1 -0
- package/dist/chunk-HSGEEBBL.js +37 -0
- package/dist/{chunk-UXWEE3QI.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-YR5TSPXB.js → chunk-LB6RSCLL.js} +12 -10
- package/dist/{chunk-DOXSWT4Q.js → chunk-OZKRF7MS.js} +6 -6
- package/dist/{chunk-SEAHGHCE.js → chunk-PHXEADSL.js} +40 -7
- package/dist/{chunk-F52SIIDG.js → chunk-QDKJTPAT.js} +1 -1
- package/dist/{chunk-J4KFVOCQ.js → chunk-SQOFGPNX.js} +1 -1
- package/dist/{chunk-NJ7JT26G.js → chunk-VGADRFG6.js} +1 -1
- package/dist/{chunk-HZNOAJGB.js → chunk-VKJUVPTJ.js} +2 -2
- package/dist/{chunk-ERYKGH4J.js → chunk-XIR6N66V.js} +1 -1
- package/dist/{chunk-CCH775Y6.js → chunk-XLM6COJA.js} +1 -1
- package/dist/{chunk-CCCJ6BPW.js → chunk-Y7BALRVB.js} +73 -134
- package/dist/chunk-YHG74UFF.js +12 -0
- package/dist/src/index.d.ts +7 -22
- package/dist/src/index.js +11 -8
- package/package.json +6 -3
- package/dist/chunk-BLDDXHHA.js +0 -66
- package/dist/chunk-UKSQWTBH.js +0 -208
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HOOK_BUDGETS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-G3LXA2ZP.js";
|
|
4
4
|
|
|
5
5
|
// src/codex-config.ts
|
|
6
6
|
import { createHash } from "crypto";
|
|
@@ -14,12 +14,12 @@ var codexHooksJson = () => join(codexHome(), "hooks.json");
|
|
|
14
14
|
var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
|
|
15
15
|
var codexSkillDir = () => join(codexHome(), "skills", "pushary");
|
|
16
16
|
var CODEX_HOOK_EVENTS = [
|
|
17
|
-
{ event: "PermissionRequest", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
|
|
18
|
-
{ event: "PreToolUse", matcher: "Bash|apply_patch", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
|
|
19
|
-
{ event: "PostToolUse", matcher: "Bash|apply_patch", timeout: 10 },
|
|
20
|
-
{ event: "UserPromptSubmit", timeout: 10 },
|
|
21
|
-
{ event: "Stop", timeout: 10 },
|
|
22
|
-
{ event: "SessionStart", matcher: "startup|resume", timeout: 10 }
|
|
17
|
+
{ event: "PermissionRequest", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
|
|
18
|
+
{ event: "PreToolUse", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
|
|
19
|
+
{ event: "PostToolUse", matcher: "Bash|apply_patch", delivery: "telemetry", timeout: 10 },
|
|
20
|
+
{ event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
|
|
21
|
+
{ event: "Stop", delivery: "telemetry", timeout: 10 },
|
|
22
|
+
{ event: "SessionStart", matcher: "startup|resume", delivery: "telemetry", timeout: 10 }
|
|
23
23
|
];
|
|
24
24
|
var CODEX_EVENT_KEY = {
|
|
25
25
|
PermissionRequest: "permission_request",
|
|
@@ -139,6 +139,38 @@ var untrustedCodexHookEvents = (config, hooksJsonPath, command) => {
|
|
|
139
139
|
return stored !== codexHookTrustHash(definition, command);
|
|
140
140
|
}).map((definition) => definition.event);
|
|
141
141
|
};
|
|
142
|
+
var removeCodexMcpServer = (config) => {
|
|
143
|
+
const servers = asRecord(config.mcp_servers);
|
|
144
|
+
if (!servers?.pushary) return false;
|
|
145
|
+
delete servers.pushary;
|
|
146
|
+
if (Object.keys(servers).length === 0) delete config.mcp_servers;
|
|
147
|
+
return true;
|
|
148
|
+
};
|
|
149
|
+
var removeCodexHookTrust = (config, hooksJsonPath) => {
|
|
150
|
+
const state = asRecord(asRecord(config.hooks)?.state);
|
|
151
|
+
if (!state) return false;
|
|
152
|
+
let changed = false;
|
|
153
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
154
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event);
|
|
155
|
+
if (key in state) {
|
|
156
|
+
delete state[key];
|
|
157
|
+
changed = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (changed && Object.keys(state).length === 0) {
|
|
161
|
+
const hooks = asRecord(config.hooks);
|
|
162
|
+
if (hooks) {
|
|
163
|
+
delete hooks.state;
|
|
164
|
+
if (Object.keys(hooks).length === 0) delete config.hooks;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return changed;
|
|
168
|
+
};
|
|
169
|
+
var removeCodexSettings = (config, hooksJsonPath) => {
|
|
170
|
+
const mcp = removeCodexMcpServer(config);
|
|
171
|
+
const trust = removeCodexHookTrust(config, hooksJsonPath);
|
|
172
|
+
return mcp || trust;
|
|
173
|
+
};
|
|
142
174
|
var addCodexHookTrust = (config, hooksJsonPath, command) => {
|
|
143
175
|
const hooks = ensureRecord(config, "hooks");
|
|
144
176
|
const state = ensureRecord(hooks, "state");
|
|
@@ -183,6 +215,7 @@ export {
|
|
|
183
215
|
hasCodexHooks,
|
|
184
216
|
missingCodexHookEvents,
|
|
185
217
|
untrustedCodexHookEvents,
|
|
218
|
+
removeCodexSettings,
|
|
186
219
|
addCodexHookTrust,
|
|
187
220
|
claudeSettings,
|
|
188
221
|
claudeJson,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasGeminiHooks
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OZKRF7MS.js";
|
|
4
4
|
import {
|
|
5
5
|
hasCodexHooks
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-PHXEADSL.js";
|
|
7
7
|
|
|
8
8
|
// src/diagnostics/wiring.ts
|
|
9
9
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -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);
|
|
@@ -196,9 +132,10 @@ var readLastGoodMode = (apiKey) => {
|
|
|
196
132
|
return null;
|
|
197
133
|
}
|
|
198
134
|
};
|
|
199
|
-
var writeLastGoodMode = (apiKey, state) => {
|
|
135
|
+
var writeLastGoodMode = (apiKey, state, sessionId) => {
|
|
200
136
|
try {
|
|
201
|
-
|
|
137
|
+
const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
|
|
138
|
+
writeFileSync(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
|
|
202
139
|
} catch {
|
|
203
140
|
}
|
|
204
141
|
};
|
|
@@ -219,8 +156,18 @@ var degradedFallback = (apiKey, authoritative = false) => {
|
|
|
219
156
|
}
|
|
220
157
|
return unknownMode();
|
|
221
158
|
};
|
|
222
|
-
var
|
|
223
|
-
var
|
|
159
|
+
var MODE_REUSE_MS = CACHE_TTL_MS;
|
|
160
|
+
var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
|
|
161
|
+
const last = readLastGoodMode(apiKey);
|
|
162
|
+
if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
|
|
163
|
+
if (Date.now() - last.at >= maxAgeMs) return null;
|
|
164
|
+
return { ...last.state, degraded: false };
|
|
165
|
+
};
|
|
166
|
+
var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0) => {
|
|
167
|
+
if (reuseWithinMs > 0) {
|
|
168
|
+
const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
|
|
169
|
+
if (reusable) return reusable;
|
|
170
|
+
}
|
|
224
171
|
try {
|
|
225
172
|
const baseUrl = getBaseUrl();
|
|
226
173
|
const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
|
|
@@ -231,20 +178,8 @@ var fetchModeState = async (apiKey, sessionId) => {
|
|
|
231
178
|
if (!response.ok) {
|
|
232
179
|
return degradedFallback(apiKey, response.status === 401 || response.status === 403);
|
|
233
180
|
}
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
const state = {
|
|
237
|
-
mode: isApprovalMode(mode) ? mode : null,
|
|
238
|
-
kill: data.kill === true,
|
|
239
|
-
policyVersion: toPolicyVersion(data.policyVersion),
|
|
240
|
-
relayUrl: typeof data.relayUrl === "string" && data.relayUrl.length > 0 ? data.relayUrl : null,
|
|
241
|
-
trainingConsent: data.trainingConsent === true,
|
|
242
|
-
// Validated rather than cast: a malformed contract must read as no contract,
|
|
243
|
-
// never as one that silently matches nothing.
|
|
244
|
-
scope: isScopeContract(data.scope) ? data.scope : null,
|
|
245
|
-
degraded: false
|
|
246
|
-
};
|
|
247
|
-
writeLastGoodMode(apiKey, state);
|
|
181
|
+
const state = modeStateFromResponse(await response.json());
|
|
182
|
+
writeLastGoodMode(apiKey, state, sessionId);
|
|
248
183
|
return state;
|
|
249
184
|
} catch {
|
|
250
185
|
return degradedFallback(apiKey);
|
|
@@ -253,9 +188,11 @@ var fetchModeState = async (apiKey, sessionId) => {
|
|
|
253
188
|
var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
|
|
254
189
|
|
|
255
190
|
// src/api.ts
|
|
191
|
+
import { randomUUID } from "crypto";
|
|
256
192
|
installProxyDispatcher();
|
|
257
193
|
var askUser = async (apiKey, params, timeoutMs = 15e3) => {
|
|
258
|
-
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 });
|
|
259
196
|
if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
|
|
260
197
|
return result;
|
|
261
198
|
};
|
|
@@ -274,7 +211,7 @@ var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
|
|
|
274
211
|
await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: 3, timeoutMs });
|
|
275
212
|
};
|
|
276
213
|
|
|
277
|
-
// src/describe.ts
|
|
214
|
+
// ../hook-mapping/src/describe.ts
|
|
278
215
|
import { isAbsolute, relative } from "path";
|
|
279
216
|
var hookPrefixes = {
|
|
280
217
|
Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
|
|
@@ -443,6 +380,36 @@ var deriveBlocker = (mode, reason) => {
|
|
|
443
380
|
return text.slice(0, DECISION_LINE_MAX);
|
|
444
381
|
};
|
|
445
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
|
+
|
|
446
413
|
// src/pending.ts
|
|
447
414
|
import { join as join2 } from "path";
|
|
448
415
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -491,7 +458,7 @@ var removePendingSession = (sessionId) => {
|
|
|
491
458
|
|
|
492
459
|
// src/repo.ts
|
|
493
460
|
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
494
|
-
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";
|
|
495
462
|
var MAX_PARENT_WALK = 64;
|
|
496
463
|
var GIT_FILE_PREFIX = "gitdir:";
|
|
497
464
|
var findGitDir = (startDir) => {
|
|
@@ -550,9 +517,9 @@ var deriveRepoKey = (cwd) => {
|
|
|
550
517
|
const normalized = remote ? normalizeRepoRemote(remote) : void 0;
|
|
551
518
|
if (normalized) return normalized;
|
|
552
519
|
const root = gitDir.endsWith(".git") ? dirname(gitDir) : dir;
|
|
553
|
-
return localRepoKey(
|
|
520
|
+
return localRepoKey(basename2(root));
|
|
554
521
|
}
|
|
555
|
-
return localRepoKey(
|
|
522
|
+
return localRepoKey(basename2(resolve(dir)));
|
|
556
523
|
} catch {
|
|
557
524
|
return void 0;
|
|
558
525
|
}
|
|
@@ -857,7 +824,7 @@ var throttlePass = (key, windowMs) => {
|
|
|
857
824
|
};
|
|
858
825
|
|
|
859
826
|
// src/events.ts
|
|
860
|
-
import { basename as
|
|
827
|
+
import { basename as basename3, join as join6 } from "path";
|
|
861
828
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
862
829
|
import { tmpdir as tmpdir5 } from "os";
|
|
863
830
|
var INTENT_DIR = join6(tmpdir5(), "pushary-intent");
|
|
@@ -1046,14 +1013,14 @@ var reportEvent = async (event, options = {}) => {
|
|
|
1046
1013
|
var NON_GATED_MIRROR_TOOLS = /* @__PURE__ */ new Set(["TodoWrite"]);
|
|
1047
1014
|
var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1048
1015
|
try {
|
|
1049
|
-
const projectName =
|
|
1016
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1050
1017
|
const action = describeToolCall(input.tool_name, input.tool_input, "event");
|
|
1051
1018
|
const lookup = toPolicyLookup(input.tool_name, input.tool_input);
|
|
1052
1019
|
const toolResult = input.tool_response ?? input.tool_result;
|
|
1053
1020
|
const isError = isToolResultError(toolResult);
|
|
1054
1021
|
const receiptsEnabled = process.env.PUSHARY_RECEIPTS !== "off";
|
|
1055
1022
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1056
|
-
const liveMode = await fetchModeState(getApiKey(), input.session_id);
|
|
1023
|
+
const liveMode = await fetchModeState(getApiKey(), input.session_id, MODE_REUSE_MS);
|
|
1057
1024
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1058
1025
|
let additionalContext;
|
|
1059
1026
|
if (late.length > 0 && agent.type === CLAUDE_CODE_AGENT.type) {
|
|
@@ -1098,13 +1065,12 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1098
1065
|
return void 0;
|
|
1099
1066
|
}
|
|
1100
1067
|
};
|
|
1101
|
-
var TASK_TITLE_MAX_LENGTH = 120;
|
|
1102
1068
|
var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1103
1069
|
try {
|
|
1104
|
-
const projectName =
|
|
1070
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1105
1071
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1106
1072
|
const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
|
|
1107
|
-
const taskTitle = titlesEnabled ? input.prompt
|
|
1073
|
+
const taskTitle = titlesEnabled ? taskTitleFrom(input.prompt) : void 0;
|
|
1108
1074
|
if (input.prompt) saveLastPrompt(sessionKey, input.prompt);
|
|
1109
1075
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1110
1076
|
let additionalContext;
|
|
@@ -1129,24 +1095,9 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1129
1095
|
return void 0;
|
|
1130
1096
|
}
|
|
1131
1097
|
};
|
|
1132
|
-
var STOP_SUMMARY_MAX_LENGTH = 200;
|
|
1133
|
-
var summarizeFinalMessage = (message) => {
|
|
1134
|
-
if (typeof message !== "string") return "Session ended";
|
|
1135
|
-
const collapsed = message.replace(/\s+/g, " ").trim();
|
|
1136
|
-
if (!collapsed) return "Session ended";
|
|
1137
|
-
return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
|
|
1138
|
-
};
|
|
1139
|
-
var countArray = (value) => Array.isArray(value) ? value.length : 0;
|
|
1140
|
-
var summarizeStop = (input) => {
|
|
1141
|
-
const running = countArray(input.background_tasks);
|
|
1142
|
-
if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
|
|
1143
|
-
const crons = countArray(input.session_crons);
|
|
1144
|
-
if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
|
|
1145
|
-
return summarizeFinalMessage(input.last_assistant_message);
|
|
1146
|
-
};
|
|
1147
1098
|
var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1148
1099
|
try {
|
|
1149
|
-
const projectName =
|
|
1100
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1150
1101
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1151
1102
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1152
1103
|
const tasks = [
|
|
@@ -1182,8 +1133,8 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1182
1133
|
};
|
|
1183
1134
|
var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1184
1135
|
try {
|
|
1185
|
-
const projectName =
|
|
1186
|
-
const action = input.source
|
|
1136
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1137
|
+
const action = sessionStartAction(input.source);
|
|
1187
1138
|
await reportEvent({
|
|
1188
1139
|
event: "session_start",
|
|
1189
1140
|
agentType: agent.type,
|
|
@@ -1194,18 +1145,10 @@ var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1194
1145
|
} catch {
|
|
1195
1146
|
}
|
|
1196
1147
|
};
|
|
1197
|
-
var SESSION_END_ACTIONS = {
|
|
1198
|
-
clear: "Session cleared",
|
|
1199
|
-
logout: "Logged out",
|
|
1200
|
-
resume: "Session suspended",
|
|
1201
|
-
prompt_input_exit: "Session ended",
|
|
1202
|
-
bypass_permissions_disabled: "Session ended",
|
|
1203
|
-
other: "Session ended"
|
|
1204
|
-
};
|
|
1205
1148
|
var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1206
1149
|
try {
|
|
1207
|
-
const projectName =
|
|
1208
|
-
const action =
|
|
1150
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1151
|
+
const action = sessionEndAction(input.reason);
|
|
1209
1152
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1210
1153
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
1211
1154
|
if (late.length > 0) {
|
|
@@ -1228,7 +1171,7 @@ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set(["idle_prompt", "agent_nee
|
|
|
1228
1171
|
var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
|
|
1229
1172
|
var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1230
1173
|
try {
|
|
1231
|
-
const projectName =
|
|
1174
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1232
1175
|
const notifType = input.notification_type ?? input.type ?? "";
|
|
1233
1176
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1234
1177
|
if (NOTIFICATION_PUSH_TYPES.has(notifType)) {
|
|
@@ -1239,7 +1182,7 @@ var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1239
1182
|
} catch {
|
|
1240
1183
|
return;
|
|
1241
1184
|
}
|
|
1242
|
-
const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
|
|
1185
|
+
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1243
1186
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1244
1187
|
await sendNotification(apiKey, {
|
|
1245
1188
|
title: `${agent.label} is waiting`,
|
|
@@ -1301,8 +1244,8 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1301
1244
|
} catch {
|
|
1302
1245
|
return;
|
|
1303
1246
|
}
|
|
1304
|
-
const projectName =
|
|
1305
|
-
const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
|
|
1247
|
+
const projectName = basename3(input.cwd ?? process.cwd());
|
|
1248
|
+
const mode = await fetchModeState(apiKey, input.session_id, MODE_REUSE_MS).catch(() => null);
|
|
1306
1249
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1307
1250
|
const reason = describeStopFailure(input.error);
|
|
1308
1251
|
await sendNotification(apiKey, {
|
|
@@ -1320,10 +1263,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1320
1263
|
export {
|
|
1321
1264
|
policyRequestFor,
|
|
1322
1265
|
getPolicy,
|
|
1323
|
-
resolveAutoResolveOrigin,
|
|
1324
|
-
resolvePolicy,
|
|
1325
|
-
resolvePolicyAcross,
|
|
1326
|
-
isModeStateDegraded,
|
|
1327
1266
|
fetchModeState,
|
|
1328
1267
|
fetchModeOverride,
|
|
1329
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,26 +21,9 @@ 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
|
|
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
|
-
}
|
|
42
|
-
declare const fetchModeState: (apiKey: string, sessionId?: string) => Promise<ModeState>;
|
|
25
|
+
|
|
26
|
+
declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
|
|
43
27
|
declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
|
|
44
28
|
|
|
45
29
|
interface UsageReport {
|
|
@@ -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,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-LB6RSCLL.js";
|
|
4
|
+
import "../chunk-GM54KC62.js";
|
|
5
|
+
import "../chunk-HSGEEBBL.js";
|
|
6
|
+
import "../chunk-VT4IVERX.js";
|
|
7
|
+
import "../chunk-VGADRFG6.js";
|
|
8
|
+
import "../chunk-YHG74UFF.js";
|
|
7
9
|
import {
|
|
8
10
|
askUser,
|
|
9
11
|
cancelQuestion,
|
|
@@ -14,20 +16,21 @@ import {
|
|
|
14
16
|
handlePostToolUse,
|
|
15
17
|
handleStop,
|
|
16
18
|
reportEvent,
|
|
17
|
-
resolvePolicy,
|
|
18
19
|
waitForAnswer
|
|
19
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-Y7BALRVB.js";
|
|
20
21
|
import "../chunk-RN3NOEJF.js";
|
|
21
22
|
import "../chunk-BSZYIAZL.js";
|
|
22
23
|
import "../chunk-SAF6HGAA.js";
|
|
23
|
-
import "../chunk-
|
|
24
|
+
import "../chunk-ES3FAX5X.js";
|
|
24
25
|
import "../chunk-7QLSKOSU.js";
|
|
25
26
|
import "../chunk-KZERVKTD.js";
|
|
26
27
|
import {
|
|
27
28
|
getApiKey,
|
|
28
29
|
getBaseUrl
|
|
29
30
|
} from "../chunk-2UMNXADU.js";
|
|
30
|
-
import
|
|
31
|
+
import {
|
|
32
|
+
resolvePolicy
|
|
33
|
+
} from "../chunk-G3LXA2ZP.js";
|
|
31
34
|
export {
|
|
32
35
|
askUser,
|
|
33
36
|
cancelQuestion,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
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",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"pushary-suggestions": "./dist/bin/pushary-suggestions.js",
|
|
69
69
|
"pushary-upgrade": "./dist/bin/pushary-upgrade.js",
|
|
70
70
|
"pushary-bell": "./dist/bin/pushary-bell.js",
|
|
71
|
-
"pushary-bell-hook": "./dist/bin/pushary-bell-hook.js"
|
|
71
|
+
"pushary-bell-hook": "./dist/bin/pushary-bell-hook.js",
|
|
72
|
+
"pushary-disconnect": "./dist/bin/pushary-disconnect.js"
|
|
72
73
|
},
|
|
73
74
|
"files": [
|
|
74
75
|
"dist",
|
|
@@ -79,7 +80,8 @@
|
|
|
79
80
|
"build": "node scripts/bundle-plugin.mjs && tsup",
|
|
80
81
|
"dev": "tsup --watch",
|
|
81
82
|
"test": "node ../../scripts/ci/run-tests.mjs src bin",
|
|
82
|
-
"typecheck": "tsc --noEmit"
|
|
83
|
+
"typecheck": "tsc --noEmit",
|
|
84
|
+
"gen:hooks-spec": "bun scripts/generate-hooks-spec.ts"
|
|
83
85
|
},
|
|
84
86
|
"dependencies": {
|
|
85
87
|
"@inquirer/prompts": "^8.4.2",
|
|
@@ -90,6 +92,7 @@
|
|
|
90
92
|
},
|
|
91
93
|
"devDependencies": {
|
|
92
94
|
"@pushary/contracts": "workspace:*",
|
|
95
|
+
"@pushary/hook-mapping": "workspace:*",
|
|
93
96
|
"tsup": "^8.0.0",
|
|
94
97
|
"typescript": "^5.0.0"
|
|
95
98
|
}
|