pushary 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 (186) hide show
  1. package/CHANGELOG.md +1943 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/data/SKILL.md +591 -0
  5. package/data/cowork/SKILL.md +77 -0
  6. package/data/cursor-plugin/.cursor-plugin/plugin.json +25 -0
  7. package/data/cursor-plugin/CHANGELOG.md +70 -0
  8. package/data/cursor-plugin/CONTRIBUTING.md +40 -0
  9. package/data/cursor-plugin/LICENSE +21 -0
  10. package/data/cursor-plugin/README.md +113 -0
  11. package/data/cursor-plugin/SECURITY.md +33 -0
  12. package/data/cursor-plugin/assets/logo.png +0 -0
  13. package/data/cursor-plugin/commands/notify-when-done.md +14 -0
  14. package/data/cursor-plugin/commands/pushary-test.md +13 -0
  15. package/data/cursor-plugin/hooks/hooks.json +75 -0
  16. package/data/cursor-plugin/mcp.json +11 -0
  17. package/data/cursor-plugin/rules/pushary.mdc +40 -0
  18. package/data/cursor-plugin/scripts/pushary-gate.mjs +724 -0
  19. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +137 -0
  20. package/data/cursor-plugin/scripts/redaction.mjs +51 -0
  21. package/data/cursor-plugin/skills/pushary/SKILL.md +584 -0
  22. package/data/vscode-plugin/.claude-plugin/plugin.json +28 -0
  23. package/data/vscode-plugin/.mcp.json +11 -0
  24. package/data/vscode-plugin/CHANGELOG.md +49 -0
  25. package/data/vscode-plugin/CONTRIBUTING.md +40 -0
  26. package/data/vscode-plugin/LICENSE +21 -0
  27. package/data/vscode-plugin/README.md +128 -0
  28. package/data/vscode-plugin/SECURITY.md +56 -0
  29. package/data/vscode-plugin/assets/logo.png +0 -0
  30. package/data/vscode-plugin/commands/notify-when-done.md +14 -0
  31. package/data/vscode-plugin/commands/pushary-test.md +13 -0
  32. package/data/vscode-plugin/hooks/hooks.json +60 -0
  33. package/data/vscode-plugin/scripts/pushary-gate.mjs +762 -0
  34. package/data/vscode-plugin/scripts/redaction.mjs +51 -0
  35. package/data/vscode-plugin/skills/pushary/SKILL.md +584 -0
  36. package/dist/bin/pushary-bell-hook.d.ts +1 -0
  37. package/dist/bin/pushary-bell-hook.js +46 -0
  38. package/dist/bin/pushary-bell.d.ts +1 -0
  39. package/dist/bin/pushary-bell.js +146 -0
  40. package/dist/bin/pushary-claude.d.ts +1 -0
  41. package/dist/bin/pushary-claude.js +1916 -0
  42. package/dist/bin/pushary-clean.d.ts +1 -0
  43. package/dist/bin/pushary-clean.js +1300 -0
  44. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  45. package/dist/bin/pushary-codex-bridge.js +599 -0
  46. package/dist/bin/pushary-codex-hook.d.ts +1 -0
  47. package/dist/bin/pushary-codex-hook.js +886 -0
  48. package/dist/bin/pushary-codex.d.ts +1 -0
  49. package/dist/bin/pushary-codex.js +125 -0
  50. package/dist/bin/pushary-connect.d.ts +1 -0
  51. package/dist/bin/pushary-connect.js +100 -0
  52. package/dist/bin/pushary-cowork.d.ts +1 -0
  53. package/dist/bin/pushary-cowork.js +36 -0
  54. package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
  55. package/dist/bin/pushary-daemon-supervisor.js +27 -0
  56. package/dist/bin/pushary-daemon.d.ts +1 -0
  57. package/dist/bin/pushary-daemon.js +878 -0
  58. package/dist/bin/pushary-disconnect.d.ts +1 -0
  59. package/dist/bin/pushary-disconnect.js +264 -0
  60. package/dist/bin/pushary-doctor.d.ts +1 -0
  61. package/dist/bin/pushary-doctor.js +1433 -0
  62. package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
  63. package/dist/bin/pushary-elicitation-hook.js +149 -0
  64. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  65. package/dist/bin/pushary-gemini-bridge.js +339 -0
  66. package/dist/bin/pushary-gemini-hook.d.ts +1 -0
  67. package/dist/bin/pushary-gemini-hook.js +381 -0
  68. package/dist/bin/pushary-hook.d.ts +1 -0
  69. package/dist/bin/pushary-hook.js +95 -0
  70. package/dist/bin/pushary-login.d.ts +1 -0
  71. package/dist/bin/pushary-login.js +198 -0
  72. package/dist/bin/pushary-logout.d.ts +1 -0
  73. package/dist/bin/pushary-logout.js +147 -0
  74. package/dist/bin/pushary-mcp.d.ts +1 -0
  75. package/dist/bin/pushary-mcp.js +114 -0
  76. package/dist/bin/pushary-mode.d.ts +1 -0
  77. package/dist/bin/pushary-mode.js +130 -0
  78. package/dist/bin/pushary-notification-hook.d.ts +1 -0
  79. package/dist/bin/pushary-notification-hook.js +56 -0
  80. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  81. package/dist/bin/pushary-opencode-hook.js +338 -0
  82. package/dist/bin/pushary-permission-denied-hook.d.ts +1 -0
  83. package/dist/bin/pushary-permission-denied-hook.js +60 -0
  84. package/dist/bin/pushary-permission-hook.d.ts +1 -0
  85. package/dist/bin/pushary-permission-hook.js +65 -0
  86. package/dist/bin/pushary-post-hook.d.ts +1 -0
  87. package/dist/bin/pushary-post-hook.js +70 -0
  88. package/dist/bin/pushary-prompt-hook.d.ts +1 -0
  89. package/dist/bin/pushary-prompt-hook.js +61 -0
  90. package/dist/bin/pushary-session-end-hook.d.ts +1 -0
  91. package/dist/bin/pushary-session-end-hook.js +59 -0
  92. package/dist/bin/pushary-session-start-hook.d.ts +1 -0
  93. package/dist/bin/pushary-session-start-hook.js +72 -0
  94. package/dist/bin/pushary-setup.d.ts +1 -0
  95. package/dist/bin/pushary-setup.js +2660 -0
  96. package/dist/bin/pushary-stats.d.ts +1 -0
  97. package/dist/bin/pushary-stats.js +44 -0
  98. package/dist/bin/pushary-status.d.ts +1 -0
  99. package/dist/bin/pushary-status.js +262 -0
  100. package/dist/bin/pushary-stop-hook.d.ts +1 -0
  101. package/dist/bin/pushary-stop-hook.js +63 -0
  102. package/dist/bin/pushary-stopfailure-hook.d.ts +1 -0
  103. package/dist/bin/pushary-stopfailure-hook.js +56 -0
  104. package/dist/bin/pushary-suggestions.d.ts +1 -0
  105. package/dist/bin/pushary-suggestions.js +100 -0
  106. package/dist/bin/pushary-transcript-register.d.ts +1 -0
  107. package/dist/bin/pushary-transcript-register.js +40 -0
  108. package/dist/bin/pushary-transcripts.d.ts +1 -0
  109. package/dist/bin/pushary-transcripts.js +77 -0
  110. package/dist/bin/pushary-upgrade.d.ts +1 -0
  111. package/dist/bin/pushary-upgrade.js +599 -0
  112. package/dist/bin/pushary-wait.d.ts +1 -0
  113. package/dist/bin/pushary-wait.js +122 -0
  114. package/dist/bin/pushary.d.ts +1 -0
  115. package/dist/bin/pushary.js +74 -0
  116. package/dist/chunk-2ABTSGFT.js +173 -0
  117. package/dist/chunk-2OY3ZZ5N.js +124 -0
  118. package/dist/chunk-3EGEA4KH.js +44 -0
  119. package/dist/chunk-3EVPNIBE.js +185 -0
  120. package/dist/chunk-5OP5MQI7.js +45 -0
  121. package/dist/chunk-6J2JC2RT.js +149 -0
  122. package/dist/chunk-7QLSKOSU.js +19 -0
  123. package/dist/chunk-7Z7Q4GGS.js +88 -0
  124. package/dist/chunk-A2A2YTMG.js +835 -0
  125. package/dist/chunk-AHV4LNB5.js +377 -0
  126. package/dist/chunk-AXPCESYO.js +197 -0
  127. package/dist/chunk-C2WCPBF7.js +86 -0
  128. package/dist/chunk-CJVVKOSR.js +73 -0
  129. package/dist/chunk-CULUZTWQ.js +2117 -0
  130. package/dist/chunk-CVQYGMSR.js +142 -0
  131. package/dist/chunk-CVWG2N3Y.js +16 -0
  132. package/dist/chunk-D2FX3EPW.js +15 -0
  133. package/dist/chunk-DB3ONZB4.js +44 -0
  134. package/dist/chunk-DE4Y6TCC.js +160 -0
  135. package/dist/chunk-E4FJTUH4.js +249 -0
  136. package/dist/chunk-EPCXUP3P.js +41 -0
  137. package/dist/chunk-EX3GAPA4.js +44 -0
  138. package/dist/chunk-EYK3GSRH.js +143 -0
  139. package/dist/chunk-GMXKITVA.js +70 -0
  140. package/dist/chunk-GPEGOGRJ.js +49 -0
  141. package/dist/chunk-GU3EJCVV.js +43 -0
  142. package/dist/chunk-HQ6G3B5R.js +59 -0
  143. package/dist/chunk-HZVVNGEW.js +22 -0
  144. package/dist/chunk-IPVKH2ST.js +231 -0
  145. package/dist/chunk-J5TIV3J6.js +238 -0
  146. package/dist/chunk-JU6W3XAU.js +64 -0
  147. package/dist/chunk-JYG4B33G.js +227 -0
  148. package/dist/chunk-JZTHGAPA.js +503 -0
  149. package/dist/chunk-KGDWDSNL.js +102 -0
  150. package/dist/chunk-KVQBWI5T.js +99 -0
  151. package/dist/chunk-KZERVKTD.js +16 -0
  152. package/dist/chunk-LXQ5FBBD.js +47 -0
  153. package/dist/chunk-M5ICYGHT.js +138 -0
  154. package/dist/chunk-M5JDZ45W.js +943 -0
  155. package/dist/chunk-MDEPU45E.js +1226 -0
  156. package/dist/chunk-MPWXGONV.js +157 -0
  157. package/dist/chunk-N6IOSZOE.js +185 -0
  158. package/dist/chunk-NGDD6TXY.js +192 -0
  159. package/dist/chunk-NQTHFF4W.js +2830 -0
  160. package/dist/chunk-OKXS7WDZ.js +68 -0
  161. package/dist/chunk-OLQRRFBX.js +21 -0
  162. package/dist/chunk-P7WQEVWA.js +307 -0
  163. package/dist/chunk-POFBE6ZY.js +12 -0
  164. package/dist/chunk-PQ4K74WL.js +30 -0
  165. package/dist/chunk-PUZZELE6.js +37 -0
  166. package/dist/chunk-Q7E4I6UA.js +767 -0
  167. package/dist/chunk-QCGNGEGE.js +15 -0
  168. package/dist/chunk-R7437YI3.js +151 -0
  169. package/dist/chunk-RHR6NARM.js +305 -0
  170. package/dist/chunk-SAXBC4AZ.js +60 -0
  171. package/dist/chunk-SGZJ5LYH.js +76 -0
  172. package/dist/chunk-SIFVFP6T.js +70 -0
  173. package/dist/chunk-SLFABHW3.js +150 -0
  174. package/dist/chunk-T7E75EKA.js +67 -0
  175. package/dist/chunk-TY4JT7W3.js +38 -0
  176. package/dist/chunk-U4TMAANR.js +111 -0
  177. package/dist/chunk-UI36QSBI.js +177 -0
  178. package/dist/chunk-VQKRG3AC.js +339 -0
  179. package/dist/chunk-WE2J62NC.js +1131 -0
  180. package/dist/chunk-XHKBHWLX.js +14 -0
  181. package/dist/chunk-ZSZSSJCV.js +275 -0
  182. package/dist/chunk-ZVSKMPBZ.js +135 -0
  183. package/dist/reapply-CW3MA66V.js +34 -0
  184. package/dist/src/index.d.ts +204 -0
  185. package/dist/src/index.js +64 -0
  186. package/package.json +108 -0
@@ -0,0 +1,886 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ takeHookHoldBudget
4
+ } from "../chunk-D2FX3EPW.js";
5
+ import {
6
+ defersToNativeMode,
7
+ reportMachineDenial
8
+ } from "../chunk-CJVVKOSR.js";
9
+ import {
10
+ triggerAutoUpdate
11
+ } from "../chunk-UI36QSBI.js";
12
+ import {
13
+ isGatingMoment,
14
+ recordKeylessMoment
15
+ } from "../chunk-JU6W3XAU.js";
16
+ import {
17
+ readHookInput
18
+ } from "../chunk-KGDWDSNL.js";
19
+ import {
20
+ announceSubscriptionRequired,
21
+ denyReasonFrom,
22
+ isDeferAnswer
23
+ } from "../chunk-5OP5MQI7.js";
24
+ import "../chunk-JZTHGAPA.js";
25
+ import "../chunk-SGZJ5LYH.js";
26
+ import "../chunk-EYK3GSRH.js";
27
+ import "../chunk-ZVSKMPBZ.js";
28
+ import "../chunk-C2WCPBF7.js";
29
+ import "../chunk-6J2JC2RT.js";
30
+ import "../chunk-WE2J62NC.js";
31
+ import "../chunk-3EVPNIBE.js";
32
+ import "../chunk-OKXS7WDZ.js";
33
+ import "../chunk-MPWXGONV.js";
34
+ import "../chunk-SAXBC4AZ.js";
35
+ import "../chunk-OLQRRFBX.js";
36
+ import "../chunk-XHKBHWLX.js";
37
+ import {
38
+ CODEX_AGENT,
39
+ UNNAMED_ACTION,
40
+ blockedActionFingerprint,
41
+ codexAllow,
42
+ codexCallKey,
43
+ codexDeny,
44
+ codexPass,
45
+ codexReviewerDenialsSince,
46
+ consumeCodexCallDecision,
47
+ deriveActionBody,
48
+ deriveBlocker,
49
+ deriveToolTarget,
50
+ describeApplyPatch,
51
+ describeToolCall,
52
+ fetchModeState,
53
+ forgetBlockedActionHint,
54
+ getPolicy,
55
+ handleCompaction,
56
+ handlePostToolUse,
57
+ handleSessionEnd,
58
+ handleStop,
59
+ handleSubagentStop,
60
+ handleUserPrompt,
61
+ hasBlockedActionHint,
62
+ isCodexGuardianRolloutHead,
63
+ permissionTimeoutDecision,
64
+ policyRequestFor,
65
+ preToolUseTimeoutDecision,
66
+ readCodexTurnContext,
67
+ readLastPrompt,
68
+ recordBlockedActionHint,
69
+ recordCodexCallDecision,
70
+ repoKeyFor,
71
+ reportEvent,
72
+ scanCodexGuardianReviews,
73
+ scopePathFor,
74
+ sessionIdentityOf,
75
+ toCodexWire,
76
+ toPolicyLookup,
77
+ toPolicyLookups,
78
+ userScope
79
+ } from "../chunk-CULUZTWQ.js";
80
+ import {
81
+ DEFAULT_SESSION
82
+ } from "../chunk-N6IOSZOE.js";
83
+ import "../chunk-EPCXUP3P.js";
84
+ import {
85
+ DECISION_STOP_REASON,
86
+ UNVERIFIABLE_APPROVAL_REASON,
87
+ askUser,
88
+ cancelQuestion,
89
+ claimBlockedAction,
90
+ isDecisionStopped,
91
+ isLocalQuestionHandoff,
92
+ pollForAnswer,
93
+ requestGate,
94
+ sendNotification,
95
+ unavailableQuestionUnlessSubscriptionRequired,
96
+ waitForQuestion
97
+ } from "../chunk-NGDD6TXY.js";
98
+ import {
99
+ exitAfterClientReports
100
+ } from "../chunk-CVQYGMSR.js";
101
+ import "../chunk-POFBE6ZY.js";
102
+ import "../chunk-GMXKITVA.js";
103
+ import "../chunk-P7WQEVWA.js";
104
+ import {
105
+ SubscriptionRequiredError
106
+ } from "../chunk-M5ICYGHT.js";
107
+ import "../chunk-2ABTSGFT.js";
108
+ import "../chunk-2OY3ZZ5N.js";
109
+ import {
110
+ getMachineId
111
+ } from "../chunk-LXQ5FBBD.js";
112
+ import "../chunk-7QLSKOSU.js";
113
+ import {
114
+ EXIT
115
+ } from "../chunk-KZERVKTD.js";
116
+ import "../chunk-DB3ONZB4.js";
117
+ import {
118
+ canonicalJson
119
+ } from "../chunk-A2A2YTMG.js";
120
+ import {
121
+ writeFileAtomic
122
+ } from "../chunk-T7E75EKA.js";
123
+ import {
124
+ getApiKey
125
+ } from "../chunk-GPEGOGRJ.js";
126
+ import "../chunk-E4FJTUH4.js";
127
+ import {
128
+ CODEX_RESCUE_TOOL_NAME,
129
+ GATE_BLOCK_REASON_MAX_LENGTH,
130
+ GATE_WIRE,
131
+ KILL_REASON,
132
+ codexStopRescueOutput,
133
+ editAsksAtTheAgentsPrompt,
134
+ effectiveWaitSeconds,
135
+ hookHoldWindowMs,
136
+ hookWaitClamped,
137
+ hookWaitDeadline,
138
+ resolveGate
139
+ } from "../chunk-NQTHFF4W.js";
140
+
141
+ // bin/pushary-codex-hook.ts
142
+ import { basename } from "path";
143
+
144
+ // src/codex-reviewer.ts
145
+ import { createHash as createHash2 } from "crypto";
146
+
147
+ // src/codex-rollout.ts
148
+ import { createHash } from "crypto";
149
+ import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync } from "fs";
150
+ import { tmpdir } from "os";
151
+ import { dirname, isAbsolute, join, relative } from "path";
152
+ var CODEX_ROLLOUT_TAIL_MAX_BYTES = 8 * 1024 * 1024;
153
+ var CODEX_ROLLOUT_HEAD_MAX_BYTES = 1024 * 1024;
154
+ var CODEX_GUARDIAN_READ_MAX_BYTES = 8 * 1024 * 1024;
155
+ var CODEX_GUARDIAN_SCAN_MAX_DAYS = 31;
156
+ var CODEX_GUARDIAN_SCAN_MAX_FILES = 2e3;
157
+ var CODEX_ROLLOUT_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
158
+ var READ_CHUNK_BYTES = 64 * 1024;
159
+ var TURN_CONTEXT_LINE_MAX_BYTES = 1024 * 1024;
160
+ var MTIME_SLACK_MS = 2e3;
161
+ var NEWLINE = 10;
162
+ var TURN_CONTEXT_MARKER = '"turn_context"';
163
+ var ROLLOUT_FILE = /^rollout-.*\.jsonl$/;
164
+ var DATE_SEGMENTS = /^(\d{4})\/(\d{2})\/(\d{2})$/;
165
+ var cacheKey = (...parts) => createHash("sha256").update(parts.join("\n"), "utf-8").digest("hex").slice(0, 32);
166
+ var turnCacheDir = () => join(tmpdir(), `pushary-codex-turns-${userScope()}`);
167
+ var guardianCursorDir = () => join(tmpdir(), `pushary-codex-guardian-${userScope()}`);
168
+ var readJsonRecord = (path) => {
169
+ try {
170
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
171
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
172
+ } catch {
173
+ return null;
174
+ }
175
+ };
176
+ var sweepExpired = (dir, nowMs) => {
177
+ try {
178
+ for (const name of readdirSync(dir)) {
179
+ const path = join(dir, name);
180
+ try {
181
+ if (nowMs - statSync(path).mtimeMs >= CODEX_ROLLOUT_CACHE_TTL_MS) rmSync(path, { force: true });
182
+ } catch {
183
+ continue;
184
+ }
185
+ }
186
+ } catch {
187
+ return;
188
+ }
189
+ };
190
+ var writeJsonRecord = (dir, name, record) => {
191
+ try {
192
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
193
+ else sweepExpired(dir, Date.now());
194
+ writeFileAtomic(join(dir, name), JSON.stringify(record), 384);
195
+ return true;
196
+ } catch {
197
+ return false;
198
+ }
199
+ };
200
+ var readBytes = (path, offset, length) => {
201
+ let fd;
202
+ try {
203
+ fd = openSync(path, "r");
204
+ const buffer = Buffer.alloc(length);
205
+ const read = readSync(fd, buffer, 0, length, offset);
206
+ return read === length ? buffer : null;
207
+ } catch {
208
+ return null;
209
+ } finally {
210
+ if (fd !== void 0) closeSync(fd);
211
+ }
212
+ };
213
+ var locate = (region, base, from, to, read, marker) => {
214
+ if (to <= from) return null;
215
+ const text = region.toString("utf-8", from, to);
216
+ if (!text.includes(marker)) return null;
217
+ const value = read(text);
218
+ return value ? { value, offset: base + from, length: to - from } : null;
219
+ };
220
+ var findLastRolloutLine = (path, maxBytes, read, marker) => {
221
+ let fd;
222
+ try {
223
+ fd = openSync(path, "r");
224
+ const size = fstatSync(fd).size;
225
+ const floor = Math.max(0, size - maxBytes);
226
+ let carry = Buffer.alloc(0);
227
+ let position = size;
228
+ while (position > floor) {
229
+ const start = Math.max(floor, position - READ_CHUNK_BYTES);
230
+ const chunk = Buffer.alloc(position - start);
231
+ if (readSync(fd, chunk, 0, chunk.length, start) !== chunk.length) return null;
232
+ const region = carry.length > 0 ? Buffer.concat([chunk, carry]) : chunk;
233
+ let lineEnd = region.length;
234
+ let newline = region.lastIndexOf(NEWLINE, lineEnd - 1);
235
+ while (newline >= 0) {
236
+ const found = locate(region, start, newline + 1, lineEnd, read, marker);
237
+ if (found) return found;
238
+ lineEnd = newline;
239
+ newline = lineEnd > 0 ? region.lastIndexOf(NEWLINE, lineEnd - 1) : -1;
240
+ }
241
+ if (start === 0) return locate(region, 0, 0, lineEnd, read, marker);
242
+ carry = region.subarray(0, lineEnd);
243
+ position = start;
244
+ }
245
+ return null;
246
+ } catch {
247
+ return null;
248
+ } finally {
249
+ if (fd !== void 0) closeSync(fd);
250
+ }
251
+ };
252
+ var isWholeLineAt = (path, offset, length) => {
253
+ const before = offset > 0 ? 1 : 0;
254
+ const bytes = readBytes(path, offset - before, length + before + 1);
255
+ if (!bytes) return null;
256
+ if (before === 1 && bytes[0] !== NEWLINE) return null;
257
+ if (bytes[bytes.length - 1] !== NEWLINE) return null;
258
+ return bytes.toString("utf-8", before, before + length);
259
+ };
260
+ var cachedTurn = (file, transcriptPath, turnId) => {
261
+ const cached = readJsonRecord(file);
262
+ if (!cached || cached.transcriptPath !== transcriptPath || cached.turnId !== turnId) return null;
263
+ const { offset, length } = cached;
264
+ if (typeof offset !== "number" || typeof length !== "number") return null;
265
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length <= 0) return null;
266
+ if (length > TURN_CONTEXT_LINE_MAX_BYTES) return null;
267
+ const line = isWholeLineAt(transcriptPath, offset, length);
268
+ const context = line === null ? null : readCodexTurnContext(line);
269
+ return context?.turnId === turnId ? context : null;
270
+ };
271
+ var readCodexTurn = (call, limits = {}) => {
272
+ const { transcriptPath, sessionId, turnId } = call;
273
+ if (!transcriptPath || !isAbsolute(transcriptPath) || !sessionId || !turnId) return null;
274
+ const name = cacheKey(sessionId, turnId);
275
+ const cached = cachedTurn(join(turnCacheDir(), name), transcriptPath, turnId);
276
+ if (cached) return cached;
277
+ const found = findLastRolloutLine(
278
+ transcriptPath,
279
+ limits.tailMaxBytes ?? CODEX_ROLLOUT_TAIL_MAX_BYTES,
280
+ readCodexTurnContext,
281
+ TURN_CONTEXT_MARKER
282
+ );
283
+ if (!found || found.value.turnId !== turnId) return null;
284
+ writeJsonRecord(turnCacheDir(), name, { transcriptPath, turnId, offset: found.offset, length: found.length });
285
+ return found.value;
286
+ };
287
+ var readFirstLine = (path, maxBytes) => {
288
+ let fd;
289
+ try {
290
+ fd = openSync(path, "r");
291
+ let collected = Buffer.alloc(0);
292
+ while (collected.length < maxBytes) {
293
+ const chunk = Buffer.alloc(Math.min(READ_CHUNK_BYTES, maxBytes - collected.length));
294
+ const read = readSync(fd, chunk, 0, chunk.length, collected.length);
295
+ if (read === 0) return null;
296
+ const newline = chunk.subarray(0, read).indexOf(NEWLINE);
297
+ if (newline >= 0) return Buffer.concat([collected, chunk.subarray(0, newline)]).toString("utf-8");
298
+ collected = Buffer.concat([collected, chunk.subarray(0, read)]);
299
+ }
300
+ return null;
301
+ } catch {
302
+ return null;
303
+ } finally {
304
+ if (fd !== void 0) closeSync(fd);
305
+ }
306
+ };
307
+ var sessionsLayoutOf = (transcriptPath) => {
308
+ const dayDir = dirname(transcriptPath);
309
+ const root = dirname(dirname(dirname(dayDir)));
310
+ const match = DATE_SEGMENTS.exec(relative(root, dayDir).split(/[\\/]/).join("/"));
311
+ if (!match) return null;
312
+ return { root, year: Number(match[1]), month: Number(match[2]), day: Number(match[3]) };
313
+ };
314
+ var isInside = (root, path) => {
315
+ const within = relative(root, path);
316
+ return within.length > 0 && !within.startsWith("..") && !isAbsolute(within);
317
+ };
318
+ var isGuardianRollout = (path, sessionId, headMaxBytes) => {
319
+ const head = readFirstLine(path, headMaxBytes);
320
+ return head !== null && isCodexGuardianRolloutHead(head, sessionId);
321
+ };
322
+ var pad = (value) => String(value).padStart(2, "0");
323
+ var dayDirectories = (layout, now) => {
324
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
325
+ const directories = [];
326
+ for (let offset = 0; offset <= CODEX_GUARDIAN_SCAN_MAX_DAYS; offset++) {
327
+ const date = new Date(layout.year, layout.month - 1, layout.day + offset);
328
+ if (offset > 0 && date.getTime() > today) break;
329
+ directories.push(join(layout.root, String(date.getFullYear()), pad(date.getMonth() + 1), pad(date.getDate())));
330
+ }
331
+ return directories;
332
+ };
333
+ var createdAtMs = (transcriptPath, turnStartedAt) => {
334
+ try {
335
+ const birth = statSync(transcriptPath).birthtimeMs;
336
+ return birth > 0 ? birth : Date.parse(turnStartedAt);
337
+ } catch {
338
+ return Date.parse(turnStartedAt);
339
+ }
340
+ };
341
+ var discoverGuardianRollout = (call, layout, now, limits) => {
342
+ const sinceMs = createdAtMs(call.transcriptPath, call.turnStartedAt) - MTIME_SLACK_MS;
343
+ if (!Number.isFinite(sinceMs)) return null;
344
+ const headMaxBytes = limits.headMaxBytes ?? CODEX_ROLLOUT_HEAD_MAX_BYTES;
345
+ const maxFiles = limits.maxFiles ?? CODEX_GUARDIAN_SCAN_MAX_FILES;
346
+ let examined = 0;
347
+ let best = null;
348
+ for (const directory of dayDirectories(layout, now)) {
349
+ let names;
350
+ try {
351
+ names = readdirSync(directory).sort();
352
+ } catch {
353
+ continue;
354
+ }
355
+ for (const name of names) {
356
+ if (!ROLLOUT_FILE.test(name)) continue;
357
+ const path = join(directory, name);
358
+ if (path === call.transcriptPath) continue;
359
+ if (++examined > maxFiles) return best?.path ?? null;
360
+ let mtimeMs;
361
+ try {
362
+ const stat = statSync(path);
363
+ if (!stat.isFile()) continue;
364
+ mtimeMs = stat.mtimeMs;
365
+ } catch {
366
+ continue;
367
+ }
368
+ if (mtimeMs < sinceMs || best && mtimeMs <= best.mtimeMs) continue;
369
+ if (isGuardianRollout(path, call.sessionId, headMaxBytes)) best = { path, mtimeMs };
370
+ }
371
+ }
372
+ return best?.path ?? null;
373
+ };
374
+ var readGuardianCursor = (file, sessionId) => {
375
+ const cursor = readJsonRecord(file);
376
+ if (!cursor || cursor.sessionId !== sessionId || typeof cursor.path !== "string") return null;
377
+ const offset = cursor.offset;
378
+ if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0) return null;
379
+ return { path: cursor.path, offset };
380
+ };
381
+ var readCompleteLines = (path, from, maxBytes) => {
382
+ let fd;
383
+ try {
384
+ fd = openSync(path, "r");
385
+ const size = fstatSync(fd).size;
386
+ let start = from > size ? 0 : from;
387
+ const skipsPartial = size - start > maxBytes;
388
+ if (skipsPartial) start = size - maxBytes;
389
+ const buffer = Buffer.alloc(size - start);
390
+ if (buffer.length > 0 && readSync(fd, buffer, 0, buffer.length, start) !== buffer.length) return null;
391
+ let lineStart = 0;
392
+ if (skipsPartial) {
393
+ const newline = buffer.indexOf(NEWLINE);
394
+ if (newline < 0) return { lines: [], end: from };
395
+ lineStart = newline + 1;
396
+ }
397
+ const lines = [];
398
+ for (let newline = buffer.indexOf(NEWLINE, lineStart); newline >= 0; newline = buffer.indexOf(NEWLINE, lineStart)) {
399
+ lines.push({ offset: start + lineStart, text: buffer.toString("utf-8", lineStart, newline) });
400
+ lineStart = newline + 1;
401
+ }
402
+ return { lines, end: start + lineStart };
403
+ } catch {
404
+ return null;
405
+ } finally {
406
+ if (fd !== void 0) closeSync(fd);
407
+ }
408
+ };
409
+ var takeCodexReviewerDenials = (call, options = {}) => {
410
+ const { transcriptPath, sessionId, turnStartedAt } = call;
411
+ if (!isAbsolute(transcriptPath) || !sessionId || !Number.isFinite(Date.parse(turnStartedAt))) return null;
412
+ const layout = sessionsLayoutOf(transcriptPath);
413
+ if (!layout) return null;
414
+ const headMaxBytes = options.headMaxBytes ?? CODEX_ROLLOUT_HEAD_MAX_BYTES;
415
+ const cursorName = cacheKey(sessionId);
416
+ const cursor = readGuardianCursor(join(guardianCursorDir(), cursorName), sessionId);
417
+ const remembered = cursor && isInside(layout.root, cursor.path) && cursor.path !== transcriptPath && isGuardianRollout(cursor.path, sessionId, headMaxBytes) ? cursor : null;
418
+ const guardianPath = remembered?.path ?? discoverGuardianRollout(call, layout, options.now ?? /* @__PURE__ */ new Date(), options);
419
+ if (!guardianPath) return null;
420
+ const from = remembered?.offset ?? 0;
421
+ const read = readCompleteLines(guardianPath, from, options.guardianMaxBytes ?? CODEX_GUARDIAN_READ_MAX_BYTES);
422
+ if (!read) return null;
423
+ const scan = scanCodexGuardianReviews(read.lines);
424
+ const next = scan.pendingOffset ?? read.end;
425
+ const denials = codexReviewerDenialsSince(scan.reviews, turnStartedAt);
426
+ if (!remembered || next !== from) {
427
+ const saved = writeJsonRecord(guardianCursorDir(), cursorName, { sessionId, path: guardianPath, offset: next });
428
+ if (!saved && denials.length > 0) return null;
429
+ }
430
+ return { guardianPath, denials };
431
+ };
432
+
433
+ // src/codex-reviewer.ts
434
+ var START_MS = Date.now();
435
+ var CLAIM_TIMEOUT_MS = 4e3;
436
+ var GATE_TIMEOUT_MS = 15e3;
437
+ var CANCEL_TIMEOUT_MS = 4e3;
438
+ var CODEX_RESCUE_STOP_RESERVE_MS = 2e4;
439
+ var codexRescueEndsAt = (startMs, holdBudgetSeconds) => startMs + hookHoldWindowMs("codex", holdBudgetSeconds) - CODEX_RESCUE_STOP_RESERVE_MS;
440
+ var codexRescueDeadline = (startMs, deadlineSeconds, nowMs, holdBudgetSeconds) => Math.min(hookWaitDeadline(startMs, deadlineSeconds, "codex", nowMs), codexRescueEndsAt(startMs, holdBudgetSeconds));
441
+ var codexApprovalsReviewer = (call) => readCodexTurn({ transcriptPath: call.transcript_path, sessionId: call.session_id, turnId: call.turn_id })?.approvalsReviewer;
442
+ var claimCodexReviewerRetry = async (apiKey, call) => {
443
+ if (!call.tool_name || !call.tool_input) return false;
444
+ const fingerprint = blockedActionFingerprint({
445
+ source: "codex",
446
+ sessionId: call.session_id,
447
+ toolName: call.tool_name,
448
+ toolInput: call.tool_input,
449
+ cwd: call.cwd
450
+ });
451
+ if (!fingerprint || !hasBlockedActionHint(fingerprint)) return false;
452
+ const granted = await claimBlockedAction(apiKey, {
453
+ v: GATE_WIRE,
454
+ source: "codex",
455
+ event: "PermissionRequest",
456
+ toolName: call.tool_name,
457
+ toolInputs: [call.tool_input],
458
+ sessionId: call.session_id,
459
+ machineId: getMachineId(),
460
+ cwd: call.cwd,
461
+ repoKey: repoKeyFor(call.cwd),
462
+ permissionMode: call.permission_mode
463
+ }, CLAIM_TIMEOUT_MS);
464
+ if (granted !== null) forgetBlockedActionHint(fingerprint);
465
+ return granted === true;
466
+ };
467
+ var blockReasonOf = (rationale) => rationale?.trim().slice(0, GATE_BLOCK_REASON_MAX_LENGTH).trim() || void 0;
468
+ var reviewToolUseId = (guardianPath, denial) => `codex-review-${createHash2("sha256").update(canonicalJson([guardianPath, denial.offset, denial.plannedAction]), "utf-8").digest("hex").slice(0, 40)}`;
469
+ var isRescueApproval = (output) => {
470
+ if (!output) return false;
471
+ try {
472
+ const parsed = JSON.parse(output);
473
+ return parsed?.decision === "block" && typeof parsed.reason === "string" && parsed.reason.length > 0;
474
+ } catch {
475
+ return false;
476
+ }
477
+ };
478
+ var isAffirmativeAnswer = (answer) => answer.answered && !isDecisionStopped(answer) && answer.value === "yes";
479
+ var approveOnce = (fingerprint, output) => recordBlockedActionHint(fingerprint) ? output : void 0;
480
+ var rescueRequest = (call, sessionId, guardianPath, denial) => {
481
+ const cwd = denial.cwd ?? call.cwd;
482
+ const blockReason = blockReasonOf(denial.rationale);
483
+ return {
484
+ v: GATE_WIRE,
485
+ source: "codex",
486
+ event: "Stop",
487
+ toolName: CODEX_RESCUE_TOOL_NAME,
488
+ toolInputs: [{ command: denial.command }],
489
+ sessionId,
490
+ toolUseId: reviewToolUseId(guardianPath, denial),
491
+ machineId: getMachineId(),
492
+ cwd,
493
+ ...call.cwd ? { sessionCwd: call.cwd } : {},
494
+ repoKey: repoKeyFor(cwd),
495
+ permissionMode: call.permission_mode,
496
+ createDecision: true,
497
+ localSurfaceAvailable: false,
498
+ ...blockReason ? { blockReason } : {},
499
+ approvalsReviewer: "auto_review",
500
+ blockedBy: "auto_review"
501
+ };
502
+ };
503
+ var askAboutDenial = async (apiKey, call, sessionId, guardianPath, denial, holdBudgetSeconds) => {
504
+ const request = rescueRequest(call, sessionId, guardianPath, denial);
505
+ const fingerprint = blockedActionFingerprint({
506
+ source: "codex",
507
+ sessionId,
508
+ toolName: request.toolName,
509
+ toolInput: request.toolInputs[0],
510
+ cwd: request.cwd
511
+ });
512
+ if (!fingerprint) return void 0;
513
+ const reply = await requestGate(apiKey, request, GATE_TIMEOUT_MS);
514
+ if (!reply) return void 0;
515
+ if (reply.kind === "no_opinion") return isRescueApproval(reply.output) ? approveOnce(fingerprint, reply.output) : void 0;
516
+ if (reply.kind !== "ask" || !reply.correlationId || reply.deadlineSeconds === void 0) return void 0;
517
+ if (reply.noDevices) {
518
+ await cancelQuestion(apiKey, reply.correlationId, AbortSignal.timeout(CANCEL_TIMEOUT_MS)).catch(() => void 0);
519
+ return void 0;
520
+ }
521
+ const deadline = codexRescueDeadline(START_MS, reply.deadlineSeconds, Date.now(), holdBudgetSeconds);
522
+ const answer = await pollForAnswer(apiKey, reply.correlationId, deadline);
523
+ if (!isAffirmativeAnswer(answer)) return void 0;
524
+ const approve = reply.settlementOutputs?.approve;
525
+ return approveOnce(
526
+ fingerprint,
527
+ isRescueApproval(approve) ? approve : JSON.stringify(codexStopRescueOutput("allow", denial.command))
528
+ );
529
+ };
530
+ var rescueCodexReviewerDenial = async (call, holdBudgetSeconds) => {
531
+ let apiKey;
532
+ try {
533
+ apiKey = getApiKey();
534
+ } catch {
535
+ return void 0;
536
+ }
537
+ try {
538
+ const { session_id: sessionId, transcript_path: transcriptPath } = call;
539
+ if (!sessionId || !transcriptPath) return void 0;
540
+ if (holdBudgetSeconds === void 0 || codexRescueEndsAt(START_MS, holdBudgetSeconds) - Date.now() <= GATE_TIMEOUT_MS) return void 0;
541
+ const turn = readCodexTurn({ transcriptPath, sessionId, turnId: call.turn_id });
542
+ if (turn?.approvalsReviewer !== "auto_review" || !turn.startedAt) return void 0;
543
+ const taken = takeCodexReviewerDenials({ transcriptPath, sessionId, turnStartedAt: turn.startedAt });
544
+ const denial = taken?.denials.at(-1);
545
+ if (!taken || !denial) return void 0;
546
+ return await askAboutDenial(apiKey, call, sessionId, taken.guardianPath, denial, holdBudgetSeconds);
547
+ } catch {
548
+ return void 0;
549
+ }
550
+ };
551
+
552
+ // bin/pushary-codex-hook.ts
553
+ var callKeyFor = (input) => codexCallKey({
554
+ sessionId: input.session_id,
555
+ turnId: input.turn_id,
556
+ toolName: input.tool_name,
557
+ toolInput: input.tool_input
558
+ });
559
+ var START_MS2 = Date.now();
560
+ var agentNameFor = (input) => `Codex - ${basename(input.cwd ?? process.cwd())}`;
561
+ var describeFor = (input, lookup) => input.tool_name === "apply_patch" ? describeApplyPatch(input.tool_input?.command) ?? describeToolCall(lookup.tool, lookup.input, "hook") : describeToolCall(lookup.tool, lookup.input, "hook");
562
+ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
563
+ const projectName = basename(input.cwd ?? process.cwd());
564
+ const description = describeFor(input, lookup);
565
+ const result = await askUser(apiKey, {
566
+ question: `Allow ${description || UNNAMED_ACTION}?`,
567
+ type: "confirm",
568
+ context: `Agent wants to run this in ${projectName}`,
569
+ agentName: agentNameFor(input),
570
+ sessionId: input.session_id,
571
+ machineId: getMachineId(),
572
+ toolName: lookup.tool,
573
+ toolTarget: deriveToolTarget(lookup.tool, lookup.input),
574
+ intent: readLastPrompt(input.session_id || DEFAULT_SESSION),
575
+ action: description,
576
+ blocker: deriveBlocker(mode),
577
+ actionBody: deriveActionBody(input.tool_name ?? "", input.tool_input ?? {}),
578
+ repoKey: repoKeyFor(input.cwd),
579
+ // Codex has always sent this; it is what claimNotify and markApproved already
580
+ // key on locally. Passing it through lets the server collapse a retried create
581
+ // the same way, instead of putting the same approval on the phone twice.
582
+ toolUseId: input.tool_use_id
583
+ }).catch(unavailableQuestionUnlessSubscriptionRequired);
584
+ const now = Date.now();
585
+ const deadline = hookWaitDeadline(START_MS2, waitSeconds, "codex", now);
586
+ const clampCut = hookWaitClamped(START_MS2, waitSeconds, "codex", now);
587
+ const answer = await waitForQuestion(apiKey, result, deadline);
588
+ return { answer, localHandoff: !answer.answered && isLocalQuestionHandoff(result), clampCut };
589
+ };
590
+ var codexStepsAside = (error, input) => {
591
+ announceSubscriptionRequired(error.upgradeUrl, input.session_id);
592
+ return codexPass();
593
+ };
594
+ var passAfterFailure = (error, input) => error instanceof SubscriptionRequiredError ? codexStepsAside(error, input) : codexPass();
595
+ var notifyApprovalNeeded = async (apiKey, input, lookup) => {
596
+ try {
597
+ await sendNotification(apiKey, {
598
+ title: "Agent needs approval",
599
+ body: describeFor(input, lookup),
600
+ agentName: agentNameFor(input),
601
+ sessionId: input.session_id,
602
+ machineId: getMachineId()
603
+ });
604
+ } catch {
605
+ }
606
+ };
607
+ var codexGate = async (apiKey, input) => {
608
+ const modeState = await fetchModeState(apiKey, input.session_id);
609
+ const policyRequest = policyRequestFor("codex");
610
+ const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
611
+ const lookups = toPolicyLookups(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
612
+ const config = await getPolicy(apiKey, modeState.policyVersion, policyRequest.agent, policyRequest.repoAware, input.session_id);
613
+ const gateInput = {
614
+ modeState,
615
+ config,
616
+ toolName: lookups.tool,
617
+ toolInputs: lookups.inputs,
618
+ // Skipped entirely without a contract, which is the state of nearly every
619
+ // session. A multi-file patch contributes one path per file.
620
+ scopePaths: modeState.scope ? lookups.inputs.map((entry) => scopePathFor(lookups.tool, entry, input.cwd)).filter((path) => typeof path === "string") : [],
621
+ cwd: input.cwd,
622
+ repoKey: repoKeyFor(input.cwd),
623
+ sessionId: input.session_id
624
+ };
625
+ return { verdict: resolveGate(gateInput), lookup, gateInput };
626
+ };
627
+ var PUSHARY_OWN_TOOL_PREFIX = "mcp__pushary__";
628
+ var isPusharyOwnTool = (toolName) => typeof toolName === "string" && toolName.startsWith(PUSHARY_OWN_TOOL_PREFIX);
629
+ var decidePermissionRequest = async (input) => {
630
+ if (isPusharyOwnTool(input.tool_name)) return codexPass();
631
+ try {
632
+ const apiKey = getApiKey();
633
+ const { verdict, lookup, gateInput } = await codexGate(apiKey, input);
634
+ if (verdict.kind === "kill" || verdict.kind === "deny") {
635
+ await reportMachineDenial({ ...input, tool_name: lookup.tool, tool_input: lookup.input }, verdict, CODEX_AGENT);
636
+ return codexDeny(verdict.kind === "kill" ? KILL_REASON : verdict.reason);
637
+ }
638
+ const recorded = consumeCodexCallDecision(callKeyFor(input));
639
+ if (recorded?.kind === "denied") return codexDeny(recorded.reason);
640
+ if (recorded?.kind === "handled") return codexPass();
641
+ if (await claimCodexReviewerRetry(apiKey, input)) return codexAllow();
642
+ if (codexApprovalsReviewer(input) === "auto_review") return codexPass();
643
+ if (verdict.kind === "allow") return codexAllow();
644
+ if (verdict.kind === "defer") return codexPass();
645
+ if (!recorded && editAsksAtTheAgentsPrompt(gateInput, verdict)) return codexPass();
646
+ const toolPolicy = verdict.policy;
647
+ if (toolPolicy.mode === "terminal_only") return codexPass();
648
+ if (toolPolicy.mode === "notify_only") {
649
+ await notifyApprovalNeeded(apiKey, input, lookup);
650
+ return codexPass();
651
+ }
652
+ const waitSeconds = toolPolicy.mode === "push_first" ? toolPolicy.pushFirstSeconds : effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex");
653
+ const asked = recorded ? { ...input, tool_use_id: recorded.toolUseId } : input;
654
+ const { answer, localHandoff, clampCut } = await pushQuestion(apiKey, asked, lookup, waitSeconds, toolPolicy.mode);
655
+ if (isDecisionStopped(answer)) return codexDeny(DECISION_STOP_REASON);
656
+ if (localHandoff) return codexPass();
657
+ if (answer.answered) {
658
+ if (isDeferAnswer(answer.value)) return codexPass();
659
+ if (answer.value === "yes") return codexAllow();
660
+ return codexDeny(denyReasonFrom(answer.value, answer.note));
661
+ }
662
+ if (toolPolicy.mode === "push_first") return codexPass();
663
+ if (clampCut) return codexPass();
664
+ return permissionTimeoutDecision(toolPolicy.timeoutAction);
665
+ } catch (error) {
666
+ return passAfterFailure(error, input);
667
+ }
668
+ };
669
+ var recordPreToolUse = (key, outcome) => {
670
+ recordCodexCallDecision(
671
+ key,
672
+ outcome.kind === "deny" ? { kind: "denied", reason: outcome.reason } : { kind: "handled" }
673
+ );
674
+ return outcome;
675
+ };
676
+ var recordApproval = (key, input) => {
677
+ if (input.tool_use_id) recordCodexCallDecision(key, { kind: "approved", toolUseId: input.tool_use_id });
678
+ return codexPass();
679
+ };
680
+ var permissionRequestNeverFires = (permissionMode) => permissionMode === "bypassPermissions";
681
+ var decidePushFirstBeforeTheTool = async (apiKey, input, lookup, pushFirstSeconds, callKey) => {
682
+ let pushed;
683
+ try {
684
+ pushed = await pushQuestion(apiKey, input, lookup, pushFirstSeconds, "push_first");
685
+ } catch (error) {
686
+ return recordPreToolUse(callKey, passAfterFailure(error, input));
687
+ }
688
+ const { answer, localHandoff } = pushed;
689
+ if (isDecisionStopped(answer)) return codexDeny(DECISION_STOP_REASON);
690
+ if (localHandoff) {
691
+ return recordPreToolUse(callKey, codexPass());
692
+ }
693
+ if (answer.answered) {
694
+ if (isDeferAnswer(answer.value)) return recordPreToolUse(callKey, codexPass());
695
+ if (answer.value === "yes") return recordApproval(callKey, input);
696
+ return recordPreToolUse(callKey, codexDeny(denyReasonFrom(answer.value, answer.note)));
697
+ }
698
+ return recordPreToolUse(callKey, codexPass());
699
+ };
700
+ var decidePreToolUse = async (input) => {
701
+ if (isPusharyOwnTool(input.tool_name)) return codexPass();
702
+ let apiKey;
703
+ try {
704
+ apiKey = getApiKey();
705
+ } catch {
706
+ try {
707
+ const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
708
+ if (isGatingMoment(lookup.tool, lookup.input)) {
709
+ recordKeylessMoment({
710
+ ts: Date.now(),
711
+ tool: lookup.tool,
712
+ project: basename(input.cwd ?? process.cwd()),
713
+ sessionId: input.session_id
714
+ });
715
+ }
716
+ } catch {
717
+ }
718
+ return codexPass();
719
+ }
720
+ try {
721
+ const { verdict, lookup } = await codexGate(apiKey, input);
722
+ if (verdict.kind === "kill" || verdict.kind === "deny") {
723
+ await reportMachineDenial({ ...input, tool_name: lookup.tool, tool_input: lookup.input }, verdict, CODEX_AGENT);
724
+ return codexDeny(verdict.kind === "kill" ? KILL_REASON : verdict.reason);
725
+ }
726
+ if (verdict.kind === "allow") return codexPass();
727
+ if (codexApprovalsReviewer(input) === "auto_review") return codexPass();
728
+ if (defersToNativeMode(input.permission_mode, input.tool_name ?? "")) return codexPass();
729
+ if (verdict.kind === "defer") return codexPass();
730
+ const toolPolicy = verdict.policy;
731
+ const callKey = callKeyFor(input);
732
+ if (toolPolicy.mode === "push_only") {
733
+ let pushed;
734
+ try {
735
+ pushed = await pushQuestion(apiKey, input, lookup, effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex"), "push_only");
736
+ } catch (error) {
737
+ if (error instanceof SubscriptionRequiredError) return recordPreToolUse(callKey, codexStepsAside(error, input));
738
+ return recordPreToolUse(callKey, codexDeny(UNVERIFIABLE_APPROVAL_REASON));
739
+ }
740
+ const { answer, localHandoff, clampCut } = pushed;
741
+ if (isDecisionStopped(answer)) return codexDeny(DECISION_STOP_REASON);
742
+ if (localHandoff) return recordPreToolUse(callKey, codexPass());
743
+ if (answer.answered) {
744
+ if (isDeferAnswer(answer.value)) return recordPreToolUse(callKey, codexPass());
745
+ if (answer.value === "yes") return recordApproval(callKey, input);
746
+ return recordPreToolUse(callKey, codexDeny(denyReasonFrom(answer.value, answer.note)));
747
+ }
748
+ if (clampCut) return recordPreToolUse(callKey, codexPass());
749
+ return recordPreToolUse(callKey, preToolUseTimeoutDecision(toolPolicy.timeoutAction));
750
+ }
751
+ if (toolPolicy.mode === "notify_only") {
752
+ await notifyApprovalNeeded(apiKey, input, lookup);
753
+ recordCodexCallDecision(callKey, { kind: "handled" });
754
+ return codexPass();
755
+ }
756
+ if (toolPolicy.mode === "push_first" && permissionRequestNeverFires(input.permission_mode)) {
757
+ return decidePushFirstBeforeTheTool(apiKey, input, lookup, toolPolicy.pushFirstSeconds, callKey);
758
+ }
759
+ return codexPass();
760
+ } catch {
761
+ return codexPass();
762
+ }
763
+ };
764
+ var repairCodexHoldsQuietly = async () => {
765
+ try {
766
+ const { repairCodexHolds } = await import("../reapply-CW3MA66V.js");
767
+ repairCodexHolds();
768
+ } catch {
769
+ return;
770
+ }
771
+ };
772
+ var reportSessionStart = async (input, action = "Session started") => {
773
+ try {
774
+ await reportEvent({
775
+ event: "session_begin",
776
+ agentType: CODEX_AGENT.type,
777
+ agentName: agentNameFor(input),
778
+ action,
779
+ // Not `sessionId: input.session_id`. On SubagentStart that would announce
780
+ // the parent session a second time under the same id; this gives the
781
+ // subagent its own row and names the parent beside it.
782
+ ...sessionIdentityOf(input)
783
+ }, { maxAttempts: 1, timeoutMs: 5e3 });
784
+ } catch {
785
+ }
786
+ };
787
+ var asToolResult = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
788
+ var emit = (wire) => {
789
+ if (wire) process.stdout.write(JSON.stringify(wire));
790
+ };
791
+ var emitContext = (event, additionalContext) => {
792
+ if (!additionalContext) return;
793
+ process.stdout.write(JSON.stringify({
794
+ hookSpecificOutput: { hookEventName: event, additionalContext }
795
+ }));
796
+ };
797
+ var main = async () => {
798
+ const input = await readHookInput("codex");
799
+ if (!input) {
800
+ return exitAfterClientReports(EXIT.OK);
801
+ }
802
+ try {
803
+ switch (input.hook_event_name) {
804
+ case "PermissionRequest":
805
+ emit(toCodexWire("PermissionRequest", await decidePermissionRequest(input)));
806
+ break;
807
+ case "PreToolUse":
808
+ emit(toCodexWire("PreToolUse", await decidePreToolUse(input)));
809
+ break;
810
+ case "PostToolUse": {
811
+ const lookup = toPolicyLookup(input.tool_name ?? "", input.tool_input ?? {}, input.cwd);
812
+ emitContext("PostToolUse", await handlePostToolUse({
813
+ tool_name: lookup.tool,
814
+ tool_input: lookup.input,
815
+ tool_result: asToolResult(input.tool_response),
816
+ cwd: input.cwd,
817
+ session_id: input.session_id,
818
+ tool_use_id: input.tool_use_id
819
+ }, CODEX_AGENT));
820
+ break;
821
+ }
822
+ case "UserPromptSubmit":
823
+ emitContext("UserPromptSubmit", await handleUserPrompt({
824
+ prompt: input.prompt,
825
+ cwd: input.cwd,
826
+ session_id: input.session_id
827
+ }, CODEX_AGENT));
828
+ break;
829
+ case "Stop": {
830
+ const rescued = await rescueCodexReviewerDenial(input, takeHookHoldBudget());
831
+ if (rescued) {
832
+ process.stdout.write(rescued);
833
+ break;
834
+ }
835
+ await handleStop({
836
+ cwd: input.cwd,
837
+ session_id: input.session_id
838
+ }, CODEX_AGENT);
839
+ break;
840
+ }
841
+ case "SessionStart":
842
+ await repairCodexHoldsQuietly();
843
+ triggerAutoUpdate({ isSubagentStart: Boolean(input.agent_id) });
844
+ await reportSessionStart(input);
845
+ break;
846
+ case "SessionEnd":
847
+ await handleSessionEnd({
848
+ cwd: input.cwd,
849
+ session_id: input.session_id,
850
+ reason: input.reason
851
+ }, CODEX_AGENT, { reconcile: false, reportAttempts: 1, reportTimeoutMs: 1200 });
852
+ break;
853
+ // Observability only, and none of them carries context back, so none uses
854
+ // emitContext. Each goes through the same handler Claude Code's own hook
855
+ // does, rather than a Codex-shaped copy of it, so the two agents cannot
856
+ // drift into reporting the same moment differently.
857
+ case "SubagentStart":
858
+ await reportSessionStart(input, "Subagent started");
859
+ break;
860
+ case "SubagentStop":
861
+ await handleSubagentStop({
862
+ hook_event_name: input.hook_event_name,
863
+ cwd: input.cwd,
864
+ session_id: input.session_id,
865
+ agent_id: input.agent_id,
866
+ agent_type: input.agent_type,
867
+ last_assistant_message: input.last_assistant_message
868
+ }, CODEX_AGENT);
869
+ break;
870
+ case "PreCompact":
871
+ case "PostCompact":
872
+ await handleCompaction({
873
+ hook_event_name: input.hook_event_name,
874
+ trigger: input.trigger,
875
+ cwd: input.cwd,
876
+ session_id: input.session_id,
877
+ agent_id: input.agent_id
878
+ }, CODEX_AGENT);
879
+ break;
880
+ default:
881
+ break;
882
+ }
883
+ } catch {
884
+ }
885
+ };
886
+ main();