@pushary/agent-hooks 1.7.3 → 1.8.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 (102) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +13 -1
  3. package/data/cursor-plugin/scripts/pushary-gate.mjs +2 -21
  4. package/data/cursor-plugin/scripts/redaction.mjs +51 -0
  5. package/data/vscode-plugin/scripts/pushary-gate.mjs +1 -21
  6. package/data/vscode-plugin/scripts/redaction.mjs +51 -0
  7. package/dist/bin/pushary-bell-hook.js +23 -23
  8. package/dist/bin/pushary-bell.js +9 -9
  9. package/dist/bin/pushary-claude.js +26 -26
  10. package/dist/bin/pushary-clean.js +91 -107
  11. package/dist/bin/pushary-codex-bridge.js +18 -18
  12. package/dist/bin/pushary-codex-hook.js +42 -43
  13. package/dist/bin/pushary-codex.js +13 -13
  14. package/dist/bin/pushary-connect.js +13 -13
  15. package/dist/bin/pushary-cowork.js +5 -5
  16. package/dist/bin/pushary-daemon.js +27 -27
  17. package/dist/bin/pushary-disconnect.js +18 -18
  18. package/dist/bin/pushary-doctor.js +85 -79
  19. package/dist/bin/pushary-elicitation-hook.js +25 -25
  20. package/dist/bin/pushary-gemini-bridge.js +12 -12
  21. package/dist/bin/pushary-gemini-hook.js +29 -30
  22. package/dist/bin/pushary-hook.js +32 -32
  23. package/dist/bin/pushary-login.js +12 -12
  24. package/dist/bin/pushary-logout.js +7 -7
  25. package/dist/bin/pushary-mcp.js +85 -28
  26. package/dist/bin/pushary-mode.js +9 -9
  27. package/dist/bin/pushary-notification-hook.js +28 -28
  28. package/dist/bin/pushary-opencode-hook.js +28 -29
  29. package/dist/bin/pushary-permission-denied-hook.js +32 -32
  30. package/dist/bin/pushary-permission-hook.js +31 -31
  31. package/dist/bin/pushary-post-hook.js +28 -28
  32. package/dist/bin/pushary-prompt-hook.js +28 -28
  33. package/dist/bin/pushary-session-end-hook.js +28 -28
  34. package/dist/bin/pushary-session-start-hook.js +32 -32
  35. package/dist/bin/pushary-setup.js +60 -127
  36. package/dist/bin/pushary-stats.js +5 -5
  37. package/dist/bin/pushary-status.js +15 -15
  38. package/dist/bin/pushary-stop-hook.js +28 -28
  39. package/dist/bin/pushary-stopfailure-hook.js +28 -28
  40. package/dist/bin/pushary-suggestions.js +4 -4
  41. package/dist/bin/pushary-transcript-register.js +19 -19
  42. package/dist/bin/pushary-transcripts.js +7 -7
  43. package/dist/bin/pushary-upgrade.js +231 -105
  44. package/dist/bin/pushary-wait.js +9 -9
  45. package/dist/bin/pushary.js +6 -6
  46. package/dist/{chunk-EEEJ2JIL.js → chunk-2ABTSGFT.js} +3 -3
  47. package/dist/{chunk-DUQO4C4I.js → chunk-2OY3ZZ5N.js} +3 -3
  48. package/dist/{chunk-MIXWMA73.js → chunk-3EVPNIBE.js} +3 -3
  49. package/dist/{chunk-WTJM7PSL.js → chunk-6J2JC2RT.js} +3 -3
  50. package/dist/{chunk-EADXTSHE.js → chunk-7Z7Q4GGS.js} +2 -2
  51. package/dist/{chunk-MZBRL5H5.js → chunk-A2A2YTMG.js} +108 -25
  52. package/dist/{chunk-KEL5MXRL.js → chunk-AHV4LNB5.js} +1 -1
  53. package/dist/{chunk-G3OMOTJ4.js → chunk-AXPCESYO.js} +3 -3
  54. package/dist/{chunk-3EUBQARL.js → chunk-C2WCPBF7.js} +1 -1
  55. package/dist/chunk-CJVVKOSR.js +73 -0
  56. package/dist/{chunk-U6RVDUJ5.js → chunk-CULUZTWQ.js} +283 -41
  57. package/dist/{chunk-N5MZH5A6.js → chunk-CVQYGMSR.js} +4 -4
  58. package/dist/{chunk-VKVSRCBO.js → chunk-D2FX3EPW.js} +1 -1
  59. package/dist/{chunk-62CVUUNU.js → chunk-DB3ONZB4.js} +2 -2
  60. package/dist/{chunk-WGMW7OXW.js → chunk-DE4Y6TCC.js} +6 -6
  61. package/dist/{chunk-YYCTF4Q5.js → chunk-E4FJTUH4.js} +1 -1
  62. package/dist/{chunk-EUGNVLMG.js → chunk-EPCXUP3P.js} +3 -1
  63. package/dist/{chunk-PRNFSORD.js → chunk-EX3GAPA4.js} +1 -1
  64. package/dist/{chunk-TCVMK6GI.js → chunk-EYK3GSRH.js} +4 -4
  65. package/dist/{chunk-R6OYCUNK.js → chunk-GPEGOGRJ.js} +1 -1
  66. package/dist/chunk-GU3EJCVV.js +43 -0
  67. package/dist/{chunk-Y4ZH64UK.js → chunk-HQ6G3B5R.js} +2 -2
  68. package/dist/{chunk-5S2VBSJI.js → chunk-HZVVNGEW.js} +2 -2
  69. package/dist/{chunk-IWJKSIB4.js → chunk-IPVKH2ST.js} +1 -1
  70. package/dist/{chunk-UOQRM7JQ.js → chunk-J5TIV3J6.js} +5 -5
  71. package/dist/{chunk-IRZG3HEU.js → chunk-JU6W3XAU.js} +1 -1
  72. package/dist/{chunk-G62JL2NT.js → chunk-JYG4B33G.js} +1 -1
  73. package/dist/{chunk-OTNOANMX.js → chunk-JZTHGAPA.js} +8 -8
  74. package/dist/{chunk-7RUJKCR4.js → chunk-KGDWDSNL.js} +2 -2
  75. package/dist/{chunk-6JQFIHZC.js → chunk-KVQBWI5T.js} +3 -3
  76. package/dist/{chunk-OVC5QXN5.js → chunk-LXQ5FBBD.js} +1 -1
  77. package/dist/{chunk-VE7TXLR4.js → chunk-M5ICYGHT.js} +3 -3
  78. package/dist/{chunk-YPUBA2FL.js → chunk-M5JDZ45W.js} +4 -4
  79. package/dist/{chunk-O3JDWNLU.js → chunk-MDEPU45E.js} +183 -28
  80. package/dist/{chunk-P2VIBGXA.js → chunk-MPWXGONV.js} +1 -1
  81. package/dist/{chunk-YC27PROF.js → chunk-NGDD6TXY.js} +20 -5
  82. package/dist/{chunk-G4GLCLKK.js → chunk-NQTHFF4W.js} +138 -17
  83. package/dist/{chunk-NOI3XTCY.js → chunk-OKXS7WDZ.js} +1 -1
  84. package/dist/{chunk-LPNJLKBQ.js → chunk-OLQRRFBX.js} +1 -1
  85. package/dist/{chunk-D6OODZCM.js → chunk-P7WQEVWA.js} +2 -2
  86. package/dist/{chunk-62VT3BPH.js → chunk-Q7E4I6UA.js} +28 -16
  87. package/dist/{chunk-BIH2Y2HP.js → chunk-R7437YI3.js} +2 -2
  88. package/dist/{chunk-RLW3JWDY.js → chunk-RHR6NARM.js} +2 -2
  89. package/dist/{chunk-WPX67AKT.js → chunk-SAXBC4AZ.js} +1 -1
  90. package/dist/{chunk-IPSRMLCJ.js → chunk-SIFVFP6T.js} +1 -1
  91. package/dist/{chunk-7OA34PNW.js → chunk-SLFABHW3.js} +40 -10
  92. package/dist/{chunk-AWVNAYGD.js → chunk-TY4JT7W3.js} +1 -1
  93. package/dist/{chunk-KJXUIPS4.js → chunk-UI36QSBI.js} +28 -16
  94. package/dist/{chunk-G6RRVJOG.js → chunk-VQKRG3AC.js} +6 -6
  95. package/dist/{chunk-ZKJ34T2F.js → chunk-WE2J62NC.js} +31 -17
  96. package/dist/{chunk-LHC45YUC.js → chunk-ZSZSSJCV.js} +1 -1
  97. package/dist/{chunk-BGBLLHZ4.js → chunk-ZVSKMPBZ.js} +1 -1
  98. package/dist/{reapply-PH5T4BGR.js → reapply-CW3MA66V.js} +9 -9
  99. package/dist/src/index.js +20 -20
  100. package/package.json +1 -1
  101. package/dist/chunk-5GB22QYL.js +0 -255
  102. package/dist/chunk-VYPJHGG6.js +0 -92
@@ -11,35 +11,43 @@ import {
11
11
  savePendingCommand
12
12
  } from "./chunk-N6IOSZOE.js";
13
13
  import {
14
+ IDLE_PROMPT_NOTIFICATION,
14
15
  NOTIFICATION_PUSH_TYPES,
15
16
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
16
- } from "./chunk-EUGNVLMG.js";
17
+ } from "./chunk-EPCXUP3P.js";
17
18
  import {
18
19
  cancelQuestion,
19
20
  isPolicyConfig,
20
21
  sendNotification,
21
22
  waitForAnswer
22
- } from "./chunk-YC27PROF.js";
23
+ } from "./chunk-NGDD6TXY.js";
23
24
  import {
24
25
  clientErrorKindOf,
25
26
  reportClientError
26
- } from "./chunk-N5MZH5A6.js";
27
+ } from "./chunk-CVQYGMSR.js";
27
28
  import {
28
29
  withRetry
29
- } from "./chunk-VE7TXLR4.js";
30
+ } from "./chunk-M5ICYGHT.js";
30
31
  import {
31
32
  getMachineId
32
- } from "./chunk-OVC5QXN5.js";
33
+ } from "./chunk-LXQ5FBBD.js";
33
34
  import {
34
35
  reportedInstallSource
35
- } from "./chunk-62CVUUNU.js";
36
+ } from "./chunk-DB3ONZB4.js";
37
+ import {
38
+ canonicalJson
39
+ } from "./chunk-A2A2YTMG.js";
40
+ import {
41
+ writeFileAtomic
42
+ } from "./chunk-T7E75EKA.js";
36
43
  import {
37
44
  getApiKey,
38
45
  getBaseUrl
39
- } from "./chunk-R6OYCUNK.js";
46
+ } from "./chunk-GPEGOGRJ.js";
40
47
  import {
41
48
  ACTION_BODY_MAX,
42
49
  AGENT_LABELS_BY_TYPE,
50
+ BLOCKED_ACTION_GRANT_TTL_SECONDS,
43
51
  DECISION_LINE_MAX,
44
52
  FILE_TARGET_TOOLS,
45
53
  GATE_MAX_TOOL_INPUTS,
@@ -47,6 +55,7 @@ import {
47
55
  HOOK_SOURCES,
48
56
  PENDING_COMMAND_PREFIX,
49
57
  REPO_AWARE_AGENTS,
58
+ blockedActionIdentity,
50
59
  canonicalToolName,
51
60
  gateOutputFor,
52
61
  hookCompatibleProfileBySource,
@@ -59,8 +68,9 @@ import {
59
68
  redactSecretsDeep,
60
69
  resolveAutoResolveOrigin,
61
70
  resolvePolicy,
62
- toToolLookup
63
- } from "./chunk-G4GLCLKK.js";
71
+ toToolLookup,
72
+ truncateLine
73
+ } from "./chunk-NQTHFF4W.js";
64
74
 
65
75
  // src/policy.ts
66
76
  import { createHash } from "crypto";
@@ -550,7 +560,7 @@ var scopePathFor = (toolName, toolInput, cwd) => {
550
560
  };
551
561
  var firstString = (...values) => values.find((v) => typeof v === "string" && v.length > 0);
552
562
  var ACTION_BODY_TRUNCATION_MARKER = "\n\u2026 [truncated]";
553
- var capActionBody = (text) => text.length <= ACTION_BODY_MAX ? text : `${text.slice(0, ACTION_BODY_MAX - ACTION_BODY_TRUNCATION_MARKER.length)}${ACTION_BODY_TRUNCATION_MARKER}`;
563
+ var capActionBody = (text) => truncateLine(text, ACTION_BODY_MAX, ACTION_BODY_TRUNCATION_MARKER);
554
564
  var rawActionBody = (toolName, toolInput) => {
555
565
  if (toolName === "Edit" || toolName === "replace") {
556
566
  const before = firstString(toolInput.old_string);
@@ -577,7 +587,7 @@ var deriveActionBody = (toolName, toolInput) => {
577
587
  return void 0;
578
588
  }
579
589
  };
580
- var deriveAction = (toolName, toolInput) => describeToolCall(toolName, toolInput, "hook").slice(0, DECISION_LINE_MAX);
590
+ var deriveAction = (toolName, toolInput) => truncateLine(describeToolCall(toolName, toolInput, "hook"), DECISION_LINE_MAX);
581
591
  var BLOCKER_BY_MODE = {
582
592
  push_only: "Approval required before this runs",
583
593
  push_first: "Paused for your approval before continuing",
@@ -585,7 +595,7 @@ var BLOCKER_BY_MODE = {
585
595
  };
586
596
  var deriveBlocker = (mode, reason) => {
587
597
  const text = reason?.replace(/\s+/g, " ").trim() || BLOCKER_BY_MODE[mode] || "Waiting for your approval";
588
- return text.slice(0, DECISION_LINE_MAX);
598
+ return truncateLine(text, DECISION_LINE_MAX);
589
599
  };
590
600
 
591
601
  // ../hook-mapping/src/map.ts
@@ -1203,6 +1213,96 @@ var permissionTimeoutDecision = (timeoutAction) => {
1203
1213
  };
1204
1214
  var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
1205
1215
 
1216
+ // src/codex-call-decision.ts
1217
+ import { createHash as createHash3 } from "crypto";
1218
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
1219
+ import { tmpdir as tmpdir3, userInfo } from "os";
1220
+ import { join as join4 } from "path";
1221
+ var CODEX_DECISION_TTL_MS = 60 * 1e3;
1222
+ var userScope = () => {
1223
+ try {
1224
+ const info = userInfo();
1225
+ const id = typeof info.uid === "number" && info.uid >= 0 ? String(info.uid) : info.username;
1226
+ return id.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || "default";
1227
+ } catch {
1228
+ return "default";
1229
+ }
1230
+ };
1231
+ var decisionDir = () => join4(tmpdir3(), `pushary-codex-decisions-${userScope()}`);
1232
+ var decisionFile = (key) => join4(decisionDir(), key);
1233
+ var PRESENTATION_ONLY_INPUT_KEYS = ["description"];
1234
+ var identifyingToolInput = (toolInput) => {
1235
+ if (!toolInput) return {};
1236
+ const identifying = {};
1237
+ for (const [key, value] of Object.entries(toolInput)) {
1238
+ if (PRESENTATION_ONLY_INPUT_KEYS.includes(key)) continue;
1239
+ identifying[key] = value;
1240
+ }
1241
+ return identifying;
1242
+ };
1243
+ var codexCallKey = (identity) => {
1244
+ if (!identity.sessionId || !identity.turnId || !identity.toolName) return null;
1245
+ const material = canonicalJson([
1246
+ identity.sessionId,
1247
+ identity.turnId,
1248
+ identity.toolName,
1249
+ identifyingToolInput(identity.toolInput)
1250
+ ]);
1251
+ return createHash3("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
1252
+ };
1253
+ var parseDecision = (raw) => {
1254
+ try {
1255
+ const parsed = JSON.parse(raw);
1256
+ if (parsed.kind === "approved") {
1257
+ return typeof parsed.toolUseId === "string" && parsed.toolUseId ? { kind: "approved", toolUseId: parsed.toolUseId } : null;
1258
+ }
1259
+ if (parsed.kind === "handled") return { kind: "handled" };
1260
+ if (parsed.kind === "denied") {
1261
+ return { kind: "denied", reason: typeof parsed.reason === "string" ? parsed.reason : "" };
1262
+ }
1263
+ return null;
1264
+ } catch {
1265
+ return null;
1266
+ }
1267
+ };
1268
+ var sweepExpired = (dir, nowMs) => {
1269
+ try {
1270
+ for (const name of readdirSync(dir)) {
1271
+ const path = join4(dir, name);
1272
+ try {
1273
+ if (nowMs - statSync3(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync(path, { force: true });
1274
+ } catch {
1275
+ continue;
1276
+ }
1277
+ }
1278
+ } catch {
1279
+ return;
1280
+ }
1281
+ };
1282
+ var recordCodexCallDecision = (key, decision) => {
1283
+ if (!key) return;
1284
+ try {
1285
+ const dir = decisionDir();
1286
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
1287
+ else sweepExpired(dir, Date.now());
1288
+ writeFileSync3(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
1289
+ } catch {
1290
+ return;
1291
+ }
1292
+ };
1293
+ var consumeCodexCallDecision = (key) => {
1294
+ if (!key) return null;
1295
+ const path = decisionFile(key);
1296
+ try {
1297
+ const fresh = Date.now() - statSync3(path).mtimeMs < CODEX_DECISION_TTL_MS;
1298
+ const raw = readFileSync4(path, "utf-8");
1299
+ rmSync(path, { force: true });
1300
+ return fresh ? parseDecision(raw) : null;
1301
+ } catch {
1302
+ return null;
1303
+ }
1304
+ };
1305
+
1206
1306
  // src/driver.ts
1207
1307
  var DRIVER_ENV = "PUSHARY_DRIVER";
1208
1308
  var PHONE_DRIVER = "phone";
@@ -1213,50 +1313,182 @@ var phoneDrivenEnv = (base) => ({
1213
1313
  });
1214
1314
 
1215
1315
  // src/throttle.ts
1216
- import { join as join4 } from "path";
1217
- import { tmpdir as tmpdir3 } from "os";
1218
- import { createHash as createHash3 } from "crypto";
1219
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, statSync as statSync3, writeFileSync as writeFileSync3, rmSync } from "fs";
1220
- var THROTTLE_DIR = join4(tmpdir3(), "pushary-throttle");
1221
- var markerPath = (key) => join4(THROTTLE_DIR, createHash3("sha256").update(key).digest("hex").slice(0, 16));
1316
+ import { join as join5 } from "path";
1317
+ import { tmpdir as tmpdir4 } from "os";
1318
+ import { createHash as createHash4 } from "crypto";
1319
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync4, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "fs";
1320
+ var THROTTLE_DIR = join5(tmpdir4(), "pushary-throttle");
1321
+ var markerPath = (key) => join5(THROTTLE_DIR, createHash4("sha256").update(key).digest("hex").slice(0, 16));
1222
1322
  var claimThrottle = (key, windowMs) => {
1223
1323
  try {
1224
1324
  const path = markerPath(key);
1225
- if (existsSync3(path) && Date.now() - statSync3(path).mtimeMs < windowMs) return "throttled";
1226
- mkdirSync2(THROTTLE_DIR, { recursive: true });
1227
- writeFileSync3(path, "", "utf-8");
1325
+ if (existsSync4(path) && Date.now() - statSync4(path).mtimeMs < windowMs) return "throttled";
1326
+ mkdirSync3(THROTTLE_DIR, { recursive: true });
1327
+ writeFileSync4(path, "", "utf-8");
1228
1328
  return "passed";
1229
1329
  } catch {
1230
1330
  return "unavailable";
1231
1331
  }
1232
1332
  };
1233
1333
  var releaseThrottle = (key) => {
1234
- rmSync(markerPath(key), { force: true });
1334
+ rmSync2(markerPath(key), { force: true });
1335
+ };
1336
+
1337
+ // src/blocked-action-hint.ts
1338
+ import { createHash as createHash5 } from "crypto";
1339
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync as rmSync3, statSync as statSync5, utimesSync } from "fs";
1340
+ import { tmpdir as tmpdir5 } from "os";
1341
+ import { join as join6 } from "path";
1342
+ var BLOCKED_ACTION_HINT_TTL_MS = BLOCKED_ACTION_GRANT_TTL_SECONDS * 1e3;
1343
+ var FINGERPRINT = /^[0-9a-f]{64}$/;
1344
+ var isFingerprint = (value) => typeof value === "string" && FINGERPRINT.test(value);
1345
+ var blockedActionSubject = (source, sessionId, toolName, toolInput, cwd) => {
1346
+ if (source !== "claude") return { source, sessionId, toolName, toolInput };
1347
+ return cwd ? { source, sessionId, cwd, toolName, toolInput } : null;
1348
+ };
1349
+ var blockedActionFingerprint = ({ source = "claude", sessionId, toolName, toolInput, cwd }) => {
1350
+ if (!sessionId || !toolInput) return null;
1351
+ const canonical = canonicalGateInputs(source, toolName, [toolInput], cwd);
1352
+ if (!canonical || canonical.toolInputs.length !== 1) return null;
1353
+ const [canonicalInput] = canonical.toolInputs;
1354
+ if (!canonicalInput) return null;
1355
+ const subject = blockedActionSubject(source, sessionId, canonical.toolName, canonicalInput, cwd);
1356
+ return subject ? createHash5("sha256").update(blockedActionIdentity(subject), "utf8").digest("hex") : null;
1357
+ };
1358
+ var hintDir = () => join6(tmpdir5(), `pushary-blocked-action-hints-${userScope()}`);
1359
+ var hintFile = (fingerprint) => join6(hintDir(), fingerprint);
1360
+ var isExpired = (mtimeMs, nowMs) => nowMs - mtimeMs >= BLOCKED_ACTION_HINT_TTL_MS;
1361
+ var sweepExpired2 = (dir, nowMs) => {
1362
+ try {
1363
+ for (const name of readdirSync2(dir)) {
1364
+ const path = join6(dir, name);
1365
+ try {
1366
+ if (isExpired(statSync5(path).mtimeMs, nowMs)) rmSync3(path, { force: true });
1367
+ } catch {
1368
+ continue;
1369
+ }
1370
+ }
1371
+ } catch {
1372
+ return;
1373
+ }
1374
+ };
1375
+ var recordBlockedActionHint = (fingerprint, approved) => {
1376
+ if (!isFingerprint(fingerprint)) return false;
1377
+ try {
1378
+ const dir = hintDir();
1379
+ if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true, mode: 448 });
1380
+ else sweepExpired2(dir, Date.now());
1381
+ writeFileAtomic(hintFile(fingerprint), JSON.stringify({ fingerprint, ...approved }), 384);
1382
+ return true;
1383
+ } catch {
1384
+ return false;
1385
+ }
1386
+ };
1387
+ var readLiveHint = (fingerprint) => {
1388
+ const path = hintFile(fingerprint);
1389
+ try {
1390
+ const stat = statSync5(path);
1391
+ if (!stat.isFile()) return null;
1392
+ if (isExpired(stat.mtimeMs, Date.now())) {
1393
+ rmSync3(path, { force: true });
1394
+ return null;
1395
+ }
1396
+ const parsed = JSON.parse(readFileSync5(path, "utf-8"));
1397
+ return parsed?.fingerprint === fingerprint ? parsed : null;
1398
+ } catch {
1399
+ return null;
1400
+ }
1401
+ };
1402
+ var hasBlockedActionHint = (fingerprint) => isFingerprint(fingerprint) && readLiveHint(fingerprint) !== null;
1403
+ var approvalIn = (fingerprint, sessionId) => {
1404
+ const hint = readLiveHint(fingerprint);
1405
+ if (hint?.sessionId !== sessionId || typeof hint.toolName !== "string") return null;
1406
+ return { fingerprint, sessionId, toolName: hint.toolName, reminded: hint.reminded === true };
1407
+ };
1408
+ var approvalAwaitingRetry = (sessionId) => {
1409
+ if (!sessionId) return null;
1410
+ try {
1411
+ for (const name of readdirSync2(hintDir())) {
1412
+ const approval = isFingerprint(name) ? approvalIn(name, sessionId) : null;
1413
+ if (approval) return approval;
1414
+ }
1415
+ return null;
1416
+ } catch {
1417
+ return null;
1418
+ }
1419
+ };
1420
+ var markApprovalReminded = (approval) => {
1421
+ const path = hintFile(approval.fingerprint);
1422
+ try {
1423
+ const { atime, mtime } = statSync5(path);
1424
+ const { fingerprint, sessionId, toolName } = approval;
1425
+ writeFileAtomic(path, JSON.stringify({ fingerprint, sessionId, toolName, reminded: true }), 384);
1426
+ utimesSync(path, atime, mtime);
1427
+ } catch {
1428
+ return;
1429
+ }
1430
+ };
1431
+ var forgetBlockedActionHint = (fingerprint) => {
1432
+ if (!isFingerprint(fingerprint)) return;
1433
+ try {
1434
+ rmSync3(hintFile(fingerprint), { force: true });
1435
+ } catch {
1436
+ return;
1437
+ }
1438
+ };
1439
+
1440
+ // src/background-work.ts
1441
+ import { createHash as createHash6 } from "crypto";
1442
+ import { mkdirSync as mkdirSync5, rmSync as rmSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
1443
+ import { tmpdir as tmpdir6 } from "os";
1444
+ import { join as join7 } from "path";
1445
+ var BACKGROUND_WORK_TTL_MS = 60 * 60 * 1e3;
1446
+ var backgroundWorkDir = () => join7(tmpdir6(), "pushary-background-work");
1447
+ var markerFor = (sessionKey) => join7(backgroundWorkDir(), createHash6("sha256").update(sessionKey).digest("hex"));
1448
+ var hasRunningBackgroundTasks = (backgroundTasks) => Array.isArray(backgroundTasks) && backgroundTasks.length > 0;
1449
+ var noteBackgroundWork = (sessionKey, running) => {
1450
+ try {
1451
+ if (!running) {
1452
+ rmSync4(markerFor(sessionKey), { force: true });
1453
+ return;
1454
+ }
1455
+ mkdirSync5(backgroundWorkDir(), { recursive: true, mode: 448 });
1456
+ writeFileSync5(markerFor(sessionKey), "", { mode: 384 });
1457
+ } catch {
1458
+ return;
1459
+ }
1460
+ };
1461
+ var isWaitingOnBackgroundWork = (sessionKey, nowMs = Date.now()) => {
1462
+ try {
1463
+ return nowMs - statSync6(markerFor(sessionKey)).mtimeMs < BACKGROUND_WORK_TTL_MS;
1464
+ } catch {
1465
+ return false;
1466
+ }
1235
1467
  };
1236
1468
 
1237
1469
  // src/events.ts
1238
- import { basename as basename3, join as join5 } from "path";
1239
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
1240
- import { tmpdir as tmpdir4 } from "os";
1241
- var INTENT_DIR = join5(tmpdir4(), "pushary-intent");
1470
+ import { basename as basename3, join as join8 } from "path";
1471
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
1472
+ import { tmpdir as tmpdir7 } from "os";
1473
+ var INTENT_DIR = join8(tmpdir7(), "pushary-intent");
1242
1474
  var INTENT_MAX_BYTES = 2048;
1243
1475
  var INTENT_GRACE_MS = 10 * 60 * 1e3;
1244
1476
  var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
1245
- var intentFile = (sessionId) => join5(INTENT_DIR, sanitizeSession(sessionId));
1477
+ var intentFile = (sessionId) => join8(INTENT_DIR, sanitizeSession(sessionId));
1246
1478
  var saveLastPrompt = (sessionId, prompt) => {
1247
1479
  try {
1248
1480
  const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
1249
1481
  if (!trimmed) return;
1250
- mkdirSync3(INTENT_DIR, { recursive: true });
1251
- writeFileSync4(intentFile(sessionId), trimmed, "utf-8");
1482
+ mkdirSync6(INTENT_DIR, { recursive: true });
1483
+ writeFileSync6(intentFile(sessionId), trimmed, "utf-8");
1252
1484
  } catch {
1253
1485
  }
1254
1486
  };
1255
1487
  var readLastPrompt = (sessionId) => {
1256
1488
  try {
1257
1489
  const path = intentFile(sessionId);
1258
- if (isDefaultSession(sessionId) && statSync4(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
1259
- const value = readFileSync4(path, "utf-8").trim();
1490
+ if (isDefaultSession(sessionId) && statSync7(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
1491
+ const value = readFileSync6(path, "utf-8").trim();
1260
1492
  return value.length > 0 ? value : void 0;
1261
1493
  } catch {
1262
1494
  return void 0;
@@ -1320,8 +1552,8 @@ var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
1320
1552
  var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
1321
1553
  const request = policyRequestFor(agentType);
1322
1554
  const path = policyCacheFile(apiKey, request.agent, request.repoAware);
1323
- if (!existsSync4(path)) return null;
1324
- const cached = JSON.parse(readFileSync4(path, "utf-8"));
1555
+ if (!existsSync6(path)) return null;
1556
+ const cached = JSON.parse(readFileSync6(path, "utf-8"));
1325
1557
  if (!isPolicyConfig(cached)) return null;
1326
1558
  if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
1327
1559
  if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
@@ -1618,12 +1850,15 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
1618
1850
  return void 0;
1619
1851
  }
1620
1852
  };
1853
+ var UNUSED_APPROVAL_INSTRUCTION = "The person allowed, from Pushary, the call auto mode blocked. Send that call again now, exactly as it was with nothing changed, to use their approval. It covers only that exact call.";
1854
+ var phoneInstructionOf = (pendingCommand) => typeof pendingCommand === "string" && pendingCommand.trim().length > 0 ? `The user sent a new instruction from their phone via Pushary: ${pendingCommand.trim()}` : null;
1621
1855
  var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1622
1856
  const deadline = Date.now() + HOOK_BUDGET_MS;
1623
1857
  const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
1624
1858
  try {
1625
1859
  const projectName = basename3(input.cwd ?? process.cwd());
1626
1860
  const sessionKey = input.session_id || DEFAULT_SESSION;
1861
+ noteBackgroundWork(sessionKey, hasRunningBackgroundTasks(input.background_tasks));
1627
1862
  const late = await reconcilePendingQuestions(sessionKey, void 0, signal);
1628
1863
  const tasks = [
1629
1864
  reportEvent({
@@ -1642,13 +1877,9 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1642
1877
  if (!isDefaultSession(sessionKey) && listPendingQuestions(sessionKey).length === 0) removePendingSession(sessionKey);
1643
1878
  const [reported] = await Promise.allSettled(tasks);
1644
1879
  const pendingCommand = reported.status === "fulfilled" ? reported.value?.pendingCommand : void 0;
1645
- if (typeof pendingCommand === "string" && pendingCommand.trim().length > 0) {
1646
- return {
1647
- decision: "block",
1648
- reason: `The user sent a new instruction from their phone via Pushary: ${pendingCommand.trim()}`
1649
- };
1650
- }
1651
- return void 0;
1880
+ const unusedApproval = !input.stop_hook_active && approvalAwaitingRetry(input.session_id) !== null;
1881
+ const instructions = [unusedApproval ? UNUSED_APPROVAL_INSTRUCTION : null, phoneInstructionOf(pendingCommand)].filter((instruction) => instruction !== null);
1882
+ return instructions.length > 0 ? { decision: "block", reason: instructions.join("\n\n") } : void 0;
1652
1883
  } catch {
1653
1884
  return void 0;
1654
1885
  }
@@ -1726,6 +1957,7 @@ var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT, options = {}) =>
1726
1957
  }
1727
1958
  };
1728
1959
  var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
1960
+ var pushesNotification = (notifType, sessionKey) => NOTIFICATION_PUSH_TYPES.has(notifType) && !(notifType === IDLE_PROMPT_NOTIFICATION && isWaitingOnBackgroundWork(sessionKey));
1729
1961
  var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
1730
1962
  const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
1731
1963
  try {
@@ -1757,7 +1989,7 @@ var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
1757
1989
  // and all three have to fit inside Claude Code's 10s Notification budget.
1758
1990
  }, { maxAttempts: 1, timeoutMs: 2500, signal }).catch(() => {
1759
1991
  });
1760
- if (!teammateIdle && NOTIFICATION_PUSH_TYPES.has(notifType)) {
1992
+ if (!teammateIdle && pushesNotification(notifType, sessionKey)) {
1761
1993
  if (claimThrottle(`notify:${sessionKey}:${notifType}`, NOTIFICATION_THROTTLE_MS) !== "passed") return;
1762
1994
  let apiKey;
1763
1995
  try {
@@ -1856,6 +2088,16 @@ export {
1856
2088
  toPolicyLookups,
1857
2089
  permissionTimeoutDecision,
1858
2090
  preToolUseTimeoutDecision,
2091
+ userScope,
2092
+ codexCallKey,
2093
+ recordCodexCallDecision,
2094
+ consumeCodexCallDecision,
2095
+ blockedActionFingerprint,
2096
+ recordBlockedActionHint,
2097
+ hasBlockedActionHint,
2098
+ approvalAwaitingRetry,
2099
+ markApprovalReminded,
2100
+ forgetBlockedActionHint,
1859
2101
  readLastPrompt,
1860
2102
  CLAUDE_CODE_AGENT,
1861
2103
  AUTO_DECISION_WINDOW_MS,
@@ -4,18 +4,18 @@ import {
4
4
  } from "./chunk-POFBE6ZY.js";
5
5
  import {
6
6
  isNetworkFailure
7
- } from "./chunk-DUQO4C4I.js";
7
+ } from "./chunk-2OY3ZZ5N.js";
8
8
  import {
9
9
  clientReportClaims
10
- } from "./chunk-MZBRL5H5.js";
10
+ } from "./chunk-A2A2YTMG.js";
11
11
  import {
12
12
  getApiKey,
13
13
  getBaseUrl
14
- } from "./chunk-R6OYCUNK.js";
14
+ } from "./chunk-GPEGOGRJ.js";
15
15
  import {
16
16
  CLIENT_REPORT_PATH,
17
17
  degradedLine
18
- } from "./chunk-G4GLCLKK.js";
18
+ } from "./chunk-NQTHFF4W.js";
19
19
 
20
20
  // src/client-report-claim.ts
21
21
  import { createHash } from "crypto";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_HOLD_BUDGET_ENV,
3
3
  hookHoldBudgetProof
4
- } from "./chunk-G4GLCLKK.js";
4
+ } from "./chunk-NQTHFF4W.js";
5
5
 
6
6
  // src/hold-budget.ts
7
7
  var takeHookHoldBudget = (args = process.argv.slice(2), env = process.env) => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  configFilePath
3
- } from "./chunk-R6OYCUNK.js";
3
+ } from "./chunk-GPEGOGRJ.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-G4GLCLKK.js";
6
+ } from "./chunk-NQTHFF4W.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -2,27 +2,27 @@ import {
2
2
  detectProviderLiveCapabilities,
3
3
  hasSpawnProvider,
4
4
  resolveControlPolicy
5
- } from "./chunk-Y4ZH64UK.js";
5
+ } from "./chunk-HQ6G3B5R.js";
6
6
  import {
7
7
  daemonHasRemoteProof,
8
8
  daemonServiceManagerAvailable,
9
9
  hasDaemonServiceDefinition,
10
10
  readDaemonNeedsLogin,
11
11
  readDaemonStatus
12
- } from "./chunk-ZKJ34T2F.js";
12
+ } from "./chunk-WE2J62NC.js";
13
13
  import {
14
14
  readControlMode,
15
15
  readKeySource
16
- } from "./chunk-D6OODZCM.js";
16
+ } from "./chunk-P7WQEVWA.js";
17
17
  import {
18
18
  getMachineId
19
- } from "./chunk-OVC5QXN5.js";
19
+ } from "./chunk-LXQ5FBBD.js";
20
20
  import {
21
21
  getBaseUrl
22
- } from "./chunk-R6OYCUNK.js";
22
+ } from "./chunk-GPEGOGRJ.js";
23
23
  import {
24
24
  getPackageVersion
25
- } from "./chunk-G4GLCLKK.js";
25
+ } from "./chunk-NQTHFF4W.js";
26
26
 
27
27
  // src/machines.ts
28
28
  import { hostname } from "os";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SETUP_AGENT_CHOICES
3
- } from "./chunk-G4GLCLKK.js";
3
+ } from "./chunk-NQTHFF4W.js";
4
4
 
5
5
  // src/cli/commands.ts
6
6
  var NPX_LATEST_COMMAND = "npx @pushary/agent-hooks@latest";
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  PRETOOLUSE_GATED_TOOLS
3
- } from "./chunk-G4GLCLKK.js";
3
+ } from "./chunk-NQTHFF4W.js";
4
4
 
5
5
  // src/claude-tools.ts
6
6
  var POSTTOOLUSE_MIRROR_ONLY_TOOLS = ["TodoWrite", "Task", "WebFetch", "WebSearch"];
7
7
  var POSTTOOLUSE_MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...POSTTOOLUSE_MIRROR_ONLY_TOOLS];
8
8
 
9
9
  // src/claude-notifications.ts
10
+ var IDLE_PROMPT_NOTIFICATION = "idle_prompt";
10
11
  var NOTIFICATION_MATCHED_TYPES = [
11
12
  // Blocks. The agent, or one of its workers, is waiting on a person.
12
13
  "permission_prompt",
@@ -34,6 +35,7 @@ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set([
34
35
  export {
35
36
  POSTTOOLUSE_MIRROR_ONLY_TOOLS,
36
37
  POSTTOOLUSE_MIRRORED_TOOLS,
38
+ IDLE_PROMPT_NOTIFICATION,
37
39
  NOTIFICATION_MATCHED_TYPES,
38
40
  NOTIFICATION_PUSH_TYPES
39
41
  };
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  describeKeyCheck,
6
6
  keyCheckFromResponse
7
- } from "./chunk-EEEJ2JIL.js";
7
+ } from "./chunk-2ABTSGFT.js";
8
8
  import {
9
9
  EXIT
10
10
  } from "./chunk-KZERVKTD.js";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  AGENT_CHOICES
3
- } from "./chunk-BGBLLHZ4.js";
3
+ } from "./chunk-ZVSKMPBZ.js";
4
4
  import {
5
5
  isBridgeLocator
6
- } from "./chunk-3EUBQARL.js";
6
+ } from "./chunk-C2WCPBF7.js";
7
7
  import {
8
8
  pusharyHookExecutable
9
- } from "./chunk-WTJM7PSL.js";
9
+ } from "./chunk-6J2JC2RT.js";
10
10
  import {
11
11
  BELL_HOOK_BINARY
12
- } from "./chunk-NOI3XTCY.js";
12
+ } from "./chunk-OKXS7WDZ.js";
13
13
 
14
14
  // src/setup/hook-ownership.ts
15
15
  var commandsIn = (value) => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  NPX_LATEST_COMMAND
3
- } from "./chunk-YYCTF4Q5.js";
3
+ } from "./chunk-E4FJTUH4.js";
4
4
 
5
5
  // src/config.ts
6
6
  import { readFileSync } from "fs";
@@ -0,0 +1,43 @@
1
+ // src/diagnostics/service-runtime.ts
2
+ var unescapeXml = (value) => value.replaceAll("&quot;", '"').replaceAll("&apos;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
3
+ var unquoteSystemd = (value) => value.replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("%%", "%");
4
+ var readServiceExecPath = (contents, platform) => {
5
+ if (platform === "darwin") {
6
+ const program = contents.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];
7
+ return program ? unescapeXml(program) : null;
8
+ }
9
+ if (platform === "linux") {
10
+ const exec = contents.match(/^ExecStart="((?:[^"\\]|\\.)*)"/m)?.[1];
11
+ return exec ? unquoteSystemd(exec) : null;
12
+ }
13
+ if (platform === "win32") {
14
+ const command = contents.match(/<Command>([^<]*)<\/Command>/)?.[1];
15
+ return command ? unescapeXml(command) : null;
16
+ }
17
+ return null;
18
+ };
19
+ var MANAGER_SEGMENTS = [
20
+ [".nvm", "nvm"],
21
+ ["nvm", "nvm"],
22
+ ["fnm", "fnm"],
23
+ [".fnm", "fnm"],
24
+ [".volta", "volta"],
25
+ ["volta", "volta"],
26
+ [".asdf", "asdf"],
27
+ ["asdf", "asdf"]
28
+ ];
29
+ var versionManagerOf = (execPath) => {
30
+ const segments = execPath.split(/[/\\]/);
31
+ for (const segment of segments) {
32
+ const hit = MANAGER_SEGMENTS.find(([name]) => name === segment);
33
+ if (hit) return hit[1];
34
+ }
35
+ return null;
36
+ };
37
+ var describeVersionManagerPin = (execPath, manager) => `The background service is pinned to ${execPath}, which ${manager} manages. Switching Node versions strands it; run pushary daemon install afterwards.`;
38
+
39
+ export {
40
+ readServiceExecPath,
41
+ versionManagerOf,
42
+ describeVersionManagerPin
43
+ };