@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.
@@ -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
@@ -12,7 +12,7 @@ import {
12
12
  import "../chunk-VT4IVERX.js";
13
13
  import {
14
14
  claudeSettings
15
- } from "../chunk-VL5ZNK6V.js";
15
+ } from "../chunk-NAPRGHZ3.js";
16
16
  import "../chunk-GIRT7677.js";
17
17
  import {
18
18
  createIo
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureDaemonRunning
4
- } from "../chunk-KCAFCGGY.js";
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-LGVHYFIB.js";
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-VL5ZNK6V.js";
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-DPUOW6NS.js";
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-VL5ZNK6V.js";
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-LGVHYFIB.js";
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-VL5ZNK6V.js";
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-KCAFCGGY.js";
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-LGVHYFIB.js";
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-VL5ZNK6V.js";
29
+ import "../chunk-NAPRGHZ3.js";
30
30
  import "../chunk-GIRT7677.js";
31
31
  import {
32
32
  getMachineId
@@ -31,7 +31,7 @@ import {
31
31
  geminiSettings,
32
32
  removeCodexHooks,
33
33
  removeCodexSettings
34
- } from "../chunk-VL5ZNK6V.js";
34
+ } from "../chunk-NAPRGHZ3.js";
35
35
  import "../chunk-GIRT7677.js";
36
36
  import {
37
37
  exitOnHelpFlag
@@ -6,10 +6,10 @@ import {
6
6
  claudeWired,
7
7
  codexWired,
8
8
  geminiWired
9
- } from "../chunk-TAYHEJFF.js";
9
+ } from "../chunk-QXZ2CKRK.js";
10
10
  import {
11
11
  readDaemonStatus
12
- } from "../chunk-KCAFCGGY.js";
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-DPUOW6NS.js";
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-VL5ZNK6V.js";
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-LGVHYFIB.js";
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-VL5ZNK6V.js";
20
+ import "../chunk-NAPRGHZ3.js";
21
21
  import "../chunk-GIRT7677.js";
22
22
  import {
23
23
  getMachineId
@@ -4,7 +4,7 @@ import {
4
4
  codexConfigToml,
5
5
  cursorUserMcp,
6
6
  geminiSettings
7
- } from "../chunk-VL5ZNK6V.js";
7
+ } from "../chunk-NAPRGHZ3.js";
8
8
  import "../chunk-GIRT7677.js";
9
9
  import {
10
10
  createIo
@@ -6,13 +6,13 @@ import {
6
6
  claudeWired,
7
7
  codexWired,
8
8
  geminiWired
9
- } from "../chunk-TAYHEJFF.js";
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-KCAFCGGY.js";
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-DPUOW6NS.js";
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-VL5ZNK6V.js";
83
+ } from "../chunk-NAPRGHZ3.js";
84
84
  import "../chunk-GIRT7677.js";
85
85
  import {
86
86
  reportEvent
@@ -37,7 +37,7 @@ import {
37
37
  hasCodexHooks,
38
38
  vscodePluginDir,
39
39
  vscodePluginMcp
40
- } from "../chunk-VL5ZNK6V.js";
40
+ } from "../chunk-NAPRGHZ3.js";
41
41
  import "../chunk-GIRT7677.js";
42
42
  import {
43
43
  exitOnHelpFlag
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-VL5ZNK6V.js";
3
+ } from "./chunk-NAPRGHZ3.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VT4IVERX.js";
8
8
  import {
9
9
  pusharyDir
10
- } from "./chunk-VL5ZNK6V.js";
10
+ } from "./chunk-NAPRGHZ3.js";
11
11
  import {
12
12
  getMachineId
13
13
  } from "./chunk-RN3NOEJF.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-ZGC3W23N.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-VL5ZNK6V.js";
6
+ } from "./chunk-NAPRGHZ3.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -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: codexHookTrustHash(definition, command) };
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";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-K42RXJPG.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-VL5ZNK6V.js";
6
+ } from "./chunk-NAPRGHZ3.js";
7
7
 
8
8
  // src/diagnostics/wiring.ts
9
9
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.87.3",
3
+ "version": "0.87.4",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",