@pushary/agent-hooks 0.95.8 → 0.97.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.
- package/CHANGELOG.md +17 -0
- package/dist/bin/pushary-bell-hook.js +19 -21
- package/dist/bin/pushary-bell.js +6 -8
- package/dist/bin/pushary-claude.js +21 -23
- package/dist/bin/pushary-clean.js +19 -22
- package/dist/bin/pushary-codex-bridge.js +13 -14
- package/dist/bin/pushary-codex-hook.js +452 -104
- package/dist/bin/pushary-codex.js +8 -8
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-cowork.js +3 -3
- package/dist/bin/pushary-daemon.js +25 -27
- package/dist/bin/pushary-disconnect.js +6 -8
- package/dist/bin/pushary-doctor.js +28 -23
- package/dist/bin/pushary-elicitation-hook.js +19 -21
- package/dist/bin/pushary-gemini-bridge.js +11 -12
- package/dist/bin/pushary-gemini-hook.js +22 -24
- package/dist/bin/pushary-hook.js +24 -26
- package/dist/bin/pushary-login.js +7 -8
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +21 -23
- package/dist/bin/pushary-opencode-hook.js +22 -24
- package/dist/bin/pushary-permission-denied-hook.js +28 -27
- package/dist/bin/pushary-permission-hook.js +24 -26
- package/dist/bin/pushary-post-hook.js +21 -23
- package/dist/bin/pushary-prompt-hook.js +21 -23
- package/dist/bin/pushary-session-end-hook.js +21 -23
- package/dist/bin/pushary-session-start-hook.js +24 -26
- package/dist/bin/pushary-setup.js +34 -38
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +10 -11
- package/dist/bin/pushary-stop-hook.js +21 -23
- package/dist/bin/pushary-stopfailure-hook.js +21 -23
- package/dist/bin/pushary-transcript-register.js +17 -19
- package/dist/bin/pushary-transcripts.js +5 -5
- package/dist/bin/pushary-upgrade.js +17 -20
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-RE25RJ5A.js → chunk-3SNTEHOH.js} +4 -4
- package/dist/{chunk-R3OZ5ISU.js → chunk-4AXWBCCO.js} +4 -4
- package/dist/{chunk-XEVOPOKT.js → chunk-5ODZEFP3.js} +1 -1
- package/dist/{chunk-NOVA7KZ2.js → chunk-67RNDRKL.js} +260 -32
- package/dist/{chunk-3YFJGKJQ.js → chunk-6VXZKOBB.js} +8 -8
- package/dist/{chunk-ATKGKAP6.js → chunk-6WJKYD3Q.js} +8 -8
- package/dist/{chunk-EMGGUO2Q.js → chunk-A4AIISZZ.js} +1 -1
- package/dist/{chunk-N6FAXQDD.js → chunk-AXSDMDJI.js} +1 -1
- package/dist/{chunk-XO3JR3OP.js → chunk-B3557BDL.js} +2 -2
- package/dist/{chunk-QJPYJVL3.js → chunk-BYH75I7Y.js} +1 -1
- package/dist/{chunk-VERXHIQF.js → chunk-C2FYSFTQ.js} +1 -1
- package/dist/{chunk-SKPXQRKN.js → chunk-CDSA3NTB.js} +1 -1
- package/dist/{chunk-HUGIKB5O.js → chunk-DGUMR5LZ.js} +6 -6
- package/dist/{chunk-54W7ELDQ.js → chunk-DNLFSRCF.js} +2 -2
- package/dist/{chunk-R6LBGKWQ.js → chunk-DOKFLZWL.js} +10 -14
- package/dist/{chunk-U5RIXR7I.js → chunk-EHQC3GWL.js} +1 -1
- package/dist/{chunk-QD73WATQ.js → chunk-FU5SQKAY.js} +15 -4
- package/dist/{chunk-TY6IJY6I.js → chunk-GGYH6LIE.js} +1 -1
- package/dist/{chunk-XZWUOCJZ.js → chunk-H4VNAOCG.js} +1 -1
- package/dist/{chunk-MC5WUOJJ.js → chunk-HUZW6SEO.js} +75 -1
- package/dist/{chunk-QV266EGS.js → chunk-IBRX7LRE.js} +8 -10
- package/dist/{chunk-UKMLSQXA.js → chunk-ICUBJNFL.js} +2 -2
- package/dist/{chunk-E47WV7BJ.js → chunk-IZKCA35M.js} +2 -2
- package/dist/{chunk-F4TMY6XI.js → chunk-K7MLXXPY.js} +2 -2
- package/dist/{chunk-Y6SCMXR6.js → chunk-KDPYXMUI.js} +2 -2
- package/dist/{chunk-XS33QBJA.js → chunk-LGXUGYUH.js} +50 -1
- package/dist/{chunk-I7LKI4WO.js → chunk-LYWTLNMG.js} +6 -8
- package/dist/chunk-OYTOLQ3N.js +15 -0
- package/dist/{chunk-XUTSZYYJ.js → chunk-QFAZ6QCT.js} +1 -4
- package/dist/{chunk-FRXZMCVA.js → chunk-ROTIPRM4.js} +1 -1
- package/dist/{chunk-BQL7UDIB.js → chunk-SW37HB27.js} +1 -1
- package/dist/{chunk-Z4JVEL7R.js → chunk-VCKQ45QA.js} +191 -2
- package/dist/{chunk-SDNTRS76.js → chunk-VDTC5DEM.js} +1 -1
- package/dist/{chunk-VMQU7YEJ.js → chunk-VF7YOT2W.js} +1 -1
- package/dist/{chunk-5GKNAQTG.js → chunk-VOC55PQV.js} +1 -1
- package/dist/{chunk-L7NOG547.js → chunk-VTUN7WSG.js} +1 -1
- package/dist/{chunk-GOPIMNVC.js → chunk-WIKEADOS.js} +1 -1
- package/dist/chunk-XDMHLQMH.js +202 -0
- package/dist/{chunk-4DUEIUUO.js → chunk-YRXVC4HL.js} +1 -1
- package/dist/{chunk-A22VQ5ZL.js → chunk-YX3U2CLP.js} +111 -55
- package/dist/src/index.js +11 -11
- package/package.json +1 -1
- package/dist/chunk-3AJIK5AO.js +0 -57
- package/dist/chunk-L4ZCFMWW.js +0 -177
- package/dist/chunk-VDI645ZX.js +0 -18
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "../chunk-
|
|
3
|
+
takeHookHoldBudget
|
|
4
|
+
} from "../chunk-OYTOLQ3N.js";
|
|
5
|
+
import {
|
|
6
|
+
blockedActionFingerprint,
|
|
7
|
+
codexCallKey,
|
|
8
|
+
consumeCodexCallDecision,
|
|
9
|
+
defersToNativeMode,
|
|
10
|
+
forgetBlockedActionHint,
|
|
11
|
+
hasBlockedActionHint,
|
|
12
|
+
recordBlockedActionHint,
|
|
13
|
+
recordCodexCallDecision,
|
|
14
|
+
userScope
|
|
15
|
+
} from "../chunk-XDMHLQMH.js";
|
|
5
16
|
import {
|
|
6
17
|
isGatingMoment,
|
|
7
18
|
recordKeylessMoment
|
|
8
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-VOC55PQV.js";
|
|
9
20
|
import {
|
|
10
21
|
readHookInput
|
|
11
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-5ODZEFP3.js";
|
|
12
23
|
import {
|
|
13
24
|
denyReasonFrom,
|
|
14
25
|
isDeferAnswer
|
|
15
26
|
} from "../chunk-YHG74UFF.js";
|
|
16
|
-
import "../chunk-
|
|
17
|
-
import "../chunk-
|
|
18
|
-
import "../chunk-
|
|
19
|
-
import "../chunk-
|
|
20
|
-
import "../chunk-
|
|
21
|
-
import "../chunk-
|
|
22
|
-
import "../chunk-
|
|
23
|
-
import "../chunk-
|
|
24
|
-
import "../chunk-
|
|
25
|
-
import "../chunk-
|
|
26
|
-
import "../chunk-SKPXQRKN.js";
|
|
27
|
+
import "../chunk-6VXZKOBB.js";
|
|
28
|
+
import "../chunk-A4AIISZZ.js";
|
|
29
|
+
import "../chunk-4AXWBCCO.js";
|
|
30
|
+
import "../chunk-H4VNAOCG.js";
|
|
31
|
+
import "../chunk-BYH75I7Y.js";
|
|
32
|
+
import "../chunk-FU5SQKAY.js";
|
|
33
|
+
import "../chunk-LYWTLNMG.js";
|
|
34
|
+
import "../chunk-IBRX7LRE.js";
|
|
35
|
+
import "../chunk-WIKEADOS.js";
|
|
36
|
+
import "../chunk-CDSA3NTB.js";
|
|
27
37
|
import "../chunk-EQRS3SGM.js";
|
|
28
|
-
import "../chunk-
|
|
38
|
+
import "../chunk-C2FYSFTQ.js";
|
|
29
39
|
import "../chunk-XHKBHWLX.js";
|
|
30
|
-
import "../chunk-L4ZCFMWW.js";
|
|
31
40
|
import {
|
|
32
41
|
CODEX_AGENT,
|
|
33
42
|
DEFAULT_SESSION,
|
|
@@ -52,100 +61,97 @@ import {
|
|
|
52
61
|
toCodexWire,
|
|
53
62
|
toPolicyLookup,
|
|
54
63
|
toPolicyLookups
|
|
55
|
-
} from "../chunk-
|
|
64
|
+
} from "../chunk-DGUMR5LZ.js";
|
|
56
65
|
import {
|
|
57
66
|
UNNAMED_ACTION,
|
|
67
|
+
codexReviewerDenialsSince,
|
|
58
68
|
deriveActionBody,
|
|
59
69
|
deriveBlocker,
|
|
60
70
|
deriveToolTarget,
|
|
61
71
|
describeToolCall,
|
|
72
|
+
isCodexGuardianRolloutHead,
|
|
73
|
+
readCodexTurnContext,
|
|
74
|
+
scanCodexGuardianReviews,
|
|
62
75
|
scopePathFor,
|
|
63
76
|
sessionIdentityOf
|
|
64
|
-
} from "../chunk-
|
|
65
|
-
import "../chunk-
|
|
77
|
+
} from "../chunk-VCKQ45QA.js";
|
|
78
|
+
import "../chunk-QFAZ6QCT.js";
|
|
66
79
|
import {
|
|
67
80
|
DECISION_STOP_REASON,
|
|
68
81
|
askUser,
|
|
82
|
+
cancelQuestion,
|
|
83
|
+
claimBlockedAction,
|
|
69
84
|
isDecisionStopped,
|
|
70
85
|
isLocalQuestionHandoff,
|
|
86
|
+
pollForAnswer,
|
|
87
|
+
requestGate,
|
|
71
88
|
sendNotification,
|
|
72
89
|
waitForQuestion
|
|
73
|
-
} from "../chunk-
|
|
90
|
+
} from "../chunk-LGXUGYUH.js";
|
|
74
91
|
import {
|
|
75
92
|
getMachineId
|
|
76
|
-
} from "../chunk-
|
|
93
|
+
} from "../chunk-IZKCA35M.js";
|
|
77
94
|
import {
|
|
78
95
|
canonicalJson
|
|
79
|
-
} from "../chunk-
|
|
96
|
+
} from "../chunk-67RNDRKL.js";
|
|
80
97
|
import "../chunk-GMXKITVA.js";
|
|
81
98
|
import "../chunk-DINDL2CF.js";
|
|
82
|
-
import "../chunk-
|
|
99
|
+
import "../chunk-VF7YOT2W.js";
|
|
83
100
|
import "../chunk-EYRGBYZG.js";
|
|
84
101
|
import "../chunk-SAF6HGAA.js";
|
|
85
102
|
import "../chunk-7QLSKOSU.js";
|
|
86
103
|
import "../chunk-KZERVKTD.js";
|
|
87
|
-
import
|
|
104
|
+
import {
|
|
105
|
+
writeFileAtomic
|
|
106
|
+
} from "../chunk-VTUN7WSG.js";
|
|
88
107
|
import {
|
|
89
108
|
getApiKey
|
|
90
109
|
} from "../chunk-2UMNXADU.js";
|
|
91
110
|
import {
|
|
111
|
+
CODEX_RESCUE_TOOL_NAME,
|
|
92
112
|
DECISION_LINE_MAX,
|
|
113
|
+
GATE_BLOCK_REASON_MAX_LENGTH,
|
|
114
|
+
GATE_WIRE,
|
|
93
115
|
KILL_REASON,
|
|
116
|
+
codexStopRescueOutput,
|
|
94
117
|
effectiveWaitSeconds,
|
|
118
|
+
hookHoldWindowMs,
|
|
95
119
|
hookWaitClamped,
|
|
96
120
|
hookWaitDeadline,
|
|
97
121
|
resolveGate
|
|
98
|
-
} from "../chunk-
|
|
122
|
+
} from "../chunk-HUZW6SEO.js";
|
|
99
123
|
|
|
100
124
|
// bin/pushary-codex-hook.ts
|
|
101
125
|
import { basename } from "path";
|
|
102
126
|
|
|
103
|
-
// src/codex-
|
|
127
|
+
// src/codex-reviewer.ts
|
|
128
|
+
import { createHash as createHash2 } from "crypto";
|
|
129
|
+
|
|
130
|
+
// src/codex-rollout.ts
|
|
104
131
|
import { createHash } from "crypto";
|
|
105
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync
|
|
106
|
-
import { tmpdir
|
|
107
|
-
import { join } from "path";
|
|
108
|
-
var
|
|
109
|
-
var
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
var
|
|
119
|
-
var
|
|
120
|
-
var
|
|
121
|
-
var
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (PRESENTATION_ONLY_INPUT_KEYS.includes(key)) continue;
|
|
126
|
-
identifying[key] = value;
|
|
127
|
-
}
|
|
128
|
-
return identifying;
|
|
129
|
-
};
|
|
130
|
-
var codexCallKey = (identity) => {
|
|
131
|
-
if (!identity.sessionId || !identity.turnId || !identity.toolName) return null;
|
|
132
|
-
const material = canonicalJson([
|
|
133
|
-
identity.sessionId,
|
|
134
|
-
identity.turnId,
|
|
135
|
-
identity.toolName,
|
|
136
|
-
identifyingToolInput(identity.toolInput)
|
|
137
|
-
]);
|
|
138
|
-
return createHash("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
|
|
139
|
-
};
|
|
140
|
-
var parseDecision = (raw) => {
|
|
132
|
+
import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync } from "fs";
|
|
133
|
+
import { tmpdir } from "os";
|
|
134
|
+
import { dirname, isAbsolute, join, relative } from "path";
|
|
135
|
+
var CODEX_ROLLOUT_TAIL_MAX_BYTES = 8 * 1024 * 1024;
|
|
136
|
+
var CODEX_ROLLOUT_HEAD_MAX_BYTES = 1024 * 1024;
|
|
137
|
+
var CODEX_GUARDIAN_READ_MAX_BYTES = 8 * 1024 * 1024;
|
|
138
|
+
var CODEX_GUARDIAN_SCAN_MAX_DAYS = 31;
|
|
139
|
+
var CODEX_GUARDIAN_SCAN_MAX_FILES = 2e3;
|
|
140
|
+
var CODEX_ROLLOUT_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
141
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
142
|
+
var TURN_CONTEXT_LINE_MAX_BYTES = 1024 * 1024;
|
|
143
|
+
var MTIME_SLACK_MS = 2e3;
|
|
144
|
+
var NEWLINE = 10;
|
|
145
|
+
var TURN_CONTEXT_MARKER = '"turn_context"';
|
|
146
|
+
var ROLLOUT_FILE = /^rollout-.*\.jsonl$/;
|
|
147
|
+
var DATE_SEGMENTS = /^(\d{4})\/(\d{2})\/(\d{2})$/;
|
|
148
|
+
var cacheKey = (...parts) => createHash("sha256").update(parts.join("\n"), "utf-8").digest("hex").slice(0, 32);
|
|
149
|
+
var turnCacheDir = () => join(tmpdir(), `pushary-codex-turns-${userScope()}`);
|
|
150
|
+
var guardianCursorDir = () => join(tmpdir(), `pushary-codex-guardian-${userScope()}`);
|
|
151
|
+
var readJsonRecord = (path) => {
|
|
141
152
|
try {
|
|
142
|
-
const parsed = JSON.parse(
|
|
143
|
-
|
|
144
|
-
if (parsed.kind === "handled") return { kind: "handled" };
|
|
145
|
-
if (parsed.kind === "denied") {
|
|
146
|
-
return { kind: "denied", reason: typeof parsed.reason === "string" ? parsed.reason : "" };
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
153
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
154
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
149
155
|
} catch {
|
|
150
156
|
return null;
|
|
151
157
|
}
|
|
@@ -155,7 +161,7 @@ var sweepExpired = (dir, nowMs) => {
|
|
|
155
161
|
for (const name of readdirSync(dir)) {
|
|
156
162
|
const path = join(dir, name);
|
|
157
163
|
try {
|
|
158
|
-
if (nowMs - statSync(path).mtimeMs >=
|
|
164
|
+
if (nowMs - statSync(path).mtimeMs >= CODEX_ROLLOUT_CACHE_TTL_MS) rmSync(path, { force: true });
|
|
159
165
|
} catch {
|
|
160
166
|
continue;
|
|
161
167
|
}
|
|
@@ -164,27 +170,365 @@ var sweepExpired = (dir, nowMs) => {
|
|
|
164
170
|
return;
|
|
165
171
|
}
|
|
166
172
|
};
|
|
167
|
-
var
|
|
168
|
-
if (!key) return;
|
|
173
|
+
var writeJsonRecord = (dir, name, record) => {
|
|
169
174
|
try {
|
|
170
|
-
const dir = decisionDir();
|
|
171
175
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
172
176
|
else sweepExpired(dir, Date.now());
|
|
173
|
-
|
|
177
|
+
writeFileAtomic(join(dir, name), JSON.stringify(record), 384);
|
|
178
|
+
return true;
|
|
174
179
|
} catch {
|
|
175
|
-
return;
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var readBytes = (path, offset, length) => {
|
|
184
|
+
let fd;
|
|
185
|
+
try {
|
|
186
|
+
fd = openSync(path, "r");
|
|
187
|
+
const buffer = Buffer.alloc(length);
|
|
188
|
+
const read = readSync(fd, buffer, 0, length, offset);
|
|
189
|
+
return read === length ? buffer : null;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
} finally {
|
|
193
|
+
if (fd !== void 0) closeSync(fd);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var locate = (region, base, from, to, read, marker) => {
|
|
197
|
+
if (to <= from) return null;
|
|
198
|
+
const text = region.toString("utf-8", from, to);
|
|
199
|
+
if (!text.includes(marker)) return null;
|
|
200
|
+
const value = read(text);
|
|
201
|
+
return value ? { value, offset: base + from, length: to - from } : null;
|
|
202
|
+
};
|
|
203
|
+
var findLastRolloutLine = (path, maxBytes, read, marker) => {
|
|
204
|
+
let fd;
|
|
205
|
+
try {
|
|
206
|
+
fd = openSync(path, "r");
|
|
207
|
+
const size = fstatSync(fd).size;
|
|
208
|
+
const floor = Math.max(0, size - maxBytes);
|
|
209
|
+
let carry = Buffer.alloc(0);
|
|
210
|
+
let position = size;
|
|
211
|
+
while (position > floor) {
|
|
212
|
+
const start = Math.max(floor, position - READ_CHUNK_BYTES);
|
|
213
|
+
const chunk = Buffer.alloc(position - start);
|
|
214
|
+
if (readSync(fd, chunk, 0, chunk.length, start) !== chunk.length) return null;
|
|
215
|
+
const region = carry.length > 0 ? Buffer.concat([chunk, carry]) : chunk;
|
|
216
|
+
let lineEnd = region.length;
|
|
217
|
+
let newline = region.lastIndexOf(NEWLINE, lineEnd - 1);
|
|
218
|
+
while (newline >= 0) {
|
|
219
|
+
const found = locate(region, start, newline + 1, lineEnd, read, marker);
|
|
220
|
+
if (found) return found;
|
|
221
|
+
lineEnd = newline;
|
|
222
|
+
newline = lineEnd > 0 ? region.lastIndexOf(NEWLINE, lineEnd - 1) : -1;
|
|
223
|
+
}
|
|
224
|
+
if (start === 0) return locate(region, 0, 0, lineEnd, read, marker);
|
|
225
|
+
carry = region.subarray(0, lineEnd);
|
|
226
|
+
position = start;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
} finally {
|
|
232
|
+
if (fd !== void 0) closeSync(fd);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var isWholeLineAt = (path, offset, length) => {
|
|
236
|
+
const before = offset > 0 ? 1 : 0;
|
|
237
|
+
const bytes = readBytes(path, offset - before, length + before + 1);
|
|
238
|
+
if (!bytes) return null;
|
|
239
|
+
if (before === 1 && bytes[0] !== NEWLINE) return null;
|
|
240
|
+
if (bytes[bytes.length - 1] !== NEWLINE) return null;
|
|
241
|
+
return bytes.toString("utf-8", before, before + length);
|
|
242
|
+
};
|
|
243
|
+
var cachedTurn = (file, transcriptPath, turnId) => {
|
|
244
|
+
const cached = readJsonRecord(file);
|
|
245
|
+
if (!cached || cached.transcriptPath !== transcriptPath || cached.turnId !== turnId) return null;
|
|
246
|
+
const { offset, length } = cached;
|
|
247
|
+
if (typeof offset !== "number" || typeof length !== "number") return null;
|
|
248
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length <= 0) return null;
|
|
249
|
+
if (length > TURN_CONTEXT_LINE_MAX_BYTES) return null;
|
|
250
|
+
const line = isWholeLineAt(transcriptPath, offset, length);
|
|
251
|
+
const context = line === null ? null : readCodexTurnContext(line);
|
|
252
|
+
return context?.turnId === turnId ? context : null;
|
|
253
|
+
};
|
|
254
|
+
var readCodexTurn = (call, limits = {}) => {
|
|
255
|
+
const { transcriptPath, sessionId, turnId } = call;
|
|
256
|
+
if (!transcriptPath || !isAbsolute(transcriptPath) || !sessionId || !turnId) return null;
|
|
257
|
+
const name = cacheKey(sessionId, turnId);
|
|
258
|
+
const cached = cachedTurn(join(turnCacheDir(), name), transcriptPath, turnId);
|
|
259
|
+
if (cached) return cached;
|
|
260
|
+
const found = findLastRolloutLine(
|
|
261
|
+
transcriptPath,
|
|
262
|
+
limits.tailMaxBytes ?? CODEX_ROLLOUT_TAIL_MAX_BYTES,
|
|
263
|
+
readCodexTurnContext,
|
|
264
|
+
TURN_CONTEXT_MARKER
|
|
265
|
+
);
|
|
266
|
+
if (!found || found.value.turnId !== turnId) return null;
|
|
267
|
+
writeJsonRecord(turnCacheDir(), name, { transcriptPath, turnId, offset: found.offset, length: found.length });
|
|
268
|
+
return found.value;
|
|
269
|
+
};
|
|
270
|
+
var readFirstLine = (path, maxBytes) => {
|
|
271
|
+
let fd;
|
|
272
|
+
try {
|
|
273
|
+
fd = openSync(path, "r");
|
|
274
|
+
let collected = Buffer.alloc(0);
|
|
275
|
+
while (collected.length < maxBytes) {
|
|
276
|
+
const chunk = Buffer.alloc(Math.min(READ_CHUNK_BYTES, maxBytes - collected.length));
|
|
277
|
+
const read = readSync(fd, chunk, 0, chunk.length, collected.length);
|
|
278
|
+
if (read === 0) return null;
|
|
279
|
+
const newline = chunk.subarray(0, read).indexOf(NEWLINE);
|
|
280
|
+
if (newline >= 0) return Buffer.concat([collected, chunk.subarray(0, newline)]).toString("utf-8");
|
|
281
|
+
collected = Buffer.concat([collected, chunk.subarray(0, read)]);
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
} finally {
|
|
287
|
+
if (fd !== void 0) closeSync(fd);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var sessionsLayoutOf = (transcriptPath) => {
|
|
291
|
+
const dayDir = dirname(transcriptPath);
|
|
292
|
+
const root = dirname(dirname(dirname(dayDir)));
|
|
293
|
+
const match = DATE_SEGMENTS.exec(relative(root, dayDir).split(/[\\/]/).join("/"));
|
|
294
|
+
if (!match) return null;
|
|
295
|
+
return { root, year: Number(match[1]), month: Number(match[2]), day: Number(match[3]) };
|
|
296
|
+
};
|
|
297
|
+
var isInside = (root, path) => {
|
|
298
|
+
const within = relative(root, path);
|
|
299
|
+
return within.length > 0 && !within.startsWith("..") && !isAbsolute(within);
|
|
300
|
+
};
|
|
301
|
+
var isGuardianRollout = (path, sessionId, headMaxBytes) => {
|
|
302
|
+
const head = readFirstLine(path, headMaxBytes);
|
|
303
|
+
return head !== null && isCodexGuardianRolloutHead(head, sessionId);
|
|
304
|
+
};
|
|
305
|
+
var pad = (value) => String(value).padStart(2, "0");
|
|
306
|
+
var dayDirectories = (layout, now) => {
|
|
307
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
308
|
+
const directories = [];
|
|
309
|
+
for (let offset = 0; offset <= CODEX_GUARDIAN_SCAN_MAX_DAYS; offset++) {
|
|
310
|
+
const date = new Date(layout.year, layout.month - 1, layout.day + offset);
|
|
311
|
+
if (offset > 0 && date.getTime() > today) break;
|
|
312
|
+
directories.push(join(layout.root, String(date.getFullYear()), pad(date.getMonth() + 1), pad(date.getDate())));
|
|
313
|
+
}
|
|
314
|
+
return directories;
|
|
315
|
+
};
|
|
316
|
+
var createdAtMs = (transcriptPath, turnStartedAt) => {
|
|
317
|
+
try {
|
|
318
|
+
const birth = statSync(transcriptPath).birthtimeMs;
|
|
319
|
+
return birth > 0 ? birth : Date.parse(turnStartedAt);
|
|
320
|
+
} catch {
|
|
321
|
+
return Date.parse(turnStartedAt);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var discoverGuardianRollout = (call, layout, now, limits) => {
|
|
325
|
+
const sinceMs = createdAtMs(call.transcriptPath, call.turnStartedAt) - MTIME_SLACK_MS;
|
|
326
|
+
if (!Number.isFinite(sinceMs)) return null;
|
|
327
|
+
const headMaxBytes = limits.headMaxBytes ?? CODEX_ROLLOUT_HEAD_MAX_BYTES;
|
|
328
|
+
const maxFiles = limits.maxFiles ?? CODEX_GUARDIAN_SCAN_MAX_FILES;
|
|
329
|
+
let examined = 0;
|
|
330
|
+
let best = null;
|
|
331
|
+
for (const directory of dayDirectories(layout, now)) {
|
|
332
|
+
let names;
|
|
333
|
+
try {
|
|
334
|
+
names = readdirSync(directory).sort();
|
|
335
|
+
} catch {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
for (const name of names) {
|
|
339
|
+
if (!ROLLOUT_FILE.test(name)) continue;
|
|
340
|
+
const path = join(directory, name);
|
|
341
|
+
if (path === call.transcriptPath) continue;
|
|
342
|
+
if (++examined > maxFiles) return best?.path ?? null;
|
|
343
|
+
let mtimeMs;
|
|
344
|
+
try {
|
|
345
|
+
const stat = statSync(path);
|
|
346
|
+
if (!stat.isFile()) continue;
|
|
347
|
+
mtimeMs = stat.mtimeMs;
|
|
348
|
+
} catch {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (mtimeMs < sinceMs || best && mtimeMs <= best.mtimeMs) continue;
|
|
352
|
+
if (isGuardianRollout(path, call.sessionId, headMaxBytes)) best = { path, mtimeMs };
|
|
353
|
+
}
|
|
176
354
|
}
|
|
355
|
+
return best?.path ?? null;
|
|
356
|
+
};
|
|
357
|
+
var readGuardianCursor = (file, sessionId) => {
|
|
358
|
+
const cursor = readJsonRecord(file);
|
|
359
|
+
if (!cursor || cursor.sessionId !== sessionId || typeof cursor.path !== "string") return null;
|
|
360
|
+
const offset = cursor.offset;
|
|
361
|
+
if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0) return null;
|
|
362
|
+
return { path: cursor.path, offset };
|
|
177
363
|
};
|
|
178
|
-
var
|
|
179
|
-
|
|
180
|
-
const path = decisionFile(key);
|
|
364
|
+
var readCompleteLines = (path, from, maxBytes) => {
|
|
365
|
+
let fd;
|
|
181
366
|
try {
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
367
|
+
fd = openSync(path, "r");
|
|
368
|
+
const size = fstatSync(fd).size;
|
|
369
|
+
let start = from > size ? 0 : from;
|
|
370
|
+
const skipsPartial = size - start > maxBytes;
|
|
371
|
+
if (skipsPartial) start = size - maxBytes;
|
|
372
|
+
const buffer = Buffer.alloc(size - start);
|
|
373
|
+
if (buffer.length > 0 && readSync(fd, buffer, 0, buffer.length, start) !== buffer.length) return null;
|
|
374
|
+
let lineStart = 0;
|
|
375
|
+
if (skipsPartial) {
|
|
376
|
+
const newline = buffer.indexOf(NEWLINE);
|
|
377
|
+
if (newline < 0) return { lines: [], end: from };
|
|
378
|
+
lineStart = newline + 1;
|
|
379
|
+
}
|
|
380
|
+
const lines = [];
|
|
381
|
+
for (let newline = buffer.indexOf(NEWLINE, lineStart); newline >= 0; newline = buffer.indexOf(NEWLINE, lineStart)) {
|
|
382
|
+
lines.push({ offset: start + lineStart, text: buffer.toString("utf-8", lineStart, newline) });
|
|
383
|
+
lineStart = newline + 1;
|
|
384
|
+
}
|
|
385
|
+
return { lines, end: start + lineStart };
|
|
186
386
|
} catch {
|
|
187
387
|
return null;
|
|
388
|
+
} finally {
|
|
389
|
+
if (fd !== void 0) closeSync(fd);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
var takeCodexReviewerDenials = (call, options = {}) => {
|
|
393
|
+
const { transcriptPath, sessionId, turnStartedAt } = call;
|
|
394
|
+
if (!isAbsolute(transcriptPath) || !sessionId || !Number.isFinite(Date.parse(turnStartedAt))) return null;
|
|
395
|
+
const layout = sessionsLayoutOf(transcriptPath);
|
|
396
|
+
if (!layout) return null;
|
|
397
|
+
const headMaxBytes = options.headMaxBytes ?? CODEX_ROLLOUT_HEAD_MAX_BYTES;
|
|
398
|
+
const cursorName = cacheKey(sessionId);
|
|
399
|
+
const cursor = readGuardianCursor(join(guardianCursorDir(), cursorName), sessionId);
|
|
400
|
+
const remembered = cursor && isInside(layout.root, cursor.path) && cursor.path !== transcriptPath && isGuardianRollout(cursor.path, sessionId, headMaxBytes) ? cursor : null;
|
|
401
|
+
const guardianPath = remembered?.path ?? discoverGuardianRollout(call, layout, options.now ?? /* @__PURE__ */ new Date(), options);
|
|
402
|
+
if (!guardianPath) return null;
|
|
403
|
+
const from = remembered?.offset ?? 0;
|
|
404
|
+
const read = readCompleteLines(guardianPath, from, options.guardianMaxBytes ?? CODEX_GUARDIAN_READ_MAX_BYTES);
|
|
405
|
+
if (!read) return null;
|
|
406
|
+
const scan = scanCodexGuardianReviews(read.lines);
|
|
407
|
+
const next = scan.pendingOffset ?? read.end;
|
|
408
|
+
const denials = codexReviewerDenialsSince(scan.reviews, turnStartedAt);
|
|
409
|
+
if (!remembered || next !== from) {
|
|
410
|
+
const saved = writeJsonRecord(guardianCursorDir(), cursorName, { sessionId, path: guardianPath, offset: next });
|
|
411
|
+
if (!saved && denials.length > 0) return null;
|
|
412
|
+
}
|
|
413
|
+
return { guardianPath, denials };
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/codex-reviewer.ts
|
|
417
|
+
var START_MS = Date.now();
|
|
418
|
+
var CLAIM_TIMEOUT_MS = 4e3;
|
|
419
|
+
var GATE_TIMEOUT_MS = 15e3;
|
|
420
|
+
var CANCEL_TIMEOUT_MS = 4e3;
|
|
421
|
+
var CODEX_RESCUE_STOP_RESERVE_MS = 2e4;
|
|
422
|
+
var codexRescueEndsAt = (startMs, holdBudgetSeconds) => startMs + hookHoldWindowMs("codex", holdBudgetSeconds) - CODEX_RESCUE_STOP_RESERVE_MS;
|
|
423
|
+
var codexRescueDeadline = (startMs, deadlineSeconds, nowMs, holdBudgetSeconds) => Math.min(hookWaitDeadline(startMs, deadlineSeconds, "codex", nowMs), codexRescueEndsAt(startMs, holdBudgetSeconds));
|
|
424
|
+
var codexApprovalsReviewer = (call) => readCodexTurn({ transcriptPath: call.transcript_path, sessionId: call.session_id, turnId: call.turn_id })?.approvalsReviewer;
|
|
425
|
+
var claimCodexReviewerRetry = async (apiKey, call) => {
|
|
426
|
+
if (!call.tool_name || !call.tool_input) return false;
|
|
427
|
+
const fingerprint = blockedActionFingerprint({
|
|
428
|
+
source: "codex",
|
|
429
|
+
sessionId: call.session_id,
|
|
430
|
+
toolName: call.tool_name,
|
|
431
|
+
toolInput: call.tool_input,
|
|
432
|
+
cwd: call.cwd
|
|
433
|
+
});
|
|
434
|
+
if (!fingerprint || !hasBlockedActionHint(fingerprint)) return false;
|
|
435
|
+
const granted = await claimBlockedAction(apiKey, {
|
|
436
|
+
v: GATE_WIRE,
|
|
437
|
+
source: "codex",
|
|
438
|
+
event: "PermissionRequest",
|
|
439
|
+
toolName: call.tool_name,
|
|
440
|
+
toolInputs: [call.tool_input],
|
|
441
|
+
sessionId: call.session_id,
|
|
442
|
+
machineId: getMachineId(),
|
|
443
|
+
cwd: call.cwd,
|
|
444
|
+
repoKey: repoKeyFor(call.cwd),
|
|
445
|
+
permissionMode: call.permission_mode
|
|
446
|
+
}, CLAIM_TIMEOUT_MS);
|
|
447
|
+
if (granted !== null) forgetBlockedActionHint(fingerprint);
|
|
448
|
+
return granted === true;
|
|
449
|
+
};
|
|
450
|
+
var blockReasonOf = (rationale) => rationale?.trim().slice(0, GATE_BLOCK_REASON_MAX_LENGTH).trim() || void 0;
|
|
451
|
+
var reviewToolUseId = (guardianPath, denial) => `codex-review-${createHash2("sha256").update(canonicalJson([guardianPath, denial.offset, denial.plannedAction]), "utf-8").digest("hex").slice(0, 40)}`;
|
|
452
|
+
var isRescueApproval = (output) => {
|
|
453
|
+
if (!output) return false;
|
|
454
|
+
try {
|
|
455
|
+
const parsed = JSON.parse(output);
|
|
456
|
+
return parsed?.decision === "block" && typeof parsed.reason === "string" && parsed.reason.length > 0;
|
|
457
|
+
} catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
var isAffirmativeAnswer = (answer) => answer.answered && !isDecisionStopped(answer) && answer.value === "yes";
|
|
462
|
+
var approveOnce = (fingerprint, output) => recordBlockedActionHint(fingerprint) ? output : void 0;
|
|
463
|
+
var rescueRequest = (call, sessionId, guardianPath, denial) => {
|
|
464
|
+
const cwd = denial.cwd ?? call.cwd;
|
|
465
|
+
const blockReason = blockReasonOf(denial.rationale);
|
|
466
|
+
return {
|
|
467
|
+
v: GATE_WIRE,
|
|
468
|
+
source: "codex",
|
|
469
|
+
event: "Stop",
|
|
470
|
+
toolName: CODEX_RESCUE_TOOL_NAME,
|
|
471
|
+
toolInputs: [{ command: denial.command }],
|
|
472
|
+
sessionId,
|
|
473
|
+
toolUseId: reviewToolUseId(guardianPath, denial),
|
|
474
|
+
machineId: getMachineId(),
|
|
475
|
+
cwd,
|
|
476
|
+
...call.cwd ? { sessionCwd: call.cwd } : {},
|
|
477
|
+
repoKey: repoKeyFor(cwd),
|
|
478
|
+
permissionMode: call.permission_mode,
|
|
479
|
+
createDecision: true,
|
|
480
|
+
localSurfaceAvailable: false,
|
|
481
|
+
...blockReason ? { blockReason } : {},
|
|
482
|
+
approvalsReviewer: "auto_review",
|
|
483
|
+
blockedBy: "auto_review"
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
var askAboutDenial = async (apiKey, call, sessionId, guardianPath, denial, holdBudgetSeconds) => {
|
|
487
|
+
const request = rescueRequest(call, sessionId, guardianPath, denial);
|
|
488
|
+
const fingerprint = blockedActionFingerprint({
|
|
489
|
+
source: "codex",
|
|
490
|
+
sessionId,
|
|
491
|
+
toolName: request.toolName,
|
|
492
|
+
toolInput: request.toolInputs[0],
|
|
493
|
+
cwd: request.cwd
|
|
494
|
+
});
|
|
495
|
+
if (!fingerprint) return void 0;
|
|
496
|
+
const reply = await requestGate(apiKey, request, GATE_TIMEOUT_MS);
|
|
497
|
+
if (!reply) return void 0;
|
|
498
|
+
if (reply.kind === "no_opinion") return isRescueApproval(reply.output) ? approveOnce(fingerprint, reply.output) : void 0;
|
|
499
|
+
if (reply.kind !== "ask" || !reply.correlationId || reply.deadlineSeconds === void 0) return void 0;
|
|
500
|
+
if (reply.noDevices) {
|
|
501
|
+
await cancelQuestion(apiKey, reply.correlationId, AbortSignal.timeout(CANCEL_TIMEOUT_MS)).catch(() => void 0);
|
|
502
|
+
return void 0;
|
|
503
|
+
}
|
|
504
|
+
const deadline = codexRescueDeadline(START_MS, reply.deadlineSeconds, Date.now(), holdBudgetSeconds);
|
|
505
|
+
const answer = await pollForAnswer(apiKey, reply.correlationId, deadline);
|
|
506
|
+
if (!isAffirmativeAnswer(answer)) return void 0;
|
|
507
|
+
const approve = reply.settlementOutputs?.approve;
|
|
508
|
+
return approveOnce(
|
|
509
|
+
fingerprint,
|
|
510
|
+
isRescueApproval(approve) ? approve : JSON.stringify(codexStopRescueOutput("allow", denial.command))
|
|
511
|
+
);
|
|
512
|
+
};
|
|
513
|
+
var rescueCodexReviewerDenial = async (call, holdBudgetSeconds) => {
|
|
514
|
+
let apiKey;
|
|
515
|
+
try {
|
|
516
|
+
apiKey = getApiKey();
|
|
517
|
+
} catch {
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const { session_id: sessionId, transcript_path: transcriptPath } = call;
|
|
522
|
+
if (!sessionId || !transcriptPath) return void 0;
|
|
523
|
+
if (holdBudgetSeconds === void 0 || codexRescueEndsAt(START_MS, holdBudgetSeconds) - Date.now() <= GATE_TIMEOUT_MS) return void 0;
|
|
524
|
+
const turn = readCodexTurn({ transcriptPath, sessionId, turnId: call.turn_id });
|
|
525
|
+
if (turn?.approvalsReviewer !== "auto_review" || !turn.startedAt) return void 0;
|
|
526
|
+
const taken = takeCodexReviewerDenials({ transcriptPath, sessionId, turnStartedAt: turn.startedAt });
|
|
527
|
+
const denial = taken?.denials.at(-1);
|
|
528
|
+
if (!taken || !denial) return void 0;
|
|
529
|
+
return await askAboutDenial(apiKey, call, sessionId, taken.guardianPath, denial, holdBudgetSeconds);
|
|
530
|
+
} catch {
|
|
531
|
+
return void 0;
|
|
188
532
|
}
|
|
189
533
|
};
|
|
190
534
|
|
|
@@ -195,7 +539,7 @@ var callKeyFor = (input) => codexCallKey({
|
|
|
195
539
|
toolName: input.tool_name,
|
|
196
540
|
toolInput: input.tool_input
|
|
197
541
|
});
|
|
198
|
-
var
|
|
542
|
+
var START_MS2 = Date.now();
|
|
199
543
|
var agentNameFor = (input) => `Codex - ${basename(input.cwd ?? process.cwd())}`;
|
|
200
544
|
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");
|
|
201
545
|
var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
|
|
@@ -221,8 +565,8 @@ var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
|
|
|
221
565
|
toolUseId: input.tool_use_id
|
|
222
566
|
}).catch(() => ({ correlationId: "", status: "unavailable" }));
|
|
223
567
|
const now = Date.now();
|
|
224
|
-
const deadline = hookWaitDeadline(
|
|
225
|
-
const clampCut = hookWaitClamped(
|
|
568
|
+
const deadline = hookWaitDeadline(START_MS2, waitSeconds, "codex", now);
|
|
569
|
+
const clampCut = hookWaitClamped(START_MS2, waitSeconds, "codex", now);
|
|
226
570
|
const answer = await waitForQuestion(apiKey, result, deadline);
|
|
227
571
|
return { answer, localHandoff: !answer.answered && isLocalQuestionHandoff(result), clampCut };
|
|
228
572
|
};
|
|
@@ -258,23 +602,21 @@ var codexGate = async (apiKey, input) => {
|
|
|
258
602
|
});
|
|
259
603
|
return { verdict, lookup };
|
|
260
604
|
};
|
|
261
|
-
var fromRecordedDecision = (decision) => {
|
|
262
|
-
if (decision.kind === "approved") return codexAllow();
|
|
263
|
-
if (decision.kind === "denied") return codexDeny(decision.reason);
|
|
264
|
-
return codexPass();
|
|
265
|
-
};
|
|
266
605
|
var PUSHARY_OWN_TOOL_PREFIX = "mcp__pushary__";
|
|
267
606
|
var isPusharyOwnTool = (toolName) => typeof toolName === "string" && toolName.startsWith(PUSHARY_OWN_TOOL_PREFIX);
|
|
268
607
|
var decidePermissionRequest = async (input) => {
|
|
269
608
|
if (isPusharyOwnTool(input.tool_name)) return codexPass();
|
|
270
|
-
const recorded = consumeCodexCallDecision(callKeyFor(input));
|
|
271
|
-
if (recorded) return fromRecordedDecision(recorded);
|
|
272
609
|
try {
|
|
273
610
|
const apiKey = getApiKey();
|
|
274
611
|
const { verdict, lookup } = await codexGate(apiKey, input);
|
|
275
612
|
if (verdict.kind === "kill") return codexDeny(KILL_REASON);
|
|
276
|
-
if (verdict.kind === "allow") return codexAllow();
|
|
277
613
|
if (verdict.kind === "deny") return codexDeny(verdict.reason);
|
|
614
|
+
const recorded = consumeCodexCallDecision(callKeyFor(input));
|
|
615
|
+
if (recorded?.kind === "denied") return codexDeny(recorded.reason);
|
|
616
|
+
if (recorded?.kind === "handled") return codexPass();
|
|
617
|
+
if (await claimCodexReviewerRetry(apiKey, input)) return codexAllow();
|
|
618
|
+
if (codexApprovalsReviewer(input) === "auto_review") return codexPass();
|
|
619
|
+
if (verdict.kind === "allow") return codexAllow();
|
|
278
620
|
if (verdict.kind === "defer") return codexPass();
|
|
279
621
|
const toolPolicy = verdict.policy;
|
|
280
622
|
if (toolPolicy.mode === "terminal_only") return codexPass();
|
|
@@ -283,7 +625,8 @@ var decidePermissionRequest = async (input) => {
|
|
|
283
625
|
return codexPass();
|
|
284
626
|
}
|
|
285
627
|
const waitSeconds = toolPolicy.mode === "push_first" ? toolPolicy.pushFirstSeconds : effectiveWaitSeconds(toolPolicy.timeoutAction, toolPolicy.timeoutSeconds, "codex");
|
|
286
|
-
const {
|
|
628
|
+
const asked = recorded ? { ...input, tool_use_id: recorded.toolUseId } : input;
|
|
629
|
+
const { answer, localHandoff, clampCut } = await pushQuestion(apiKey, asked, lookup, waitSeconds, toolPolicy.mode);
|
|
287
630
|
if (isDecisionStopped(answer)) return codexDeny(DECISION_STOP_REASON);
|
|
288
631
|
if (localHandoff) return codexPass();
|
|
289
632
|
if (answer.answered) {
|
|
@@ -305,6 +648,10 @@ var recordPreToolUse = (key, outcome) => {
|
|
|
305
648
|
);
|
|
306
649
|
return outcome;
|
|
307
650
|
};
|
|
651
|
+
var recordApproval = (key, input) => {
|
|
652
|
+
if (input.tool_use_id) recordCodexCallDecision(key, { kind: "approved", toolUseId: input.tool_use_id });
|
|
653
|
+
return codexPass();
|
|
654
|
+
};
|
|
308
655
|
var permissionRequestNeverFires = (permissionMode) => permissionMode === "bypassPermissions";
|
|
309
656
|
var decidePushFirstBeforeTheTool = async (apiKey, input, lookup, pushFirstSeconds, callKey) => {
|
|
310
657
|
let pushed;
|
|
@@ -320,10 +667,7 @@ var decidePushFirstBeforeTheTool = async (apiKey, input, lookup, pushFirstSecond
|
|
|
320
667
|
}
|
|
321
668
|
if (answer.answered) {
|
|
322
669
|
if (isDeferAnswer(answer.value)) return recordPreToolUse(callKey, codexPass());
|
|
323
|
-
if (answer.value === "yes")
|
|
324
|
-
recordCodexCallDecision(callKey, { kind: "approved" });
|
|
325
|
-
return codexPass();
|
|
326
|
-
}
|
|
670
|
+
if (answer.value === "yes") return recordApproval(callKey, input);
|
|
327
671
|
return recordPreToolUse(callKey, codexDeny(denyReasonFrom(answer.value, answer.note)));
|
|
328
672
|
}
|
|
329
673
|
return recordPreToolUse(callKey, codexPass());
|
|
@@ -353,6 +697,7 @@ var decidePreToolUse = async (input) => {
|
|
|
353
697
|
if (verdict.kind === "kill") return codexDeny(KILL_REASON);
|
|
354
698
|
if (verdict.kind === "allow") return codexPass();
|
|
355
699
|
if (verdict.kind === "deny") return codexDeny(verdict.reason);
|
|
700
|
+
if (codexApprovalsReviewer(input) === "auto_review") return codexPass();
|
|
356
701
|
if (defersToNativeMode(input.permission_mode, input.tool_name ?? "")) return codexPass();
|
|
357
702
|
if (verdict.kind === "defer") return codexPass();
|
|
358
703
|
const toolPolicy = verdict.policy;
|
|
@@ -369,10 +714,7 @@ var decidePreToolUse = async (input) => {
|
|
|
369
714
|
if (localHandoff) return recordPreToolUse(callKey, codexPass());
|
|
370
715
|
if (answer.answered) {
|
|
371
716
|
if (isDeferAnswer(answer.value)) return recordPreToolUse(callKey, codexPass());
|
|
372
|
-
if (answer.value === "yes")
|
|
373
|
-
recordCodexCallDecision(callKey, { kind: "approved" });
|
|
374
|
-
return codexPass();
|
|
375
|
-
}
|
|
717
|
+
if (answer.value === "yes") return recordApproval(callKey, input);
|
|
376
718
|
return recordPreToolUse(callKey, codexDeny(denyReasonFrom(answer.value, answer.note)));
|
|
377
719
|
}
|
|
378
720
|
if (clampCut) return recordPreToolUse(callKey, codexPass());
|
|
@@ -448,12 +790,18 @@ var main = async () => {
|
|
|
448
790
|
session_id: input.session_id
|
|
449
791
|
}, CODEX_AGENT));
|
|
450
792
|
break;
|
|
451
|
-
case "Stop":
|
|
793
|
+
case "Stop": {
|
|
794
|
+
const rescued = await rescueCodexReviewerDenial(input, takeHookHoldBudget());
|
|
795
|
+
if (rescued) {
|
|
796
|
+
process.stdout.write(rescued);
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
452
799
|
await handleStop({
|
|
453
800
|
cwd: input.cwd,
|
|
454
801
|
session_id: input.session_id
|
|
455
802
|
}, CODEX_AGENT);
|
|
456
803
|
break;
|
|
804
|
+
}
|
|
457
805
|
case "SessionStart":
|
|
458
806
|
await reportSessionStart(input);
|
|
459
807
|
break;
|