@pushary/agent-hooks 1.3.0 → 1.4.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/data/SKILL.md +3 -3
  4. package/data/cursor-plugin/skills/pushary/SKILL.md +2 -2
  5. package/data/vscode-plugin/skills/pushary/SKILL.md +2 -2
  6. package/dist/bin/pushary-bell-hook.js +20 -20
  7. package/dist/bin/pushary-bell.js +9 -9
  8. package/dist/bin/pushary-claude.js +20 -20
  9. package/dist/bin/pushary-clean.js +472 -94
  10. package/dist/bin/pushary-codex-bridge.js +11 -11
  11. package/dist/bin/pushary-codex-hook.js +30 -28
  12. package/dist/bin/pushary-codex.js +7 -7
  13. package/dist/bin/pushary-connect.js +9 -9
  14. package/dist/bin/pushary-cowork.js +5 -5
  15. package/dist/bin/pushary-daemon.js +26 -26
  16. package/dist/bin/pushary-disconnect.js +13 -13
  17. package/dist/bin/pushary-doctor.js +86 -24
  18. package/dist/bin/pushary-elicitation-hook.js +19 -19
  19. package/dist/bin/pushary-gemini-bridge.js +10 -10
  20. package/dist/bin/pushary-gemini-hook.js +21 -21
  21. package/dist/bin/pushary-hook.js +34 -44
  22. package/dist/bin/pushary-login.js +10 -10
  23. package/dist/bin/pushary-logout.js +7 -7
  24. package/dist/bin/pushary-mode.js +5 -5
  25. package/dist/bin/pushary-notification-hook.js +22 -22
  26. package/dist/bin/pushary-opencode-hook.js +21 -21
  27. package/dist/bin/pushary-permission-denied-hook.js +26 -26
  28. package/dist/bin/pushary-permission-hook.js +32 -31
  29. package/dist/bin/pushary-post-hook.js +22 -22
  30. package/dist/bin/pushary-prompt-hook.js +22 -22
  31. package/dist/bin/pushary-session-end-hook.js +22 -22
  32. package/dist/bin/pushary-session-start-hook.js +24 -24
  33. package/dist/bin/pushary-setup.js +39 -60
  34. package/dist/bin/pushary-stats.js +4 -4
  35. package/dist/bin/pushary-status.js +13 -13
  36. package/dist/bin/pushary-stop-hook.js +22 -22
  37. package/dist/bin/pushary-stopfailure-hook.js +22 -22
  38. package/dist/bin/pushary-suggestions.js +2 -2
  39. package/dist/bin/pushary-transcript-register.js +18 -18
  40. package/dist/bin/pushary-transcripts.js +8 -8
  41. package/dist/bin/pushary-upgrade.js +17 -17
  42. package/dist/bin/pushary-wait.js +5 -5
  43. package/dist/bin/pushary.js +1 -1
  44. package/dist/{chunk-3AIFWTVB.js → chunk-2TOTCUJC.js} +2 -2
  45. package/dist/{chunk-X3KIGNIP.js → chunk-43Z3RLS5.js} +83 -24
  46. package/dist/{chunk-YF4JDCU4.js → chunk-5F5QQMCA.js} +1 -1
  47. package/dist/{chunk-ZHL62DRA.js → chunk-5IBVIMWT.js} +3 -3
  48. package/dist/{chunk-BYGEZI7B.js → chunk-5MSBI6NP.js} +1 -1
  49. package/dist/{chunk-WPJS3OZH.js → chunk-7IEB2GV4.js} +100 -9
  50. package/dist/{chunk-2PLEJ7SY.js → chunk-7PQZYCEP.js} +1 -1
  51. package/dist/{chunk-OE6U2AXM.js → chunk-A7XIL2WD.js} +3 -3
  52. package/dist/{chunk-A2URMNFM.js → chunk-BYAXXBNO.js} +1 -1
  53. package/dist/{chunk-DON24PNT.js → chunk-D4TNSUDY.js} +1 -1
  54. package/dist/{chunk-EU6JH7KU.js → chunk-ESLLVHDY.js} +8 -8
  55. package/dist/{chunk-LZQVAEWP.js → chunk-FAH3D74E.js} +2 -1
  56. package/dist/{chunk-MU275U5A.js → chunk-GIXLZFEY.js} +2 -2
  57. package/dist/{chunk-4V24U44H.js → chunk-GLPWCQ6V.js} +2 -2
  58. package/dist/{chunk-BL4SQNO6.js → chunk-GS5EI2YL.js} +1 -1
  59. package/dist/{chunk-EKQAKSGI.js → chunk-H25UB3CG.js} +1 -1
  60. package/dist/{chunk-BNDHXPTQ.js → chunk-HGN6VZTY.js} +20 -13
  61. package/dist/{chunk-WXXDHGTZ.js → chunk-IF46POXX.js} +5 -4
  62. package/dist/{chunk-A5ZWYQFV.js → chunk-JCWCTBYH.js} +4 -4
  63. package/dist/{chunk-IAEVVUXO.js → chunk-KZIXHLPQ.js} +1 -1
  64. package/dist/{chunk-FGVNU7XD.js → chunk-NBCTKC4Z.js} +34 -3
  65. package/dist/{chunk-XBO42HKH.js → chunk-NYHNLMQO.js} +12 -3
  66. package/dist/{chunk-K4TAUYUJ.js → chunk-O5L2OJBB.js} +3 -3
  67. package/dist/{chunk-DVO4SZMJ.js → chunk-O6RN46OS.js} +3 -3
  68. package/dist/{chunk-POAFXBNH.js → chunk-P4JVVJ2D.js} +1 -1
  69. package/dist/{chunk-E7WBNGJS.js → chunk-Q4UAMWXK.js} +1 -1
  70. package/dist/{chunk-KS4P6F3R.js → chunk-RGZY77CD.js} +4 -4
  71. package/dist/{chunk-K7UIAE7I.js → chunk-RQM5SMMR.js} +2 -2
  72. package/dist/{chunk-UVXNYYHG.js → chunk-S3CLZSV3.js} +6 -6
  73. package/dist/{chunk-B3S4SF3D.js → chunk-S3EUZZJB.js} +1 -1
  74. package/dist/{chunk-XBJW4MV5.js → chunk-SGZ3IAWF.js} +1 -1
  75. package/dist/{chunk-YKLCWWEI.js → chunk-SGZJ5LYH.js} +1 -1
  76. package/dist/{chunk-4LL6LG5X.js → chunk-T7E75EKA.js} +4 -3
  77. package/dist/{chunk-GOQHN2UH.js → chunk-TWWLVOPB.js} +1 -1
  78. package/dist/{chunk-RVDHN2TJ.js → chunk-U4FB623J.js} +2 -2
  79. package/dist/{chunk-RHLUJE4H.js → chunk-UCGQEAKZ.js} +1 -1
  80. package/dist/{chunk-KPNGPRYU.js → chunk-UIN3RD2U.js} +4 -4
  81. package/dist/{chunk-XEXOHUJ6.js → chunk-UTYTPDWN.js} +4 -4
  82. package/dist/{chunk-EJZCV5G4.js → chunk-VENQSVKA.js} +1 -1
  83. package/dist/{chunk-EYMMU5HC.js → chunk-XJWMPQ6X.js} +1 -1
  84. package/dist/{chunk-JYEGOYQC.js → chunk-YYRZPEMA.js} +1 -1
  85. package/dist/{chunk-N43EQPJK.js → chunk-ZIGGEKZW.js} +41 -7
  86. package/dist/{reapply-FUCAKR6Z.js → reapply-B3S2LRMT.js} +9 -9
  87. package/dist/src/index.d.ts +20 -17
  88. package/dist/src/index.js +13 -13
  89. package/package.json +1 -1
  90. /package/dist/{chunk-KBITFQST.js → chunk-P32LPT2L.js} +0 -0
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reapplyAllAgents
4
- } from "../chunk-UVXNYYHG.js";
5
- import "../chunk-XBJW4MV5.js";
4
+ } from "../chunk-S3CLZSV3.js";
5
+ import "../chunk-SGZ3IAWF.js";
6
6
  import {
7
7
  waitForMachineReadiness
8
- } from "../chunk-KPNGPRYU.js";
9
- import "../chunk-ZHL62DRA.js";
8
+ } from "../chunk-UIN3RD2U.js";
9
+ import "../chunk-5IBVIMWT.js";
10
10
  import {
11
11
  detectProviderLiveCapabilities,
12
12
  hasSpawnProvider,
13
13
  resolveControlPolicy
14
- } from "../chunk-4V24U44H.js";
15
- import "../chunk-DVO4SZMJ.js";
14
+ } from "../chunk-GLPWCQ6V.js";
15
+ import "../chunk-O6RN46OS.js";
16
16
  import {
17
17
  daemonHasRemoteProof,
18
18
  daemonServiceManagerAvailable,
@@ -23,13 +23,13 @@ import {
23
23
  replaceDaemonService,
24
24
  stopDaemonService,
25
25
  waitForDaemonRemoteProof
26
- } from "../chunk-KS4P6F3R.js";
27
- import "../chunk-OE6U2AXM.js";
26
+ } from "../chunk-RGZY77CD.js";
27
+ import "../chunk-A7XIL2WD.js";
28
28
  import {
29
29
  needsShell
30
30
  } from "../chunk-XHKBHWLX.js";
31
- import "../chunk-IAEVVUXO.js";
32
- import "../chunk-K7UIAE7I.js";
31
+ import "../chunk-KZIXHLPQ.js";
32
+ import "../chunk-RQM5SMMR.js";
33
33
  import {
34
34
  claudeJson,
35
35
  codexConfigToml,
@@ -39,24 +39,24 @@ import {
39
39
  npmErrorMessage,
40
40
  resolveGlobalBinary,
41
41
  vscodePluginDir
42
- } from "../chunk-RHLUJE4H.js";
42
+ } from "../chunk-UCGQEAKZ.js";
43
43
  import {
44
44
  exitOnHelpFlag
45
- } from "../chunk-GOQHN2UH.js";
46
- import "../chunk-LZQVAEWP.js";
45
+ } from "../chunk-TWWLVOPB.js";
46
+ import "../chunk-FAH3D74E.js";
47
47
  import {
48
48
  isNewerVersion
49
49
  } from "../chunk-DINDL2CF.js";
50
50
  import {
51
51
  readControlMode
52
- } from "../chunk-XBO42HKH.js";
52
+ } from "../chunk-NYHNLMQO.js";
53
53
  import "../chunk-KZERVKTD.js";
54
- import "../chunk-POAFXBNH.js";
55
- import "../chunk-4LL6LG5X.js";
54
+ import "../chunk-P4JVVJ2D.js";
55
+ import "../chunk-T7E75EKA.js";
56
56
  import {
57
57
  getApiKey
58
58
  } from "../chunk-2UMNXADU.js";
59
- import "../chunk-WPJS3OZH.js";
59
+ import "../chunk-7IEB2GV4.js";
60
60
 
61
61
  // bin/pushary-upgrade.ts
62
62
  import { existsSync } from "fs";
@@ -4,15 +4,15 @@ import {
4
4
  } from "../chunk-CVWG2N3Y.js";
5
5
  import {
6
6
  exitOnFailedResponse
7
- } from "../chunk-BL4SQNO6.js";
8
- import "../chunk-DON24PNT.js";
7
+ } from "../chunk-GS5EI2YL.js";
8
+ import "../chunk-D4TNSUDY.js";
9
9
  import {
10
10
  createIo
11
11
  } from "../chunk-5RUIFDTP.js";
12
12
  import {
13
13
  exitOnHelpFlag
14
- } from "../chunk-GOQHN2UH.js";
15
- import "../chunk-LZQVAEWP.js";
14
+ } from "../chunk-TWWLVOPB.js";
15
+ import "../chunk-FAH3D74E.js";
16
16
  import {
17
17
  rejectUnknownFlags
18
18
  } from "../chunk-GMXKITVA.js";
@@ -28,7 +28,7 @@ import {
28
28
  getApiKey,
29
29
  getBaseUrl
30
30
  } from "../chunk-2UMNXADU.js";
31
- import "../chunk-WPJS3OZH.js";
31
+ import "../chunk-7IEB2GV4.js";
32
32
 
33
33
  // bin/pushary-wait.ts
34
34
  exitOnHelpFlag("wait");
@@ -3,7 +3,7 @@ import {
3
3
  COMMANDS,
4
4
  isCommandName,
5
5
  renderHelp
6
- } from "../chunk-LZQVAEWP.js";
6
+ } from "../chunk-FAH3D74E.js";
7
7
  import {
8
8
  getPackageVersion
9
9
  } from "../chunk-DINDL2CF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SETUP_FLAGS
3
- } from "./chunk-BYGEZI7B.js";
3
+ } from "./chunk-5MSBI6NP.js";
4
4
  import {
5
5
  parseFlags
6
6
  } from "./chunk-GMXKITVA.js";
@@ -14,7 +14,7 @@ import {
14
14
  CLAUDE_CONNECTOR_URL,
15
15
  COWORK_CONNECTOR_LIMIT,
16
16
  COWORK_STANDING_INSTRUCTIONS
17
- } from "./chunk-WPJS3OZH.js";
17
+ } from "./chunk-7IEB2GV4.js";
18
18
 
19
19
  // src/cli/cowork.ts
20
20
  import { readFileSync } from "fs";
@@ -1,22 +1,24 @@
1
1
  import {
2
+ activeHookSource,
2
3
  activeIdentity
3
- } from "./chunk-BNDHXPTQ.js";
4
+ } from "./chunk-HGN6VZTY.js";
4
5
  import {
5
6
  blockedActionFingerprint,
6
7
  defersToNativeMode,
7
8
  forgetBlockedActionHint,
8
9
  hasBlockedActionHint,
9
10
  recordBlockedActionHint
10
- } from "./chunk-A5ZWYQFV.js";
11
+ } from "./chunk-JCWCTBYH.js";
11
12
  import {
12
13
  isGatingMoment,
13
14
  recordKeylessMoment
14
- } from "./chunk-YF4JDCU4.js";
15
+ } from "./chunk-5F5QQMCA.js";
15
16
  import {
16
17
  denyReasonFrom,
17
18
  isDeferAnswer
18
19
  } from "./chunk-YHG74UFF.js";
19
20
  import {
21
+ AUTO_DECISION_WINDOW_MS,
20
22
  DEFAULT_SESSION,
21
23
  UNNAMED_ACTION,
22
24
  deriveAction,
@@ -34,7 +36,7 @@ import {
34
36
  scopePathFor,
35
37
  settleQuestionsForToolUse,
36
38
  throttlePass
37
- } from "./chunk-WXXDHGTZ.js";
39
+ } from "./chunk-IF46POXX.js";
38
40
  import {
39
41
  DECISION_STOP_REASON,
40
42
  askUser,
@@ -49,18 +51,22 @@ import {
49
51
  } from "./chunk-LGXUGYUH.js";
50
52
  import {
51
53
  getMachineId
52
- } from "./chunk-K7UIAE7I.js";
54
+ } from "./chunk-RQM5SMMR.js";
53
55
  import {
54
56
  getApiKey,
55
57
  getBaseUrl
56
58
  } from "./chunk-2UMNXADU.js";
57
59
  import {
60
+ AGENT_QUESTION_STRATEGY,
58
61
  GATE_BLOCK_REASON_MAX_LENGTH,
59
62
  GATE_WIRE,
63
+ agentQuestionStrategy,
60
64
  buildDecisionEpisodeFeatures,
61
65
  claudePermissionDeniedOutput,
62
66
  claudePermissionRequestOutput,
63
67
  claudePreToolUseOutput,
68
+ claudePromptsBeforeAnEdit,
69
+ editAsksAtTheAgentsPrompt,
64
70
  effectiveWaitSeconds,
65
71
  hookHoldWindowMs,
66
72
  hookWaitClamped,
@@ -69,11 +75,13 @@ import {
69
75
  isBlockedActionRequest,
70
76
  isPermissionRequestOwnedByPreToolUse,
71
77
  isPusharyOwnTool,
78
+ machineDecisionFor,
72
79
  parseAgentQuestionAnswers,
73
80
  parseAgentQuestions,
74
81
  resolveGate,
75
- stoppedOutput
76
- } from "./chunk-WPJS3OZH.js";
82
+ stoppedOutput,
83
+ wireVerdictFor
84
+ } from "./chunk-7IEB2GV4.js";
77
85
 
78
86
  // src/decision-episode.ts
79
87
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -103,12 +111,38 @@ import { tmpdir } from "os";
103
111
  import { existsSync, writeFileSync } from "fs";
104
112
  var START_MS = Date.now();
105
113
  var ACTIVE_AGENT = activeIdentity(process.argv);
114
+ var ACTIVE_SOURCE = activeHookSource(process.argv);
106
115
  var agentNameIn = (projectName) => `${ACTIVE_AGENT.label} - ${projectName}`;
107
116
  var allow = (reason) => claudePreToolUseOutput("allow", reason);
108
117
  var deny = (reason) => claudePreToolUseOutput("deny", reason);
109
118
  var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
110
119
  var ask = (reason) => claudePreToolUseOutput("ask", reason);
111
120
  var halted = (reason) => stoppedOutput(deny(reason), reason);
121
+ var reportMachineDecision = async (input, verdict) => {
122
+ const decision = machineDecisionFor(wireVerdictFor(verdict, 0));
123
+ if (!decision) return;
124
+ const sessionKey = input.session_id || DEFAULT_SESSION;
125
+ const toolTarget = deriveToolTarget(input.tool_name, input.tool_input);
126
+ const throttleKey = `machinedecision:${sessionKey}:${input.tool_name}:${decision.origin}:${toolTarget ?? ""}`;
127
+ if (!throttlePass(throttleKey, AUTO_DECISION_WINDOW_MS)) return;
128
+ await reportEvent(
129
+ {
130
+ event: "permission_denied",
131
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
132
+ toolUseId: input.tool_use_id,
133
+ agentType: ACTIVE_AGENT.type,
134
+ agentName: agentNameIn(basename(input.cwd ?? process.cwd())),
135
+ action: permissionDeniedAction(input.tool_name),
136
+ sessionId: input.session_id,
137
+ decisionSource: "policy_auto",
138
+ decisionOrigin: decision.origin,
139
+ decisionOutcome: decision.outcome,
140
+ toolName: input.tool_name,
141
+ toolTarget
142
+ },
143
+ { maxAttempts: 1, timeoutMs: 800 }
144
+ ).catch(() => void 0);
145
+ };
112
146
  var storedWaitDeadline = (result, requestedDeadline) => {
113
147
  const stored = result.waitEndsAt ? Date.parse(result.waitEndsAt) : Number.NaN;
114
148
  return Number.isFinite(stored) ? Math.min(stored, requestedDeadline) : requestedDeadline;
@@ -450,19 +484,25 @@ var handleKeyless = (input) => {
450
484
  }
451
485
  return void 0;
452
486
  };
453
- var claudeGateInput = (modeState, config, input) => {
454
- const scopePath = modeState.scope ? scopePathFor(input.tool_name, input.tool_input, input.cwd) : void 0;
487
+ var claudeGateInput = (modeState, config, input, canonical) => {
488
+ const toolName = canonical?.toolName ?? input.tool_name;
489
+ const toolInputs = canonical?.toolInputs ?? [input.tool_input ?? {}];
455
490
  return {
456
491
  modeState,
457
492
  config,
458
- toolName: input.tool_name,
459
- toolInputs: [input.tool_input ?? {}],
460
- scopePaths: scopePath ? [scopePath] : [],
493
+ toolName,
494
+ toolInputs,
495
+ scopePaths: modeState.scope ? toolInputs.flatMap((args) => {
496
+ const path = scopePathFor(toolName, args, input.cwd);
497
+ return path ? [path] : [];
498
+ }) : [],
461
499
  cwd: input.cwd,
462
500
  repoKey: repoKeyFor(input.cwd),
463
501
  sessionId: input.session_id
464
502
  };
465
503
  };
504
+ var unreadableReason = "Pushary could not read every target of this call. Use a supported command or a single-file edit.";
505
+ var batchScopeReason = "This patch exceeds the agreed scope. Split it into single-file edits so each additional path can be approved.";
466
506
  var BLOCKED_ACTION_GATE_TIMEOUT_MS = 15e3;
467
507
  var BLOCKED_ACTION_CLAIM_TIMEOUT_MS = 4e3;
468
508
  var BLOCKED_ACTION_ALLOW_REASON = "Approved from Pushary after auto mode blocked it";
@@ -502,7 +542,7 @@ var claimedBlockedActionRetry = async (apiKey, input) => {
502
542
  return false;
503
543
  }
504
544
  };
505
- var handlePreToolUse = async (input) => {
545
+ var handlePreToolUse = async (input, canonical) => {
506
546
  if (isPusharyOwnTool(input.tool_name)) return void 0;
507
547
  let apiKey;
508
548
  try {
@@ -513,9 +553,19 @@ var handlePreToolUse = async (input) => {
513
553
  try {
514
554
  const modeState = await fetchModeState(apiKey, input.session_id);
515
555
  const policy = await getPolicy(apiKey, modeState.policyVersion, ACTIVE_AGENT.type, true, input.session_id);
516
- const verdict = resolveGate(claudeGateInput(modeState, policy, input));
517
- if (verdict.kind === "kill") return halted(verdict.reason);
518
- if (verdict.kind === "deny") return deny(verdict.reason);
556
+ const gateInput = claudeGateInput(modeState, policy, input, canonical);
557
+ const verdict = resolveGate(gateInput);
558
+ if (verdict.kind === "kill") {
559
+ await reportMachineDecision(input, verdict);
560
+ return halted(verdict.reason);
561
+ }
562
+ if (verdict.kind === "deny") {
563
+ await reportMachineDecision(input, verdict);
564
+ return deny(verdict.reason);
565
+ }
566
+ if (canonical === null) return deny(unreadableReason);
567
+ if (verdict.kind === "gate" && verdict.scopeReason && (canonical?.toolInputs.length ?? 0) > 1) return deny(batchScopeReason);
568
+ if (agentQuestionStrategy(ACTIVE_SOURCE, "PreToolUse", input.tool_name) === AGENT_QUESTION_STRATEGY.native) return void 0;
519
569
  if (input.tool_name === "AskUserQuestion") {
520
570
  return handleAskUserQuestion(apiKey, input, modeState);
521
571
  }
@@ -523,9 +573,10 @@ var handlePreToolUse = async (input) => {
523
573
  return handleExitPlanMode(apiKey, input);
524
574
  }
525
575
  if (await claimedBlockedActionRetry(apiKey, input)) return allow(BLOCKED_ACTION_ALLOW_REASON);
526
- if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
576
+ if (!(verdict.kind === "gate" && verdict.scopeReason) && shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
527
577
  return void 0;
528
578
  }
579
+ if (claudePromptsBeforeAnEdit(input.permission_mode) && editAsksAtTheAgentsPrompt(gateInput, verdict)) return handleTerminalOnly();
529
580
  switch (verdict.kind) {
530
581
  case "allow":
531
582
  return allow();
@@ -553,8 +604,8 @@ var toPermissionRequestOutput = (hookOutput) => {
553
604
  if (d.permissionDecision === "deny") return permReqDeny(d.permissionDecisionReason);
554
605
  return void 0;
555
606
  };
556
- var handlePermissionRequest = async (input) => {
557
- if (isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode)) return void 0;
607
+ var handlePermissionRequest = async (input, canonical) => {
608
+ if (canonical === void 0 && isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode)) return void 0;
558
609
  if (isPusharyOwnTool(input.tool_name)) return void 0;
559
610
  let apiKey;
560
611
  try {
@@ -565,14 +616,22 @@ var handlePermissionRequest = async (input) => {
565
616
  try {
566
617
  const modeState = await fetchModeState(apiKey, input.session_id);
567
618
  const policy = await getPolicy(apiKey, modeState.policyVersion, ACTIVE_AGENT.type, true, input.session_id);
568
- const verdict = resolveGate(claudeGateInput(modeState, policy, input));
619
+ const verdict = resolveGate(claudeGateInput(modeState, policy, input, canonical));
620
+ if (verdict.kind === "kill") {
621
+ await reportMachineDecision(input, verdict);
622
+ return permReqHalted(verdict.reason);
623
+ }
624
+ if (verdict.kind === "deny") {
625
+ await reportMachineDecision(input, verdict);
626
+ return permReqDeny(verdict.reason);
627
+ }
628
+ if (canonical === null) return permReqDeny(unreadableReason);
629
+ if (agentQuestionStrategy(ACTIVE_SOURCE, "PermissionRequest", input.tool_name) === AGENT_QUESTION_STRATEGY.native) return void 0;
630
+ if (verdict.kind === "gate" && verdict.scopeReason && (canonical?.toolInputs.length ?? 0) > 1) return permReqDeny(batchScopeReason);
631
+ if (!(verdict.kind === "gate" && verdict.scopeReason) && isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode)) return void 0;
569
632
  switch (verdict.kind) {
570
- case "kill":
571
- return permReqHalted(verdict.reason);
572
633
  case "allow":
573
634
  return permReqAllow();
574
- case "deny":
575
- return permReqDeny(verdict.reason);
576
635
  // No opinion, so Claude Code's own dialog shows.
577
636
  case "defer":
578
637
  return void 0;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-WPJS3OZH.js";
3
+ } from "./chunk-7IEB2GV4.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  supportedClaudeEvents
4
- } from "./chunk-OE6U2AXM.js";
4
+ } from "./chunk-A7XIL2WD.js";
5
5
  import {
6
6
  guardBinary,
7
7
  holdBudgetOf,
8
8
  isProtectedByScope,
9
9
  registerHookEntry
10
- } from "./chunk-RHLUJE4H.js";
10
+ } from "./chunk-UCGQEAKZ.js";
11
11
  import {
12
12
  HOOK_LOCATOR_BINARY,
13
13
  withHookHoldBudget
14
- } from "./chunk-WPJS3OZH.js";
14
+ } from "./chunk-7IEB2GV4.js";
15
15
 
16
16
  // src/claude-config.ts
17
17
  import { join } from "path";
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  COMPATIBLE_AGENT_TYPES,
9
9
  isValidApiKey
10
- } from "./chunk-WPJS3OZH.js";
10
+ } from "./chunk-7IEB2GV4.js";
11
11
 
12
12
  // src/setup/options.ts
13
13
  var AGENT_CHOICES = ["claude_code", "codex", "gemini_cli", "hermes", "cursor", "vscode", "opencode", ...COMPATIBLE_AGENT_TYPES, "custom"];
@@ -154,6 +154,7 @@ var AGENTS = {
154
154
  ...defaults,
155
155
  label: "Qwen Code",
156
156
  hookSource: "qwen",
157
+ bridged: true,
157
158
  policyAgent: "qwen_code",
158
159
  repoAware: true,
159
160
  disconnectable: true,
@@ -166,6 +167,7 @@ var AGENTS = {
166
167
  ...defaults,
167
168
  label: "CodeBuddy",
168
169
  hookSource: "codebuddy",
170
+ bridged: true,
169
171
  policyAgent: "codebuddy",
170
172
  repoAware: true,
171
173
  disconnectable: true,
@@ -178,6 +180,7 @@ var AGENTS = {
178
180
  ...defaults,
179
181
  label: "Qoder",
180
182
  hookSource: "qoder",
183
+ bridged: true,
181
184
  policyAgent: "qoder",
182
185
  repoAware: true,
183
186
  disconnectable: true,
@@ -191,6 +194,7 @@ var AGENTS = {
191
194
  ...defaults,
192
195
  label: "Droid",
193
196
  hookSource: "droid",
197
+ bridged: true,
194
198
  policyAgent: "droid",
195
199
  repoAware: true,
196
200
  disconnectable: true,
@@ -204,6 +208,7 @@ var AGENTS = {
204
208
  ...defaults,
205
209
  label: "Devin CLI",
206
210
  hookSource: "devin",
211
+ bridged: true,
207
212
  policyAgent: "devin",
208
213
  repoAware: true,
209
214
  disconnectable: true,
@@ -211,7 +216,7 @@ var AGENTS = {
211
216
  connectVia: "Native hooks",
212
217
  setupCommand: "npx @pushary/agent-hooks setup",
213
218
  enforcementDetail: "exec, write and edit through native PreToolUse hooks, answered in Devin's own approve/block schema.",
214
- capabilityNote: "Devin CLI answers PreToolUse with `{decision}` rather than a permission decision. That schema has no third value, so where another agent would be asked, Pushary stays silent and Devin's own prompt decides; and it cannot carry an updated tool input, so Devin's own question and plan dialogs are left alone rather than answered from the phone without the answer reaching it. It also fires no Notification event."
219
+ capabilityNote: "Devin CLI answers PreToolUse with `{decision}` rather than a permission decision. Pushary blocks an unanswered approval because this adapter cannot request a native prompt reliably. Question and plan dialogs remain with Devin; this adapter does not render their answers. It also fires no Notification event."
215
220
  },
216
221
  windsurf: {
217
222
  ...defaults,
@@ -287,7 +292,8 @@ var toToolLookup = (profile, toolName, toolInput, resolvePath = (path) => path)
287
292
  var renderPreToolUseVerdict = (profile, verdict) => {
288
293
  if (profile.wire === "permission_decision") return verdict;
289
294
  const { permissionDecision, permissionDecisionReason, updatedInput } = verdict.hookSpecificOutput;
290
- if (permissionDecision === "ask" || updatedInput) return void 0;
295
+ if (permissionDecision === "ask") return { decision: "block", reason: permissionDecisionReason ?? "Pushary requires an explicit approval before this tool can run." };
296
+ if (updatedInput) return void 0;
291
297
  const stop = verdict.continue === false ? { continue: false, ...verdict.stopReason ? { stopReason: verdict.stopReason } : {} } : {};
292
298
  return {
293
299
  decision: permissionDecision === "allow" ? "approve" : "block",
@@ -328,7 +334,9 @@ var QWEN_CODE = {
328
334
  replace: "Edit",
329
335
  read_file: "Read",
330
336
  WriteFile: "Write",
331
- ReadFile: "Read"
337
+ ReadFile: "Read",
338
+ ask_user_question: "AskUserQuestion",
339
+ exit_plan_mode: "ExitPlanMode"
332
340
  },
333
341
  extraMatchers: ["mcp__.*"],
334
342
  pathInputKeys: ["absolute_path"],
@@ -410,12 +418,14 @@ var DEVIN = {
410
418
  exec: "Bash",
411
419
  write: "Write",
412
420
  edit: "Edit",
421
+ apply_patch: "Edit",
422
+ notebook_edit: "NotebookEdit",
413
423
  read: "Read",
414
424
  webfetch: "WebFetch",
415
425
  web_search: "WebSearch",
416
426
  run_subagent: "Task"
417
427
  },
418
- extraMatchers: ["mcp__.*"],
428
+ extraMatchers: ["mcp__.*", "mcp_call_tool", "write_to_process"],
419
429
  pathInputKeys: [],
420
430
  shellCommandKeys: [],
421
431
  shellContextKeys: [],
@@ -436,6 +446,18 @@ var compatibleTargetFiles = (profile) => {
436
446
  return [...seen.values()];
437
447
  };
438
448
 
449
+ // ../contracts/src/degraded.ts
450
+ var DEGRADED_PREFIX = "[degraded]";
451
+ var degradedPayload = (subsystem, report) => ({
452
+ subsystem,
453
+ reason: report.reason,
454
+ fallback: report.fallback,
455
+ ...report.siteId ? { siteId: report.siteId } : {},
456
+ ...report.sessionId ? { sessionId: report.sessionId } : {},
457
+ ...report.event ? { event: report.event } : {}
458
+ });
459
+ var degradedLine = (subsystem, report) => `${DEGRADED_PREFIX} ${JSON.stringify(degradedPayload(subsystem, report))}`;
460
+
439
461
  // ../contracts/src/index.ts
440
462
  var KEY_SCOPES = ["full_access", "agent", "connector", "bound"];
441
463
  var normalizeKeyScope = (value) => typeof value === "string" && KEY_SCOPES.includes(value) ? value : null;
@@ -585,6 +607,8 @@ var hookWaitClamped = (startMs, policyWaitSeconds, agent, nowMs) => startMs + ho
585
607
  var effectiveWaitSeconds = (timeoutAction, policySeconds, agent) => timeoutAction === "wait" ? hookMaxWaitSeconds(agent) : policySeconds;
586
608
  var OPENCODE_APPROVAL_EVENT = "permission.ask";
587
609
  var CURSOR_GATE_EVENTS = ["beforeShellExecution", "beforeMCPExecution", "preToolUse"];
610
+ var SUPPORT_EMAIL = "business@pushary.com";
611
+ var SUPPORT_DISCORD = "https://discord.gg/9v3VvUByrr";
588
612
  var HOOK_LOCATOR_BINARY = "pushary-bridge";
589
613
  var HOOK_BATCH_MAX_BYTES = 4 * 1024 * 1024;
590
614
  var REPO_KEY_MAX_LENGTH = 200;
@@ -718,6 +742,7 @@ var shellCommandSegments = (command) => {
718
742
  index += 1;
719
743
  continue;
720
744
  }
745
+ if (quote === SHELL_DOUBLE_QUOTE && (char === "`" || char === SHELL_SUBSTITUTION_SIGIL && next === SHELL_GROUP_OPEN)) return null;
721
746
  if (char === quote) quote = null;
722
747
  current += char;
723
748
  continue;
@@ -1066,7 +1091,7 @@ var verdictForPolicy = (policy) => {
1066
1091
  if (isAutoApprove(policy)) return "allow";
1067
1092
  return "requires_human";
1068
1093
  };
1069
- var policyIntent = (policy) => policy.timeoutAction === "deny" ? PATTERN_INTENT.restrict : PATTERN_INTENT.grant;
1094
+ var policyIntent = (policy) => isAutoApprove(policy) ? PATTERN_INTENT.grant : PATTERN_INTENT.restrict;
1070
1095
  var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
1071
1096
  let best;
1072
1097
  let bestWeight = 0;
@@ -1326,6 +1351,10 @@ var ANSWER_SOURCES = [
1326
1351
  ];
1327
1352
  var isAnswerSource = (value) => typeof value === "string" && ANSWER_SOURCES.includes(value);
1328
1353
  var SETTLED_OUTCOMES = ["approve", "deny"];
1354
+ var SETTLED_OUTCOME = {
1355
+ approve: "approve",
1356
+ deny: "deny"
1357
+ };
1329
1358
  var isSettledOutcome = (value) => typeof value === "string" && SETTLED_OUTCOMES.includes(value);
1330
1359
  var classifyDecisionRisk = (toolName, toolTarget) => {
1331
1360
  const parts = [toolName, toolTarget ?? ""];
@@ -1603,6 +1632,10 @@ var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repo
1603
1632
  };
1604
1633
  var isModeStateDegraded = (state) => state?.degraded === true;
1605
1634
  var KILL_REASON = "Stopped by user, this agent was halted from Pushary";
1635
+ var GATE_ALLOW_SOURCE = {
1636
+ allowlist: "allowlist",
1637
+ policy: "policy"
1638
+ };
1606
1639
  var PUSHARY_TOOL_PREFIX = "mcp__pushary__";
1607
1640
  var isPusharyOwnTool = (toolName) => toolName.startsWith(PUSHARY_TOOL_PREFIX);
1608
1641
  var strictestScopeVerdict = (scope, paths) => {
@@ -1631,7 +1664,15 @@ var shouldDeferToNativeMode = (permissionMode, toolName, options = {}) => {
1631
1664
  if (permissionMode === "bypassPermissions") return options.deferBypass === true;
1632
1665
  return false;
1633
1666
  };
1634
- var ASKED_IN_EVERY_MODE_TOOLS = ["AskUserQuestion", "ExitPlanMode"];
1667
+ var AGENT_QUESTION_TOOL = "AskUserQuestion";
1668
+ var AGENT_QUESTION_STRATEGY = { native: "native_dialog", updatedInput: "updated_input", none: "not_a_question" };
1669
+ var UPDATED_INPUT_QUESTION_SOURCES = /* @__PURE__ */ new Set(["claude", "qoder", "codebuddy", "droid"]);
1670
+ function agentQuestionStrategy(source, event, toolName) {
1671
+ if (toolName !== AGENT_QUESTION_TOOL) return AGENT_QUESTION_STRATEGY.none;
1672
+ if (source !== null && event === "PreToolUse" && UPDATED_INPUT_QUESTION_SOURCES.has(source)) return AGENT_QUESTION_STRATEGY.updatedInput;
1673
+ return AGENT_QUESTION_STRATEGY.native;
1674
+ }
1675
+ var ASKED_IN_EVERY_MODE_TOOLS = [AGENT_QUESTION_TOOL, "ExitPlanMode"];
1635
1676
  var ASKED_IN_EVERY_MODE = new Set(ASKED_IN_EVERY_MODE_TOOLS);
1636
1677
  var isAskedInEveryMode = (toolName) => ASKED_IN_EVERY_MODE.has(toolName);
1637
1678
  var NATIVE_BLOCK_EVENTS = /* @__PURE__ */ new Set(["PermissionDenied"]);
@@ -1710,6 +1751,8 @@ var resolveGate = (input) => {
1710
1751
  scopePath: scopeReason && "path" in scopeVerdict ? widenablePath(scopeVerdict.path) : void 0
1711
1752
  };
1712
1753
  };
1754
+ var editAsksAtTheAgentsPrompt = (input, verdict) => verdict.kind === "gate" && !verdict.scopeReason && (input.modeState.mode ?? input.config.modeOverride ?? null) === null && verdict.policy.origin === "builtin" && verdict.policy.mode === "push_first" && ACCEPT_EDITS_DEFER_TOOLS.has(input.toolName);
1755
+ var claudePromptsBeforeAnEdit = (permissionMode) => permissionMode === "default";
1713
1756
  var toPolicyVersion = (value) => typeof value === "string" || typeof value === "number" ? String(value) : null;
1714
1757
  var modeStateFromResponse = (data) => {
1715
1758
  const mode = data.override?.mode;
@@ -1729,6 +1772,42 @@ var modeStateFromResponse = (data) => {
1729
1772
  var GATE_WIRE = 1;
1730
1773
  var GATE_MAX_TOOL_INPUTS = 32;
1731
1774
  var GATE_BLOCK_REASON_MAX_LENGTH = 200;
1775
+ var wireVerdictFor = (verdict, deadlineSeconds) => {
1776
+ switch (verdict.kind) {
1777
+ case "kill":
1778
+ return { kind: "kill", reason: verdict.reason };
1779
+ case "allow":
1780
+ return { kind: "allow", source: verdict.source, policy: verdict.policy };
1781
+ case "deny":
1782
+ return { kind: "deny", reason: verdict.reason, policy: verdict.policy };
1783
+ case "defer":
1784
+ return { kind: "no_opinion", reason: verdict.reason === "own_tool" ? "not_gated" : "degraded" };
1785
+ case "gate":
1786
+ return {
1787
+ kind: "ask",
1788
+ policy: verdict.policy,
1789
+ deadlineSeconds,
1790
+ ...verdict.scopeReason ? { scopeReason: verdict.scopeReason } : {},
1791
+ ...verdict.scopePath ? { scopePath: verdict.scopePath } : {}
1792
+ };
1793
+ }
1794
+ };
1795
+ var machineDecisionFor = (verdict) => {
1796
+ switch (verdict.kind) {
1797
+ case "kill":
1798
+ return { origin: DECISION_ORIGIN.killSwitch, outcome: SETTLED_OUTCOME.deny };
1799
+ case "allow":
1800
+ return {
1801
+ origin: verdict.source === GATE_ALLOW_SOURCE.allowlist ? DECISION_ORIGIN.safeReadonly : DECISION_ORIGIN.autopilot,
1802
+ outcome: SETTLED_OUTCOME.approve
1803
+ };
1804
+ case "deny":
1805
+ return { origin: DECISION_ORIGIN.autopilot, outcome: SETTLED_OUTCOME.deny };
1806
+ case "ask":
1807
+ case "no_opinion":
1808
+ return null;
1809
+ }
1810
+ };
1732
1811
  var claudePermissionDeniedOutput = (decision) => ({
1733
1812
  hookSpecificOutput: {
1734
1813
  hookEventName: "PermissionDenied",
@@ -1789,7 +1868,6 @@ var claudePermissionRequestOutput = (decision, reason, updatedInput) => ({
1789
1868
  decision: decision === "allow" ? { behavior: "allow", ...updatedInput ? { updatedInput } : {} } : { behavior: "deny", ...reason ? { message: reason } : {} }
1790
1869
  }
1791
1870
  });
1792
- var COMPATIBLE_GATE_EVENT = "PreToolUse";
1793
1871
  var gateOutputFor = (source, event, decision, reason) => {
1794
1872
  let output;
1795
1873
  if (source === "claude" || source === "vscode") {
@@ -1829,8 +1907,12 @@ var gateOutputFor = (source, event, decision, reason) => {
1829
1907
  };
1830
1908
  } else {
1831
1909
  const profile = hookCompatibleProfileBySource(source);
1832
- if (profile && event === COMPATIBLE_GATE_EVENT) {
1833
- output = renderPreToolUseVerdict(profile, claudePreToolUseOutput(decision, reason));
1910
+ if (profile?.events.includes(event)) {
1911
+ if (event === "PreToolUse") {
1912
+ output = renderPreToolUseVerdict(profile, claudePreToolUseOutput(decision, reason));
1913
+ } else if (event === "PermissionRequest" && decision !== "ask") {
1914
+ output = claudePermissionRequestOutput(decision, reason);
1915
+ }
1834
1916
  }
1835
1917
  }
1836
1918
  return output;
@@ -1999,6 +2081,7 @@ export {
1999
2081
  hookCompatibleProfile,
2000
2082
  hookCompatibleProfileBySource,
2001
2083
  compatibleTargetFiles,
2084
+ degradedLine,
2002
2085
  hookWritePath,
2003
2086
  isHookActivationReport,
2004
2087
  hookActivationResult,
@@ -2018,6 +2101,8 @@ export {
2018
2101
  effectiveWaitSeconds,
2019
2102
  OPENCODE_APPROVAL_EVENT,
2020
2103
  CURSOR_GATE_EVENTS,
2104
+ SUPPORT_EMAIL,
2105
+ SUPPORT_DISCORD,
2021
2106
  HOOK_LOCATOR_BINARY,
2022
2107
  normalizeRepoRemote,
2023
2108
  localRepoKey,
@@ -2036,6 +2121,8 @@ export {
2036
2121
  KILL_REASON,
2037
2122
  isPusharyOwnTool,
2038
2123
  shouldDeferToNativeMode,
2124
+ AGENT_QUESTION_STRATEGY,
2125
+ agentQuestionStrategy,
2039
2126
  CODEX_RESCUE_TOOL_NAME,
2040
2127
  isBlockedActionRequest,
2041
2128
  BLOCKED_ACTION_GRANT_TTL_SECONDS,
@@ -2044,10 +2131,14 @@ export {
2044
2131
  isPermissionRequestOwnedByPreToolUse,
2045
2132
  CLAUDE_PRETOOLUSE_MATCHER,
2046
2133
  resolveGate,
2134
+ editAsksAtTheAgentsPrompt,
2135
+ claudePromptsBeforeAnEdit,
2047
2136
  modeStateFromResponse,
2048
2137
  GATE_WIRE,
2049
2138
  GATE_MAX_TOOL_INPUTS,
2050
2139
  GATE_BLOCK_REASON_MAX_LENGTH,
2140
+ wireVerdictFor,
2141
+ machineDecisionFor,
2051
2142
  claudePermissionDeniedOutput,
2052
2143
  codexStopRescueOutput,
2053
2144
  stoppedOutput,