@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
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
AGENT_LABELS_BY_TYPE,
|
|
4
4
|
DECISION_LINE_MAX,
|
|
5
5
|
FILE_TARGET_TOOLS,
|
|
6
|
+
GATE_MAX_TOOL_INPUTS,
|
|
6
7
|
redactSecrets,
|
|
7
8
|
redactSecretsDeep
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-HUZW6SEO.js";
|
|
9
10
|
|
|
10
11
|
// ../hook-mapping/src/describe.ts
|
|
11
12
|
import { isAbsolute, relative, resolve } from "path";
|
|
@@ -173,6 +174,90 @@ var OPENCODE_TOOL_NAMES = {
|
|
|
173
174
|
websearch: "WebSearch",
|
|
174
175
|
task: "Task"
|
|
175
176
|
};
|
|
177
|
+
var canonicalGateInputs = (source, toolName, toolInputs, cwd) => {
|
|
178
|
+
if (source === "codex" && toolName === "apply_patch" || source === "vscode" && ["apply_patch", "applyPatch", "copilot_applyPatch"].includes(toolName)) {
|
|
179
|
+
const paths = [...new Set(toolInputs.flatMap((input) => parseApplyPatchFiles(input.command ?? input.input ?? input.patch)))];
|
|
180
|
+
if (paths.length > GATE_MAX_TOOL_INPUTS) return null;
|
|
181
|
+
return {
|
|
182
|
+
toolName: "Edit",
|
|
183
|
+
toolInputs: paths.length === 0 ? toolInputs.map((input) => ({ file_path: input.command })) : paths.map((path) => ({ file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path }))
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (source === "opencode") {
|
|
187
|
+
const canonical2 = OPENCODE_TOOL_NAMES[toolName] ?? toolName;
|
|
188
|
+
return {
|
|
189
|
+
toolName: canonical2,
|
|
190
|
+
toolInputs: toolInputs.map((input) => {
|
|
191
|
+
const path = input.file_path;
|
|
192
|
+
return typeof path === "string" && path.length > 0 && cwd && !isAbsolute(path) ? { ...input, file_path: resolve(cwd, path) } : input;
|
|
193
|
+
})
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (source === "vscode" || source === "cursor") {
|
|
197
|
+
if (source === "vscode" && toolName.startsWith("mcp_pushary_")) return { toolName: `mcp__pushary__${toolName.slice("mcp_pushary_".length)}`, toolInputs };
|
|
198
|
+
const alias = toolName.replace(/^copilot_/, "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
199
|
+
const shells = [
|
|
200
|
+
"runterminalcommand",
|
|
201
|
+
"runinterminal",
|
|
202
|
+
"runcommand",
|
|
203
|
+
"terminalcommand",
|
|
204
|
+
"executecommand",
|
|
205
|
+
"runinterminalcommand",
|
|
206
|
+
"bash",
|
|
207
|
+
"shell",
|
|
208
|
+
"terminal",
|
|
209
|
+
"sendtoterminal",
|
|
210
|
+
"powershell",
|
|
211
|
+
"localshell"
|
|
212
|
+
];
|
|
213
|
+
const canonical2 = shells.includes(alias) ? "Bash" : ["createfile", "write", "createdirectory", "createnewjupyternotebook"].includes(alias) ? "Write" : ["replacestringinfile", "multireplacestringinfile", "replacestring", "multireplacestring", "inserteditintofile", "insertedit", "edit", "editfile", "editfiles", "editnotebookfile", "editnotebook"].includes(alias) ? "Edit" : [
|
|
214
|
+
"readfile",
|
|
215
|
+
"viewimage",
|
|
216
|
+
"listdir",
|
|
217
|
+
"listdirectory",
|
|
218
|
+
"filesearch",
|
|
219
|
+
"findfiles",
|
|
220
|
+
"grepsearch",
|
|
221
|
+
"findtextinfiles",
|
|
222
|
+
"semanticsearch",
|
|
223
|
+
"searchcodebase",
|
|
224
|
+
"searchworkspacesymbols",
|
|
225
|
+
"geterrors",
|
|
226
|
+
"getchangedfiles",
|
|
227
|
+
"readprojectstructure",
|
|
228
|
+
"getnotebooksummary",
|
|
229
|
+
"readnotebookcelloutput",
|
|
230
|
+
"testsearch",
|
|
231
|
+
"findtestfiles",
|
|
232
|
+
"testfailure",
|
|
233
|
+
"getvscodeapi",
|
|
234
|
+
"getterminaloutput",
|
|
235
|
+
"gettaskoutput",
|
|
236
|
+
"terminalselection",
|
|
237
|
+
"terminallastcommand"
|
|
238
|
+
].includes(alias) ? "Read" : toolName;
|
|
239
|
+
const inputs = toolInputs.flatMap((input) => ["multireplacestringinfile", "multireplacestring"].includes(alias) && Array.isArray(input.replacements) ? input.replacements.filter((item) => !!item && typeof item === "object" && !Array.isArray(item)) : alias === "editfiles" && Array.isArray(input.files) ? input.files.map((file) => typeof file === "string" ? { file_path: file } : file).filter((file) => !!file && typeof file === "object" && !Array.isArray(file)) : [input]);
|
|
240
|
+
if (inputs.length > GATE_MAX_TOOL_INPUTS) return null;
|
|
241
|
+
return { toolName: canonical2, toolInputs: inputs.map((input) => {
|
|
242
|
+
if (canonical2 === "Bash") return { ...input, command: [input.command, input.commandLine, input.command_line, input.cmd, input.script, input.input, input.text].find((value) => typeof value === "string") };
|
|
243
|
+
const path = input.file_path ?? input.filePath ?? input.path;
|
|
244
|
+
return typeof path === "string" ? { ...input, file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path } : input;
|
|
245
|
+
}) };
|
|
246
|
+
}
|
|
247
|
+
if (source !== "gemini") return { toolName, toolInputs };
|
|
248
|
+
if (toolName === "run_shell_command") {
|
|
249
|
+
return { toolName: "Bash", toolInputs: toolInputs.map((input) => ({ command: input.command })) };
|
|
250
|
+
}
|
|
251
|
+
const canonical = { write_file: "Write", replace: "Edit", read_file: "Read" }[toolName];
|
|
252
|
+
if (!canonical) return { toolName, toolInputs };
|
|
253
|
+
return {
|
|
254
|
+
toolName: canonical,
|
|
255
|
+
toolInputs: toolInputs.map((input) => {
|
|
256
|
+
const path = [input.file_path, input.absolute_path, input.path].find((value) => typeof value === "string" && value.length > 0);
|
|
257
|
+
return path ? { file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path } : {};
|
|
258
|
+
})
|
|
259
|
+
};
|
|
260
|
+
};
|
|
176
261
|
var EXIT_CODE_FAILURE = /^Error: Exit code \d+/;
|
|
177
262
|
var isToolResultError = (toolResult) => {
|
|
178
263
|
try {
|
|
@@ -388,6 +473,105 @@ var sessionIdentityOf = (payload) => {
|
|
|
388
473
|
return { sessionId: agentId, parentSessionId: sessionId };
|
|
389
474
|
};
|
|
390
475
|
|
|
476
|
+
// ../hook-mapping/src/codex-rollout.ts
|
|
477
|
+
var CODEX_TURN_CONTEXT_TYPE = "turn_context";
|
|
478
|
+
var CODEX_AUTO_REVIEWER = "auto_review";
|
|
479
|
+
var CODEX_GUARDIAN_THREAD_SOURCE = "guardian_review";
|
|
480
|
+
var CODEX_PLANNED_ACTION_MARKER = "Planned action JSON:\n";
|
|
481
|
+
var CODEX_APPROVAL_REQUEST_END = ">>> APPROVAL REQUEST END";
|
|
482
|
+
var CODEX_SHELL_TOOL = "exec_command";
|
|
483
|
+
var CODEX_SHELL_COMMAND_FLAGS = ["-lc", "-c"];
|
|
484
|
+
var asRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
485
|
+
var parseRecord = (text) => {
|
|
486
|
+
try {
|
|
487
|
+
return asRecord(JSON.parse(text));
|
|
488
|
+
} catch {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
var nonEmptyString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
493
|
+
var isTimestamp = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
494
|
+
var readCodexTurnContext = (line) => {
|
|
495
|
+
const entry = parseRecord(line);
|
|
496
|
+
if (entry?.type !== CODEX_TURN_CONTEXT_TYPE) return null;
|
|
497
|
+
const payload = asRecord(entry.payload);
|
|
498
|
+
const turnId = nonEmptyString(payload?.turn_id);
|
|
499
|
+
if (!payload || !turnId) return null;
|
|
500
|
+
return {
|
|
501
|
+
turnId,
|
|
502
|
+
approvalsReviewer: payload.approvals_reviewer === CODEX_AUTO_REVIEWER ? "auto_review" : "user",
|
|
503
|
+
...isTimestamp(entry.timestamp) ? { startedAt: entry.timestamp } : {}
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
var isCodexGuardianRolloutHead = (line, sessionId) => {
|
|
507
|
+
if (!sessionId) return false;
|
|
508
|
+
const entry = parseRecord(line);
|
|
509
|
+
const payload = asRecord(entry?.payload);
|
|
510
|
+
return entry?.type === "session_meta" && payload?.parent_thread_id === sessionId && payload.thread_source === CODEX_GUARDIAN_THREAD_SOURCE;
|
|
511
|
+
};
|
|
512
|
+
var readCodexPlannedAction = (message) => {
|
|
513
|
+
const marker = message.lastIndexOf(CODEX_PLANNED_ACTION_MARKER);
|
|
514
|
+
if (marker < 0) return null;
|
|
515
|
+
const body = message.slice(marker + CODEX_PLANNED_ACTION_MARKER.length);
|
|
516
|
+
const end = body.lastIndexOf(CODEX_APPROVAL_REQUEST_END);
|
|
517
|
+
if (end < 0 || body.slice(end + CODEX_APPROVAL_REQUEST_END.length).trim().length > 0) return null;
|
|
518
|
+
return parseRecord(body.slice(0, end).trim());
|
|
519
|
+
};
|
|
520
|
+
var readCodexReviewVerdict = (message) => {
|
|
521
|
+
const verdict = parseRecord(message.trim());
|
|
522
|
+
if (verdict?.outcome !== "allow" && verdict?.outcome !== "deny") return null;
|
|
523
|
+
const rationale = typeof verdict.rationale === "string" ? verdict.rationale.trim() : "";
|
|
524
|
+
return { outcome: verdict.outcome, ...rationale ? { rationale } : {} };
|
|
525
|
+
};
|
|
526
|
+
var scanCodexGuardianReviews = (lines) => {
|
|
527
|
+
const reviews = [];
|
|
528
|
+
let pending = null;
|
|
529
|
+
for (const line of lines) {
|
|
530
|
+
const entry = parseRecord(line.text);
|
|
531
|
+
const payload = asRecord(entry?.payload);
|
|
532
|
+
if (entry?.type !== "event_msg" || typeof payload?.message !== "string") continue;
|
|
533
|
+
if (payload.type === "user_message") {
|
|
534
|
+
const plannedAction = readCodexPlannedAction(payload.message);
|
|
535
|
+
pending = plannedAction ? { offset: line.offset, plannedAction, ...isTimestamp(entry.timestamp) ? { requestedAt: entry.timestamp } : {} } : null;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (payload.type !== "agent_message" || !pending) continue;
|
|
539
|
+
const verdict = readCodexReviewVerdict(payload.message);
|
|
540
|
+
if (!verdict) continue;
|
|
541
|
+
reviews.push({ ...pending, verdict });
|
|
542
|
+
pending = null;
|
|
543
|
+
}
|
|
544
|
+
return { reviews, ...pending ? { pendingOffset: pending.offset } : {} };
|
|
545
|
+
};
|
|
546
|
+
var codexPlannedShellCommand = (plannedAction) => {
|
|
547
|
+
if (plannedAction?.tool !== CODEX_SHELL_TOOL) return null;
|
|
548
|
+
const argv = plannedAction.command;
|
|
549
|
+
if (!Array.isArray(argv) || argv.length !== 3) return null;
|
|
550
|
+
const [shell, flag, command] = argv;
|
|
551
|
+
if (typeof shell !== "string" || shell.trim().length === 0) return null;
|
|
552
|
+
if (typeof flag !== "string" || !CODEX_SHELL_COMMAND_FLAGS.includes(flag)) return null;
|
|
553
|
+
if (typeof command !== "string" || command.trim().length === 0) return null;
|
|
554
|
+
const cwd = nonEmptyString(plannedAction.cwd);
|
|
555
|
+
return { command, ...cwd ? { cwd } : {} };
|
|
556
|
+
};
|
|
557
|
+
var codexReviewerDenialsSince = (reviews, since) => {
|
|
558
|
+
const sinceMs = Date.parse(since);
|
|
559
|
+
if (!Number.isFinite(sinceMs)) return [];
|
|
560
|
+
return reviews.flatMap((review) => {
|
|
561
|
+
if (review.verdict.outcome !== "deny" || !review.requestedAt) return [];
|
|
562
|
+
if (Date.parse(review.requestedAt) < sinceMs) return [];
|
|
563
|
+
const shell = codexPlannedShellCommand(review.plannedAction);
|
|
564
|
+
if (!shell) return [];
|
|
565
|
+
return [{
|
|
566
|
+
...shell,
|
|
567
|
+
offset: review.offset,
|
|
568
|
+
requestedAt: review.requestedAt,
|
|
569
|
+
plannedAction: review.plannedAction,
|
|
570
|
+
...review.verdict.rationale ? { rationale: review.verdict.rationale } : {}
|
|
571
|
+
}];
|
|
572
|
+
});
|
|
573
|
+
};
|
|
574
|
+
|
|
391
575
|
export {
|
|
392
576
|
HOOK_FALLBACK_MAX,
|
|
393
577
|
UNNAMED_ACTION,
|
|
@@ -396,6 +580,7 @@ export {
|
|
|
396
580
|
parseApplyPatchOps,
|
|
397
581
|
parseApplyPatchFiles,
|
|
398
582
|
OPENCODE_TOOL_NAMES,
|
|
583
|
+
canonicalGateInputs,
|
|
399
584
|
isToolResultError,
|
|
400
585
|
toolResultErrorText,
|
|
401
586
|
deriveReceiptMeta,
|
|
@@ -415,5 +600,9 @@ export {
|
|
|
415
600
|
notificationAction,
|
|
416
601
|
describeStopFailure,
|
|
417
602
|
permissionDeniedAction,
|
|
418
|
-
sessionIdentityOf
|
|
603
|
+
sessionIdentityOf,
|
|
604
|
+
readCodexTurnContext,
|
|
605
|
+
isCodexGuardianRolloutHead,
|
|
606
|
+
scanCodexGuardianReviews,
|
|
607
|
+
codexReviewerDenialsSince
|
|
419
608
|
};
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-2UMNXADU.js";
|
|
4
4
|
import {
|
|
5
5
|
normalizeInstallSource
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HUZW6SEO.js";
|
|
7
7
|
|
|
8
8
|
// src/cli/fsx.ts
|
|
9
9
|
import { copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalGateInputs
|
|
3
|
+
} from "./chunk-VCKQ45QA.js";
|
|
4
|
+
import {
|
|
5
|
+
canonicalJson
|
|
6
|
+
} from "./chunk-67RNDRKL.js";
|
|
7
|
+
import {
|
|
8
|
+
writeFileAtomic
|
|
9
|
+
} from "./chunk-VTUN7WSG.js";
|
|
10
|
+
import {
|
|
11
|
+
BLOCKED_ACTION_GRANT_TTL_SECONDS,
|
|
12
|
+
blockedActionIdentity,
|
|
13
|
+
shouldDeferToNativeMode
|
|
14
|
+
} from "./chunk-HUZW6SEO.js";
|
|
15
|
+
|
|
16
|
+
// src/native-defer.ts
|
|
17
|
+
var nativeDeferOptions = () => {
|
|
18
|
+
const respect = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
|
|
19
|
+
const bypass = process.env.PUSHARY_DEFER_BYPASS;
|
|
20
|
+
return {
|
|
21
|
+
respectPermissionMode: !(respect === "0" || respect === "false"),
|
|
22
|
+
deferBypass: bypass === "1" || bypass === "true"
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
var defersToNativeMode = (permissionMode, toolName) => shouldDeferToNativeMode(permissionMode, toolName, nativeDeferOptions());
|
|
26
|
+
|
|
27
|
+
// src/codex-call-decision.ts
|
|
28
|
+
import { createHash } from "crypto";
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
|
|
30
|
+
import { tmpdir, userInfo } from "os";
|
|
31
|
+
import { join } from "path";
|
|
32
|
+
var CODEX_DECISION_TTL_MS = 60 * 1e3;
|
|
33
|
+
var userScope = () => {
|
|
34
|
+
try {
|
|
35
|
+
const info = userInfo();
|
|
36
|
+
const id = typeof info.uid === "number" && info.uid >= 0 ? String(info.uid) : info.username;
|
|
37
|
+
return id.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || "default";
|
|
38
|
+
} catch {
|
|
39
|
+
return "default";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var decisionDir = () => join(tmpdir(), `pushary-codex-decisions-${userScope()}`);
|
|
43
|
+
var decisionFile = (key) => join(decisionDir(), key);
|
|
44
|
+
var PRESENTATION_ONLY_INPUT_KEYS = ["description"];
|
|
45
|
+
var identifyingToolInput = (toolInput) => {
|
|
46
|
+
if (!toolInput) return {};
|
|
47
|
+
const identifying = {};
|
|
48
|
+
for (const [key, value] of Object.entries(toolInput)) {
|
|
49
|
+
if (PRESENTATION_ONLY_INPUT_KEYS.includes(key)) continue;
|
|
50
|
+
identifying[key] = value;
|
|
51
|
+
}
|
|
52
|
+
return identifying;
|
|
53
|
+
};
|
|
54
|
+
var codexCallKey = (identity) => {
|
|
55
|
+
if (!identity.sessionId || !identity.turnId || !identity.toolName) return null;
|
|
56
|
+
const material = canonicalJson([
|
|
57
|
+
identity.sessionId,
|
|
58
|
+
identity.turnId,
|
|
59
|
+
identity.toolName,
|
|
60
|
+
identifyingToolInput(identity.toolInput)
|
|
61
|
+
]);
|
|
62
|
+
return createHash("sha256").update(material, "utf-8").digest("hex").slice(0, 32);
|
|
63
|
+
};
|
|
64
|
+
var parseDecision = (raw) => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
if (parsed.kind === "approved") {
|
|
68
|
+
return typeof parsed.toolUseId === "string" && parsed.toolUseId ? { kind: "approved", toolUseId: parsed.toolUseId } : null;
|
|
69
|
+
}
|
|
70
|
+
if (parsed.kind === "handled") return { kind: "handled" };
|
|
71
|
+
if (parsed.kind === "denied") {
|
|
72
|
+
return { kind: "denied", reason: typeof parsed.reason === "string" ? parsed.reason : "" };
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var sweepExpired = (dir, nowMs) => {
|
|
80
|
+
try {
|
|
81
|
+
for (const name of readdirSync(dir)) {
|
|
82
|
+
const path = join(dir, name);
|
|
83
|
+
try {
|
|
84
|
+
if (nowMs - statSync(path).mtimeMs >= CODEX_DECISION_TTL_MS) rmSync(path, { force: true });
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var recordCodexCallDecision = (key, decision) => {
|
|
94
|
+
if (!key) return;
|
|
95
|
+
try {
|
|
96
|
+
const dir = decisionDir();
|
|
97
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
98
|
+
else sweepExpired(dir, Date.now());
|
|
99
|
+
writeFileSync(decisionFile(key), JSON.stringify(decision), { encoding: "utf-8", mode: 384 });
|
|
100
|
+
} catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var consumeCodexCallDecision = (key) => {
|
|
105
|
+
if (!key) return null;
|
|
106
|
+
const path = decisionFile(key);
|
|
107
|
+
try {
|
|
108
|
+
const fresh = Date.now() - statSync(path).mtimeMs < CODEX_DECISION_TTL_MS;
|
|
109
|
+
const raw = readFileSync(path, "utf-8");
|
|
110
|
+
rmSync(path, { force: true });
|
|
111
|
+
return fresh ? parseDecision(raw) : null;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/blocked-action-hint.ts
|
|
118
|
+
import { createHash as createHash2 } from "crypto";
|
|
119
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
120
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
121
|
+
import { join as join2 } from "path";
|
|
122
|
+
var BLOCKED_ACTION_HINT_TTL_MS = BLOCKED_ACTION_GRANT_TTL_SECONDS * 1e3;
|
|
123
|
+
var FINGERPRINT = /^[0-9a-f]{64}$/;
|
|
124
|
+
var isFingerprint = (value) => typeof value === "string" && FINGERPRINT.test(value);
|
|
125
|
+
var blockedActionSubject = (source, sessionId, toolName, toolInput, cwd) => {
|
|
126
|
+
if (source !== "claude") return { source, sessionId, toolName, toolInput };
|
|
127
|
+
return cwd ? { source, sessionId, cwd, toolName, toolInput } : null;
|
|
128
|
+
};
|
|
129
|
+
var blockedActionFingerprint = ({ source = "claude", sessionId, toolName, toolInput, cwd }) => {
|
|
130
|
+
if (!sessionId || !toolInput) return null;
|
|
131
|
+
const canonical = canonicalGateInputs(source, toolName, [toolInput], cwd);
|
|
132
|
+
if (!canonical || canonical.toolInputs.length !== 1) return null;
|
|
133
|
+
const [canonicalInput] = canonical.toolInputs;
|
|
134
|
+
if (!canonicalInput) return null;
|
|
135
|
+
const subject = blockedActionSubject(source, sessionId, canonical.toolName, canonicalInput, cwd);
|
|
136
|
+
return subject ? createHash2("sha256").update(blockedActionIdentity(subject), "utf8").digest("hex") : null;
|
|
137
|
+
};
|
|
138
|
+
var hintDir = () => join2(tmpdir2(), `pushary-blocked-action-hints-${userScope()}`);
|
|
139
|
+
var hintFile = (fingerprint) => join2(hintDir(), fingerprint);
|
|
140
|
+
var isExpired = (mtimeMs, nowMs) => nowMs - mtimeMs >= BLOCKED_ACTION_HINT_TTL_MS;
|
|
141
|
+
var sweepExpired2 = (dir, nowMs) => {
|
|
142
|
+
try {
|
|
143
|
+
for (const name of readdirSync2(dir)) {
|
|
144
|
+
const path = join2(dir, name);
|
|
145
|
+
try {
|
|
146
|
+
if (isExpired(statSync2(path).mtimeMs, nowMs)) rmSync2(path, { force: true });
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var recordBlockedActionHint = (fingerprint) => {
|
|
156
|
+
if (!isFingerprint(fingerprint)) return false;
|
|
157
|
+
try {
|
|
158
|
+
const dir = hintDir();
|
|
159
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
160
|
+
else sweepExpired2(dir, Date.now());
|
|
161
|
+
writeFileAtomic(hintFile(fingerprint), JSON.stringify({ fingerprint }), 384);
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var hasBlockedActionHint = (fingerprint) => {
|
|
168
|
+
if (!isFingerprint(fingerprint)) return false;
|
|
169
|
+
const path = hintFile(fingerprint);
|
|
170
|
+
try {
|
|
171
|
+
const stat = statSync2(path);
|
|
172
|
+
if (!stat.isFile()) return false;
|
|
173
|
+
if (isExpired(stat.mtimeMs, Date.now())) {
|
|
174
|
+
rmSync2(path, { force: true });
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
178
|
+
return parsed?.fingerprint === fingerprint;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var forgetBlockedActionHint = (fingerprint) => {
|
|
184
|
+
if (!isFingerprint(fingerprint)) return;
|
|
185
|
+
try {
|
|
186
|
+
rmSync2(hintFile(fingerprint), { force: true });
|
|
187
|
+
} catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export {
|
|
193
|
+
defersToNativeMode,
|
|
194
|
+
userScope,
|
|
195
|
+
codexCallKey,
|
|
196
|
+
recordCodexCallDecision,
|
|
197
|
+
consumeCodexCallDecision,
|
|
198
|
+
blockedActionFingerprint,
|
|
199
|
+
recordBlockedActionHint,
|
|
200
|
+
hasBlockedActionHint,
|
|
201
|
+
forgetBlockedActionHint
|
|
202
|
+
};
|