@pushary/agent-hooks 0.54.1 → 0.56.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/dist/bin/pushary-claude.js +6 -4
- package/dist/bin/pushary-clean.js +3 -3
- package/dist/bin/pushary-codex-hook.js +3 -3
- package/dist/bin/pushary-codex.js +2 -2
- package/dist/bin/pushary-doctor.js +3 -3
- package/dist/bin/pushary-gemini-hook.js +3 -3
- package/dist/bin/pushary-hook.js +5 -5
- package/dist/bin/pushary-notification-hook.js +2 -2
- package/dist/bin/pushary-permission-denied-hook.js +5 -5
- package/dist/bin/pushary-permission-hook.js +5 -5
- package/dist/bin/pushary-post-hook.js +2 -2
- package/dist/bin/pushary-prompt-hook.js +2 -2
- package/dist/bin/pushary-session-end-hook.js +2 -2
- package/dist/bin/pushary-session-start-hook.js +2 -2
- package/dist/bin/pushary-setup.js +4 -4
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-stop-hook.js +2 -2
- package/dist/bin/pushary-stopfailure-hook.js +2 -2
- package/dist/bin/pushary-upgrade.js +2 -2
- package/dist/{chunk-SWNBIHDX.js → chunk-3EOK3M44.js} +148 -49
- package/dist/{chunk-WYQSLB7K.js → chunk-5TECIV6T.js} +1 -1
- package/dist/{chunk-SVRDT5PP.js → chunk-GQPIOJA3.js} +9 -8
- package/dist/{chunk-PZWZ77QH.js → chunk-GSERIC6Q.js} +1 -1
- package/dist/{chunk-T2EEBR2D.js → chunk-QYQO6RHJ.js} +1 -1
- package/dist/{chunk-SY3NBF67.js → chunk-SYAAGYLZ.js} +158 -5
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +5 -5
- package/package.json +2 -2
|
@@ -13,17 +13,18 @@ import {
|
|
|
13
13
|
describeToolCall,
|
|
14
14
|
fetchModeState,
|
|
15
15
|
getPolicy,
|
|
16
|
+
repoKeyFor,
|
|
16
17
|
reportEvent,
|
|
17
18
|
resolvePolicy,
|
|
18
19
|
waitForAnswer
|
|
19
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-3EOK3M44.js";
|
|
20
21
|
import {
|
|
21
22
|
getMachineId
|
|
22
23
|
} from "../chunk-RN3NOEJF.js";
|
|
23
24
|
import "../chunk-DWED7BS3.js";
|
|
24
25
|
import {
|
|
25
26
|
redactSecrets
|
|
26
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-SYAAGYLZ.js";
|
|
27
28
|
import {
|
|
28
29
|
getApiKey,
|
|
29
30
|
getBaseUrl
|
|
@@ -528,8 +529,8 @@ var createRemoteApprover = (deps) => {
|
|
|
528
529
|
const sessionId = deps.getSessionId?.();
|
|
529
530
|
const modeState = await fetchModeState2(deps.apiKey, sessionId);
|
|
530
531
|
if (modeState.kill) return deny("Stopped by user \u2014 this agent was halted from Pushary");
|
|
531
|
-
const policyConfig = await getPolicy2(deps.apiKey, modeState.policyVersion, "claude_code");
|
|
532
|
-
const policy = resolvePolicy2(policyConfig, toolName, modeState.mode, input);
|
|
532
|
+
const policyConfig = await getPolicy2(deps.apiKey, modeState.policyVersion, "claude_code", true);
|
|
533
|
+
const policy = resolvePolicy2(policyConfig, toolName, modeState.mode, input, deps.cwd, repoKeyFor(deps.cwd));
|
|
533
534
|
if (policy.timeoutSeconds === 0 && policy.timeoutAction === "approve") return allow(input);
|
|
534
535
|
if (policy.timeoutSeconds === 0 && policy.timeoutAction === "deny") {
|
|
535
536
|
return deny(`Denied by policy for ${policy.tool}`);
|
|
@@ -1341,6 +1342,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1341
1342
|
apiKey,
|
|
1342
1343
|
machineId,
|
|
1343
1344
|
projectName,
|
|
1345
|
+
cwd: process.cwd(),
|
|
1344
1346
|
getSessionId: () => sessionId
|
|
1345
1347
|
});
|
|
1346
1348
|
const initialModeState = await fetchModeState(apiKey).catch(() => null);
|
|
@@ -6,15 +6,15 @@ import {
|
|
|
6
6
|
removeCodexHooks,
|
|
7
7
|
removeGeminiSettings,
|
|
8
8
|
removeInstructionBlock
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-GSERIC6Q.js";
|
|
10
10
|
import {
|
|
11
11
|
execNpm
|
|
12
12
|
} from "../chunk-J7JWI3KU.js";
|
|
13
13
|
import {
|
|
14
14
|
removeClaudeMcpServers,
|
|
15
15
|
removePusharySettings
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import "../chunk-
|
|
16
|
+
} from "../chunk-QYQO6RHJ.js";
|
|
17
|
+
import "../chunk-SYAAGYLZ.js";
|
|
18
18
|
|
|
19
19
|
// bin/pushary-clean.ts
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync } from "fs";
|
|
@@ -31,21 +31,21 @@ import {
|
|
|
31
31
|
toCodexWire,
|
|
32
32
|
toPolicyLookup,
|
|
33
33
|
waitForAnswer
|
|
34
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-3EOK3M44.js";
|
|
35
35
|
import {
|
|
36
36
|
getMachineId
|
|
37
37
|
} from "../chunk-RN3NOEJF.js";
|
|
38
38
|
import {
|
|
39
39
|
isGatingMoment,
|
|
40
40
|
recordKeylessMoment
|
|
41
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-5TECIV6T.js";
|
|
42
42
|
import "../chunk-DWED7BS3.js";
|
|
43
43
|
import {
|
|
44
44
|
DECISION_LINE_MAX,
|
|
45
45
|
effectiveWaitSeconds,
|
|
46
46
|
hookWaitClamped,
|
|
47
47
|
hookWaitDeadline
|
|
48
|
-
} from "../chunk-
|
|
48
|
+
} from "../chunk-SYAAGYLZ.js";
|
|
49
49
|
import {
|
|
50
50
|
getApiKey
|
|
51
51
|
} from "../chunk-NKXSILEW.js";
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
askUser,
|
|
4
4
|
reportEvent,
|
|
5
5
|
waitForAnswer
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-3EOK3M44.js";
|
|
7
7
|
import {
|
|
8
8
|
getMachineId
|
|
9
9
|
} from "../chunk-RN3NOEJF.js";
|
|
10
10
|
import "../chunk-DWED7BS3.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-SYAAGYLZ.js";
|
|
12
12
|
import {
|
|
13
13
|
getApiKey
|
|
14
14
|
} from "../chunk-NKXSILEW.js";
|
|
@@ -11,18 +11,18 @@ import {
|
|
|
11
11
|
missingGeminiHookEvents,
|
|
12
12
|
readCodexMcpAuth,
|
|
13
13
|
untrustedCodexHookEvents
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-GSERIC6Q.js";
|
|
15
15
|
import {
|
|
16
16
|
execNpm
|
|
17
17
|
} from "../chunk-J7JWI3KU.js";
|
|
18
18
|
import {
|
|
19
19
|
readLedgerSummary
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-5TECIV6T.js";
|
|
21
21
|
import {
|
|
22
22
|
callMcpTool,
|
|
23
23
|
sendMcpRequest
|
|
24
24
|
} from "../chunk-DWED7BS3.js";
|
|
25
|
-
import "../chunk-
|
|
25
|
+
import "../chunk-SYAAGYLZ.js";
|
|
26
26
|
import {
|
|
27
27
|
getBaseUrl
|
|
28
28
|
} from "../chunk-NKXSILEW.js";
|
|
@@ -23,21 +23,21 @@ import {
|
|
|
23
23
|
savePendingQuestion,
|
|
24
24
|
sendNotification,
|
|
25
25
|
waitForAnswer
|
|
26
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-3EOK3M44.js";
|
|
27
27
|
import {
|
|
28
28
|
getMachineId
|
|
29
29
|
} from "../chunk-RN3NOEJF.js";
|
|
30
30
|
import {
|
|
31
31
|
isGatingMoment,
|
|
32
32
|
recordKeylessMoment
|
|
33
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-5TECIV6T.js";
|
|
34
34
|
import "../chunk-DWED7BS3.js";
|
|
35
35
|
import {
|
|
36
36
|
DECISION_LINE_MAX,
|
|
37
37
|
effectiveWaitSeconds,
|
|
38
38
|
hookWaitClamped,
|
|
39
39
|
hookWaitDeadline
|
|
40
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-SYAAGYLZ.js";
|
|
41
41
|
import {
|
|
42
42
|
getApiKey
|
|
43
43
|
} from "../chunk-NKXSILEW.js";
|
package/dist/bin/pushary-hook.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePreToolUse
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-GQPIOJA3.js";
|
|
5
|
+
import "../chunk-QYQO6RHJ.js";
|
|
6
6
|
import "../chunk-YHG74UFF.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-3EOK3M44.js";
|
|
8
8
|
import "../chunk-RN3NOEJF.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-5TECIV6T.js";
|
|
10
10
|
import "../chunk-DWED7BS3.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-SYAAGYLZ.js";
|
|
12
12
|
import "../chunk-NKXSILEW.js";
|
|
13
13
|
|
|
14
14
|
// bin/pushary-hook.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleNotification
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-notification-hook.ts
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePermissionDenied
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-GQPIOJA3.js";
|
|
5
|
+
import "../chunk-QYQO6RHJ.js";
|
|
6
6
|
import "../chunk-YHG74UFF.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-3EOK3M44.js";
|
|
8
8
|
import "../chunk-RN3NOEJF.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-5TECIV6T.js";
|
|
10
10
|
import "../chunk-DWED7BS3.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-SYAAGYLZ.js";
|
|
12
12
|
import "../chunk-NKXSILEW.js";
|
|
13
13
|
|
|
14
14
|
// bin/pushary-permission-denied-hook.ts
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePermissionRequest
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-GQPIOJA3.js";
|
|
5
|
+
import "../chunk-QYQO6RHJ.js";
|
|
6
6
|
import "../chunk-YHG74UFF.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-3EOK3M44.js";
|
|
8
8
|
import "../chunk-RN3NOEJF.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-5TECIV6T.js";
|
|
10
10
|
import "../chunk-DWED7BS3.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-SYAAGYLZ.js";
|
|
12
12
|
import "../chunk-NKXSILEW.js";
|
|
13
13
|
|
|
14
14
|
// bin/pushary-permission-hook.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handlePostToolUse
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-post-hook.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleUserPrompt
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-prompt-hook.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleSessionEnd
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-session-end-hook.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleSessionStart
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-session-start-hook.ts
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
renderAgentInstructions,
|
|
14
14
|
renderProjectAgentInstructions,
|
|
15
15
|
writeInstructionBlock
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-GSERIC6Q.js";
|
|
17
17
|
import {
|
|
18
18
|
execNpm,
|
|
19
19
|
npmErrorMessage,
|
|
@@ -25,15 +25,15 @@ import {
|
|
|
25
25
|
addClaudeMcpServer,
|
|
26
26
|
addPusharyHooks,
|
|
27
27
|
addPusharyToolPermissions
|
|
28
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-QYQO6RHJ.js";
|
|
29
29
|
import {
|
|
30
30
|
reportEvent
|
|
31
|
-
} from "../chunk-
|
|
31
|
+
} from "../chunk-3EOK3M44.js";
|
|
32
32
|
import "../chunk-RN3NOEJF.js";
|
|
33
33
|
import "../chunk-DWED7BS3.js";
|
|
34
34
|
import {
|
|
35
35
|
isValidApiKey
|
|
36
|
-
} from "../chunk-
|
|
36
|
+
} from "../chunk-SYAAGYLZ.js";
|
|
37
37
|
import "../chunk-NKXSILEW.js";
|
|
38
38
|
|
|
39
39
|
// bin/pushary-setup.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleStop
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-stop-hook.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
handleStopFailure
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-3EOK3M44.js";
|
|
5
5
|
import "../chunk-RN3NOEJF.js";
|
|
6
6
|
import "../chunk-DWED7BS3.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-SYAAGYLZ.js";
|
|
8
8
|
import "../chunk-NKXSILEW.js";
|
|
9
9
|
|
|
10
10
|
// bin/pushary-stopfailure-hook.ts
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
addClaudeMcpServer,
|
|
9
9
|
addPusharyHooks,
|
|
10
10
|
addPusharyToolPermissions
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-QYQO6RHJ.js";
|
|
12
|
+
import "../chunk-SYAAGYLZ.js";
|
|
13
13
|
import {
|
|
14
14
|
getApiKey
|
|
15
15
|
} from "../chunk-NKXSILEW.js";
|
|
@@ -9,14 +9,17 @@ import {
|
|
|
9
9
|
ACTION_BODY_MAX,
|
|
10
10
|
DECISION_LINE_MAX,
|
|
11
11
|
classifyDecisionRisk,
|
|
12
|
-
extractPolicyArg,
|
|
13
12
|
isApprovalMode,
|
|
13
|
+
isRepoKeyMatch,
|
|
14
14
|
isSafeReadOnlyCommand,
|
|
15
|
+
localRepoKey,
|
|
15
16
|
matchRankWeight,
|
|
16
|
-
|
|
17
|
+
matchToolPatternAny,
|
|
18
|
+
normalizeRepoRemote,
|
|
19
|
+
policyArgForms,
|
|
17
20
|
redactSecrets,
|
|
18
21
|
redactSecretsDeep
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-SYAAGYLZ.js";
|
|
20
23
|
import {
|
|
21
24
|
getApiKey,
|
|
22
25
|
getBaseUrl
|
|
@@ -47,14 +50,19 @@ var isWaitForAnswerResponse = (data) => {
|
|
|
47
50
|
|
|
48
51
|
// src/policy.ts
|
|
49
52
|
var CACHE_TTL_MS = 60 * 1e3;
|
|
50
|
-
var
|
|
51
|
-
const
|
|
53
|
+
var policyCacheFile = (apiKey, agent, repoAware = false) => {
|
|
54
|
+
const parts = [apiKey, agent, repoAware ? "repoAware" : void 0].filter(Boolean);
|
|
55
|
+
const hash = createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
|
|
52
56
|
return join(tmpdir(), `pushary-policy-${hash}.json`);
|
|
53
57
|
};
|
|
54
|
-
var fetchPolicy = async (apiKey, agent) => {
|
|
58
|
+
var fetchPolicy = async (apiKey, agent, repoAware = false) => {
|
|
55
59
|
return withRetry(async () => {
|
|
56
60
|
const baseUrl = getBaseUrl();
|
|
57
|
-
const
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
if (agent) params.set("agent", agent);
|
|
63
|
+
if (repoAware) params.set("repoAware", "1");
|
|
64
|
+
const query = params.toString();
|
|
65
|
+
const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
|
|
58
66
|
const response = await fetch(url, {
|
|
59
67
|
headers: { "Authorization": `Bearer ${apiKey}` },
|
|
60
68
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -67,8 +75,8 @@ var fetchPolicy = async (apiKey, agent) => {
|
|
|
67
75
|
return raw;
|
|
68
76
|
}, { maxAttempts: 2 });
|
|
69
77
|
};
|
|
70
|
-
var getPolicy = async (apiKey, expectedVersion, agent) => {
|
|
71
|
-
const path =
|
|
78
|
+
var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
|
|
79
|
+
const path = policyCacheFile(apiKey, agent, repoAware);
|
|
72
80
|
let staleCache = null;
|
|
73
81
|
if (existsSync(path)) {
|
|
74
82
|
try {
|
|
@@ -85,7 +93,7 @@ var getPolicy = async (apiKey, expectedVersion, agent) => {
|
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
try {
|
|
88
|
-
const policy = await fetchPolicy(apiKey, agent);
|
|
96
|
+
const policy = await fetchPolicy(apiKey, agent, repoAware);
|
|
89
97
|
try {
|
|
90
98
|
writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null }), "utf-8");
|
|
91
99
|
} catch {
|
|
@@ -96,18 +104,23 @@ var getPolicy = async (apiKey, expectedVersion, agent) => {
|
|
|
96
104
|
throw new Error("Failed to fetch policy and no cached policy available");
|
|
97
105
|
}
|
|
98
106
|
};
|
|
99
|
-
var findBestMatch = (policies, toolName,
|
|
107
|
+
var findBestMatch = (policies, toolName, args, repoKey) => {
|
|
100
108
|
let best;
|
|
101
109
|
let bestWeight = 0;
|
|
110
|
+
let bestScoped = -1;
|
|
102
111
|
let bestLength = -1;
|
|
103
112
|
for (const candidate of policies) {
|
|
104
|
-
|
|
113
|
+
if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
|
|
114
|
+
const rank = matchToolPatternAny(candidate.tool, toolName, args);
|
|
105
115
|
if (rank === "none") continue;
|
|
106
116
|
const weight = matchRankWeight(rank);
|
|
117
|
+
const scoped = candidate.repoKey ? 1 : 0;
|
|
107
118
|
const length = rank === "prefix" ? candidate.tool.length : -1;
|
|
108
|
-
|
|
119
|
+
const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
|
|
120
|
+
if (better) {
|
|
109
121
|
best = { policy: candidate, rank };
|
|
110
122
|
bestWeight = weight;
|
|
123
|
+
bestScoped = scoped;
|
|
111
124
|
bestLength = length;
|
|
112
125
|
}
|
|
113
126
|
}
|
|
@@ -120,18 +133,20 @@ var autoApprove = (tool) => ({
|
|
|
120
133
|
mode: "terminal_only",
|
|
121
134
|
pushFirstSeconds: 0
|
|
122
135
|
});
|
|
123
|
-
var resolveAutoResolveOrigin = (config, toolName, toolInput) => {
|
|
124
|
-
const
|
|
125
|
-
const
|
|
136
|
+
var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey) => {
|
|
137
|
+
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
138
|
+
const arg = args[0];
|
|
139
|
+
const match = findBestMatch(config.policies, toolName, args, repoKey);
|
|
126
140
|
const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
|
|
127
141
|
if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
|
|
128
142
|
return "safe_readonly";
|
|
129
143
|
}
|
|
130
144
|
return "policy_timeout";
|
|
131
145
|
};
|
|
132
|
-
var resolvePolicy = (config, toolName, modeOverride, toolInput) => {
|
|
133
|
-
const
|
|
134
|
-
const
|
|
146
|
+
var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey) => {
|
|
147
|
+
const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
|
|
148
|
+
const arg = args[0];
|
|
149
|
+
const match = findBestMatch(config.policies, toolName, args, repoKey);
|
|
135
150
|
let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
|
|
136
151
|
tool: toolName,
|
|
137
152
|
timeoutSeconds: config.defaultTimeoutSeconds,
|
|
@@ -639,17 +654,101 @@ var permissionTimeoutDecision = (timeoutAction) => {
|
|
|
639
654
|
};
|
|
640
655
|
var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
|
|
641
656
|
|
|
657
|
+
// src/repo.ts
|
|
658
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
659
|
+
import { basename, dirname, isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "path";
|
|
660
|
+
var MAX_PARENT_WALK = 64;
|
|
661
|
+
var GIT_FILE_PREFIX = "gitdir:";
|
|
662
|
+
var findGitDir = (startDir) => {
|
|
663
|
+
let current = resolve2(startDir);
|
|
664
|
+
for (let depth = 0; depth < MAX_PARENT_WALK; depth += 1) {
|
|
665
|
+
const candidate = join4(current, ".git");
|
|
666
|
+
try {
|
|
667
|
+
if (existsSync3(candidate)) {
|
|
668
|
+
if (statSync3(candidate).isDirectory()) return candidate;
|
|
669
|
+
const pointer = readFileSync3(candidate, "utf-8").trim();
|
|
670
|
+
if (pointer.startsWith(GIT_FILE_PREFIX)) {
|
|
671
|
+
const target = pointer.slice(GIT_FILE_PREFIX.length).trim();
|
|
672
|
+
return isAbsolute2(target) ? target : resolve2(current, target);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
const parent = dirname(current);
|
|
678
|
+
if (parent === current) return void 0;
|
|
679
|
+
current = parent;
|
|
680
|
+
}
|
|
681
|
+
return void 0;
|
|
682
|
+
};
|
|
683
|
+
var configPathFor = (gitDir) => {
|
|
684
|
+
const index = gitDir.replace(/\\/g, "/").indexOf("/worktrees/");
|
|
685
|
+
return index === -1 ? join4(gitDir, "config") : join4(gitDir.slice(0, index), "config");
|
|
686
|
+
};
|
|
687
|
+
var ORIGIN_SECTION = /^\[remote "origin"\]/;
|
|
688
|
+
var SECTION_HEADER = /^\[/;
|
|
689
|
+
var URL_LINE = /^url\s*=\s*(.+)$/;
|
|
690
|
+
var readOriginRemote = (gitDir) => {
|
|
691
|
+
try {
|
|
692
|
+
const configPath = configPathFor(gitDir);
|
|
693
|
+
if (!existsSync3(configPath)) return void 0;
|
|
694
|
+
let inOrigin = false;
|
|
695
|
+
for (const rawLine of readFileSync3(configPath, "utf-8").split("\n")) {
|
|
696
|
+
const line = rawLine.trim();
|
|
697
|
+
if (SECTION_HEADER.test(line)) {
|
|
698
|
+
inOrigin = ORIGIN_SECTION.test(line);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (!inOrigin) continue;
|
|
702
|
+
const url = URL_LINE.exec(line);
|
|
703
|
+
if (url) return url[1]?.trim();
|
|
704
|
+
}
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
return void 0;
|
|
708
|
+
};
|
|
709
|
+
var deriveRepoKey = (cwd) => {
|
|
710
|
+
const dir = cwd && cwd.length > 0 ? cwd : process.cwd();
|
|
711
|
+
try {
|
|
712
|
+
const gitDir = findGitDir(dir);
|
|
713
|
+
if (gitDir) {
|
|
714
|
+
const remote = readOriginRemote(gitDir);
|
|
715
|
+
const normalized = remote ? normalizeRepoRemote(remote) : void 0;
|
|
716
|
+
if (normalized) return normalized;
|
|
717
|
+
const root = gitDir.endsWith(".git") ? dirname(gitDir) : dir;
|
|
718
|
+
return localRepoKey(basename(root));
|
|
719
|
+
}
|
|
720
|
+
return localRepoKey(basename(resolve2(dir)));
|
|
721
|
+
} catch {
|
|
722
|
+
return void 0;
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
var repoKeyCache = /* @__PURE__ */ new Map();
|
|
726
|
+
var REPO_KEY_ENV = "PUSHARY_REPO_KEY";
|
|
727
|
+
var repoKeyFor = (cwd) => {
|
|
728
|
+
const override = process.env[REPO_KEY_ENV];
|
|
729
|
+
if (typeof override === "string") {
|
|
730
|
+
const trimmed = override.trim();
|
|
731
|
+
if (trimmed.toLowerCase() === "off") return void 0;
|
|
732
|
+
if (trimmed.length > 0) return trimmed.toLowerCase();
|
|
733
|
+
}
|
|
734
|
+
const dir = cwd && cwd.length > 0 ? cwd : process.cwd();
|
|
735
|
+
if (repoKeyCache.has(dir)) return repoKeyCache.get(dir);
|
|
736
|
+
const derived = deriveRepoKey(dir);
|
|
737
|
+
repoKeyCache.set(dir, derived);
|
|
738
|
+
return derived;
|
|
739
|
+
};
|
|
740
|
+
|
|
642
741
|
// src/throttle.ts
|
|
643
|
-
import { join as
|
|
742
|
+
import { join as join5 } from "path";
|
|
644
743
|
import { tmpdir as tmpdir4 } from "os";
|
|
645
744
|
import { createHash as createHash2 } from "crypto";
|
|
646
|
-
import { existsSync as
|
|
647
|
-
var THROTTLE_DIR =
|
|
648
|
-
var markerPath = (key) =>
|
|
745
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
746
|
+
var THROTTLE_DIR = join5(tmpdir4(), "pushary-throttle");
|
|
747
|
+
var markerPath = (key) => join5(THROTTLE_DIR, createHash2("sha256").update(key).digest("hex").slice(0, 16));
|
|
649
748
|
var throttlePass = (key, windowMs) => {
|
|
650
749
|
try {
|
|
651
750
|
const path = markerPath(key);
|
|
652
|
-
if (
|
|
751
|
+
if (existsSync4(path) && Date.now() - statSync4(path).mtimeMs < windowMs) return false;
|
|
653
752
|
mkdirSync3(THROTTLE_DIR, { recursive: true });
|
|
654
753
|
writeFileSync4(path, "", "utf-8");
|
|
655
754
|
return true;
|
|
@@ -659,15 +758,14 @@ var throttlePass = (key, windowMs) => {
|
|
|
659
758
|
};
|
|
660
759
|
|
|
661
760
|
// src/events.ts
|
|
662
|
-
import { basename, join as
|
|
663
|
-
import {
|
|
664
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
761
|
+
import { basename as basename2, join as join6 } from "path";
|
|
762
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
665
763
|
import { tmpdir as tmpdir5 } from "os";
|
|
666
|
-
var INTENT_DIR =
|
|
764
|
+
var INTENT_DIR = join6(tmpdir5(), "pushary-intent");
|
|
667
765
|
var INTENT_MAX_BYTES = 2048;
|
|
668
766
|
var INTENT_GRACE_MS = 10 * 60 * 1e3;
|
|
669
767
|
var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
670
|
-
var intentFile = (sessionId) =>
|
|
768
|
+
var intentFile = (sessionId) => join6(INTENT_DIR, sanitizeSession(sessionId));
|
|
671
769
|
var saveLastPrompt = (sessionId, prompt) => {
|
|
672
770
|
try {
|
|
673
771
|
const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
|
|
@@ -680,8 +778,8 @@ var saveLastPrompt = (sessionId, prompt) => {
|
|
|
680
778
|
var readLastPrompt = (sessionId) => {
|
|
681
779
|
try {
|
|
682
780
|
const path = intentFile(sessionId);
|
|
683
|
-
if (isDefaultSession(sessionId) &&
|
|
684
|
-
const value =
|
|
781
|
+
if (isDefaultSession(sessionId) && statSync5(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
|
|
782
|
+
const value = readFileSync4(path, "utf-8").trim();
|
|
685
783
|
return value.length > 0 ? value : void 0;
|
|
686
784
|
} catch {
|
|
687
785
|
return void 0;
|
|
@@ -737,20 +835,19 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
|
|
|
737
835
|
var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
|
|
738
836
|
var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
739
837
|
var readFreshCachedPolicy = (apiKey) => {
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const cached = JSON.parse(readFileSync3(path, "utf-8"));
|
|
838
|
+
const path = policyCacheFile(apiKey);
|
|
839
|
+
if (!existsSync5(path)) return null;
|
|
840
|
+
const cached = JSON.parse(readFileSync4(path, "utf-8"));
|
|
744
841
|
if (!isPolicyConfig(cached)) return null;
|
|
745
842
|
if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
|
|
746
843
|
return cached;
|
|
747
844
|
};
|
|
748
|
-
var deriveDecisionSource = (toolName, toolInput, liveMode) => {
|
|
845
|
+
var deriveDecisionSource = (toolName, toolInput, liveMode, cwd) => {
|
|
749
846
|
try {
|
|
750
847
|
if (liveMode.kill) return "terminal";
|
|
751
848
|
const policy = readFreshCachedPolicy(getApiKey());
|
|
752
849
|
if (!policy) return void 0;
|
|
753
|
-
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput);
|
|
850
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
|
|
754
851
|
if (resolved.timeoutSeconds === 0 && resolved.timeoutAction === "approve") return "policy_auto";
|
|
755
852
|
if (resolved.mode === "push_only" || resolved.mode === "push_first") return "human";
|
|
756
853
|
return "terminal";
|
|
@@ -758,15 +855,15 @@ var deriveDecisionSource = (toolName, toolInput, liveMode) => {
|
|
|
758
855
|
return void 0;
|
|
759
856
|
}
|
|
760
857
|
};
|
|
761
|
-
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode) => {
|
|
858
|
+
var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, cwd) => {
|
|
762
859
|
try {
|
|
763
860
|
if (liveMode.kill) return void 0;
|
|
764
861
|
const policy = readFreshCachedPolicy(getApiKey());
|
|
765
862
|
if (!policy) return void 0;
|
|
766
|
-
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput);
|
|
863
|
+
const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
|
|
767
864
|
if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
|
|
768
865
|
return {
|
|
769
|
-
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput),
|
|
866
|
+
decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd)),
|
|
770
867
|
toolName,
|
|
771
868
|
toolTarget: deriveToolTarget(toolName, toolInput)
|
|
772
869
|
};
|
|
@@ -807,6 +904,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
807
904
|
body: JSON.stringify({
|
|
808
905
|
...event,
|
|
809
906
|
machineId: event.machineId ?? getMachineId(),
|
|
907
|
+
repoKey: event.repoKey ?? repoKeyFor(),
|
|
810
908
|
installMode: detectInstallMode(),
|
|
811
909
|
// Link-back for a phone-spawned session: forward the spawn id the daemon
|
|
812
910
|
// set in the environment so the server can map spawnId -> sessionId.
|
|
@@ -831,7 +929,7 @@ var reportEvent = async (event, options = {}) => {
|
|
|
831
929
|
var NON_GATED_MIRROR_TOOLS = /* @__PURE__ */ new Set(["TodoWrite"]);
|
|
832
930
|
var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
833
931
|
try {
|
|
834
|
-
const projectName =
|
|
932
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
835
933
|
const action = describeToolCall(input.tool_name, input.tool_input, "event");
|
|
836
934
|
const lookup = toPolicyLookup(input.tool_name, input.tool_input);
|
|
837
935
|
const toolResult = input.tool_response ?? input.tool_result;
|
|
@@ -846,8 +944,8 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
846
944
|
for (const l of late) removePendingQuestion(sessionKey, l.correlationId);
|
|
847
945
|
}
|
|
848
946
|
const isMirrorOnly = NON_GATED_MIRROR_TOOLS.has(input.tool_name);
|
|
849
|
-
const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode);
|
|
850
|
-
const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode) : void 0;
|
|
947
|
+
const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, input.cwd);
|
|
948
|
+
const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, input.cwd) : void 0;
|
|
851
949
|
const toolReport = reportEvent({
|
|
852
950
|
event: isError ? "tool_error" : "tool_complete",
|
|
853
951
|
agentType: agent.type,
|
|
@@ -886,7 +984,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
886
984
|
var TASK_TITLE_MAX_LENGTH = 120;
|
|
887
985
|
var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
888
986
|
try {
|
|
889
|
-
const projectName =
|
|
987
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
890
988
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
891
989
|
const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
|
|
892
990
|
const taskTitle = titlesEnabled ? input.prompt?.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
|
|
@@ -931,7 +1029,7 @@ var summarizeStop = (input) => {
|
|
|
931
1029
|
};
|
|
932
1030
|
var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
933
1031
|
try {
|
|
934
|
-
const projectName =
|
|
1032
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
935
1033
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
936
1034
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
937
1035
|
const tasks = [
|
|
@@ -967,7 +1065,7 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
967
1065
|
};
|
|
968
1066
|
var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
969
1067
|
try {
|
|
970
|
-
const projectName =
|
|
1068
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
971
1069
|
const action = input.source === "resume" ? "Session resumed" : "Session started";
|
|
972
1070
|
await reportEvent({
|
|
973
1071
|
event: "session_start",
|
|
@@ -989,7 +1087,7 @@ var SESSION_END_ACTIONS = {
|
|
|
989
1087
|
};
|
|
990
1088
|
var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
991
1089
|
try {
|
|
992
|
-
const projectName =
|
|
1090
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
993
1091
|
const action = typeof input.reason === "string" && SESSION_END_ACTIONS[input.reason] || "Session ended";
|
|
994
1092
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
995
1093
|
const late = await reconcilePendingQuestions(sessionKey);
|
|
@@ -1013,7 +1111,7 @@ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set(["idle_prompt", "agent_nee
|
|
|
1013
1111
|
var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
|
|
1014
1112
|
var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
1015
1113
|
try {
|
|
1016
|
-
const projectName =
|
|
1114
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
1017
1115
|
const notifType = input.notification_type ?? input.type ?? "";
|
|
1018
1116
|
const sessionKey = input.session_id || DEFAULT_SESSION;
|
|
1019
1117
|
if (NOTIFICATION_PUSH_TYPES.has(notifType)) {
|
|
@@ -1083,7 +1181,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
|
|
|
1083
1181
|
} catch {
|
|
1084
1182
|
return;
|
|
1085
1183
|
}
|
|
1086
|
-
const projectName =
|
|
1184
|
+
const projectName = basename2(input.cwd ?? process.cwd());
|
|
1087
1185
|
const mode = await fetchModeState(apiKey, input.session_id).catch(() => null);
|
|
1088
1186
|
if (mode?.kill || mode?.mode === "terminal_only") return;
|
|
1089
1187
|
const reason = describeStopFailure(input.error);
|
|
@@ -1115,6 +1213,7 @@ export {
|
|
|
1115
1213
|
deriveBlocker,
|
|
1116
1214
|
DEFAULT_SESSION,
|
|
1117
1215
|
savePendingQuestion,
|
|
1216
|
+
repoKeyFor,
|
|
1118
1217
|
throttlePass,
|
|
1119
1218
|
readLastUserPrompt,
|
|
1120
1219
|
CODEX_AGENT,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PRETOOLUSE_HANDLED_TOOLS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QYQO6RHJ.js";
|
|
4
4
|
import {
|
|
5
5
|
denyReasonFrom,
|
|
6
6
|
isDeferAnswer
|
|
@@ -18,25 +18,26 @@ import {
|
|
|
18
18
|
getPolicy,
|
|
19
19
|
readLastPrompt,
|
|
20
20
|
readLastUserPrompt,
|
|
21
|
+
repoKeyFor,
|
|
21
22
|
resolvePolicy,
|
|
22
23
|
savePendingQuestion,
|
|
23
24
|
sendNotification,
|
|
24
25
|
throttlePass,
|
|
25
26
|
waitForAnswer
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-3EOK3M44.js";
|
|
27
28
|
import {
|
|
28
29
|
getMachineId
|
|
29
30
|
} from "./chunk-RN3NOEJF.js";
|
|
30
31
|
import {
|
|
31
32
|
isGatingMoment,
|
|
32
33
|
recordKeylessMoment
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-5TECIV6T.js";
|
|
34
35
|
import {
|
|
35
36
|
buildDecisionEpisodeFeatures,
|
|
36
37
|
effectiveWaitSeconds,
|
|
37
38
|
hookWaitClamped,
|
|
38
39
|
hookWaitDeadline
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-SYAAGYLZ.js";
|
|
40
41
|
import {
|
|
41
42
|
getApiKey,
|
|
42
43
|
getBaseUrl
|
|
@@ -385,7 +386,7 @@ var handlePreToolUse = async (input) => {
|
|
|
385
386
|
}
|
|
386
387
|
try {
|
|
387
388
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
388
|
-
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code");
|
|
389
|
+
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
|
|
389
390
|
if (modeState.kill) {
|
|
390
391
|
return deny("Stopped by user, this agent was halted from Pushary");
|
|
391
392
|
}
|
|
@@ -395,7 +396,7 @@ var handlePreToolUse = async (input) => {
|
|
|
395
396
|
if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
|
|
396
397
|
return void 0;
|
|
397
398
|
}
|
|
398
|
-
const toolPolicy = resolvePolicy(policy, input.tool_name, modeState.mode, input.tool_input);
|
|
399
|
+
const toolPolicy = resolvePolicy(policy, input.tool_name, modeState.mode, input.tool_input, input.cwd, repoKeyFor(input.cwd));
|
|
399
400
|
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") {
|
|
400
401
|
return allow();
|
|
401
402
|
}
|
|
@@ -440,9 +441,9 @@ var handlePermissionRequest = async (input) => {
|
|
|
440
441
|
}
|
|
441
442
|
try {
|
|
442
443
|
const modeState = await fetchModeState(apiKey, input.session_id);
|
|
443
|
-
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code");
|
|
444
|
+
const policy = await getPolicy(apiKey, modeState.policyVersion, "claude_code", true);
|
|
444
445
|
if (modeState.kill) return permReqDeny("Stopped by user \u2014 this agent was halted from Pushary");
|
|
445
|
-
const toolPolicy = resolvePolicy(policy, input.tool_name, modeState.mode, input.tool_input);
|
|
446
|
+
const toolPolicy = resolvePolicy(policy, input.tool_name, modeState.mode, input.tool_input, input.cwd, repoKeyFor(input.cwd));
|
|
446
447
|
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") return permReqAllow();
|
|
447
448
|
if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "deny") {
|
|
448
449
|
return permReqDeny(`Denied by policy for ${toolPolicy.tool}`);
|
|
@@ -19,8 +19,112 @@ var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
|
|
|
19
19
|
};
|
|
20
20
|
var hookWaitClamped = (startMs, policyWaitSeconds, agent, nowMs) => startMs + hookMaxWaitSeconds(agent) * 1e3 < nowMs + Math.max(policyWaitSeconds, 0) * 1e3;
|
|
21
21
|
var effectiveWaitSeconds = (timeoutAction, policySeconds, agent) => timeoutAction === "wait" ? hookMaxWaitSeconds(agent) : policySeconds;
|
|
22
|
+
var REPO_KEY_MAX_LENGTH = 200;
|
|
23
|
+
var isRepoKeyMatch = (ruleRepoKey, currentRepoKey) => {
|
|
24
|
+
if (!ruleRepoKey) return true;
|
|
25
|
+
return ruleRepoKey === currentRepoKey;
|
|
26
|
+
};
|
|
27
|
+
var SCP_LIKE_REMOTE = /^[A-Za-z0-9._-]+@([A-Za-z0-9._-]+):(.+)$/;
|
|
28
|
+
var URL_LIKE_REMOTE = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(.+)$/;
|
|
29
|
+
var NUMERIC_PORT = /:\d+$/;
|
|
30
|
+
var stripCredentials = (host) => {
|
|
31
|
+
const at = host.lastIndexOf("@");
|
|
32
|
+
return at === -1 ? host : host.slice(at + 1);
|
|
33
|
+
};
|
|
34
|
+
var normalizeRepoRemote = (remoteUrl) => {
|
|
35
|
+
const raw = remoteUrl.trim();
|
|
36
|
+
if (raw.length === 0) return void 0;
|
|
37
|
+
let hostAndPath;
|
|
38
|
+
const scp = SCP_LIKE_REMOTE.exec(raw);
|
|
39
|
+
if (scp) {
|
|
40
|
+
hostAndPath = `${scp[1]}/${scp[2]}`;
|
|
41
|
+
} else {
|
|
42
|
+
const url = URL_LIKE_REMOTE.exec(raw);
|
|
43
|
+
if (!url) return void 0;
|
|
44
|
+
if (/^file:/i.test(raw)) return void 0;
|
|
45
|
+
hostAndPath = url[1];
|
|
46
|
+
}
|
|
47
|
+
const slash = hostAndPath.indexOf("/");
|
|
48
|
+
if (slash <= 0) return void 0;
|
|
49
|
+
const host = stripCredentials(hostAndPath.slice(0, slash)).replace(NUMERIC_PORT, "");
|
|
50
|
+
const path = hostAndPath.slice(slash + 1).split("/").filter((segment) => segment.length > 0).join("/");
|
|
51
|
+
if (host.length === 0 || path.length === 0) return void 0;
|
|
52
|
+
const key = `${host}/${path}`.replace(/\.git$/i, "").toLowerCase();
|
|
53
|
+
return key.length > REPO_KEY_MAX_LENGTH ? key.slice(0, REPO_KEY_MAX_LENGTH) : key;
|
|
54
|
+
};
|
|
55
|
+
var localRepoKey = (directoryName) => {
|
|
56
|
+
const name = directoryName.trim().replace(/[/\\]+$/, "").toLowerCase();
|
|
57
|
+
if (name.length === 0 || name === "." || name === "..") return void 0;
|
|
58
|
+
const key = `local/${name}`;
|
|
59
|
+
return key.length > REPO_KEY_MAX_LENGTH ? key.slice(0, REPO_KEY_MAX_LENGTH) : key;
|
|
60
|
+
};
|
|
22
61
|
var MATCH_RANKS = ["none", "tool", "prefix", "exact"];
|
|
23
62
|
var matchRankWeight = (rank) => MATCH_RANKS.indexOf(rank);
|
|
63
|
+
var GLOB_MAX_WILDCARDS = 8;
|
|
64
|
+
var GLOB_MAX_ARG_LENGTH = 4096;
|
|
65
|
+
var GLOB_CACHE_MAX = 256;
|
|
66
|
+
var tokenizeGlob = (glob) => {
|
|
67
|
+
let stars = 0;
|
|
68
|
+
for (const char of glob) if (char === "*") stars += 1;
|
|
69
|
+
if (stars > GLOB_MAX_WILDCARDS) return void 0;
|
|
70
|
+
const tokens = [];
|
|
71
|
+
let literal = "";
|
|
72
|
+
const flush = () => {
|
|
73
|
+
if (literal.length > 0) {
|
|
74
|
+
tokens.push({ kind: "literal", value: literal });
|
|
75
|
+
literal = "";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
79
|
+
if (glob[i] !== "*") {
|
|
80
|
+
literal += glob[i];
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
flush();
|
|
84
|
+
if (glob[i + 1] === "*") {
|
|
85
|
+
tokens.push({ kind: "globstar" });
|
|
86
|
+
i += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
tokens.push({ kind: "star" });
|
|
90
|
+
}
|
|
91
|
+
flush();
|
|
92
|
+
return tokens;
|
|
93
|
+
};
|
|
94
|
+
var SLASH = "/".charCodeAt(0);
|
|
95
|
+
var matchGlobTokens = (tokens, text) => {
|
|
96
|
+
const n = text.length;
|
|
97
|
+
let reachable = new Uint8Array(n + 1);
|
|
98
|
+
reachable[0] = 1;
|
|
99
|
+
for (const token of tokens) {
|
|
100
|
+
const next = new Uint8Array(n + 1);
|
|
101
|
+
if (token.kind === "literal") {
|
|
102
|
+
const { value } = token;
|
|
103
|
+
for (let p = 0; p <= n - value.length; p += 1) {
|
|
104
|
+
if (reachable[p] && text.startsWith(value, p)) next[p + value.length] = 1;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const spansSeparators = token.kind === "globstar";
|
|
108
|
+
let open = 0;
|
|
109
|
+
for (let p = 0; p <= n; p += 1) {
|
|
110
|
+
if (!spansSeparators && p > 0 && text.charCodeAt(p - 1) === SLASH) open = 0;
|
|
111
|
+
if (reachable[p]) open = 1;
|
|
112
|
+
if (open) next[p] = 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
reachable = next;
|
|
116
|
+
}
|
|
117
|
+
return reachable[n] === 1;
|
|
118
|
+
};
|
|
119
|
+
var globCache = /* @__PURE__ */ new Map();
|
|
120
|
+
var compiledGlob = (glob) => {
|
|
121
|
+
const cached = globCache.get(glob);
|
|
122
|
+
if (cached !== void 0 || globCache.has(glob)) return cached;
|
|
123
|
+
const compiled = tokenizeGlob(glob);
|
|
124
|
+
if (globCache.size >= GLOB_CACHE_MAX) globCache.clear();
|
|
125
|
+
globCache.set(glob, compiled);
|
|
126
|
+
return compiled;
|
|
127
|
+
};
|
|
24
128
|
var matchToolPattern = (pattern, toolName, arg) => {
|
|
25
129
|
const open = pattern.indexOf("(");
|
|
26
130
|
if (open === -1 || !pattern.endsWith(")")) {
|
|
@@ -31,19 +135,65 @@ var matchToolPattern = (pattern, toolName, arg) => {
|
|
|
31
135
|
if (inner.endsWith(":*")) {
|
|
32
136
|
return arg.startsWith(inner.slice(0, -2)) ? "prefix" : "none";
|
|
33
137
|
}
|
|
138
|
+
if (inner.includes("*")) {
|
|
139
|
+
if (arg.length > GLOB_MAX_ARG_LENGTH) return "none";
|
|
140
|
+
const compiled = compiledGlob(inner);
|
|
141
|
+
return compiled && matchGlobTokens(compiled, arg) ? "prefix" : "none";
|
|
142
|
+
}
|
|
34
143
|
return arg === inner ? "exact" : "none";
|
|
35
144
|
};
|
|
36
|
-
var
|
|
37
|
-
|
|
145
|
+
var POLICY_PATH_KEYS = {
|
|
146
|
+
Read: "file_path",
|
|
38
147
|
Edit: "file_path",
|
|
39
|
-
Write: "file_path"
|
|
148
|
+
Write: "file_path",
|
|
149
|
+
MultiEdit: "file_path",
|
|
150
|
+
NotebookEdit: "notebook_path",
|
|
151
|
+
LS: "path"
|
|
40
152
|
};
|
|
153
|
+
var POLICY_COMMAND_KEYS = {
|
|
154
|
+
Bash: "command",
|
|
155
|
+
PowerShell: "command",
|
|
156
|
+
Monitor: "command"
|
|
157
|
+
};
|
|
158
|
+
var POLICY_ARG_KEYS = { ...POLICY_COMMAND_KEYS, ...POLICY_PATH_KEYS };
|
|
41
159
|
var extractPolicyArg = (toolName, toolInput) => {
|
|
42
160
|
const key = POLICY_ARG_KEYS[toolName];
|
|
43
161
|
if (!key) return void 0;
|
|
44
162
|
const value = toolInput[key];
|
|
45
163
|
return typeof value === "string" ? value : void 0;
|
|
46
164
|
};
|
|
165
|
+
var normalizeSeparators = (value) => value.replace(/\\/g, "/");
|
|
166
|
+
var isAbsolutePath = (value) => value.startsWith("/") || /^[A-Za-z]:\//.test(value);
|
|
167
|
+
var relativeToCwd = (path, cwd) => {
|
|
168
|
+
const base = normalizeSeparators(cwd).replace(/\/+$/, "");
|
|
169
|
+
if (!base) return void 0;
|
|
170
|
+
const prefix = `${base}/`;
|
|
171
|
+
if (!path.startsWith(prefix)) return void 0;
|
|
172
|
+
const rest = path.slice(prefix.length);
|
|
173
|
+
return rest.length > 0 ? rest : void 0;
|
|
174
|
+
};
|
|
175
|
+
var policyArgForms = (toolName, toolInput, cwd) => {
|
|
176
|
+
const raw = extractPolicyArg(toolName, toolInput);
|
|
177
|
+
if (raw === void 0) return [];
|
|
178
|
+
if (!(toolName in POLICY_PATH_KEYS)) return [raw];
|
|
179
|
+
const forms = [raw];
|
|
180
|
+
const normalized = normalizeSeparators(raw);
|
|
181
|
+
if (normalized !== raw) forms.push(normalized);
|
|
182
|
+
if (cwd && isAbsolutePath(normalized)) {
|
|
183
|
+
const relative = relativeToCwd(normalized, cwd);
|
|
184
|
+
if (relative !== void 0) forms.push(relative);
|
|
185
|
+
}
|
|
186
|
+
return forms;
|
|
187
|
+
};
|
|
188
|
+
var matchToolPatternAny = (pattern, toolName, args) => {
|
|
189
|
+
if (args.length === 0) return matchToolPattern(pattern, toolName);
|
|
190
|
+
let best = "none";
|
|
191
|
+
for (const arg of args) {
|
|
192
|
+
const rank = matchToolPattern(pattern, toolName, arg);
|
|
193
|
+
if (matchRankWeight(rank) > matchRankWeight(best)) best = rank;
|
|
194
|
+
}
|
|
195
|
+
return best;
|
|
196
|
+
};
|
|
47
197
|
var SAFE_SHELL_COMMANDS = /* @__PURE__ */ new Set([
|
|
48
198
|
"ls",
|
|
49
199
|
"pwd",
|
|
@@ -581,9 +731,12 @@ export {
|
|
|
581
731
|
hookWaitDeadline,
|
|
582
732
|
hookWaitClamped,
|
|
583
733
|
effectiveWaitSeconds,
|
|
734
|
+
isRepoKeyMatch,
|
|
735
|
+
normalizeRepoRemote,
|
|
736
|
+
localRepoKey,
|
|
584
737
|
matchRankWeight,
|
|
585
|
-
|
|
586
|
-
|
|
738
|
+
policyArgForms,
|
|
739
|
+
matchToolPatternAny,
|
|
587
740
|
isSafeReadOnlyCommand,
|
|
588
741
|
isValidApiKey,
|
|
589
742
|
ACTION_BODY_MAX,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -26,9 +26,9 @@ interface ReceiptMeta {
|
|
|
26
26
|
ok: boolean;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string) => Promise<PolicyConfig>;
|
|
29
|
+
declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string, repoAware?: boolean) => Promise<PolicyConfig>;
|
|
30
30
|
type AutoResolveOrigin = 'safe_readonly' | 'policy_timeout';
|
|
31
|
-
declare const resolvePolicy: (config: PolicyConfig, toolName: string, modeOverride?: ApprovalMode | null, toolInput?: Record<string, unknown
|
|
31
|
+
declare const resolvePolicy: (config: PolicyConfig, toolName: string, modeOverride?: ApprovalMode | null, toolInput?: Record<string, unknown>, cwd?: string, repoKey?: string) => ToolPolicy;
|
|
32
32
|
interface ModeState {
|
|
33
33
|
readonly mode: ApprovalMode | null;
|
|
34
34
|
readonly kill: boolean;
|
|
@@ -57,6 +57,7 @@ interface AgentEvent {
|
|
|
57
57
|
agentName?: string;
|
|
58
58
|
action?: string;
|
|
59
59
|
machineId?: string;
|
|
60
|
+
repoKey?: string;
|
|
60
61
|
sessionId?: string;
|
|
61
62
|
error?: string;
|
|
62
63
|
taskTitle?: string;
|
package/dist/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handlePreToolUse
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-GQPIOJA3.js";
|
|
4
|
+
import "../chunk-QYQO6RHJ.js";
|
|
5
5
|
import "../chunk-YHG74UFF.js";
|
|
6
6
|
import {
|
|
7
7
|
askUser,
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
reportEvent,
|
|
16
16
|
resolvePolicy,
|
|
17
17
|
waitForAnswer
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-3EOK3M44.js";
|
|
19
19
|
import "../chunk-RN3NOEJF.js";
|
|
20
|
-
import "../chunk-
|
|
20
|
+
import "../chunk-5TECIV6T.js";
|
|
21
21
|
import "../chunk-DWED7BS3.js";
|
|
22
|
-
import "../chunk-
|
|
22
|
+
import "../chunk-SYAAGYLZ.js";
|
|
23
23
|
import {
|
|
24
24
|
getApiKey,
|
|
25
25
|
getBaseUrl
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "node scripts/bundle-plugin.mjs && tsup",
|
|
76
76
|
"dev": "tsup --watch",
|
|
77
|
-
"test": "bun test src/api.test.ts && bun test src/claude-config.test.ts && bun test src/config.test.ts && bun test src/mcp-http.test.ts && bun test src/retry.test.ts && bun test src/usage.test.ts && bun test src/validate.test.ts && bun test src/policy.test.ts && bun test src/npm.test.ts && bun test src/identity.test.ts && bun test src/pending.test.ts && bun test src/events.test.ts && bun test src/describe.test.ts && bun test src/reapply.test.ts && bun test src/suggestions.test.ts && bun test src/safe-commands.test.ts && bun test src/hook.test.ts && bun test src/codex-adapter.test.ts && bun test src/codex-config.test.ts && bun test src/gemini-adapter.test.ts && bun test src/gemini-config.test.ts && bun test src/instruction-file.test.ts && bun test src/pairing.test.ts && bun test src/wait-ladder.test.ts && bun test src/ledger.test.ts && bun test src/wrapper/wrapper.test.ts && bun test src/wrapper/messageQueue.test.ts && bun test src/wrapper/approver.test.ts && bun test src/wrapper/remoteLoop.test.ts && bun test src/wrapper/spawnClaude.test.ts && bun test src/wrapper/wsProtocol.test.ts && bun test src/wrapper/relayClient.test.ts && bun test src/wrapper/modeLoop.test.ts && bun test src/wrapper/stdinHandoff.test.ts && bun test src/wrapper/localLeg.test.ts && bun test src/crypto.test.ts && bun test src/crypto-vector.test.ts && bun test src/transcript-capture.test.ts && bun test src/transcript-sync.test.ts && bun test src/spawn-daemon.test.ts && bun test src/decision-episode.test.ts"
|
|
77
|
+
"test": "bun test src/api.test.ts && bun test src/claude-config.test.ts && bun test src/config.test.ts && bun test src/mcp-http.test.ts && bun test src/retry.test.ts && bun test src/usage.test.ts && bun test src/validate.test.ts && bun test src/policy.test.ts && bun test src/npm.test.ts && bun test src/identity.test.ts && bun test src/pending.test.ts && bun test src/events.test.ts && bun test src/describe.test.ts && bun test src/reapply.test.ts && bun test src/suggestions.test.ts && bun test src/safe-commands.test.ts && bun test src/hook.test.ts && bun test src/codex-adapter.test.ts && bun test src/codex-config.test.ts && bun test src/gemini-adapter.test.ts && bun test src/gemini-config.test.ts && bun test src/instruction-file.test.ts && bun test src/pairing.test.ts && bun test src/wait-ladder.test.ts && bun test src/ledger.test.ts && bun test src/wrapper/wrapper.test.ts && bun test src/wrapper/messageQueue.test.ts && bun test src/wrapper/approver.test.ts && bun test src/wrapper/remoteLoop.test.ts && bun test src/wrapper/spawnClaude.test.ts && bun test src/wrapper/wsProtocol.test.ts && bun test src/wrapper/relayClient.test.ts && bun test src/wrapper/modeLoop.test.ts && bun test src/wrapper/stdinHandoff.test.ts && bun test src/wrapper/localLeg.test.ts && bun test src/crypto.test.ts && bun test src/crypto-vector.test.ts && bun test src/transcript-capture.test.ts && bun test src/transcript-sync.test.ts && bun test src/spawn-daemon.test.ts && bun test src/decision-episode.test.ts && bun test src/repo.test.ts"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@inquirer/prompts": "^8.4.2",
|