@pushary/agent-hooks 0.83.1 → 0.84.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 (64) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/bin/pushary-bell-hook.js +5 -5
  3. package/dist/bin/pushary-bell.js +15 -14
  4. package/dist/bin/pushary-claude.js +72 -131
  5. package/dist/bin/pushary-clean.js +13 -12
  6. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  7. package/dist/bin/pushary-codex-bridge.js +348 -0
  8. package/dist/bin/pushary-codex-hook.js +15 -14
  9. package/dist/bin/pushary-codex.js +7 -7
  10. package/dist/bin/pushary-connect.js +13 -12
  11. package/dist/bin/pushary-daemon.js +313 -41
  12. package/dist/bin/pushary-doctor.js +48 -25
  13. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  14. package/dist/bin/pushary-gemini-bridge.js +298 -0
  15. package/dist/bin/pushary-gemini-hook.js +24 -20
  16. package/dist/bin/pushary-hook.js +14 -13
  17. package/dist/bin/pushary-login.js +15 -14
  18. package/dist/bin/pushary-logout.js +12 -11
  19. package/dist/bin/pushary-mode.js +10 -9
  20. package/dist/bin/pushary-notification-hook.js +5 -5
  21. package/dist/bin/pushary-permission-denied-hook.js +9 -9
  22. package/dist/bin/pushary-permission-hook.js +9 -9
  23. package/dist/bin/pushary-post-hook.js +5 -5
  24. package/dist/bin/pushary-prompt-hook.js +5 -5
  25. package/dist/bin/pushary-session-end-hook.js +5 -5
  26. package/dist/bin/pushary-session-start-hook.js +5 -5
  27. package/dist/bin/pushary-setup.js +42 -36
  28. package/dist/bin/pushary-stats.js +6 -5
  29. package/dist/bin/pushary-status.js +17 -16
  30. package/dist/bin/pushary-stop-hook.js +5 -5
  31. package/dist/bin/pushary-stopfailure-hook.js +5 -5
  32. package/dist/bin/pushary-suggestions.js +7 -6
  33. package/dist/bin/pushary-upgrade.js +18 -17
  34. package/dist/bin/pushary-wait.js +10 -9
  35. package/dist/bin/pushary.js +8 -6
  36. package/dist/{chunk-FFT7L2VE.js → chunk-37AD5K7W.js} +11 -11
  37. package/dist/{chunk-BT7NTTIW.js → chunk-437T77TY.js} +3 -3
  38. package/dist/{chunk-7GTDHV7O.js → chunk-6LC4WSCL.js} +1 -1
  39. package/dist/{chunk-UAWTJMXA.js → chunk-7PZPJSWI.js} +1 -1
  40. package/dist/chunk-CCH775Y6.js +343 -0
  41. package/dist/chunk-DINDL2CF.js +45 -0
  42. package/dist/{chunk-DMKSHZKF.js → chunk-DOXSWT4Q.js} +1 -1
  43. package/dist/chunk-EVJ6ULHN.js +153 -0
  44. package/dist/{chunk-CXGJTN74.js → chunk-F52SIIDG.js} +4 -4
  45. package/dist/{chunk-3JDIGKPV.js → chunk-FBGBJXVC.js} +1 -1
  46. package/dist/{chunk-V6MCBCUC.js → chunk-HZNOAJGB.js} +2 -2
  47. package/dist/{chunk-RRZZ7TFJ.js → chunk-J4KFVOCQ.js} +4 -2
  48. package/dist/{chunk-RZP3Z2WR.js → chunk-K7CSRGKZ.js} +4 -4
  49. package/dist/{chunk-ZUKH2NPJ.js → chunk-MGJBC6DD.js} +1 -44
  50. package/dist/{chunk-GQLB2GBJ.js → chunk-MHBC2XUP.js} +1 -1
  51. package/dist/{chunk-QA5HP7CR.js → chunk-MQMPG5X4.js} +59 -3
  52. package/dist/{chunk-2R63XKLG.js → chunk-NJ7JT26G.js} +1 -1
  53. package/dist/chunk-NRGRUOAR.js +25 -0
  54. package/dist/chunk-NWYGW7HY.js +117 -0
  55. package/dist/{chunk-C4SQY777.js → chunk-OPMSMF5R.js} +42 -63
  56. package/dist/{chunk-E7YX7CTM.js → chunk-QNBA5OXQ.js} +81 -25
  57. package/dist/{chunk-O55DONTG.js → chunk-SEAHGHCE.js} +1 -1
  58. package/dist/{chunk-U752S5RE.js → chunk-UKSQWTBH.js} +1 -1
  59. package/dist/{chunk-HKKERYZP.js → chunk-UXWEE3QI.js} +1 -1
  60. package/dist/{chunk-MUEW424A.js → chunk-VT4IVERX.js} +50 -3
  61. package/dist/chunk-YJCMI5GF.js +133 -0
  62. package/dist/src/index.d.ts +6 -3
  63. package/dist/src/index.js +9 -9
  64. package/package.json +3 -1
@@ -1,6 +1,30 @@
1
1
  // ../contracts/src/index.ts
2
2
  var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
3
3
  var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
4
+ var parseAgentQuestionAnswers = (questions, value) => {
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(value);
8
+ } catch {
9
+ return null;
10
+ }
11
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
12
+ const answers = parsed;
13
+ if (Object.keys(answers).length !== questions.length) return null;
14
+ for (const question of questions) {
15
+ const answer = answers[question.question];
16
+ if (typeof answer !== "string" || answer.trim().length === 0) return null;
17
+ if (!question.multiSelect) continue;
18
+ try {
19
+ const selected = JSON.parse(answer);
20
+ if (!Array.isArray(selected)) continue;
21
+ const labels = new Set(question.options.map((option) => option.label));
22
+ if (selected.length === 0 || new Set(selected).size !== selected.length || !selected.every((label) => typeof label === "string" && labels.has(label))) return null;
23
+ } catch {
24
+ }
25
+ }
26
+ return answers;
27
+ };
4
28
  var INSTALL_SOURCES = [
5
29
  /** `npx @pushary/agent-hooks@latest ...`, the documented one-liner. */
6
30
  "npx",
@@ -447,6 +471,36 @@ var isSafeReadOnlyCommand = (command) => {
447
471
  }
448
472
  return true;
449
473
  };
474
+ var isAutoApprove = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
475
+ var isAutoDeny = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
476
+ var verdictForPolicy = (policy) => {
477
+ if (isAutoDeny(policy)) return "deny";
478
+ if (isAutoApprove(policy)) return "allow";
479
+ return "requires_human";
480
+ };
481
+ var selectGoverningRule = (policies, toolName, args, repoKey, sessionId) => {
482
+ let best;
483
+ let bestWeight = 0;
484
+ let bestScoped = -1;
485
+ let bestLength = -1;
486
+ for (const candidate of policies) {
487
+ if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
488
+ if (candidate.sessionId && candidate.sessionId !== sessionId) continue;
489
+ const rank = matchToolPatternAny(candidate.tool, toolName, args);
490
+ if (rank === "none") continue;
491
+ const weight = matchRankWeight(rank);
492
+ const scoped = candidate.sessionId ? 2 : candidate.repoKey ? 1 : 0;
493
+ const length = rank === "prefix" ? candidate.tool.length : -1;
494
+ const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
495
+ if (better) {
496
+ best = { policy: candidate, rank };
497
+ bestWeight = weight;
498
+ bestScoped = scoped;
499
+ bestLength = length;
500
+ }
501
+ }
502
+ return best;
503
+ };
450
504
  var API_KEY_PATTERN = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
451
505
  var isValidApiKey = (value) => API_KEY_PATTERN.test(value);
452
506
  var ACTION_BODY_MAX = 4e3;
@@ -782,18 +836,20 @@ var isScopeContract = (value) => {
782
836
 
783
837
  export {
784
838
  isApprovalMode,
839
+ parseAgentQuestionAnswers,
785
840
  normalizeInstallSource,
786
841
  HOOK_BUDGETS,
787
842
  hookWaitDeadline,
788
843
  hookWaitClamped,
789
844
  effectiveWaitSeconds,
790
- isRepoKeyMatch,
791
845
  normalizeRepoRemote,
792
846
  localRepoKey,
793
- matchRankWeight,
794
847
  policyArgForms,
795
- matchToolPatternAny,
796
848
  isSafeReadOnlyCommand,
849
+ isAutoApprove,
850
+ isAutoDeny,
851
+ verdictForPolicy,
852
+ selectGoverningRule,
797
853
  isValidApiKey,
798
854
  ACTION_BODY_MAX,
799
855
  DECISION_LINE_MAX,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSafeReadOnlyCommand
3
- } from "./chunk-QA5HP7CR.js";
3
+ } from "./chunk-MQMPG5X4.js";
4
4
 
5
5
  // src/ledger.ts
6
6
  import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
@@ -0,0 +1,25 @@
1
+ // src/live-capabilities.ts
2
+ var CODEX_BRIDGE_CAPABILITY = "codex-bridge";
3
+ var CODEX_RESUME_CAPABILITY = "codex-resume";
4
+ var GEMINI_BRIDGE_CAPABILITY = "gemini-bridge";
5
+ var GEMINI_RESUME_CAPABILITY = "gemini-resume";
6
+ var GEMINI_BRIDGE_MIN_VERSION = [0, 40, 0];
7
+ var version = (raw) => {
8
+ const match = raw?.match(/^(\d+)\.(\d+)\.(\d+)/);
9
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
10
+ };
11
+ var geminiLiveCapabilities = (rawVersion) => {
12
+ const installed = version(rawVersion);
13
+ if (!installed) return [];
14
+ for (let index = 0; index < GEMINI_BRIDGE_MIN_VERSION.length; index++) {
15
+ if (installed[index] > GEMINI_BRIDGE_MIN_VERSION[index]) break;
16
+ if (installed[index] < GEMINI_BRIDGE_MIN_VERSION[index]) return [];
17
+ }
18
+ return [GEMINI_BRIDGE_CAPABILITY, GEMINI_RESUME_CAPABILITY];
19
+ };
20
+
21
+ export {
22
+ CODEX_BRIDGE_CAPABILITY,
23
+ CODEX_RESUME_CAPABILITY,
24
+ geminiLiveCapabilities
25
+ };
@@ -0,0 +1,117 @@
1
+ import {
2
+ getBaseUrl
3
+ } from "./chunk-2UMNXADU.js";
4
+ import {
5
+ redactSecrets
6
+ } from "./chunk-MQMPG5X4.js";
7
+
8
+ // src/crypto.ts
9
+ import nacl from "tweetnacl";
10
+ var EPH_PUB_LEN = nacl.box.publicKeyLength;
11
+ var BOX_NONCE_LEN = nacl.box.nonceLength;
12
+ var SECRETBOX_NONCE_LEN = nacl.secretbox.nonceLength;
13
+ var SESSION_KEY_LEN = nacl.secretbox.keyLength;
14
+ var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
15
+ var fromBase64 = (value) => new Uint8Array(Buffer.from(value, "base64"));
16
+ var utf8Encode = (text) => new Uint8Array(Buffer.from(text, "utf-8"));
17
+ var decodePublicKey = (value) => fromBase64(value);
18
+ var generateSessionKey = () => nacl.randomBytes(SESSION_KEY_LEN);
19
+ var wrapSessionKey = (sessionKey, accountPublicKey) => {
20
+ const ephemeral = nacl.box.keyPair();
21
+ const nonce = nacl.randomBytes(BOX_NONCE_LEN);
22
+ const box = nacl.box(sessionKey, nonce, accountPublicKey, ephemeral.secretKey);
23
+ const bundle = new Uint8Array(EPH_PUB_LEN + BOX_NONCE_LEN + box.length);
24
+ bundle.set(ephemeral.publicKey, 0);
25
+ bundle.set(nonce, EPH_PUB_LEN);
26
+ bundle.set(box, EPH_PUB_LEN + BOX_NONCE_LEN);
27
+ return toBase64(bundle);
28
+ };
29
+ var encryptRecord = (plaintext, sessionKey) => {
30
+ const nonce = nacl.randomBytes(SECRETBOX_NONCE_LEN);
31
+ const box = nacl.secretbox(utf8Encode(plaintext), nonce, sessionKey);
32
+ const bundle = new Uint8Array(SECRETBOX_NONCE_LEN + box.length);
33
+ bundle.set(nonce, 0);
34
+ bundle.set(box, SECRETBOX_NONCE_LEN);
35
+ return toBase64(bundle);
36
+ };
37
+
38
+ // src/transcript-capture.ts
39
+ import { createHash } from "crypto";
40
+ var INTERNAL_TYPES = /* @__PURE__ */ new Set([
41
+ "file-history-snapshot",
42
+ "change",
43
+ "queue-operation",
44
+ "summary"
45
+ ]);
46
+ var redactDeep = (value) => {
47
+ if (typeof value === "string") return redactSecrets(value);
48
+ if (Array.isArray(value)) return value.map(redactDeep);
49
+ if (value && typeof value === "object") {
50
+ const out = {};
51
+ for (const [key, inner] of Object.entries(value)) {
52
+ out[key] = redactDeep(inner);
53
+ }
54
+ return out;
55
+ }
56
+ return value;
57
+ };
58
+ var localIdFor = (parsed, rawLine) => typeof parsed.uuid === "string" && parsed.uuid.length > 0 ? parsed.uuid : createHash("sha256").update(rawLine).digest("hex").slice(0, 32);
59
+ var buildTranscriptRecords = (lines, sessionKey, seen = /* @__PURE__ */ new Set()) => {
60
+ const records = [];
61
+ const nextSeen = new Set(seen);
62
+ for (const line of lines) {
63
+ const trimmed = line.trim();
64
+ if (!trimmed) continue;
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(trimmed);
68
+ } catch {
69
+ continue;
70
+ }
71
+ const type = typeof parsed.type === "string" ? parsed.type : "";
72
+ if (INTERNAL_TYPES.has(type)) continue;
73
+ const localId = localIdFor(parsed, trimmed);
74
+ if (nextSeen.has(localId)) continue;
75
+ nextSeen.add(localId);
76
+ const content = encryptRecord(JSON.stringify(redactDeep(parsed)), sessionKey);
77
+ records.push({ localId, content });
78
+ }
79
+ return { records, seen: nextSeen };
80
+ };
81
+
82
+ // src/transcript-api.ts
83
+ var fetchAccountPublicKey = async (apiKey, baseUrl = getBaseUrl()) => {
84
+ try {
85
+ const res = await fetch(`${baseUrl}/api/agent/encryption-account`, {
86
+ headers: { Authorization: `Bearer ${apiKey}` },
87
+ signal: AbortSignal.timeout(5e3)
88
+ });
89
+ if (!res.ok) return null;
90
+ const data = await res.json();
91
+ return typeof data.publicKey === "string" && data.publicKey.length > 0 ? data.publicKey : null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ };
96
+ var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) => {
97
+ try {
98
+ const res = await fetch(`${baseUrl}/api/agent/transcript`, {
99
+ method: "POST",
100
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
101
+ body: JSON.stringify(params),
102
+ signal: AbortSignal.timeout(15e3)
103
+ });
104
+ return res.ok;
105
+ } catch {
106
+ return false;
107
+ }
108
+ };
109
+
110
+ export {
111
+ decodePublicKey,
112
+ generateSessionKey,
113
+ wrapSessionKey,
114
+ buildTranscriptRecords,
115
+ fetchAccountPublicKey,
116
+ uploadTranscriptRecords
117
+ };
@@ -1,3 +1,6 @@
1
+ import {
2
+ getMachineId
3
+ } from "./chunk-RN3NOEJF.js";
1
4
  import {
2
5
  callMcpTool,
3
6
  withRetry
@@ -7,30 +10,27 @@ import {
7
10
  } from "./chunk-SAF6HGAA.js";
8
11
  import {
9
12
  reportedInstallSource
10
- } from "./chunk-BT7NTTIW.js";
13
+ } from "./chunk-437T77TY.js";
14
+ import {
15
+ getApiKey,
16
+ getBaseUrl
17
+ } from "./chunk-2UMNXADU.js";
11
18
  import {
12
19
  ACTION_BODY_MAX,
13
20
  DECISION_LINE_MAX,
14
21
  classifyDecisionRisk,
15
22
  isApprovalMode,
16
- isRepoKeyMatch,
23
+ isAutoApprove,
24
+ isAutoDeny,
17
25
  isSafeReadOnlyCommand,
18
26
  isScopeContract,
19
27
  localRepoKey,
20
- matchRankWeight,
21
- matchToolPatternAny,
22
28
  normalizeRepoRemote,
23
29
  policyArgForms,
24
30
  redactSecrets,
25
- redactSecretsDeep
26
- } from "./chunk-QA5HP7CR.js";
27
- import {
28
- getMachineId
29
- } from "./chunk-RN3NOEJF.js";
30
- import {
31
- getApiKey,
32
- getBaseUrl
33
- } from "./chunk-2UMNXADU.js";
31
+ redactSecretsDeep,
32
+ selectGoverningRule
33
+ } from "./chunk-MQMPG5X4.js";
34
34
 
35
35
  // src/policy.ts
36
36
  import { createHash } from "crypto";
@@ -67,12 +67,14 @@ var policyCacheFile = (apiKey, agent, repoAware = false) => {
67
67
  const hash = createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
68
68
  return join(tmpdir(), `pushary-policy-${hash}.json`);
69
69
  };
70
- var fetchPolicy = async (apiKey, agent, repoAware = false) => {
70
+ var fetchPolicy = async (apiKey, agent, repoAware = false, sessionId) => {
71
71
  return withRetry(async () => {
72
72
  const baseUrl = getBaseUrl();
73
73
  const params = new URLSearchParams();
74
74
  if (agent) params.set("agent", agent);
75
75
  if (repoAware) params.set("repoAware", "1");
76
+ params.set("scopeAware", "1");
77
+ if (sessionId) params.set("sessionId", sessionId);
76
78
  const query = params.toString();
77
79
  const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
78
80
  const response = await fetch(url, {
@@ -87,7 +89,7 @@ var fetchPolicy = async (apiKey, agent, repoAware = false) => {
87
89
  return raw;
88
90
  }, { maxAttempts: 2 });
89
91
  };
90
- var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
92
+ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessionId) => {
91
93
  const path = policyCacheFile(apiKey, agent, repoAware);
92
94
  let staleCache = null;
93
95
  if (existsSync(path)) {
@@ -96,19 +98,20 @@ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
96
98
  const cached = JSON.parse(stat);
97
99
  if (!isPolicyConfig(cached)) throw new Error("Corrupted cache");
98
100
  const versionStale = expectedVersion != null && (cached._policyVersion ?? null) !== expectedVersion;
101
+ const sessionStale = (cached._sessionId ?? null) !== (sessionId ?? null);
99
102
  const age = cached._cachedAt ? Date.now() - cached._cachedAt : 0;
100
103
  const ttlFresh = !cached._cachedAt || age < CACHE_TTL_MS;
101
- if (ttlFresh && !versionStale) {
104
+ if (ttlFresh && !versionStale && !sessionStale) {
102
105
  return cached;
103
106
  }
104
- if (age < STALE_CACHE_MAX_MS) staleCache = cached;
107
+ if (!sessionStale && age < STALE_CACHE_MAX_MS) staleCache = cached;
105
108
  } catch {
106
109
  }
107
110
  }
108
111
  try {
109
- const policy = await fetchPolicy(apiKey, agent, repoAware);
112
+ const policy = await fetchPolicy(apiKey, agent, repoAware, sessionId);
110
113
  try {
111
- writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null }), { encoding: "utf-8", mode: 384 });
114
+ writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null, _sessionId: sessionId ?? null }), { encoding: "utf-8", mode: 384 });
112
115
  } catch {
113
116
  }
114
117
  return policy;
@@ -117,30 +120,6 @@ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false) => {
117
120
  throw new Error("Failed to fetch policy and no cached policy available");
118
121
  }
119
122
  };
120
- var findBestMatch = (policies, toolName, args, repoKey) => {
121
- let best;
122
- let bestWeight = 0;
123
- let bestScoped = -1;
124
- let bestLength = -1;
125
- for (const candidate of policies) {
126
- if (!isRepoKeyMatch(candidate.repoKey, repoKey)) continue;
127
- const rank = matchToolPatternAny(candidate.tool, toolName, args);
128
- if (rank === "none") continue;
129
- const weight = matchRankWeight(rank);
130
- const scoped = candidate.repoKey ? 1 : 0;
131
- const length = rank === "prefix" ? candidate.tool.length : -1;
132
- const better = weight > bestWeight || weight === bestWeight && scoped > bestScoped || weight === bestWeight && scoped === bestScoped && length > bestLength;
133
- if (better) {
134
- best = { policy: candidate, rank };
135
- bestWeight = weight;
136
- bestScoped = scoped;
137
- bestLength = length;
138
- }
139
- }
140
- return best;
141
- };
142
- var isAutoApprove = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "approve";
143
- var isAutoDeny = (policy) => policy.timeoutSeconds === 0 && policy.timeoutAction === "deny";
144
123
  var autoApprove = (tool) => ({
145
124
  tool,
146
125
  timeoutSeconds: 0,
@@ -148,20 +127,20 @@ var autoApprove = (tool) => ({
148
127
  mode: "terminal_only",
149
128
  pushFirstSeconds: 0
150
129
  });
151
- var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey) => {
130
+ var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessionId) => {
152
131
  const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
153
132
  const arg = args[0];
154
- const match = findBestMatch(config.policies, toolName, args, repoKey);
133
+ const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
155
134
  const governedBySpecificRule = match?.rank === "exact" || match?.rank === "prefix";
156
135
  if (!governedBySpecificRule && toolName === "Bash" && typeof arg === "string" && isSafeReadOnlyCommand(arg)) {
157
136
  return "safe_readonly";
158
137
  }
159
138
  return "policy_timeout";
160
139
  };
161
- var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey) => {
140
+ var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
162
141
  const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
163
142
  const arg = args[0];
164
- const match = findBestMatch(config.policies, toolName, args, repoKey);
143
+ const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
165
144
  let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
166
145
  tool: toolName,
167
146
  timeoutSeconds: config.defaultTimeoutSeconds,
@@ -196,9 +175,9 @@ var restraintRank = (policy) => {
196
175
  return policy.mode === "terminal_only" || policy.mode === "notify_only" ? 1 : 2;
197
176
  };
198
177
  var strictestPolicy = (a, b) => restraintRank(b) > restraintRank(a) ? b : a;
199
- var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey) => {
200
- if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey);
201
- return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey)).reduce(strictestPolicy);
178
+ var resolvePolicyAcross = (config, toolName, modeOverride, toolInputs, cwd, repoKey, sessionId) => {
179
+ if (toolInputs.length === 0) return resolvePolicy(config, toolName, modeOverride, void 0, cwd, repoKey, sessionId);
180
+ return toolInputs.map((input) => resolvePolicy(config, toolName, modeOverride, input, cwd, repoKey, sessionId)).reduce(strictestPolicy);
202
181
  };
203
182
  var isModeStateDegraded = (state) => state?.degraded === true;
204
183
  var MODE_GRACE_MS = 60 * 1e3;
@@ -954,12 +933,13 @@ var notifyLateAnswers = async (apiKey, late, agentName, sessionId) => {
954
933
  };
955
934
  var CLAUDE_CODE_AGENT = { type: "claude_code", label: "Claude Code" };
956
935
  var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
957
- var readFreshCachedPolicy = (apiKey, agentType) => {
936
+ var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
958
937
  const request = policyRequestFor(agentType);
959
938
  const path = policyCacheFile(apiKey, request.agent, request.repoAware);
960
939
  if (!existsSync5(path)) return null;
961
940
  const cached = JSON.parse(readFileSync4(path, "utf-8"));
962
941
  if (!isPolicyConfig(cached)) return null;
942
+ if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
963
943
  if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
964
944
  return cached;
965
945
  };
@@ -967,13 +947,13 @@ var autoDecisionEnabled = () => {
967
947
  const flag = process.env.PUSHARY_AUTO_DECISION_BEACON;
968
948
  return flag === "1" || flag === "true";
969
949
  };
970
- var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
950
+ var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd, sessionId) => {
971
951
  try {
972
952
  if (liveMode.kill) return "terminal";
973
953
  if (!autoDecisionEnabled()) return void 0;
974
- const policy = readFreshCachedPolicy(getApiKey(), agentType);
954
+ const policy = readFreshCachedPolicy(getApiKey(), agentType, sessionId);
975
955
  if (!policy) return void 0;
976
- const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
956
+ const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd), sessionId);
977
957
  if (isAutoApprove(resolved)) return "policy_auto";
978
958
  if (resolved.mode === "push_only" || resolved.mode === "push_first") return "human";
979
959
  return "terminal";
@@ -981,16 +961,16 @@ var deriveDecisionSource = (toolName, toolInput, liveMode, agentType, cwd) => {
981
961
  return void 0;
982
962
  }
983
963
  };
984
- var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd) => {
964
+ var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd, sessionId) => {
985
965
  try {
986
966
  if (liveMode.kill) return void 0;
987
967
  if (!autoDecisionEnabled()) return void 0;
988
- const policy = readFreshCachedPolicy(getApiKey(), agentType);
968
+ const policy = readFreshCachedPolicy(getApiKey(), agentType, sessionId);
989
969
  if (!policy) return void 0;
990
- const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd));
970
+ const resolved = resolvePolicy(policy, toolName, liveMode.mode, toolInput, cwd, repoKeyFor(cwd), sessionId);
991
971
  if (resolved.timeoutSeconds !== 0 || resolved.timeoutAction !== "approve") return void 0;
992
972
  return {
993
- decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd)),
973
+ decisionOrigin: resolveAutoResolveOrigin(policy, toolName, toolInput, cwd, repoKeyFor(cwd), sessionId),
994
974
  toolName,
995
975
  toolTarget: deriveToolTarget(toolName, toolInput)
996
976
  };
@@ -1033,7 +1013,7 @@ var reportEvent = async (event, options = {}) => {
1033
1013
  machineId: event.machineId ?? getMachineId(),
1034
1014
  // Lets the phone's Stop end this process instead of only denying its next
1035
1015
  // tool call. Ours alone: never another process, never a parent.
1036
- pid: event.pid ?? process.pid,
1016
+ pid: event.pid,
1037
1017
  repoKey: event.repoKey ?? repoKeyFor(),
1038
1018
  installMode: detectInstallMode(),
1039
1019
  // How the binary was INVOKED this time and which front door produced the
@@ -1055,6 +1035,7 @@ var reportEvent = async (event, options = {}) => {
1055
1035
  }),
1056
1036
  signal: AbortSignal.timeout(options.timeoutMs ?? 1e4)
1057
1037
  });
1038
+ if (options.requireOk && !res.ok) throw new Error(`/api/agent/event rejected (${res.status})`);
1058
1039
  try {
1059
1040
  return await res.json();
1060
1041
  } catch {
@@ -1080,8 +1061,8 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1080
1061
  for (const l of late) removePendingQuestion(sessionKey, l.correlationId);
1081
1062
  }
1082
1063
  const isMirrorOnly = NON_GATED_MIRROR_TOOLS.has(input.tool_name);
1083
- const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, agent.type, input.cwd);
1084
- const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, agent.type, input.cwd) : void 0;
1064
+ const decisionSource = isMirrorOnly ? void 0 : deriveDecisionSource(lookup.tool, lookup.input, liveMode, agent.type, input.cwd, input.session_id);
1065
+ const beacon = !isMirrorOnly && decisionSource === "policy_auto" && throttlePass(`autodecision:${sessionKey}:${lookup.tool}`, AUTO_DECISION_WINDOW_MS) ? deriveAutoDecisionBeacon(lookup.tool, lookup.input, liveMode, agent.type, input.cwd, input.session_id) : void 0;
1085
1066
  const toolReport = reportEvent({
1086
1067
  event: isError ? "tool_error" : "tool_complete",
1087
1068
  agentType: agent.type,
@@ -1338,8 +1319,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1338
1319
 
1339
1320
  export {
1340
1321
  getPolicy,
1341
- isAutoApprove,
1342
- isAutoDeny,
1343
1322
  resolveAutoResolveOrigin,
1344
1323
  resolvePolicy,
1345
1324
  resolvePolicyAcross,