@pushary/agent-hooks 1.9.9 → 1.9.11

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 (103) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/dist/bin/pushary-bell-hook.js +25 -21
  4. package/dist/bin/pushary-bell.js +9 -9
  5. package/dist/bin/pushary-claude.js +31 -34
  6. package/dist/bin/pushary-clean.js +108 -34
  7. package/dist/bin/pushary-codex-bridge.js +20 -22
  8. package/dist/bin/pushary-codex-hook.js +33 -36
  9. package/dist/bin/pushary-codex.js +12 -14
  10. package/dist/bin/pushary-connect.js +27 -29
  11. package/dist/bin/pushary-cowork.js +5 -5
  12. package/dist/bin/pushary-daemon.js +31 -33
  13. package/dist/bin/pushary-disconnect.js +29 -29
  14. package/dist/bin/pushary-doctor.js +60 -50
  15. package/dist/bin/pushary-elicitation-hook.js +26 -26
  16. package/dist/bin/pushary-gemini-bridge.js +13 -13
  17. package/dist/bin/pushary-gemini-hook.js +30 -33
  18. package/dist/bin/pushary-hook.js +31 -33
  19. package/dist/bin/pushary-login.js +12 -12
  20. package/dist/bin/pushary-logout.js +18 -78
  21. package/dist/bin/pushary-mcp.js +9 -9
  22. package/dist/bin/pushary-mode.js +12 -12
  23. package/dist/bin/pushary-notification-hook.js +24 -26
  24. package/dist/bin/pushary-opencode-hook.js +29 -32
  25. package/dist/bin/pushary-permission-denied-hook.js +29 -31
  26. package/dist/bin/pushary-permission-hook.js +28 -30
  27. package/dist/bin/pushary-post-hook.js +24 -26
  28. package/dist/bin/pushary-prompt-hook.js +24 -26
  29. package/dist/bin/pushary-session-end-hook.js +24 -26
  30. package/dist/bin/pushary-session-start-hook.js +28 -30
  31. package/dist/bin/pushary-setup.js +62 -46
  32. package/dist/bin/pushary-stats.js +5 -5
  33. package/dist/bin/pushary-status.js +30 -32
  34. package/dist/bin/pushary-stop-hook.js +24 -26
  35. package/dist/bin/pushary-stopfailure-hook.js +24 -26
  36. package/dist/bin/pushary-suggestions.js +12 -12
  37. package/dist/bin/pushary-transcript-register.js +17 -17
  38. package/dist/bin/pushary-transcripts.js +7 -7
  39. package/dist/bin/pushary-upgrade.js +30 -32
  40. package/dist/bin/pushary-wait.js +12 -12
  41. package/dist/bin/pushary.js +6 -6
  42. package/dist/{chunk-IIVZZMSO.js → chunk-2AQ4VZSA.js} +1 -1
  43. package/dist/{chunk-APP43AW7.js → chunk-2GG77ADC.js} +1 -1
  44. package/dist/{chunk-KOWFRUIT.js → chunk-2NAQRPZQ.js} +1 -1
  45. package/dist/{chunk-Z23ZBYDW.js → chunk-4FWWHCYM.js} +4 -4
  46. package/dist/{chunk-ZGADIZDS.js → chunk-4HR2ICME.js} +1 -1
  47. package/dist/{chunk-E5HOHIUV.js → chunk-4RENRBSB.js} +4 -4
  48. package/dist/{chunk-RQ32ADJP.js → chunk-5WAYSUWV.js} +2 -2
  49. package/dist/{chunk-BYLKCZ2G.js → chunk-7RWQZQV2.js} +14 -14
  50. package/dist/{chunk-KQFTBQ66.js → chunk-A6J5Z2HO.js} +514 -313
  51. package/dist/{chunk-EIAEUG7G.js → chunk-AIWZRRSK.js} +2 -2
  52. package/dist/{chunk-H5T27W7M.js → chunk-BDRXDO25.js} +2 -2
  53. package/dist/{chunk-UAP5DS6P.js → chunk-BMP2BLIR.js} +3 -3
  54. package/dist/{chunk-VIIHC42X.js → chunk-BZI7EBTB.js} +1 -1
  55. package/dist/{chunk-TO3PS646.js → chunk-C6QM5SJR.js} +2 -2
  56. package/dist/{chunk-BNSKVIAW.js → chunk-CBDJ2X5P.js} +2 -2
  57. package/dist/{chunk-COQ64C4F.js → chunk-CHDAH4HP.js} +8 -8
  58. package/dist/{chunk-M3STIAGU.js → chunk-CZYMYM7X.js} +3 -3
  59. package/dist/{chunk-MD4SVNT6.js → chunk-E6YQW43K.js} +1 -1
  60. package/dist/{chunk-5N5L6DNN.js → chunk-EJVYEOKC.js} +3 -3
  61. package/dist/{chunk-AI7RMFVV.js → chunk-ERXY2SIW.js} +19 -16
  62. package/dist/{chunk-IRHABAQT.js → chunk-FJCOOFT7.js} +1 -1
  63. package/dist/{chunk-NGKUXZID.js → chunk-GA4J6YFN.js} +2 -2
  64. package/dist/{chunk-WRPYCIV2.js → chunk-GUNZHSTL.js} +136 -8
  65. package/dist/{chunk-T6ZN4TEF.js → chunk-HX5TI6DP.js} +1 -1
  66. package/dist/{chunk-QRXPWW3M.js → chunk-I4I4UH62.js} +3 -3
  67. package/dist/{chunk-6AYSKVLL.js → chunk-KBN3UP2R.js} +3 -3
  68. package/dist/{chunk-UF6RBP5V.js → chunk-KEV2MOJH.js} +46 -21
  69. package/dist/{chunk-BZLHTMT6.js → chunk-KIVFWGG6.js} +12 -10
  70. package/dist/{chunk-HBWYMRR5.js → chunk-LBAMPNDM.js} +5 -4
  71. package/dist/{chunk-LENBKYRB.js → chunk-LUHNNZSH.js} +2 -2
  72. package/dist/{chunk-R5RIVOPL.js → chunk-LX6EWNRK.js} +15 -3
  73. package/dist/{chunk-5DCDQ5VW.js → chunk-M54ATCOZ.js} +2 -2
  74. package/dist/{chunk-6W5M3MSE.js → chunk-MHHXR7DT.js} +4 -4
  75. package/dist/{chunk-NUVGDBZC.js → chunk-MITMDERZ.js} +1 -1
  76. package/dist/{chunk-QTIUJZRX.js → chunk-MJLTABWF.js} +3 -3
  77. package/dist/{chunk-QIOIO7VN.js → chunk-MX3LPP3K.js} +6 -6
  78. package/dist/{chunk-F7R3SZ4Q.js → chunk-N6GEI3PK.js} +2 -2
  79. package/dist/{chunk-462EQA6K.js → chunk-N7HXP4PZ.js} +1 -1
  80. package/dist/{chunk-Q6C6ILUS.js → chunk-OKSU74QH.js} +2 -2
  81. package/dist/{chunk-AZOKPWVI.js → chunk-P2EJN7UB.js} +1 -1
  82. package/dist/{chunk-OAYWO5NP.js → chunk-P7N7FJ7W.js} +1 -1
  83. package/dist/{chunk-DP3WX7FW.js → chunk-PRJNUNCV.js} +2 -2
  84. package/dist/{chunk-SZ4Q6ZVS.js → chunk-PTGUJZSD.js} +5 -5
  85. package/dist/chunk-SB2GN6OP.js +71 -0
  86. package/dist/{chunk-DCCSULXK.js → chunk-STUJXZC2.js} +4 -4
  87. package/dist/{chunk-MLGIVVSG.js → chunk-TVVUJTUW.js} +1 -1
  88. package/dist/{chunk-5OP5MQI7.js → chunk-V6IW5WMK.js} +1 -1
  89. package/dist/{chunk-5RGA636G.js → chunk-VOP5U2VX.js} +145 -9
  90. package/dist/{chunk-K545B734.js → chunk-WCELLK4P.js} +2 -2
  91. package/dist/{chunk-VHPFNLRS.js → chunk-WHRYW4P2.js} +5 -5
  92. package/dist/{chunk-G7RIDXAT.js → chunk-XYCTS3WP.js} +1 -1
  93. package/dist/{chunk-6EBGOHDJ.js → chunk-YETKAI5Q.js} +6 -6
  94. package/dist/{chunk-REU2VQPH.js → chunk-Z4AOR6RZ.js} +15 -13
  95. package/dist/{chunk-B5NMSP3V.js → chunk-ZTSMQYST.js} +3 -5
  96. package/dist/{global-upgrade-H272QOGD.js → global-upgrade-VT2JS7DR.js} +6 -6
  97. package/dist/{paths-HMVOTFUN.js → paths-WRBEUNEY.js} +2 -2
  98. package/dist/{reapply-Q5NHDTVU.js → reapply-BCR6U7HO.js} +20 -22
  99. package/dist/src/index.js +19 -21
  100. package/dist/{upgrade-lock-L3GHFIRW.js → upgrade-lock-VRYMTPZQ.js} +3 -3
  101. package/package.json +1 -1
  102. package/dist/chunk-N6IOSZOE.js +0 -185
  103. package/dist/chunk-UPIZSCYD.js +0 -142
@@ -1,49 +1,35 @@
1
- import {
2
- DEFAULT_SESSION,
3
- isDefaultSession,
4
- listPendingQuestions,
5
- listPendingQuestionsForToolUse,
6
- markPendingCommandDelivered,
7
- readPendingCommand,
8
- removePendingCommand,
9
- removePendingQuestion,
10
- removePendingSession,
11
- savePendingCommand
12
- } from "./chunk-N6IOSZOE.js";
13
1
  import {
14
2
  IDLE_PROMPT_NOTIFICATION,
15
3
  NOTIFICATION_PUSH_TYPES,
16
4
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
17
- } from "./chunk-MD4SVNT6.js";
5
+ } from "./chunk-E6YQW43K.js";
18
6
  import {
19
7
  cancelQuestion,
8
+ clientErrorKindOf,
20
9
  isPolicyConfig,
10
+ reportClientError,
21
11
  sendNotification,
22
12
  waitForAnswer
23
- } from "./chunk-WRPYCIV2.js";
13
+ } from "./chunk-GUNZHSTL.js";
24
14
  import {
25
15
  withRetry
26
- } from "./chunk-6AYSKVLL.js";
27
- import {
28
- clientErrorKindOf,
29
- reportClientError
30
- } from "./chunk-UPIZSCYD.js";
16
+ } from "./chunk-KBN3UP2R.js";
31
17
  import {
32
18
  getMachineId
33
- } from "./chunk-KOWFRUIT.js";
19
+ } from "./chunk-2NAQRPZQ.js";
34
20
  import {
35
21
  reportedInstallSource
36
- } from "./chunk-BNSKVIAW.js";
22
+ } from "./chunk-CBDJ2X5P.js";
37
23
  import {
38
24
  canonicalJson
39
- } from "./chunk-Z23ZBYDW.js";
25
+ } from "./chunk-4FWWHCYM.js";
40
26
  import {
41
27
  writeFileAtomic
42
28
  } from "./chunk-T7E75EKA.js";
43
29
  import {
44
30
  getApiKey,
45
31
  getBaseUrl
46
- } from "./chunk-IRHABAQT.js";
32
+ } from "./chunk-FJCOOFT7.js";
47
33
  import {
48
34
  ACTION_BODY_MAX,
49
35
  AGENT_LABELS_BY_TYPE,
@@ -60,160 +46,19 @@ import {
60
46
  gateOutputFor,
61
47
  hookCompatibleProfileBySource,
62
48
  isAutoApprove,
49
+ isTokenCount,
63
50
  localRepoKey,
64
51
  modeStateFromResponse,
65
52
  normalizeRepoRemote,
66
53
  pendingCommandEvents,
67
54
  redactSecrets,
68
55
  redactSecretsDeep,
56
+ reportableSessionId,
69
57
  resolveAutoResolveOrigin,
70
58
  resolvePolicy,
71
59
  toToolLookup,
72
60
  truncateLine
73
- } from "./chunk-5RGA636G.js";
74
-
75
- // src/policy.ts
76
- import { createHash } from "crypto";
77
- import { existsSync, readFileSync, writeFileSync } from "fs";
78
- import { join } from "path";
79
- import { tmpdir } from "os";
80
- var CACHE_TTL_MS = 60 * 1e3;
81
- var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
82
- var policyRequestFor = (agentType) => ({
83
- agent: agentType,
84
- repoAware: REPO_AWARE_AGENTS.includes(agentType)
85
- });
86
- var policyCacheFile = (apiKey, agent, repoAware = false) => {
87
- const parts = [apiKey, agent, repoAware ? "repoAware" : void 0, "sessionToolAware"].filter(Boolean);
88
- const hash = createHash("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
89
- return join(tmpdir(), `pushary-policy-${hash}.json`);
90
- };
91
- var fetchPolicy = async (apiKey, agent, repoAware = false, sessionId) => {
92
- return withRetry(async () => {
93
- const baseUrl = getBaseUrl();
94
- const params = new URLSearchParams();
95
- if (agent) params.set("agent", agent);
96
- if (repoAware) params.set("repoAware", "1");
97
- params.set("scopeAware", "1");
98
- params.set("sessionToolAware", "1");
99
- if (sessionId) params.set("sessionId", sessionId);
100
- const query = params.toString();
101
- const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
102
- const response = await fetch(url, {
103
- headers: { "Authorization": `Bearer ${apiKey}` },
104
- signal: AbortSignal.timeout(1e4)
105
- });
106
- if (!response.ok) {
107
- throw new Error(`Failed to fetch policy: ${response.status}`);
108
- }
109
- const raw = await response.json();
110
- if (!isPolicyConfig(raw)) throw new Error("Invalid policy response");
111
- return raw;
112
- }, { maxAttempts: 2 });
113
- };
114
- var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessionId) => {
115
- const path = policyCacheFile(apiKey, agent, repoAware);
116
- let staleCache = null;
117
- if (existsSync(path)) {
118
- try {
119
- const stat = readFileSync(path, "utf-8");
120
- const cached = JSON.parse(stat);
121
- if (!isPolicyConfig(cached)) throw new Error("Corrupted cache");
122
- const versionStale = expectedVersion != null && (cached._policyVersion ?? null) !== expectedVersion;
123
- const sessionStale = (cached._sessionId ?? null) !== (sessionId ?? null);
124
- const age = cached._cachedAt ? Date.now() - cached._cachedAt : 0;
125
- const ttlFresh = !cached._cachedAt || age < CACHE_TTL_MS;
126
- if (ttlFresh && !versionStale && !sessionStale) {
127
- return cached;
128
- }
129
- if (!sessionStale && age < STALE_CACHE_MAX_MS) staleCache = cached;
130
- } catch {
131
- }
132
- }
133
- try {
134
- const policy = await fetchPolicy(apiKey, agent, repoAware, sessionId);
135
- try {
136
- writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null, _sessionId: sessionId ?? null }), { encoding: "utf-8", mode: 384 });
137
- } catch {
138
- }
139
- return policy;
140
- } catch {
141
- if (staleCache) return staleCache;
142
- throw new Error("Failed to fetch policy and no cached policy available");
143
- }
144
- };
145
- var MODE_GRACE_MS = 60 * 1e3;
146
- var modeStateCacheFile = (apiKey) => {
147
- const hash = createHash("sha256").update(`mode:${apiKey}`).digest("hex").slice(0, 12);
148
- return join(tmpdir(), `pushary-mode-${hash}.json`);
149
- };
150
- var readLastGoodMode = (apiKey) => {
151
- try {
152
- const path = modeStateCacheFile(apiKey);
153
- if (!existsSync(path)) return null;
154
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
155
- if (!parsed || typeof parsed.at !== "number" || !parsed.state) return null;
156
- return parsed;
157
- } catch {
158
- return null;
159
- }
160
- };
161
- var writeLastGoodMode = (apiKey, state, sessionId) => {
162
- try {
163
- const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
164
- writeFileSync(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
165
- } catch {
166
- }
167
- };
168
- var unknownMode = () => ({
169
- mode: null,
170
- kill: false,
171
- policyVersion: null,
172
- relayUrl: null,
173
- trainingConsent: false,
174
- scope: null,
175
- degraded: true
176
- });
177
- var degradedFallback = (apiKey, authoritative = false) => {
178
- if (authoritative) return unknownMode();
179
- const last = readLastGoodMode(apiKey);
180
- if (last && Date.now() - last.at < MODE_GRACE_MS) {
181
- return { ...last.state, degraded: false };
182
- }
183
- return unknownMode();
184
- };
185
- var MODE_REUSE_MS = CACHE_TTL_MS;
186
- var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
187
- const last = readLastGoodMode(apiKey);
188
- if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
189
- if (Date.now() - last.at >= maxAgeMs) return null;
190
- return { ...last.state, degraded: false };
191
- };
192
- var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0, signal) => {
193
- if (reuseWithinMs > 0) {
194
- const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
195
- if (reusable) return reusable;
196
- }
197
- try {
198
- const baseUrl = getBaseUrl();
199
- const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
200
- const response = await fetch(url, {
201
- headers: { "Authorization": `Bearer ${apiKey}` },
202
- signal: AbortSignal.any([AbortSignal.timeout(3e3), ...signal ? [signal] : []])
203
- });
204
- if (!response.ok) {
205
- void reportClientError({ kind: "http_status", code: `mode_http_${response.status}` });
206
- return degradedFallback(apiKey, response.status === 401 || response.status === 403);
207
- }
208
- const state = modeStateFromResponse(await response.json());
209
- writeLastGoodMode(apiKey, state, sessionId);
210
- return state;
211
- } catch (error) {
212
- void reportClientError({ kind: clientErrorKindOf(error), code: "mode_fetch_failed" });
213
- return degradedFallback(apiKey);
214
- }
215
- };
216
- var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
61
+ } from "./chunk-VOP5U2VX.js";
217
62
 
218
63
  // ../hook-mapping/src/describe.ts
219
64
  import { isAbsolute, relative, resolve } from "path";
@@ -612,7 +457,8 @@ var AGENT_IDENTITIES = {
612
457
  codebuddy: { type: "codebuddy", label: AGENT_LABELS_BY_TYPE.codebuddy },
613
458
  qoder: { type: "qoder", label: AGENT_LABELS_BY_TYPE.qoder },
614
459
  droid: { type: "droid", label: AGENT_LABELS_BY_TYPE.droid },
615
- devin: { type: "devin", label: AGENT_LABELS_BY_TYPE.devin }
460
+ devin: { type: "devin", label: AGENT_LABELS_BY_TYPE.devin },
461
+ antigravity: { type: "antigravity_cli", label: AGENT_LABELS_BY_TYPE.antigravity_cli }
616
462
  };
617
463
  var TEAMMATE_IDLE_ACTION = "A teammate went idle";
618
464
  var CLAUDE_MEANINGS = {
@@ -644,6 +490,7 @@ var MEANING_BY_HOOK = {
644
490
  qoder: compatibleMeanings("qoder"),
645
491
  droid: compatibleMeanings("droid"),
646
492
  devin: compatibleMeanings("devin"),
493
+ antigravity: compatibleMeanings("antigravity_cli"),
647
494
  codex: {
648
495
  PostToolUse: { kind: "tool", event: "tool_complete" },
649
496
  UserPromptSubmit: { kind: "prompt", event: "user_prompt" },
@@ -789,14 +636,7 @@ var sessionIdentityOf = (payload) => {
789
636
  return { sessionId: agentId, parentSessionId: sessionId };
790
637
  };
791
638
 
792
- // ../hook-mapping/src/codex-rollout.ts
793
- var CODEX_TURN_CONTEXT_TYPE = "turn_context";
794
- var CODEX_AUTO_REVIEWER = "auto_review";
795
- var CODEX_GUARDIAN_THREAD_SOURCE = "guardian_review";
796
- var CODEX_PLANNED_ACTION_MARKER = "Planned action JSON:\n";
797
- var CODEX_APPROVAL_REQUEST_END = ">>> APPROVAL REQUEST END";
798
- var CODEX_SHELL_TOOL = "exec_command";
799
- var CODEX_SHELL_COMMAND_FLAGS = ["-lc", "-c"];
639
+ // ../hook-mapping/src/json-record.ts
800
640
  var asRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
801
641
  var parseRecord = (text) => {
802
642
  try {
@@ -805,6 +645,15 @@ var parseRecord = (text) => {
805
645
  return null;
806
646
  }
807
647
  };
648
+
649
+ // ../hook-mapping/src/codex-rollout.ts
650
+ var CODEX_TURN_CONTEXT_TYPE = "turn_context";
651
+ var CODEX_AUTO_REVIEWER = "auto_review";
652
+ var CODEX_GUARDIAN_THREAD_SOURCE = "guardian_review";
653
+ var CODEX_PLANNED_ACTION_MARKER = "Planned action JSON:\n";
654
+ var CODEX_APPROVAL_REQUEST_END = ">>> APPROVAL REQUEST END";
655
+ var CODEX_SHELL_TOOL = "exec_command";
656
+ var CODEX_SHELL_COMMAND_FLAGS = ["-lc", "-c"];
808
657
  var nonEmptyString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
809
658
  var isTimestamp = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
810
659
  var readCodexTurnContext = (line) => {
@@ -888,8 +737,249 @@ var codexReviewerDenialsSince = (reviews, since) => {
888
737
  });
889
738
  };
890
739
 
740
+ // ../hook-mapping/src/transcript-usage.ts
741
+ var TRANSCRIPT_ASSISTANT_TYPE = "assistant";
742
+ var TRANSCRIPT_SYNTHETIC_MODEL = "<synthetic>";
743
+ var TRANSCRIPT_RECENT_ID_LIMIT = 200;
744
+ var TRANSCRIPT_USAGE_EVENTS = ["PostToolUse", "Stop"];
745
+ var TRANSCRIPT_USAGE_SOURCES = ["claude", "qwen", "codebuddy", "qoder", "droid", "devin"];
746
+ var COST_TRACKING_ENV = "PUSHARY_COST_TRACKING";
747
+ var COST_TRACKING_OFF = "off";
748
+ var TURN_END_EVENT = "Stop";
749
+ var DEFAULT_MODEL_PRICES = [
750
+ { match: "opus-4-1", in: 15, out: 75 },
751
+ { match: "opus-4-0", in: 15, out: 75 },
752
+ { match: "3-opus", in: 15, out: 75 },
753
+ { match: "opus", in: 5, out: 25 },
754
+ { match: "haiku", in: 1, out: 5 },
755
+ { match: "sonnet", in: 3, out: 15 }
756
+ ];
757
+ var FALLBACK_MODEL_PRICE = { match: "", in: 3, out: 15 };
758
+ var CACHE_WRITE_MULTIPLIER = 1.25;
759
+ var CACHE_READ_MULTIPLIER = 0.1;
760
+ var TOKENS_PER_PRICE_UNIT = 1e6;
761
+ var isPrice = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
762
+ var isModelPrice = (value) => {
763
+ const price = asRecord(value);
764
+ return price !== null && typeof price.match === "string" && isPrice(price.in) && isPrice(price.out);
765
+ };
766
+ var estimateMessageCostUsd = (usage, model, prices = DEFAULT_MODEL_PRICES) => {
767
+ const price = prices.find((candidate) => model.includes(candidate.match)) ?? FALLBACK_MODEL_PRICE;
768
+ const perTokenIn = price.in / TOKENS_PER_PRICE_UNIT;
769
+ const perTokenOut = price.out / TOKENS_PER_PRICE_UNIT;
770
+ return usage.inputTokens * perTokenIn + usage.outputTokens * perTokenOut + usage.cacheCreationTokens * perTokenIn * CACHE_WRITE_MULTIPLIER + usage.cacheReadTokens * perTokenIn * CACHE_READ_MULTIPLIER;
771
+ };
772
+ var transcriptTokensIn = (usage) => usage.inputTokens + usage.cacheCreationTokens + usage.cacheReadTokens;
773
+ var tokenCountOf = (value) => isTokenCount(value) ? value : 0;
774
+ var nonEmptyString2 = (value) => typeof value === "string" && value.length > 0 ? value : null;
775
+ var readTranscriptMessageUsage = (line) => {
776
+ const entry = parseRecord(line);
777
+ if (entry?.type !== TRANSCRIPT_ASSISTANT_TYPE) return null;
778
+ const message = asRecord(entry.message);
779
+ const usage = asRecord(message?.usage);
780
+ if (!message || !usage) return null;
781
+ const model = typeof message.model === "string" ? message.model : "";
782
+ if (model.includes(TRANSCRIPT_SYNTHETIC_MODEL)) return null;
783
+ return {
784
+ ...typeof message.id === "string" ? { id: message.id } : {},
785
+ model,
786
+ usage: {
787
+ inputTokens: tokenCountOf(usage.input_tokens),
788
+ outputTokens: tokenCountOf(usage.output_tokens),
789
+ cacheCreationTokens: tokenCountOf(usage.cache_creation_input_tokens),
790
+ cacheReadTokens: tokenCountOf(usage.cache_read_input_tokens)
791
+ }
792
+ };
793
+ };
794
+ var rememberTranscriptMessage = (recentIds, id) => {
795
+ if (!id) return { counted: true, recentIds };
796
+ if (recentIds.includes(id)) return { counted: false, recentIds };
797
+ return { counted: true, recentIds: [...recentIds, id].slice(-TRANSCRIPT_RECENT_ID_LIMIT) };
798
+ };
799
+ var isTranscriptUsageEvent = (event) => TRANSCRIPT_USAGE_EVENTS.includes(event);
800
+ var endsASubagentTurn = (hook) => hook.event === TURN_END_EVENT && nonEmptyString2(hook.payload.agent_id) !== null;
801
+ var transcriptUsageRequest = (hook) => {
802
+ if (hook.costTracking === COST_TRACKING_OFF) return null;
803
+ if (!TRANSCRIPT_USAGE_SOURCES.includes(hook.source) || !isTranscriptUsageEvent(hook.event)) return null;
804
+ if (endsASubagentTurn(hook)) return null;
805
+ const transcriptPath = nonEmptyString2(hook.payload.transcript_path);
806
+ const sessionId = reportableSessionId(hook.payload.session_id);
807
+ return transcriptPath && sessionId ? { transcriptPath, sessionId } : null;
808
+ };
809
+
810
+ // src/pending.ts
811
+ import { join } from "path";
812
+ import { homedir, tmpdir } from "os";
813
+ import { createHash } from "crypto";
814
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync, unlinkSync, rmSync, statSync } from "fs";
815
+ var PENDING_DIR = join(tmpdir(), "pushary-pending");
816
+ var commandRoot = () => process.env.PUSHARY_COMMAND_DIR?.trim() || join(homedir(), ".pushary", "run", "commands");
817
+ var DEFAULT_SESSION = "_no_session";
818
+ var GRACE_MS = 10 * 60 * 1e3;
819
+ var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
820
+ var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
821
+ var commandDirFor = (sessionId, namespace = "") => join(
822
+ commandRoot(),
823
+ createHash("sha256").update(namespace).digest("hex").slice(0, 16),
824
+ sanitize(sessionId)
825
+ );
826
+ var legacyCommandFile = (sessionId, namespace = "") => join(commandDirFor(sessionId, namespace), "command.json");
827
+ var commandFile = (sessionId, id, namespace = "") => join(commandDirFor(sessionId, namespace), `${createHash("sha256").update(id).digest("hex")}.json`);
828
+ var readCommandFile = (file) => {
829
+ try {
830
+ const value = JSON.parse(readFileSync(file, "utf-8"));
831
+ if (typeof value.id !== "string" || typeof value.text !== "string" || typeof value.expiresAtMs !== "number") return null;
832
+ if (value.expiresAtMs <= Date.now()) {
833
+ unlinkSync(file);
834
+ return null;
835
+ }
836
+ return {
837
+ ...value,
838
+ delivered: value.delivered === true,
839
+ ...value.failed === true ? { failed: true } : {}
840
+ };
841
+ } catch {
842
+ return null;
843
+ }
844
+ };
845
+ var readPendingCommandById = (sessionId, id, namespace = "") => {
846
+ const current = readCommandFile(commandFile(sessionId, id, namespace));
847
+ if (current?.id === id) return current;
848
+ const legacy = readCommandFile(legacyCommandFile(sessionId, namespace));
849
+ return legacy?.id === id ? legacy : null;
850
+ };
851
+ var pruneExpiredCommands = (dir) => {
852
+ let files;
853
+ try {
854
+ files = readdirSync(dir).filter((file) => file.endsWith(".json"));
855
+ } catch {
856
+ return [];
857
+ }
858
+ return files.flatMap((file) => {
859
+ const command = readCommandFile(join(dir, file));
860
+ return command ? [command] : [];
861
+ });
862
+ };
863
+ var pruneExpiredCommandStore = () => {
864
+ let namespaces;
865
+ try {
866
+ namespaces = readdirSync(commandRoot(), { withFileTypes: true });
867
+ } catch {
868
+ return;
869
+ }
870
+ for (const namespace of namespaces.filter((entry) => entry.isDirectory())) {
871
+ const namespaceDir = join(commandRoot(), namespace.name);
872
+ let sessions;
873
+ try {
874
+ sessions = readdirSync(namespaceDir, { withFileTypes: true });
875
+ } catch {
876
+ continue;
877
+ }
878
+ for (const session of sessions.filter((entry) => entry.isDirectory())) {
879
+ pruneExpiredCommands(join(namespaceDir, session.name));
880
+ }
881
+ }
882
+ };
883
+ var readPendingCommand = (sessionId, namespace = "") => {
884
+ const commands = pruneExpiredCommands(commandDirFor(sessionId, namespace));
885
+ return commands.find((command) => !command.delivered && !command.failed) ?? commands[0] ?? null;
886
+ };
887
+ var savePendingCommand = (sessionId, command, namespace = "") => {
888
+ const dir = commandDirFor(sessionId, namespace);
889
+ try {
890
+ mkdirSync(dir, { recursive: true, mode: 448 });
891
+ pruneExpiredCommandStore();
892
+ if (readPendingCommandById(sessionId, command.id, namespace)) return false;
893
+ writeFileSync(commandFile(sessionId, command.id, namespace), JSON.stringify(command), { encoding: "utf-8", mode: 384, flag: "wx" });
894
+ return true;
895
+ } catch {
896
+ return false;
897
+ }
898
+ };
899
+ var updatePendingCommand = (sessionId, id, update, namespace = "") => {
900
+ const command = readPendingCommandById(sessionId, id, namespace);
901
+ if (!command) return false;
902
+ const currentFile = commandFile(sessionId, id, namespace);
903
+ const file = existsSync(currentFile) ? currentFile : legacyCommandFile(sessionId, namespace);
904
+ const temporary = `${file}.${process.pid}.tmp`;
905
+ try {
906
+ writeFileSync(temporary, JSON.stringify({ ...command, ...update, text: "" }), { encoding: "utf-8", mode: 384 });
907
+ renameSync(temporary, file);
908
+ return true;
909
+ } catch {
910
+ try {
911
+ unlinkSync(temporary);
912
+ } catch {
913
+ }
914
+ return false;
915
+ }
916
+ };
917
+ var markPendingCommandDelivered = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: true, failed: void 0 }, namespace);
918
+ var markPendingCommandFailed = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: false, failed: true }, namespace);
919
+ var removePendingCommand = (sessionId, id, namespace = "") => {
920
+ if (!readPendingCommandById(sessionId, id, namespace)) return;
921
+ try {
922
+ unlinkSync(commandFile(sessionId, id, namespace));
923
+ } catch {
924
+ }
925
+ try {
926
+ if (readCommandFile(legacyCommandFile(sessionId, namespace))?.id === id) {
927
+ unlinkSync(legacyCommandFile(sessionId, namespace));
928
+ }
929
+ } catch {
930
+ }
931
+ };
932
+ var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
933
+ var listPendingQuestionsForToolUse = (sessionId, toolUseId) => {
934
+ if (!toolUseId) return [];
935
+ const dir = dirFor(sessionId);
936
+ let files;
937
+ try {
938
+ files = readdirSync(dir);
939
+ } catch {
940
+ return [];
941
+ }
942
+ return files.filter((name) => {
943
+ try {
944
+ return readFileSync(join(dir, name), "utf-8") === toolUseId;
945
+ } catch {
946
+ return false;
947
+ }
948
+ });
949
+ };
950
+ var listPendingQuestions = (sessionId) => {
951
+ const dir = dirFor(sessionId);
952
+ let files;
953
+ try {
954
+ files = readdirSync(dir);
955
+ } catch {
956
+ return [];
957
+ }
958
+ if (!isDefaultSession(sessionId)) return files;
959
+ const cutoff = Date.now() - GRACE_MS;
960
+ return files.filter((name) => {
961
+ try {
962
+ return statSync(join(dir, name)).mtimeMs < cutoff;
963
+ } catch {
964
+ return false;
965
+ }
966
+ });
967
+ };
968
+ var removePendingQuestion = (sessionId, correlationId) => {
969
+ try {
970
+ unlinkSync(join(dirFor(sessionId), correlationId));
971
+ } catch {
972
+ }
973
+ };
974
+ var removePendingSession = (sessionId) => {
975
+ try {
976
+ rmSync(dirFor(sessionId), { recursive: true, force: true });
977
+ } catch {
978
+ }
979
+ };
980
+
891
981
  // src/repo.ts
892
- import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
982
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
893
983
  import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "path";
894
984
  var MAX_PARENT_WALK = 64;
895
985
  var GIT_FILE_PREFIX = "gitdir:";
@@ -899,7 +989,7 @@ var findGitDir = (startDir) => {
899
989
  const candidate = join2(current, ".git");
900
990
  try {
901
991
  if (existsSync2(candidate)) {
902
- if (statSync(candidate).isDirectory()) return candidate;
992
+ if (statSync2(candidate).isDirectory()) return candidate;
903
993
  const pointer = readFileSync2(candidate, "utf-8").trim();
904
994
  if (pointer.startsWith(GIT_FILE_PREFIX)) {
905
995
  const target = pointer.slice(GIT_FILE_PREFIX.length).trim();
@@ -972,51 +1062,172 @@ var repoKeyFor = (cwd) => {
972
1062
  return derived;
973
1063
  };
974
1064
 
975
- // src/usage.ts
976
- import { closeSync, mkdirSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
1065
+ // src/policy.ts
977
1066
  import { createHash as createHash2 } from "crypto";
1067
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
978
1068
  import { join as join3 } from "path";
979
1069
  import { tmpdir as tmpdir2 } from "os";
980
- var DEFAULT_PRICES = [
981
- { match: "opus-4-1", in: 15, out: 75 },
982
- { match: "opus-4-0", in: 15, out: 75 },
983
- { match: "3-opus", in: 15, out: 75 },
984
- { match: "opus", in: 5, out: 25 },
985
- { match: "haiku", in: 1, out: 5 },
986
- { match: "sonnet", in: 3, out: 15 }
987
- ];
988
- var FALLBACK_PRICE = { match: "", in: 3, out: 15 };
989
- var CACHE_WRITE_MULTIPLIER = 1.25;
990
- var CACHE_READ_MULTIPLIER = 0.1;
991
- var RECENT_ID_LIMIT = 200;
992
- var READ_CHUNK_BYTES = 1024 * 1024;
993
- var isModelPrice = (value) => {
994
- if (!value || typeof value !== "object") return false;
995
- const candidate = value;
996
- return typeof candidate.match === "string" && typeof candidate.in === "number" && typeof candidate.out === "number";
1070
+ var CACHE_TTL_MS = 60 * 1e3;
1071
+ var STALE_CACHE_MAX_MS = 12 * 60 * 60 * 1e3;
1072
+ var policyRequestFor = (agentType) => ({
1073
+ agent: agentType,
1074
+ repoAware: REPO_AWARE_AGENTS.includes(agentType)
1075
+ });
1076
+ var policyCacheFile = (apiKey, agent, repoAware = false) => {
1077
+ const parts = [apiKey, agent, repoAware ? "repoAware" : void 0, "sessionToolAware"].filter(Boolean);
1078
+ const hash = createHash2("sha256").update(parts.join(":")).digest("hex").slice(0, 12);
1079
+ return join3(tmpdir2(), `pushary-policy-${hash}.json`);
1080
+ };
1081
+ var fetchPolicy = async (apiKey, agent, repoAware = false, sessionId) => {
1082
+ return withRetry(async () => {
1083
+ const baseUrl = getBaseUrl();
1084
+ const params = new URLSearchParams();
1085
+ if (agent) params.set("agent", agent);
1086
+ if (repoAware) params.set("repoAware", "1");
1087
+ params.set("scopeAware", "1");
1088
+ params.set("sessionToolAware", "1");
1089
+ if (sessionId) params.set("sessionId", sessionId);
1090
+ const query = params.toString();
1091
+ const url = query ? `${baseUrl}/api/mcp/policy?${query}` : `${baseUrl}/api/mcp/policy`;
1092
+ const response = await fetch(url, {
1093
+ headers: { "Authorization": `Bearer ${apiKey}` },
1094
+ signal: AbortSignal.timeout(1e4)
1095
+ });
1096
+ if (!response.ok) {
1097
+ throw new Error(`Failed to fetch policy: ${response.status}`);
1098
+ }
1099
+ const raw = await response.json();
1100
+ if (!isPolicyConfig(raw)) throw new Error("Invalid policy response");
1101
+ return raw;
1102
+ }, { maxAttempts: 2 });
1103
+ };
1104
+ var getPolicy = async (apiKey, expectedVersion, agent, repoAware = false, sessionId) => {
1105
+ const path = policyCacheFile(apiKey, agent, repoAware);
1106
+ let staleCache = null;
1107
+ if (existsSync3(path)) {
1108
+ try {
1109
+ const stat = readFileSync3(path, "utf-8");
1110
+ const cached = JSON.parse(stat);
1111
+ if (!isPolicyConfig(cached)) throw new Error("Corrupted cache");
1112
+ const versionStale = expectedVersion != null && (cached._policyVersion ?? null) !== expectedVersion;
1113
+ const sessionStale = (cached._sessionId ?? null) !== (sessionId ?? null);
1114
+ const age = cached._cachedAt ? Date.now() - cached._cachedAt : 0;
1115
+ const ttlFresh = !cached._cachedAt || age < CACHE_TTL_MS;
1116
+ if (ttlFresh && !versionStale && !sessionStale) {
1117
+ return cached;
1118
+ }
1119
+ if (!sessionStale && age < STALE_CACHE_MAX_MS) staleCache = cached;
1120
+ } catch {
1121
+ }
1122
+ }
1123
+ try {
1124
+ const policy = await fetchPolicy(apiKey, agent, repoAware, sessionId);
1125
+ try {
1126
+ writeFileSync2(path, JSON.stringify({ ...policy, _cachedAt: Date.now(), _policyVersion: expectedVersion ?? null, _sessionId: sessionId ?? null }), { encoding: "utf-8", mode: 384 });
1127
+ } catch {
1128
+ }
1129
+ return policy;
1130
+ } catch {
1131
+ if (staleCache) return staleCache;
1132
+ throw new Error("Failed to fetch policy and no cached policy available");
1133
+ }
1134
+ };
1135
+ var MODE_GRACE_MS = 60 * 1e3;
1136
+ var modeStateCacheFile = (apiKey) => {
1137
+ const hash = createHash2("sha256").update(`mode:${apiKey}`).digest("hex").slice(0, 12);
1138
+ return join3(tmpdir2(), `pushary-mode-${hash}.json`);
1139
+ };
1140
+ var readLastGoodMode = (apiKey) => {
1141
+ try {
1142
+ const path = modeStateCacheFile(apiKey);
1143
+ if (!existsSync3(path)) return null;
1144
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
1145
+ if (!parsed || typeof parsed.at !== "number" || !parsed.state) return null;
1146
+ return parsed;
1147
+ } catch {
1148
+ return null;
1149
+ }
1150
+ };
1151
+ var writeLastGoodMode = (apiKey, state, sessionId) => {
1152
+ try {
1153
+ const cached = { state, at: Date.now(), ...sessionId ? { sessionId } : {} };
1154
+ writeFileSync2(modeStateCacheFile(apiKey), JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
1155
+ } catch {
1156
+ }
1157
+ };
1158
+ var unknownMode = () => ({
1159
+ mode: null,
1160
+ kill: false,
1161
+ policyVersion: null,
1162
+ relayUrl: null,
1163
+ trainingConsent: false,
1164
+ scope: null,
1165
+ degraded: true
1166
+ });
1167
+ var degradedFallback = (apiKey, authoritative = false) => {
1168
+ if (authoritative) return unknownMode();
1169
+ const last = readLastGoodMode(apiKey);
1170
+ if (last && Date.now() - last.at < MODE_GRACE_MS) {
1171
+ return { ...last.state, degraded: false };
1172
+ }
1173
+ return unknownMode();
1174
+ };
1175
+ var MODE_REUSE_MS = CACHE_TTL_MS;
1176
+ var readReusableMode = (apiKey, sessionId, maxAgeMs) => {
1177
+ const last = readLastGoodMode(apiKey);
1178
+ if (!last || (last.sessionId ?? null) !== (sessionId ?? null)) return null;
1179
+ if (Date.now() - last.at >= maxAgeMs) return null;
1180
+ return { ...last.state, degraded: false };
1181
+ };
1182
+ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0, signal) => {
1183
+ if (reuseWithinMs > 0) {
1184
+ const reusable = readReusableMode(apiKey, sessionId, reuseWithinMs);
1185
+ if (reusable) return reusable;
1186
+ }
1187
+ try {
1188
+ const baseUrl = getBaseUrl();
1189
+ const url = sessionId ? `${baseUrl}/api/mcp/mode?session=${encodeURIComponent(sessionId)}` : `${baseUrl}/api/mcp/mode`;
1190
+ const response = await fetch(url, {
1191
+ headers: { "Authorization": `Bearer ${apiKey}` },
1192
+ signal: AbortSignal.any([AbortSignal.timeout(3e3), ...signal ? [signal] : []])
1193
+ });
1194
+ if (!response.ok) {
1195
+ void reportClientError({ kind: "http_status", code: `mode_http_${response.status}` });
1196
+ return degradedFallback(apiKey, response.status === 401 || response.status === 403);
1197
+ }
1198
+ const state = modeStateFromResponse(await response.json());
1199
+ writeLastGoodMode(apiKey, state, sessionId);
1200
+ return state;
1201
+ } catch (error) {
1202
+ void reportClientError({ kind: clientErrorKindOf(error), code: "mode_fetch_failed" });
1203
+ return degradedFallback(apiKey);
1204
+ }
997
1205
  };
1206
+ var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
1207
+
1208
+ // src/usage.ts
1209
+ import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync4, readSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
1210
+ import { createHash as createHash3 } from "crypto";
1211
+ import { join as join4 } from "path";
1212
+ import { tmpdir as tmpdir3 } from "os";
1213
+ var READ_CHUNK_BYTES = 1024 * 1024;
998
1214
  var priceTable = () => {
999
1215
  const raw = process.env.PUSHARY_MODEL_PRICING?.trim();
1000
- if (!raw) return DEFAULT_PRICES;
1216
+ if (!raw) return DEFAULT_MODEL_PRICES;
1001
1217
  try {
1002
1218
  const parsed = JSON.parse(raw);
1003
1219
  if (Array.isArray(parsed) && parsed.length > 0 && parsed.every(isModelPrice)) return parsed;
1004
1220
  } catch {
1005
1221
  }
1006
- return DEFAULT_PRICES;
1007
- };
1008
- var estimateCostUsd = (usage, model) => {
1009
- const price = priceTable().find((p) => model.includes(p.match)) ?? FALLBACK_PRICE;
1010
- const perTokenIn = price.in / 1e6;
1011
- const perTokenOut = price.out / 1e6;
1012
- return usage.inputTokens * perTokenIn + usage.outputTokens * perTokenOut + usage.cacheCreationTokens * perTokenIn * CACHE_WRITE_MULTIPLIER + usage.cacheReadTokens * perTokenIn * CACHE_READ_MULTIPLIER;
1222
+ return DEFAULT_MODEL_PRICES;
1013
1223
  };
1014
- var stateDir = () => process.env.PUSHARY_USAGE_DIR?.trim() || join3(tmpdir2(), "pushary-usage");
1015
- var stateFile = (sessionId) => join3(stateDir(), sessionId.replace(/[^a-zA-Z0-9_-]/g, "_"));
1224
+ var estimateCostUsd = (usage, model) => estimateMessageCostUsd(usage, model, priceTable());
1225
+ var stateDir = () => process.env.PUSHARY_USAGE_DIR?.trim() || join4(tmpdir3(), "pushary-usage");
1226
+ var stateFile = (sessionId) => join4(stateDir(), sessionId.replace(/[^a-zA-Z0-9_-]/g, "_"));
1016
1227
  var emptyState = () => ({ offset: 0, tokensIn: 0, tokensOut: 0, costUsd: 0, recentIds: [] });
1017
1228
  var readState = (path) => {
1018
1229
  try {
1019
- const parsed = JSON.parse(readFileSync3(path, "utf-8"));
1230
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
1020
1231
  if (typeof parsed.offset === "number" && parsed.offset >= 0 && typeof parsed.tokensIn === "number" && typeof parsed.tokensOut === "number" && typeof parsed.costUsd === "number" && Array.isArray(parsed.recentIds)) {
1021
1232
  return {
1022
1233
  offset: parsed.offset,
@@ -1048,34 +1259,15 @@ var readRange = (path, start, end) => {
1048
1259
  closeSync(fd);
1049
1260
  }
1050
1261
  };
1051
- var toCount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
1052
1262
  var applyLine = (state, line) => {
1053
- let parsed;
1054
- try {
1055
- parsed = JSON.parse(line);
1056
- } catch {
1057
- return 0;
1058
- }
1059
- if (parsed.type !== "assistant") return 0;
1060
- const usage = parsed.message?.usage;
1061
- if (!usage || typeof usage !== "object") return 0;
1062
- const model = typeof parsed.message?.model === "string" ? parsed.message.model : "";
1063
- if (model.includes("<synthetic>")) return 0;
1064
- const id = typeof parsed.message?.id === "string" ? parsed.message.id : null;
1065
- if (id) {
1066
- if (state.recentIds.includes(id)) return 0;
1067
- state.recentIds.push(id);
1068
- if (state.recentIds.length > RECENT_ID_LIMIT) state.recentIds.splice(0, state.recentIds.length - RECENT_ID_LIMIT);
1069
- }
1070
- const messageUsage = {
1071
- inputTokens: toCount(usage.input_tokens),
1072
- outputTokens: toCount(usage.output_tokens),
1073
- cacheCreationTokens: toCount(usage.cache_creation_input_tokens),
1074
- cacheReadTokens: toCount(usage.cache_read_input_tokens)
1075
- };
1076
- const cost = estimateCostUsd(messageUsage, model);
1077
- state.tokensIn += messageUsage.inputTokens + messageUsage.cacheCreationTokens + messageUsage.cacheReadTokens;
1078
- state.tokensOut += messageUsage.outputTokens;
1263
+ const message = readTranscriptMessageUsage(line);
1264
+ if (!message) return 0;
1265
+ const memory = rememberTranscriptMessage(state.recentIds, message.id);
1266
+ if (!memory.counted) return 0;
1267
+ state.recentIds = [...memory.recentIds];
1268
+ const cost = estimateCostUsd(message.usage, message.model);
1269
+ state.tokensIn += transcriptTokensIn(message.usage);
1270
+ state.tokensOut += message.usage.outputTokens;
1079
1271
  state.costUsd += cost;
1080
1272
  return cost;
1081
1273
  };
@@ -1091,7 +1283,7 @@ var extractUserText = (content) => {
1091
1283
  };
1092
1284
  var readLastUserPrompt = (transcriptPath) => {
1093
1285
  try {
1094
- const size = statSync2(transcriptPath).size;
1286
+ const size = statSync3(transcriptPath).size;
1095
1287
  const start = Math.max(0, size - USER_PROMPT_TAIL_BYTES);
1096
1288
  const lines = readRange(transcriptPath, start, size).toString("utf-8").split("\n");
1097
1289
  for (let i = lines.length - 1; i >= 0; i -= 1) {
@@ -1114,7 +1306,7 @@ var readLastUserPrompt = (transcriptPath) => {
1114
1306
  };
1115
1307
  var readNewUsage = (transcriptPath, sessionId) => {
1116
1308
  try {
1117
- const file = statSync2(transcriptPath);
1309
+ const file = statSync3(transcriptPath);
1118
1310
  const size = file.size;
1119
1311
  const path = stateFile(sessionId);
1120
1312
  let state = readState(path);
@@ -1132,14 +1324,14 @@ var readNewUsage = (transcriptPath, sessionId) => {
1132
1324
  state.offset += lastNewline + 1;
1133
1325
  }
1134
1326
  }
1135
- mkdirSync(stateDir(), { recursive: true });
1136
- writeFileSync2(path, JSON.stringify(state), "utf-8");
1327
+ mkdirSync2(stateDir(), { recursive: true });
1328
+ writeFileSync3(path, JSON.stringify(state), "utf-8");
1137
1329
  if (state.tokensIn === 0 && state.tokensOut === 0) return null;
1138
1330
  return {
1139
1331
  // Identical cursor-range reports share an identity across transport retries.
1140
1332
  // Native message identity distinguishes an in-place rotation of the range.
1141
1333
  // This does not serialize readers of different, potentially overlapping ranges.
1142
- eventId: createHash2("sha256").update(JSON.stringify([
1334
+ eventId: createHash3("sha256").update(JSON.stringify([
1143
1335
  sessionId,
1144
1336
  file.dev,
1145
1337
  file.ino,
@@ -1215,10 +1407,10 @@ var permissionTimeoutDecision = (timeoutAction) => {
1215
1407
  var preToolUseTimeoutDecision = (timeoutAction, denyReason = "No response within timeout") => timeoutAction === "deny" ? codexDeny(denyReason) : codexPass();
1216
1408
 
1217
1409
  // src/codex-call-decision.ts
1218
- import { createHash as createHash3 } from "crypto";
1219
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
1220
- import { tmpdir as tmpdir3, userInfo } from "os";
1221
- import { join as join4 } from "path";
1410
+ import { createHash as createHash4 } from "crypto";
1411
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
1412
+ import { tmpdir as tmpdir4, userInfo } from "os";
1413
+ import { join as join5 } from "path";
1222
1414
  var CODEX_DECISION_TTL_MS = 60 * 1e3;
1223
1415
  var userScope = () => {
1224
1416
  try {
@@ -1229,8 +1421,8 @@ var userScope = () => {
1229
1421
  return "default";
1230
1422
  }
1231
1423
  };
1232
- var decisionDir = () => join4(tmpdir3(), `pushary-codex-decisions-${userScope()}`);
1233
- var decisionFile = (key) => join4(decisionDir(), key);
1424
+ var decisionDir = () => join5(tmpdir4(), `pushary-codex-decisions-${userScope()}`);
1425
+ var decisionFile = (key) => join5(decisionDir(), key);
1234
1426
  var PRESENTATION_ONLY_INPUT_KEYS = ["description"];
1235
1427
  var identifyingToolInput = (toolInput) => {
1236
1428
  if (!toolInput) return {};
@@ -1249,7 +1441,7 @@ var codexCallKey = (identity) => {
1249
1441
  identity.toolName,
1250
1442
  identifyingToolInput(identity.toolInput)
1251
1443
  ]);
1252
- return createHash3("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
1444
+ return createHash4("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
1253
1445
  };
1254
1446
  var parseDecision = (raw) => {
1255
1447
  try {
@@ -1268,10 +1460,10 @@ var parseDecision = (raw) => {
1268
1460
  };
1269
1461
  var sweepExpired = (dir, nowMs) => {
1270
1462
  try {
1271
- for (const name of readdirSync(dir)) {
1272
- const path = join4(dir, name);
1463
+ for (const name of readdirSync2(dir)) {
1464
+ const path = join5(dir, name);
1273
1465
  try {
1274
- if (nowMs - statSync3(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync(path, { force: true });
1466
+ if (nowMs - statSync4(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync2(path, { force: true });
1275
1467
  } catch {
1276
1468
  continue;
1277
1469
  }
@@ -1284,9 +1476,9 @@ var recordCodexCallDecision = (key, decision) => {
1284
1476
  if (!key) return;
1285
1477
  try {
1286
1478
  const dir = decisionDir();
1287
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
1479
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true, mode: 448 });
1288
1480
  else sweepExpired(dir, Date.now());
1289
- writeFileSync3(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
1481
+ writeFileSync4(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
1290
1482
  } catch {
1291
1483
  return;
1292
1484
  }
@@ -1295,9 +1487,9 @@ var consumeCodexCallDecision = (key) => {
1295
1487
  if (!key) return null;
1296
1488
  const path = decisionFile(key);
1297
1489
  try {
1298
- const fresh = Date.now() - statSync3(path).mtimeMs < CODEX_DECISION_TTL_MS;
1299
- const raw = readFileSync4(path, "utf-8");
1300
- rmSync(path, { force: true });
1490
+ const fresh = Date.now() - statSync4(path).mtimeMs < CODEX_DECISION_TTL_MS;
1491
+ const raw = readFileSync5(path, "utf-8");
1492
+ rmSync2(path, { force: true });
1301
1493
  return fresh ? parseDecision(raw) : null;
1302
1494
  } catch {
1303
1495
  return null;
@@ -1314,32 +1506,32 @@ var phoneDrivenEnv = (base) => ({
1314
1506
  });
1315
1507
 
1316
1508
  // src/throttle.ts
1317
- import { join as join5 } from "path";
1318
- import { tmpdir as tmpdir4 } from "os";
1319
- import { createHash as createHash4 } from "crypto";
1320
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync4, writeFileSync as writeFileSync4, rmSync as rmSync2 } from "fs";
1321
- var THROTTLE_DIR = join5(tmpdir4(), "pushary-throttle");
1322
- var markerPath = (key) => join5(THROTTLE_DIR, createHash4("sha256").update(key).digest("hex").slice(0, 16));
1509
+ import { join as join6 } from "path";
1510
+ import { tmpdir as tmpdir5 } from "os";
1511
+ import { createHash as createHash5 } from "crypto";
1512
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync5, writeFileSync as writeFileSync5, rmSync as rmSync3 } from "fs";
1513
+ var THROTTLE_DIR = join6(tmpdir5(), "pushary-throttle");
1514
+ var markerPath = (key) => join6(THROTTLE_DIR, createHash5("sha256").update(key).digest("hex").slice(0, 16));
1323
1515
  var claimThrottle = (key, windowMs) => {
1324
1516
  try {
1325
1517
  const path = markerPath(key);
1326
- if (existsSync4(path) && Date.now() - statSync4(path).mtimeMs < windowMs) return "throttled";
1327
- mkdirSync3(THROTTLE_DIR, { recursive: true });
1328
- writeFileSync4(path, "", "utf-8");
1518
+ if (existsSync5(path) && Date.now() - statSync5(path).mtimeMs < windowMs) return "throttled";
1519
+ mkdirSync4(THROTTLE_DIR, { recursive: true });
1520
+ writeFileSync5(path, "", "utf-8");
1329
1521
  return "passed";
1330
1522
  } catch {
1331
1523
  return "unavailable";
1332
1524
  }
1333
1525
  };
1334
1526
  var releaseThrottle = (key) => {
1335
- rmSync2(markerPath(key), { force: true });
1527
+ rmSync3(markerPath(key), { force: true });
1336
1528
  };
1337
1529
 
1338
1530
  // src/blocked-action-hint.ts
1339
- import { createHash as createHash5 } from "crypto";
1340
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, rmSync as rmSync3, statSync as statSync5, utimesSync } from "fs";
1341
- import { tmpdir as tmpdir5 } from "os";
1342
- import { join as join6 } from "path";
1531
+ import { createHash as createHash6 } from "crypto";
1532
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync6, utimesSync } from "fs";
1533
+ import { tmpdir as tmpdir6 } from "os";
1534
+ import { join as join7 } from "path";
1343
1535
  var BLOCKED_ACTION_HINT_TTL_MS = BLOCKED_ACTION_GRANT_TTL_SECONDS * 1e3;
1344
1536
  var FINGERPRINT = /^[0-9a-f]{64}$/;
1345
1537
  var isFingerprint = (value) => typeof value === "string" && FINGERPRINT.test(value);
@@ -1354,17 +1546,17 @@ var blockedActionFingerprint = ({ source = "claude", sessionId, toolName, toolIn
1354
1546
  const [canonicalInput] = canonical.toolInputs;
1355
1547
  if (!canonicalInput) return null;
1356
1548
  const subject = blockedActionSubject(source, sessionId, canonical.toolName, canonicalInput, cwd);
1357
- return subject ? createHash5("sha256").update(blockedActionIdentity(subject), "utf8").digest("hex") : null;
1549
+ return subject ? createHash6("sha256").update(blockedActionIdentity(subject), "utf8").digest("hex") : null;
1358
1550
  };
1359
- var hintDir = () => join6(tmpdir5(), `pushary-blocked-action-hints-${userScope()}`);
1360
- var hintFile = (fingerprint) => join6(hintDir(), fingerprint);
1551
+ var hintDir = () => join7(tmpdir6(), `pushary-blocked-action-hints-${userScope()}`);
1552
+ var hintFile = (fingerprint) => join7(hintDir(), fingerprint);
1361
1553
  var isExpired = (mtimeMs, nowMs) => nowMs - mtimeMs >= BLOCKED_ACTION_HINT_TTL_MS;
1362
1554
  var sweepExpired2 = (dir, nowMs) => {
1363
1555
  try {
1364
- for (const name of readdirSync2(dir)) {
1365
- const path = join6(dir, name);
1556
+ for (const name of readdirSync3(dir)) {
1557
+ const path = join7(dir, name);
1366
1558
  try {
1367
- if (isExpired(statSync5(path).mtimeMs, nowMs)) rmSync3(path, { force: true });
1559
+ if (isExpired(statSync6(path).mtimeMs, nowMs)) rmSync4(path, { force: true });
1368
1560
  } catch {
1369
1561
  continue;
1370
1562
  }
@@ -1377,7 +1569,7 @@ var recordBlockedActionHint = (fingerprint, approved) => {
1377
1569
  if (!isFingerprint(fingerprint)) return false;
1378
1570
  try {
1379
1571
  const dir = hintDir();
1380
- if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true, mode: 448 });
1572
+ if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
1381
1573
  else sweepExpired2(dir, Date.now());
1382
1574
  writeFileAtomic(hintFile(fingerprint), JSON.stringify({ fingerprint, ...approved }), 384);
1383
1575
  return true;
@@ -1388,13 +1580,13 @@ var recordBlockedActionHint = (fingerprint, approved) => {
1388
1580
  var readLiveHint = (fingerprint) => {
1389
1581
  const path = hintFile(fingerprint);
1390
1582
  try {
1391
- const stat = statSync5(path);
1583
+ const stat = statSync6(path);
1392
1584
  if (!stat.isFile()) return null;
1393
1585
  if (isExpired(stat.mtimeMs, Date.now())) {
1394
- rmSync3(path, { force: true });
1586
+ rmSync4(path, { force: true });
1395
1587
  return null;
1396
1588
  }
1397
- const parsed = JSON.parse(readFileSync5(path, "utf-8"));
1589
+ const parsed = JSON.parse(readFileSync6(path, "utf-8"));
1398
1590
  return parsed?.fingerprint === fingerprint ? parsed : null;
1399
1591
  } catch {
1400
1592
  return null;
@@ -1409,7 +1601,7 @@ var approvalIn = (fingerprint, sessionId) => {
1409
1601
  var approvalAwaitingRetry = (sessionId) => {
1410
1602
  if (!sessionId) return null;
1411
1603
  try {
1412
- for (const name of readdirSync2(hintDir())) {
1604
+ for (const name of readdirSync3(hintDir())) {
1413
1605
  const approval = isFingerprint(name) ? approvalIn(name, sessionId) : null;
1414
1606
  if (approval) return approval;
1415
1607
  }
@@ -1421,7 +1613,7 @@ var approvalAwaitingRetry = (sessionId) => {
1421
1613
  var markApprovalReminded = (approval) => {
1422
1614
  const path = hintFile(approval.fingerprint);
1423
1615
  try {
1424
- const { atime, mtime } = statSync5(path);
1616
+ const { atime, mtime } = statSync6(path);
1425
1617
  const { fingerprint, sessionId, toolName } = approval;
1426
1618
  writeFileAtomic(path, JSON.stringify({ fingerprint, sessionId, toolName, reminded: true }), 384);
1427
1619
  utimesSync(path, atime, mtime);
@@ -1432,64 +1624,64 @@ var markApprovalReminded = (approval) => {
1432
1624
  var forgetBlockedActionHint = (fingerprint) => {
1433
1625
  if (!isFingerprint(fingerprint)) return;
1434
1626
  try {
1435
- rmSync3(hintFile(fingerprint), { force: true });
1627
+ rmSync4(hintFile(fingerprint), { force: true });
1436
1628
  } catch {
1437
1629
  return;
1438
1630
  }
1439
1631
  };
1440
1632
 
1441
1633
  // src/background-work.ts
1442
- import { createHash as createHash6 } from "crypto";
1443
- import { mkdirSync as mkdirSync5, rmSync as rmSync4, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
1444
- import { tmpdir as tmpdir6 } from "os";
1445
- import { join as join7 } from "path";
1634
+ import { createHash as createHash7 } from "crypto";
1635
+ import { mkdirSync as mkdirSync6, rmSync as rmSync5, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
1636
+ import { tmpdir as tmpdir7 } from "os";
1637
+ import { join as join8 } from "path";
1446
1638
  var BACKGROUND_WORK_TTL_MS = 60 * 60 * 1e3;
1447
- var backgroundWorkDir = () => join7(tmpdir6(), "pushary-background-work");
1448
- var markerFor = (sessionKey) => join7(backgroundWorkDir(), createHash6("sha256").update(sessionKey).digest("hex"));
1639
+ var backgroundWorkDir = () => join8(tmpdir7(), "pushary-background-work");
1640
+ var markerFor = (sessionKey) => join8(backgroundWorkDir(), createHash7("sha256").update(sessionKey).digest("hex"));
1449
1641
  var hasRunningBackgroundTasks = (backgroundTasks) => Array.isArray(backgroundTasks) && backgroundTasks.length > 0;
1450
1642
  var noteBackgroundWork = (sessionKey, running) => {
1451
1643
  try {
1452
1644
  if (!running) {
1453
- rmSync4(markerFor(sessionKey), { force: true });
1645
+ rmSync5(markerFor(sessionKey), { force: true });
1454
1646
  return;
1455
1647
  }
1456
- mkdirSync5(backgroundWorkDir(), { recursive: true, mode: 448 });
1457
- writeFileSync5(markerFor(sessionKey), "", { mode: 384 });
1648
+ mkdirSync6(backgroundWorkDir(), { recursive: true, mode: 448 });
1649
+ writeFileSync6(markerFor(sessionKey), "", { mode: 384 });
1458
1650
  } catch {
1459
1651
  return;
1460
1652
  }
1461
1653
  };
1462
1654
  var isWaitingOnBackgroundWork = (sessionKey, nowMs = Date.now()) => {
1463
1655
  try {
1464
- return nowMs - statSync6(markerFor(sessionKey)).mtimeMs < BACKGROUND_WORK_TTL_MS;
1656
+ return nowMs - statSync7(markerFor(sessionKey)).mtimeMs < BACKGROUND_WORK_TTL_MS;
1465
1657
  } catch {
1466
1658
  return false;
1467
1659
  }
1468
1660
  };
1469
1661
 
1470
1662
  // src/events.ts
1471
- import { basename as basename3, join as join8 } from "path";
1472
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, statSync as statSync7, writeFileSync as writeFileSync6 } from "fs";
1473
- import { tmpdir as tmpdir7 } from "os";
1474
- var INTENT_DIR = join8(tmpdir7(), "pushary-intent");
1663
+ import { basename as basename3, join as join9 } from "path";
1664
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync7, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
1665
+ import { tmpdir as tmpdir8 } from "os";
1666
+ var INTENT_DIR = join9(tmpdir8(), "pushary-intent");
1475
1667
  var INTENT_MAX_BYTES = 2048;
1476
1668
  var INTENT_GRACE_MS = 10 * 60 * 1e3;
1477
1669
  var sanitizeSession = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
1478
- var intentFile = (sessionId) => join8(INTENT_DIR, sanitizeSession(sessionId));
1670
+ var intentFile = (sessionId) => join9(INTENT_DIR, sanitizeSession(sessionId));
1479
1671
  var saveLastPrompt = (sessionId, prompt) => {
1480
1672
  try {
1481
1673
  const trimmed = prompt.replace(/\s+/g, " ").trim().slice(0, INTENT_MAX_BYTES);
1482
1674
  if (!trimmed) return;
1483
- mkdirSync6(INTENT_DIR, { recursive: true });
1484
- writeFileSync6(intentFile(sessionId), trimmed, "utf-8");
1675
+ mkdirSync7(INTENT_DIR, { recursive: true });
1676
+ writeFileSync7(intentFile(sessionId), trimmed, "utf-8");
1485
1677
  } catch {
1486
1678
  }
1487
1679
  };
1488
1680
  var readLastPrompt = (sessionId) => {
1489
1681
  try {
1490
1682
  const path = intentFile(sessionId);
1491
- if (isDefaultSession(sessionId) && statSync7(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
1492
- const value = readFileSync6(path, "utf-8").trim();
1683
+ if (isDefaultSession(sessionId) && statSync8(path).mtimeMs < Date.now() - INTENT_GRACE_MS) return void 0;
1684
+ const value = readFileSync7(path, "utf-8").trim();
1493
1685
  return value.length > 0 ? value : void 0;
1494
1686
  } catch {
1495
1687
  return void 0;
@@ -1553,8 +1745,8 @@ var POLICY_CACHE_TTL_MS = 5 * 60 * 1e3;
1553
1745
  var readFreshCachedPolicy = (apiKey, agentType, sessionId) => {
1554
1746
  const request = policyRequestFor(agentType);
1555
1747
  const path = policyCacheFile(apiKey, request.agent, request.repoAware);
1556
- if (!existsSync6(path)) return null;
1557
- const cached = JSON.parse(readFileSync6(path, "utf-8"));
1748
+ if (!existsSync7(path)) return null;
1749
+ const cached = JSON.parse(readFileSync7(path, "utf-8"));
1558
1750
  if (!isPolicyConfig(cached)) return null;
1559
1751
  if ((cached._sessionId ?? null) !== (sessionId ?? null)) return null;
1560
1752
  if (!cached._cachedAt || Date.now() - cached._cachedAt >= POLICY_CACHE_TTL_MS) return null;
@@ -1597,10 +1789,12 @@ var deriveAutoDecisionBeacon = (toolName, toolInput, liveMode, agentType, cwd, s
1597
1789
  };
1598
1790
  var AUTO_DECISION_WINDOW_MS = 15 * 60 * 1e3;
1599
1791
  var HOOK_ACTIVE_WINDOW_MS = 30 * 60 * 1e3;
1600
- var deriveUsage = (transcriptPath, sessionId) => {
1601
- if (!transcriptPath || process.env.PUSHARY_COST_TRACKING === "off") return void 0;
1792
+ var deriveUsage = (payload, agent, event) => {
1793
+ const source = HOOK_SOURCES.find((candidate) => AGENT_IDENTITIES[candidate].type === agent.type);
1794
+ const request = source ? transcriptUsageRequest({ source, event, payload, costTracking: process.env[COST_TRACKING_ENV] }) : null;
1795
+ if (!request) return void 0;
1602
1796
  try {
1603
- return readNewUsage(transcriptPath, sessionId || DEFAULT_SESSION) ?? void 0;
1797
+ return readNewUsage(request.transcriptPath, request.sessionId) ?? void 0;
1604
1798
  } catch {
1605
1799
  return void 0;
1606
1800
  }
@@ -1789,7 +1983,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1789
1983
  decisionSource,
1790
1984
  ...beacon ? { decisionOrigin: beacon.decisionOrigin, toolName: beacon.toolName, toolTarget: beacon.toolTarget } : {},
1791
1985
  meta: receiptsEnabled ? deriveReceiptMeta(lookup.tool, lookup.input, toolResult, input.cwd ?? process.cwd()) : void 0,
1792
- usage: deriveUsage(input.transcript_path, input.session_id)
1986
+ usage: deriveUsage(input, agent, "PostToolUse")
1793
1987
  }, {
1794
1988
  maxAttempts: 1,
1795
1989
  budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())),
@@ -1868,7 +2062,7 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1868
2062
  agentName: `${agent.label} - ${projectName}`,
1869
2063
  action: summarizeStop(input),
1870
2064
  ...sessionIdentityOf(input),
1871
- usage: deriveUsage(input.transcript_path, input.session_id)
2065
+ usage: deriveUsage(input, agent, "Stop")
1872
2066
  }, { maxAttempts: 1, budgetMs: Math.min(DRAINING_REPORT_BUDGET_MS, Math.max(0, deadline - Date.now())), signal })
1873
2067
  ];
1874
2068
  if (late.length > 0) {
@@ -2053,11 +2247,6 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
2053
2247
  };
2054
2248
 
2055
2249
  export {
2056
- policyRequestFor,
2057
- getPolicy,
2058
- fetchModeState,
2059
- fetchModeOverride,
2060
- phoneDrivenEnv,
2061
2250
  HOOK_FALLBACK_MAX,
2062
2251
  UNNAMED_ACTION,
2063
2252
  describeToolCall,
@@ -2075,9 +2264,21 @@ export {
2075
2264
  isCodexGuardianRolloutHead,
2076
2265
  scanCodexGuardianReviews,
2077
2266
  codexReviewerDenialsSince,
2267
+ phoneDrivenEnv,
2268
+ DEFAULT_SESSION,
2269
+ readPendingCommandById,
2270
+ readPendingCommand,
2271
+ savePendingCommand,
2272
+ markPendingCommandDelivered,
2273
+ markPendingCommandFailed,
2274
+ removePendingCommand,
2078
2275
  repoKeyFor,
2079
2276
  claimThrottle,
2080
2277
  releaseThrottle,
2278
+ policyRequestFor,
2279
+ getPolicy,
2280
+ fetchModeState,
2281
+ fetchModeOverride,
2081
2282
  readLastUserPrompt,
2082
2283
  CODEX_AGENT,
2083
2284
  codexAllow,