@pushary/agent-hooks 0.87.3 → 0.87.4
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/data/cursor-plugin/scripts/pushary-gate.mjs +9 -0
- package/data/cursor-plugin/scripts/pushary-gate.test.mjs +39 -0
- package/dist/bin/pushary-bell.js +1 -1
- package/dist/bin/pushary-claude.js +3 -3
- package/dist/bin/pushary-clean.js +2 -2
- package/dist/bin/pushary-codex-bridge.js +2 -2
- package/dist/bin/pushary-daemon.js +3 -3
- package/dist/bin/pushary-disconnect.js +1 -1
- package/dist/bin/pushary-doctor.js +4 -4
- package/dist/bin/pushary-gemini-bridge.js +2 -2
- package/dist/bin/pushary-logout.js +1 -1
- package/dist/bin/pushary-setup.js +4 -4
- package/dist/bin/pushary-upgrade.js +1 -1
- package/dist/{chunk-LGVHYFIB.js → chunk-BFBAHOGR.js} +1 -1
- package/dist/{chunk-KCAFCGGY.js → chunk-H5PIPIKQ.js} +1 -1
- package/dist/{chunk-DPUOW6NS.js → chunk-KULUNLRO.js} +1 -1
- package/dist/{chunk-VL5ZNK6V.js → chunk-NAPRGHZ3.js} +11 -1
- package/dist/{chunk-TAYHEJFF.js → chunk-QXZ2CKRK.js} +1 -1
- package/package.json +1 -1
|
@@ -403,6 +403,15 @@ const describeRequest = (input) => {
|
|
|
403
403
|
// same tool whichever agent called it. That portability is the only reason
|
|
404
404
|
// the policy engine is server-side.
|
|
405
405
|
const toolName = server ? `mcp__${server}__${tool}` : `mcp__${tool}`
|
|
406
|
+
|
|
407
|
+
// Never gate Pushary's own MCP tools. Asking Pushary to approve Pushary's
|
|
408
|
+
// ask_user call deadlocks: the approval cannot be delivered until the call
|
|
409
|
+
// it is gating goes through. The Claude hook carries the same guard, where
|
|
410
|
+
// it is a precaution because the matcher does not route these there. Here it
|
|
411
|
+
// is load-bearing: `beforeMCPExecution` is registered with NO matcher, so
|
|
412
|
+
// every MCP call reaches this gate, ours included.
|
|
413
|
+
if (toolName.startsWith('mcp__pushary__')) return null
|
|
414
|
+
|
|
406
415
|
const params = typeof input.tool_input === 'string'
|
|
407
416
|
? input.tool_input
|
|
408
417
|
: input.tool_input === undefined ? '' : JSON.stringify(input.tool_input)
|
|
@@ -64,6 +64,45 @@ describe('what the gate was asked about', () => {
|
|
|
64
64
|
assert.equal(describeRequest({ command: 'git push' }).toolName, 'Bash')
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* `beforeMCPExecution` is registered with NO matcher, so every MCP call in the
|
|
69
|
+
* editor reaches this gate, ours included. Gating our own tools deadlocks: the
|
|
70
|
+
* approval for `ask_user` cannot reach the phone until the `ask_user` call it
|
|
71
|
+
* is gating goes through. With `failClosed: true` on that entry it is worse
|
|
72
|
+
* than a hang, because a timeout turns into a denial of the user's own
|
|
73
|
+
* question.
|
|
74
|
+
*
|
|
75
|
+
* The Claude hook carries this guard three times over and calls it a
|
|
76
|
+
* precaution, because its matcher does not route these there. Here it is the
|
|
77
|
+
* only thing standing between the two.
|
|
78
|
+
*/
|
|
79
|
+
it('never gates Pushary\'s own MCP tools', () => {
|
|
80
|
+
for (const tool of ['ask_user', 'wait_for_answer', 'send_notification', 'propose_scope']) {
|
|
81
|
+
assert.equal(
|
|
82
|
+
describeRequest({
|
|
83
|
+
hook_event_name: 'beforeMCPExecution',
|
|
84
|
+
tool_name: tool,
|
|
85
|
+
mcp_server_name: 'pushary',
|
|
86
|
+
tool_input: '{}',
|
|
87
|
+
}),
|
|
88
|
+
null,
|
|
89
|
+
`mcp__pushary__${tool} must reach Cursor's own prompt, not ours`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// A neighbour whose name merely starts the same way is still gated. The
|
|
94
|
+
// guard is on the `mcp__pushary__` prefix, not on "contains pushary".
|
|
95
|
+
assert.equal(
|
|
96
|
+
describeRequest({
|
|
97
|
+
hook_event_name: 'beforeMCPExecution',
|
|
98
|
+
tool_name: 'run',
|
|
99
|
+
mcp_server_name: 'pushary-clone',
|
|
100
|
+
tool_input: '{}',
|
|
101
|
+
}).toolName,
|
|
102
|
+
'mcp__pushary-clone__run',
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
67
106
|
it('declines an event it does not gate rather than calling it Bash', () => {
|
|
68
107
|
// beforeReadFile is registered by nobody here on purpose: its response
|
|
69
108
|
// schema has no `ask`, so the no-opinion path this gate takes on every error
|
package/dist/bin/pushary-bell.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ensureDaemonRunning
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-H5PIPIKQ.js";
|
|
5
5
|
import {
|
|
6
6
|
buildTranscriptRecords,
|
|
7
7
|
decodePublicKey,
|
|
@@ -13,13 +13,13 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
startProcessOwnership,
|
|
15
15
|
withSpawnProcessOwnership
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-BFBAHOGR.js";
|
|
17
17
|
import {
|
|
18
18
|
needsShell,
|
|
19
19
|
spawnClaude
|
|
20
20
|
} from "../chunk-XHKBHWLX.js";
|
|
21
21
|
import "../chunk-VT4IVERX.js";
|
|
22
|
-
import "../chunk-
|
|
22
|
+
import "../chunk-NAPRGHZ3.js";
|
|
23
23
|
import {
|
|
24
24
|
isDeferAnswer
|
|
25
25
|
} from "../chunk-YHG74UFF.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
shortenHome,
|
|
10
10
|
unregisterPluginLocation,
|
|
11
11
|
vscodeSettingsTargets
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-KULUNLRO.js";
|
|
13
13
|
import "../chunk-ZGC3W23N.js";
|
|
14
14
|
import {
|
|
15
15
|
removeGeminiSettings
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
removeCodexHookTrust,
|
|
39
39
|
removeCodexHooks,
|
|
40
40
|
vscodePluginDir
|
|
41
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-NAPRGHZ3.js";
|
|
42
42
|
import "../chunk-GIRT7677.js";
|
|
43
43
|
import "../chunk-7J67NYK4.js";
|
|
44
44
|
import {
|
|
@@ -11,14 +11,14 @@ import "../chunk-QLMX5LLV.js";
|
|
|
11
11
|
import {
|
|
12
12
|
startProcessOwnership,
|
|
13
13
|
withSpawnProcessOwnership
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-BFBAHOGR.js";
|
|
15
15
|
import {
|
|
16
16
|
spawnClaude
|
|
17
17
|
} from "../chunk-XHKBHWLX.js";
|
|
18
18
|
import {
|
|
19
19
|
resolveBinary
|
|
20
20
|
} from "../chunk-VT4IVERX.js";
|
|
21
|
-
import "../chunk-
|
|
21
|
+
import "../chunk-NAPRGHZ3.js";
|
|
22
22
|
import "../chunk-GIRT7677.js";
|
|
23
23
|
import {
|
|
24
24
|
getMachineId
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
acquireDaemonLease,
|
|
9
9
|
readDaemonStatus,
|
|
10
10
|
requestDaemonHandoff
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-H5PIPIKQ.js";
|
|
12
12
|
import {
|
|
13
13
|
CODEX_BRIDGE_CAPABILITY,
|
|
14
14
|
CODEX_RESUME_CAPABILITY,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
findLiveProcessBySpawnId,
|
|
19
19
|
ownsLiveProcess
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-BFBAHOGR.js";
|
|
21
21
|
import {
|
|
22
22
|
spawnClaude
|
|
23
23
|
} from "../chunk-XHKBHWLX.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolveGlobalPackageVersion,
|
|
27
27
|
resolvePackageVersionFromBinary
|
|
28
28
|
} from "../chunk-VT4IVERX.js";
|
|
29
|
-
import "../chunk-
|
|
29
|
+
import "../chunk-NAPRGHZ3.js";
|
|
30
30
|
import "../chunk-GIRT7677.js";
|
|
31
31
|
import {
|
|
32
32
|
getMachineId
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
claudeWired,
|
|
7
7
|
codexWired,
|
|
8
8
|
geminiWired
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-QXZ2CKRK.js";
|
|
10
10
|
import {
|
|
11
11
|
readDaemonStatus
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-H5PIPIKQ.js";
|
|
13
13
|
import {
|
|
14
14
|
CURSOR_GATE_EVENTS,
|
|
15
15
|
agentProbes,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
isDetected,
|
|
20
20
|
isPluginRegistered,
|
|
21
21
|
vscodeSettingsTargets
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-KULUNLRO.js";
|
|
23
23
|
import {
|
|
24
24
|
OPENCODE_PLUGIN_PLACEHOLDER,
|
|
25
25
|
openCodePluginDir,
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
readCodexMcpAuth,
|
|
57
57
|
untrustedCodexHookEvents,
|
|
58
58
|
vscodePluginDir
|
|
59
|
-
} from "../chunk-
|
|
59
|
+
} from "../chunk-NAPRGHZ3.js";
|
|
60
60
|
import {
|
|
61
61
|
readLedgerSummary
|
|
62
62
|
} from "../chunk-VIHRP25S.js";
|
|
@@ -10,14 +10,14 @@ import "../chunk-QLMX5LLV.js";
|
|
|
10
10
|
import {
|
|
11
11
|
startProcessOwnership,
|
|
12
12
|
withSpawnProcessOwnership
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-BFBAHOGR.js";
|
|
14
14
|
import {
|
|
15
15
|
spawnClaude
|
|
16
16
|
} from "../chunk-XHKBHWLX.js";
|
|
17
17
|
import {
|
|
18
18
|
resolveBinary
|
|
19
19
|
} from "../chunk-VT4IVERX.js";
|
|
20
|
-
import "../chunk-
|
|
20
|
+
import "../chunk-NAPRGHZ3.js";
|
|
21
21
|
import "../chunk-GIRT7677.js";
|
|
22
22
|
import {
|
|
23
23
|
getMachineId
|
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
claudeWired,
|
|
7
7
|
codexWired,
|
|
8
8
|
geminiWired
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-QXZ2CKRK.js";
|
|
10
10
|
import {
|
|
11
11
|
runBrowserLogin
|
|
12
12
|
} from "../chunk-Z3Z4VK5F.js";
|
|
13
13
|
import {
|
|
14
14
|
ensureDaemonRunning
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-H5PIPIKQ.js";
|
|
16
16
|
import {
|
|
17
17
|
addClaudeMcpServer,
|
|
18
18
|
addPusharyHooks,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
shortenHome,
|
|
31
31
|
vscodeSettingsTargets,
|
|
32
32
|
writeInstructionBlock
|
|
33
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-KULUNLRO.js";
|
|
34
34
|
import {
|
|
35
35
|
OPENCODE_HOOK_BINARY,
|
|
36
36
|
addOpenCodeMcpServer,
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
geminiSettings,
|
|
81
81
|
pusharyConfigFile,
|
|
82
82
|
vscodePluginDir
|
|
83
|
-
} from "../chunk-
|
|
83
|
+
} from "../chunk-NAPRGHZ3.js";
|
|
84
84
|
import "../chunk-GIRT7677.js";
|
|
85
85
|
import {
|
|
86
86
|
reportEvent
|
|
@@ -228,10 +228,20 @@ var addCodexHookTrust = (config, hooksJsonPath, command, positions) => {
|
|
|
228
228
|
const position = positions[definition.event];
|
|
229
229
|
if (!position) continue;
|
|
230
230
|
const key = codexHookStateKey(hooksJsonPath, definition.event, position);
|
|
231
|
+
const ours = codexHookTrustHash(definition, command);
|
|
231
232
|
const existing = asRecord(state[key]) ?? {};
|
|
232
|
-
state[key] = { ...existing, trusted_hash:
|
|
233
|
+
state[key] = { ...existing, trusted_hash: ours };
|
|
234
|
+
for (const stale of staleTrustKeys(state, hooksJsonPath, definition.event, key, ours)) {
|
|
235
|
+
delete state[stale];
|
|
236
|
+
}
|
|
233
237
|
}
|
|
234
238
|
};
|
|
239
|
+
var staleTrustKeys = (state, hooksJsonPath, event, currentKey, ourHash) => {
|
|
240
|
+
const prefix = `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:`;
|
|
241
|
+
return Object.keys(state).filter(
|
|
242
|
+
(key) => key !== currentKey && key.startsWith(prefix) && asRecord(state[key])?.trusted_hash === ourHash
|
|
243
|
+
);
|
|
244
|
+
};
|
|
235
245
|
|
|
236
246
|
// src/setup/paths.ts
|
|
237
247
|
import { homedir as homedir2 } from "os";
|