@pushary/agent-hooks 0.92.1 → 0.93.1

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 (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bin/pushary-bell-hook.js +4 -4
  3. package/dist/bin/pushary-bell.js +5 -5
  4. package/dist/bin/pushary-claude.js +41 -25
  5. package/dist/bin/pushary-clean.js +11 -11
  6. package/dist/bin/pushary-codex-bridge.js +10 -10
  7. package/dist/bin/pushary-codex-hook.js +48 -14
  8. package/dist/bin/pushary-codex.js +9 -8
  9. package/dist/bin/pushary-connect.js +7 -7
  10. package/dist/bin/pushary-daemon.js +13 -13
  11. package/dist/bin/pushary-disconnect.js +5 -5
  12. package/dist/bin/pushary-doctor.js +27 -20
  13. package/dist/bin/pushary-elicitation-hook.js +5 -5
  14. package/dist/bin/pushary-gemini-bridge.js +10 -10
  15. package/dist/bin/pushary-gemini-hook.js +10 -9
  16. package/dist/bin/pushary-hook.js +12 -12
  17. package/dist/bin/pushary-login.js +7 -7
  18. package/dist/bin/pushary-logout.js +4 -4
  19. package/dist/bin/pushary-mode.js +3 -3
  20. package/dist/bin/pushary-notification-hook.js +9 -8
  21. package/dist/bin/pushary-opencode-hook.js +10 -9
  22. package/dist/bin/pushary-permission-denied-hook.js +12 -12
  23. package/dist/bin/pushary-permission-hook.js +12 -12
  24. package/dist/bin/pushary-post-hook.js +9 -8
  25. package/dist/bin/pushary-prompt-hook.js +9 -8
  26. package/dist/bin/pushary-session-end-hook.js +9 -8
  27. package/dist/bin/pushary-session-start-hook.js +13 -13
  28. package/dist/bin/pushary-setup.js +21 -21
  29. package/dist/bin/pushary-stats.js +2 -2
  30. package/dist/bin/pushary-status.js +9 -9
  31. package/dist/bin/pushary-stop-hook.js +9 -8
  32. package/dist/bin/pushary-stopfailure-hook.js +9 -8
  33. package/dist/bin/pushary-transcripts.js +4 -4
  34. package/dist/bin/pushary-upgrade.js +12 -12
  35. package/dist/bin/pushary-wait.js +3 -3
  36. package/dist/{chunk-VHWCVD42.js → chunk-2ORRI66J.js} +1 -1
  37. package/dist/{chunk-I3ORVBTP.js → chunk-36LR2LOT.js} +5 -5
  38. package/dist/{chunk-7FE2KABY.js → chunk-46P3VES5.js} +1 -1
  39. package/dist/{chunk-L4E2BIJ2.js → chunk-4PE2SQVM.js} +57 -4
  40. package/dist/{chunk-YXDYKECP.js → chunk-5HGLZGI4.js} +138 -86
  41. package/dist/{chunk-OSALSIUF.js → chunk-6BIMJIRG.js} +1 -1
  42. package/dist/{chunk-UZNKVRLE.js → chunk-6WYL3XIP.js} +1 -1
  43. package/dist/{chunk-CELX75MW.js → chunk-BSEAOCCQ.js} +1 -1
  44. package/dist/{chunk-QZCFVWA7.js → chunk-EQRS3SGM.js} +3 -1
  45. package/dist/{chunk-BSZYIAZL.js → chunk-EYRGBYZG.js} +2 -1
  46. package/dist/{chunk-QCXKQJTG.js → chunk-FYOIW5RV.js} +3 -3
  47. package/dist/{chunk-ZMKKK6DC.js → chunk-HSAVPZ46.js} +3 -3
  48. package/dist/{chunk-2ACC5M7F.js → chunk-NVPYRDCT.js} +1 -1
  49. package/dist/{chunk-CNPAT3HL.js → chunk-OB4RF2TF.js} +2 -2
  50. package/dist/{chunk-JW4KO5AF.js → chunk-ODL52O5V.js} +1 -1
  51. package/dist/{chunk-I463AEG3.js → chunk-OYNPLEXP.js} +1 -1
  52. package/dist/{chunk-2XQSN66Q.js → chunk-QGA2ZB4I.js} +1 -1
  53. package/dist/{chunk-RQPQSJJH.js → chunk-QKNLW7H6.js} +26 -11
  54. package/dist/{chunk-F7OURW3C.js → chunk-RER3FRTD.js} +7 -7
  55. package/dist/{chunk-5NAZ7SFS.js → chunk-RH3TM7TO.js} +1 -1
  56. package/dist/{chunk-PYA4FLKI.js → chunk-ROHFVGXM.js} +2 -2
  57. package/dist/{chunk-I7W3UTDK.js → chunk-RX75MRGC.js} +10 -8
  58. package/dist/{chunk-4TFXV5MW.js → chunk-TDUTNYAQ.js} +1 -1
  59. package/dist/{chunk-BXTJVM7O.js → chunk-UZFVNEEU.js} +6 -6
  60. package/dist/{chunk-Z6RFZKY4.js → chunk-WY4ALDYN.js} +1 -1
  61. package/dist/chunk-Y3UGCQQC.js +53 -0
  62. package/dist/{chunk-CV2YD2JR.js → chunk-Y6XXFXVB.js} +43 -11
  63. package/dist/{chunk-3HS4YGHB.js → chunk-YANV5TGK.js} +3 -3
  64. package/dist/{chunk-VNMKIJZB.js → chunk-Z6CP5ZZD.js} +3 -0
  65. package/dist/{chunk-HDORB7WD.js → chunk-ZO2XG3CX.js} +1 -1
  66. package/dist/{chunk-SQYBP3QQ.js → chunk-ZTJF6ANY.js} +2 -2
  67. package/dist/src/index.d.ts +33 -3
  68. package/dist/src/index.js +13 -13
  69. package/package.json +2 -2
  70. package/dist/chunk-2LAHXHBR.js +0 -23
@@ -38,16 +38,18 @@ var uploadTranscriptRecords = async (apiKey, params, baseUrl = getBaseUrl()) =>
38
38
  const body = await res.json().catch(() => null);
39
39
  const keyRegistered = body?.keyRegistered === true;
40
40
  const keyConflict = body?.keyConflict === true;
41
+ const keyUnavailable = res.status === 503;
41
42
  const ids = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
42
43
  return {
43
44
  ok: res.ok && !keyConflict && (params.dataEncryptionKey === void 0 || keyRegistered),
44
45
  keyRegistered,
45
46
  keyConflict,
47
+ keyUnavailable,
46
48
  settledIds: Array.isArray(body?.settledIds) ? ids(body.settledIds) : null,
47
49
  rejectedIds: ids(body?.rejectedIds)
48
50
  };
49
51
  } catch {
50
- return { ok: false, keyRegistered: false, keyConflict: false, settledIds: null, rejectedIds: [] };
52
+ return { ok: false, keyRegistered: false, keyConflict: false, keyUnavailable: false, settledIds: null, rejectedIds: [] };
51
53
  }
52
54
  };
53
55
 
@@ -57,6 +57,7 @@ var parseMcpResponse = (body, contentType) => {
57
57
  };
58
58
  var sendMcpRequest = async (apiKey, message, options = {}) => {
59
59
  return withRetry(async () => {
60
+ options.signal?.throwIfAborted();
60
61
  const baseUrl = options.baseUrl ?? getBaseUrl();
61
62
  const fetchFn = options.fetchFn ?? fetch;
62
63
  const headers = {
@@ -71,7 +72,7 @@ var sendMcpRequest = async (apiKey, message, options = {}) => {
71
72
  method: "POST",
72
73
  headers,
73
74
  body: JSON.stringify(message),
74
- signal: options.timeoutMs ? AbortSignal.timeout(options.timeoutMs) : void 0
75
+ signal: options.timeoutMs ? AbortSignal.any([AbortSignal.timeout(options.timeoutMs), ...options.signal ? [options.signal] : []]) : options.signal
75
76
  });
76
77
  const body = await response.text();
77
78
  const contentType = response.headers.get("content-type");
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  isBridgeLocator
3
- } from "./chunk-3HS4YGHB.js";
3
+ } from "./chunk-YANV5TGK.js";
4
4
  import {
5
5
  pusharyHookExecutable
6
- } from "./chunk-ZMKKK6DC.js";
6
+ } from "./chunk-HSAVPZ46.js";
7
7
  import {
8
8
  BELL_HOOK_BINARY
9
9
  } from "./chunk-DSTLA2TC.js";
@@ -18,7 +18,7 @@ import {
18
18
  } from "./chunk-7QLSKOSU.js";
19
19
  import {
20
20
  isValidApiKey
21
- } from "./chunk-VNMKIJZB.js";
21
+ } from "./chunk-Z6CP5ZZD.js";
22
22
 
23
23
  // src/hermes-config.ts
24
24
  import { execFileSync } from "child_process";
@@ -2,15 +2,15 @@ import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  GEMINI_HOOK_EVENTS,
4
4
  hasGeminiHooks
5
- } from "./chunk-I7W3UTDK.js";
5
+ } from "./chunk-RX75MRGC.js";
6
6
  import {
7
7
  CODEX_HOOK_EVENTS,
8
8
  hasCodexHooks
9
- } from "./chunk-CV2YD2JR.js";
9
+ } from "./chunk-Y6XXFXVB.js";
10
10
  import {
11
11
  CURSOR_GATE_EVENTS,
12
12
  HOOK_LOCATOR_BINARY
13
- } from "./chunk-VNMKIJZB.js";
13
+ } from "./chunk-Z6CP5ZZD.js";
14
14
 
15
15
  // src/diagnostics/wiring.ts
16
16
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-CV2YD2JR.js";
3
+ } from "./chunk-Y6XXFXVB.js";
4
4
  import {
5
5
  writeFileAtomic
6
6
  } from "./chunk-4LL6LG5X.js";
@@ -3,11 +3,11 @@ import {
3
3
  decodePublicKey,
4
4
  generateSessionKey,
5
5
  wrapSessionKey
6
- } from "./chunk-7FE2KABY.js";
6
+ } from "./chunk-46P3VES5.js";
7
7
  import {
8
8
  fetchTranscriptRecipients,
9
9
  uploadTranscriptRecords
10
- } from "./chunk-QZCFVWA7.js";
10
+ } from "./chunk-EQRS3SGM.js";
11
11
 
12
12
  // src/live-session.ts
13
13
  var MAX_TRANSCRIPT_QUEUE = 100;
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-VNMKIJZB.js";
6
+ } from "./chunk-Z6CP5ZZD.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  shouldDeferToNativeMode
3
- } from "./chunk-VNMKIJZB.js";
3
+ } from "./chunk-Z6CP5ZZD.js";
4
4
 
5
5
  // src/native-defer.ts
6
6
  var nativeDeferOptions = () => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getMachineId
3
- } from "./chunk-2ACC5M7F.js";
3
+ } from "./chunk-NVPYRDCT.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
@@ -1,27 +1,25 @@
1
- import {
2
- PRETOOLUSE_HANDLED_TOOLS,
3
- PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
- } from "./chunk-2LAHXHBR.js";
5
1
  import {
6
2
  defersToNativeMode
7
- } from "./chunk-I463AEG3.js";
3
+ } from "./chunk-OYNPLEXP.js";
8
4
  import {
9
5
  isGatingMoment,
10
6
  recordKeylessMoment
11
- } from "./chunk-VHWCVD42.js";
7
+ } from "./chunk-2ORRI66J.js";
12
8
  import {
13
9
  denyReasonFrom,
14
10
  isDeferAnswer
15
11
  } from "./chunk-YHG74UFF.js";
16
12
  import {
13
+ CLAUDE_CODE_AGENT,
17
14
  fetchModeState,
18
15
  getPolicy,
19
16
  readLastPrompt,
20
17
  readLastUserPrompt,
21
18
  repoKeyFor,
19
+ reportEvent,
22
20
  settleQuestionsForToolUse,
23
21
  throttlePass
24
- } from "./chunk-YXDYKECP.js";
22
+ } from "./chunk-5HGLZGI4.js";
25
23
  import {
26
24
  DEFAULT_SESSION,
27
25
  askUser,
@@ -29,7 +27,7 @@ import {
29
27
  pollForAnswer,
30
28
  savePendingQuestion,
31
29
  sendNotification
32
- } from "./chunk-F7OURW3C.js";
30
+ } from "./chunk-RER3FRTD.js";
33
31
  import {
34
32
  UNNAMED_ACTION,
35
33
  deriveAction,
@@ -37,11 +35,16 @@ import {
37
35
  deriveBlocker,
38
36
  deriveToolTarget,
39
37
  describeToolCall,
38
+ permissionDeniedAction,
40
39
  scopePathFor
41
- } from "./chunk-L4E2BIJ2.js";
40
+ } from "./chunk-4PE2SQVM.js";
41
+ import {
42
+ PRETOOLUSE_HANDLED_TOOLS,
43
+ PRETOOLUSE_PHONE_ANSWERED_TOOLS
44
+ } from "./chunk-Y3UGCQQC.js";
42
45
  import {
43
46
  getMachineId
44
- } from "./chunk-2ACC5M7F.js";
47
+ } from "./chunk-NVPYRDCT.js";
45
48
  import {
46
49
  getApiKey,
47
50
  getBaseUrl
@@ -58,7 +61,7 @@ import {
58
61
  parseAgentQuestions,
59
62
  resolveGate,
60
63
  stoppedOutput
61
- } from "./chunk-VNMKIJZB.js";
64
+ } from "./chunk-Z6CP5ZZD.js";
62
65
 
63
66
  // src/decision-episode.ts
64
67
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -582,7 +585,19 @@ var automodeRetryEnabled = () => {
582
585
  const flag = process.env.PUSHARY_AUTOMODE_RETRY;
583
586
  return flag === "1" || flag === "true";
584
587
  };
588
+ var reportPermissionDenied = (input) => reportEvent({
589
+ event: "permission_denied",
590
+ agentType: CLAUDE_CODE_AGENT.type,
591
+ agentName: `${CLAUDE_CODE_AGENT.label} - ${basename(input.cwd ?? process.cwd())}`,
592
+ action: permissionDeniedAction(input.tool_name),
593
+ sessionId: input.session_id
594
+ // Side report, not a command carrier. Without timeoutMs this takes the phone's
595
+ // queued instruction and acknowledges it delivered, and this hook can print
596
+ // only {retry}. 4s rather than the Notification path's 2.5s because
597
+ // PermissionDenied is given a 60s budget, not 10s.
598
+ }, { timeoutMs: 4e3 }).catch(() => void 0);
585
599
  var handlePermissionDenied = async (input) => {
600
+ await reportPermissionDenied(input);
586
601
  if (isPusharyOwnTool(input.tool_name)) return void 0;
587
602
  let apiKey;
588
603
  try {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  callMcpTool
3
- } from "./chunk-BSZYIAZL.js";
3
+ } from "./chunk-EYRGBYZG.js";
4
4
  import {
5
5
  installProxyDispatcher
6
6
  } from "./chunk-SAF6HGAA.js";
@@ -33,11 +33,11 @@ var askUser = async (apiKey, params, timeoutMs = 15e3) => {
33
33
  if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
34
34
  return result;
35
35
  };
36
- var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4) => {
36
+ var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4, signal) => {
37
37
  const result = await callMcpTool(apiKey, "wait_for_answer", {
38
38
  correlationId,
39
39
  timeoutMs
40
- }, { timeoutMs: timeoutMs + 1e4 });
40
+ }, { timeoutMs: timeoutMs + 1e4, signal });
41
41
  if (!isWaitForAnswerResponse(result)) throw new Error("Invalid wait_for_answer response");
42
42
  return result;
43
43
  };
@@ -59,11 +59,11 @@ var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3
59
59
  }
60
60
  return { answered: false };
61
61
  };
62
- var cancelQuestion = async (apiKey, correlationId) => {
63
- await callMcpTool(apiKey, "cancel_question", { correlationId });
62
+ var cancelQuestion = async (apiKey, correlationId, signal) => {
63
+ await callMcpTool(apiKey, "cancel_question", { correlationId }, { signal });
64
64
  };
65
- var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
66
- await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: 3, timeoutMs });
65
+ var sendNotification = async (apiKey, params, timeoutMs = 15e3, signal) => {
66
+ await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: signal ? 1 : 3, timeoutMs, signal });
67
67
  };
68
68
 
69
69
  // src/pending.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-OSALSIUF.js";
3
+ } from "./chunk-6BIMJIRG.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -12,10 +12,10 @@ import {
12
12
  } from "./chunk-5RUIFDTP.js";
13
13
  import {
14
14
  callMcpTool
15
- } from "./chunk-BSZYIAZL.js";
15
+ } from "./chunk-EYRGBYZG.js";
16
16
  import {
17
17
  reportedInstallSource
18
- } from "./chunk-JW4KO5AF.js";
18
+ } from "./chunk-ODL52O5V.js";
19
19
 
20
20
  // src/reach.ts
21
21
  var reachVerdict = (channels) => {
@@ -1,17 +1,19 @@
1
- import {
2
- PRETOOLUSE_GATED_TOOLS,
3
- PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
- } from "./chunk-2LAHXHBR.js";
5
1
  import {
6
2
  guardBinary
7
3
  } from "./chunk-EZIEXHYM.js";
4
+ import {
5
+ NOTIFICATION_MATCHED_TYPES,
6
+ POSTTOOLUSE_MIRRORED_TOOLS,
7
+ PRETOOLUSE_GATED_TOOLS,
8
+ PRETOOLUSE_PHONE_ANSWERED_TOOLS
9
+ } from "./chunk-Y3UGCQQC.js";
8
10
  import {
9
11
  isProtectedByScope
10
- } from "./chunk-CV2YD2JR.js";
12
+ } from "./chunk-Y6XXFXVB.js";
11
13
  import {
12
14
  HOOK_BUDGETS,
13
15
  HOOK_LOCATOR_BINARY
14
- } from "./chunk-VNMKIJZB.js";
16
+ } from "./chunk-Z6CP5ZZD.js";
15
17
 
16
18
  // src/claude-config.ts
17
19
  import { join } from "path";
@@ -32,7 +34,7 @@ var formatVersion = (version) => version.join(".");
32
34
 
33
35
  // src/claude-events.ts
34
36
  var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...PRETOOLUSE_PHONE_ANSWERED_TOOLS].join("|");
35
- var MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, "TodoWrite"].join("|");
37
+ var MIRRORED_TOOLS = POSTTOOLUSE_MIRRORED_TOOLS.join("|");
36
38
  var ESTABLISHED = [1, 0, 0];
37
39
  var OBSERVED = [2, 0, 0];
38
40
  var CLAUDE_HOOK_EVENTS = [
@@ -40,7 +42,7 @@ var CLAUDE_HOOK_EVENTS = [
40
42
  { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
41
43
  { event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
42
44
  { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
43
- { event: "Notification", binary: "pushary-notification-hook", matcher: "permission_prompt|idle_prompt|agent_needs_input|agent_completed", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
45
+ { event: "Notification", binary: "pushary-notification-hook", matcher: NOTIFICATION_MATCHED_TYPES.join("|"), timeout: 10, delivery: "telemetry", since: ESTABLISHED },
44
46
  { event: "SessionStart", binary: "pushary-session-start-hook", matcher: "startup|resume|clear", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
45
47
  { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
46
48
  { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-CV2YD2JR.js";
3
+ } from "./chunk-Y6XXFXVB.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-4LL6LG5X.js";
@@ -11,8 +11,9 @@ import {
11
11
  getBaseUrl
12
12
  } from "./chunk-2UMNXADU.js";
13
13
  import {
14
- isValidApiKey
15
- } from "./chunk-VNMKIJZB.js";
14
+ isValidApiKey,
15
+ normalizeKeyScope
16
+ } from "./chunk-Z6CP5ZZD.js";
16
17
 
17
18
  // src/session.ts
18
19
  var samePusharyAccount = (left, right) => left.kind === "ok" && right.kind === "ok" && left.identity.workspaceId !== "" && left.identity.workspaceId === right.identity.workspaceId && left.identity.siteId === right.identity.siteId;
@@ -20,7 +21,6 @@ var IDENTITY_PATH = "/api/v1/server/identity";
20
21
  var DEFAULT_TIMEOUT_MS = 1e4;
21
22
  var SUBSCRIPTION_REQUIRED_CODE = "subscription_required";
22
23
  var DEFAULT_UPGRADE_PATH = "/dashboard/settings?tab=billing";
23
- var KEY_SCOPES = ["full_access", "connector", "bound"];
24
24
  var SUBSCRIPTION_STATUSES = [
25
25
  "active",
26
26
  "past_due",
@@ -31,7 +31,6 @@ var SUBSCRIPTION_STATUSES = [
31
31
  "none"
32
32
  ];
33
33
  var asString = (value) => typeof value === "string" && value ? value : null;
34
- var asScope = (value) => KEY_SCOPES.includes(value) ? value : "full_access";
35
34
  var asStatus = (value) => SUBSCRIPTION_STATUSES.includes(value) ? value : "none";
36
35
  var asCount = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
37
36
  var absoluteUrl = (baseUrl, pathOrUrl) => {
@@ -43,12 +42,13 @@ var toIdentity = (body) => {
43
42
  const raw = body;
44
43
  const keyId = asString(raw.keyId);
45
44
  const siteId = asString(raw.siteId);
46
- if (!keyId || !siteId) return null;
45
+ const keyScope = normalizeKeyScope(raw.keyScope);
46
+ if (!keyId || !siteId || !keyScope) return null;
47
47
  return {
48
48
  keyId,
49
49
  keyPrefix: asString(raw.keyPrefix) ?? "",
50
50
  keyName: asString(raw.keyName) ?? "",
51
- keyScope: asScope(raw.keyScope),
51
+ keyScope,
52
52
  siteId,
53
53
  siteSlug: asString(raw.siteSlug) ?? "",
54
54
  siteName: asString(raw.siteName) ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-BXTJVM7O.js";
4
+ } from "./chunk-UZFVNEEU.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -0,0 +1,53 @@
1
+ import {
2
+ ASKED_IN_EVERY_MODE_TOOLS
3
+ } from "./chunk-Z6CP5ZZD.js";
4
+
5
+ // src/claude-tools.ts
6
+ var PRETOOLUSE_GATED_TOOLS = [
7
+ "Bash",
8
+ "Write",
9
+ "Edit",
10
+ "MultiEdit",
11
+ "NotebookEdit",
12
+ "PowerShell",
13
+ "Monitor"
14
+ ];
15
+ var PRETOOLUSE_PHONE_ANSWERED_TOOLS = ASKED_IN_EVERY_MODE_TOOLS;
16
+ var POSTTOOLUSE_MIRROR_ONLY_TOOLS = ["TodoWrite", "Task", "WebFetch", "WebSearch"];
17
+ var POSTTOOLUSE_MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...POSTTOOLUSE_MIRROR_ONLY_TOOLS];
18
+ var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, ...PRETOOLUSE_PHONE_ANSWERED_TOOLS]);
19
+
20
+ // src/claude-notifications.ts
21
+ var NOTIFICATION_MATCHED_TYPES = [
22
+ // Blocks. The agent, or one of its workers, is waiting on a person.
23
+ "permission_prompt",
24
+ "idle_prompt",
25
+ "agent_needs_input",
26
+ "worker_permission_prompt",
27
+ // An MCP server asking the person for a value. The Elicitation hook answers it;
28
+ // this is the feed row saying one is open.
29
+ "elicitation_dialog",
30
+ "elicitation_url_dialog",
31
+ // Turn boundary.
32
+ "agent_completed",
33
+ // Quota. The island already reads plan usage, and these say when a limit hit,
34
+ // went stale, or was turned off, which no polling of the usage endpoint reveals.
35
+ "quota_auto_resume_fired",
36
+ "quota_auto_resume_stale",
37
+ "quota_auto_resume_disabled"
38
+ ];
39
+ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set([
40
+ "idle_prompt",
41
+ "agent_needs_input",
42
+ "worker_permission_prompt"
43
+ ]);
44
+
45
+ export {
46
+ PRETOOLUSE_GATED_TOOLS,
47
+ PRETOOLUSE_PHONE_ANSWERED_TOOLS,
48
+ POSTTOOLUSE_MIRROR_ONLY_TOOLS,
49
+ POSTTOOLUSE_MIRRORED_TOOLS,
50
+ PRETOOLUSE_HANDLED_TOOLS,
51
+ NOTIFICATION_MATCHED_TYPES,
52
+ NOTIFICATION_PUSH_TYPES
53
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
3
  HOOK_LOCATOR_BINARY
4
- } from "./chunk-VNMKIJZB.js";
4
+ } from "./chunk-Z6CP5ZZD.js";
5
5
 
6
6
  // src/removal-scope.ts
7
7
  var namesAppLocator = (command) => command.includes(HOOK_LOCATOR_BINARY);
@@ -34,11 +34,23 @@ var CODEX_HOOK_EVENTS = [
34
34
  { event: "PostToolUse", matcher: "Bash|apply_patch", delivery: "telemetry", timeout: 10 },
35
35
  { event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
36
36
  { event: "Stop", delivery: "telemetry", timeout: 10 },
37
- { event: "SessionStart", matcher: "startup|resume", delivery: "telemetry", timeout: 10 },
37
+ // `clear` alongside startup and resume, matching what we already do on Claude
38
+ // Code. Codex names all three in its own SessionStart sources, and without it a
39
+ // session the person started by clearing is one we never see begin.
40
+ { event: "SessionStart", matcher: "startup|resume|clear", delivery: "telemetry", timeout: 10 },
38
41
  // Codex caps SessionEnd at 3s and defaults it to 1s, unlike every other event
39
42
  // here. It also runs synchronously whatever `async` says, so the handler must
40
43
  // fit the window rather than assume the 10s Claude Code allows.
41
- { event: "SessionEnd", delivery: "telemetry", timeout: 3 }
44
+ { event: "SessionEnd", delivery: "telemetry", timeout: 3 },
45
+ // Observability only, and the four Codex names we were not registering at all.
46
+ // Every one is in codex-cli 0.151.0's own `HookEventName` enum
47
+ // (subagent_start, subagent_stop, pre_compact, post_compact), and we already
48
+ // carry all four on Claude Code, so the island showed subagent and compaction
49
+ // shape for one agent and not the other. None of them decides anything.
50
+ { event: "SubagentStart", delivery: "telemetry", timeout: 5 },
51
+ { event: "SubagentStop", delivery: "telemetry", timeout: 5 },
52
+ { event: "PreCompact", matcher: "manual|auto", delivery: "telemetry", timeout: 5 },
53
+ { event: "PostCompact", matcher: "manual|auto", delivery: "telemetry", timeout: 5 }
42
54
  ];
43
55
  var CODEX_EVENT_KEY = {
44
56
  PermissionRequest: "permission_request",
@@ -47,7 +59,11 @@ var CODEX_EVENT_KEY = {
47
59
  UserPromptSubmit: "user_prompt_submit",
48
60
  Stop: "stop",
49
61
  SessionStart: "session_start",
50
- SessionEnd: "session_end"
62
+ SessionEnd: "session_end",
63
+ SubagentStart: "subagent_start",
64
+ SubagentStop: "subagent_stop",
65
+ PreCompact: "pre_compact",
66
+ PostCompact: "post_compact"
51
67
  };
52
68
  var codexEventKeyOf = (event) => event.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
53
69
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
@@ -209,14 +225,30 @@ var restoreCodexHookTrust = (config, hooksJsonPath, positions, snapshot) => {
209
225
  }
210
226
  return true;
211
227
  };
212
- var untrustedCodexHookEvents = (config, hooksJsonPath, command, positions) => {
228
+ var codexTrustGaps = (config, hooksConfig, hooksJsonPath, command, positions) => {
213
229
  const state = asRecord(asRecord(config.hooks)?.state);
214
- return CODEX_HOOK_EVENTS.filter((definition) => {
230
+ const hooks = asRecord(hooksConfig?.hooks);
231
+ const gaps = [];
232
+ for (const definition of CODEX_HOOK_EVENTS) {
215
233
  const position = positions[definition.event];
216
- if (!position) return false;
217
- const entry = asRecord(state?.[codexHookStateKey(hooksJsonPath, definition.event, position)]);
218
- return entry?.trusted_hash !== codexHookTrustHash(definition, command);
219
- }).map((definition) => definition.event);
234
+ if (!position) continue;
235
+ const stored = asRecord(state?.[codexHookStateKey(hooksJsonPath, definition.event, position)])?.trusted_hash;
236
+ const eventKey = CODEX_EVENT_KEY[definition.event] ?? definition.event;
237
+ const onDisk = asRecord(
238
+ asRecord(hooks?.[definition.event]) ?? (Array.isArray(hooks?.[definition.event]) ? hooks[definition.event][position[0]] : void 0)
239
+ );
240
+ const diskHash = onDisk ? codexEntryTrustHash(eventKey, onDisk) : void 0;
241
+ if (stored !== void 0 && diskHash !== void 0 && stored === diskHash) {
242
+ if (diskHash !== codexHookTrustHash(definition, command)) {
243
+ gaps.push({ event: definition.event, kind: "stale" });
244
+ }
245
+ continue;
246
+ }
247
+ if (stored !== (diskHash ?? codexHookTrustHash(definition, command))) {
248
+ gaps.push({ event: definition.event, kind: "untrusted" });
249
+ }
250
+ }
251
+ return gaps;
220
252
  };
221
253
  var removeCodexMcpServer = (config) => {
222
254
  const servers = asRecord(config.mcp_servers);
@@ -318,7 +350,7 @@ export {
318
350
  codexHookPositions,
319
351
  captureCodexHookTrust,
320
352
  restoreCodexHookTrust,
321
- untrustedCodexHookEvents,
353
+ codexTrustGaps,
322
354
  removeCodexHookTrust,
323
355
  removeCodexSettings,
324
356
  addCodexHookTrust,
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  openCodeConfigDirFrom
3
- } from "./chunk-CELX75MW.js";
3
+ } from "./chunk-BSEAOCCQ.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-CV2YD2JR.js";
6
+ } from "./chunk-Y6XXFXVB.js";
7
7
  import {
8
8
  HOOK_LOCATOR_BINARY
9
- } from "./chunk-VNMKIJZB.js";
9
+ } from "./chunk-Z6CP5ZZD.js";
10
10
 
11
11
  // src/vscode-config.ts
12
12
  import { homedir } from "os";
@@ -1,4 +1,6 @@
1
1
  // ../contracts/src/index.ts
2
+ var KEY_SCOPES = ["full_access", "agent", "connector", "bound"];
3
+ var normalizeKeyScope = (value) => typeof value === "string" && KEY_SCOPES.includes(value) ? value : null;
2
4
  var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
3
5
  var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
4
6
  var MAX_AGENT_QUESTIONS = 4;
@@ -1347,6 +1349,7 @@ var elicitationContentFromAnswers = (ask, answers) => {
1347
1349
  };
1348
1350
 
1349
1351
  export {
1352
+ normalizeKeyScope,
1350
1353
  parseAgentQuestions,
1351
1354
  parseAgentQuestionAnswers,
1352
1355
  normalizeInstallSource,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-I7W3UTDK.js";
3
+ } from "./chunk-RX75MRGC.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -7,10 +7,10 @@ import {
7
7
  } from "./chunk-L4ZCFMWW.js";
8
8
  import {
9
9
  getMachineId
10
- } from "./chunk-2ACC5M7F.js";
10
+ } from "./chunk-NVPYRDCT.js";
11
11
  import {
12
12
  pusharyDir
13
- } from "./chunk-CV2YD2JR.js";
13
+ } from "./chunk-Y6XXFXVB.js";
14
14
  import {
15
15
  getPackageVersion
16
16
  } from "./chunk-DINDL2CF.js";
@@ -16,10 +16,12 @@ declare const handlePreToolUse: (input: ToolInput) => Promise<HookOutput | undef
16
16
 
17
17
  declare const getPolicy: (apiKey: string, expectedVersion?: string | null, agent?: string, repoAware?: boolean, sessionId?: string) => Promise<PolicyConfig>;
18
18
 
19
- declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number) => Promise<ModeState>;
19
+ declare const fetchModeState: (apiKey: string, sessionId?: string, reuseWithinMs?: number, signal?: AbortSignal) => Promise<ModeState>;
20
20
  declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
21
21
 
22
22
  interface UsageReport {
23
+ readonly eventId: string;
24
+ readonly reportedAt: number;
23
25
  readonly tokensIn: number;
24
26
  readonly tokensOut: number;
25
27
  readonly costUsd: number;
@@ -52,8 +54,35 @@ interface AgentEvent {
52
54
  pid?: number;
53
55
  }
54
56
  interface ReportEventOptions {
57
+ signal?: AbortSignal;
55
58
  maxAttempts?: number;
56
59
  timeoutMs?: number;
60
+ /**
61
+ * Total wall clock this call may spend, acknowledge round trip included.
62
+ *
63
+ * Deliberately not `timeoutMs`. That option doubles as the "I am a side report"
64
+ * signal (`clientCanDrain` below is keyed on its absence), so a drain-carrying
65
+ * caller that set it to bound its time would silently stop delivering the
66
+ * instructions a person sent from their phone. This bounds the clock and says
67
+ * nothing about draining.
68
+ *
69
+ * Without it, a single report could spend `timeoutMs ?? 10_000` on the event
70
+ * POST and then a further `timeoutMs ?? 10_000` acknowledging a pending command,
71
+ * which is twice the ceiling the hook was given and, on Codex, twice what the
72
+ * agent will wait before killing the process mid-flight.
73
+ */
74
+ budgetMs?: number;
75
+ /**
76
+ * Tells the server not to pop a queued instruction for this report.
77
+ *
78
+ * `canDrainCommand: false` is not enough. The route's first disjunct is
79
+ * `event === "session_end" && consumesDrainedCommand(agentType)`, which never
80
+ * reads that flag, so any `session_end` from an agent whose Stop carries text
81
+ * back pops the queue destructively. A subagent's stop is a `session_end` and
82
+ * its caller throws the response away, which is a queued instruction destroyed
83
+ * rather than delivered. `skipCommandDrain` is the route's own first check.
84
+ */
85
+ skipDrain?: boolean;
57
86
  requireOk?: boolean;
58
87
  /**
59
88
  * Authenticate with this key instead of the ambient one.
@@ -102,6 +131,7 @@ declare const handleNotification: (input: {
102
131
  title?: string;
103
132
  type?: string;
104
133
  notification_type?: string;
134
+ hook_event_name?: string;
105
135
  cwd?: string;
106
136
  session_id?: string;
107
137
  }, agent?: AgentIdentity) => Promise<void>;
@@ -148,8 +178,8 @@ interface AnswerResult {
148
178
  note?: string;
149
179
  }
150
180
  declare const askUser: (apiKey: string, params: AskUserParams, timeoutMs?: number) => Promise<AskUserResponse>;
151
- declare const waitForAnswer: (apiKey: string, correlationId: string, timeoutMs?: number) => Promise<AnswerResult>;
152
- declare const cancelQuestion: (apiKey: string, correlationId: string) => Promise<void>;
181
+ declare const waitForAnswer: (apiKey: string, correlationId: string, timeoutMs?: number, signal?: AbortSignal) => Promise<AnswerResult>;
182
+ declare const cancelQuestion: (apiKey: string, correlationId: string, signal?: AbortSignal) => Promise<void>;
153
183
 
154
184
  declare const getApiKey: () => string;
155
185
  declare const getBaseUrl: () => string;