@pushary/agent-hooks 0.99.0 → 1.0.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 (79) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/bin/pushary-bell-hook.js +18 -18
  3. package/dist/bin/pushary-bell.js +6 -6
  4. package/dist/bin/pushary-claude.js +22 -24
  5. package/dist/bin/pushary-clean.js +60 -18
  6. package/dist/bin/pushary-codex-bridge.js +10 -10
  7. package/dist/bin/pushary-codex-hook.js +33 -35
  8. package/dist/bin/pushary-codex.js +6 -7
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-cowork.js +3 -3
  11. package/dist/bin/pushary-daemon.js +22 -22
  12. package/dist/bin/pushary-disconnect.js +48 -7
  13. package/dist/bin/pushary-doctor.js +58 -19
  14. package/dist/bin/pushary-elicitation-hook.js +17 -17
  15. package/dist/bin/pushary-gemini-bridge.js +9 -9
  16. package/dist/bin/pushary-gemini-hook.js +25 -27
  17. package/dist/bin/pushary-hook.js +61 -33
  18. package/dist/bin/pushary-login.js +7 -7
  19. package/dist/bin/pushary-logout.js +4 -4
  20. package/dist/bin/pushary-mode.js +3 -3
  21. package/dist/bin/pushary-notification-hook.js +26 -21
  22. package/dist/bin/pushary-opencode-hook.js +27 -29
  23. package/dist/bin/pushary-permission-denied-hook.js +24 -23
  24. package/dist/bin/pushary-permission-hook.js +32 -24
  25. package/dist/bin/pushary-post-hook.js +30 -22
  26. package/dist/bin/pushary-prompt-hook.js +26 -21
  27. package/dist/bin/pushary-session-end-hook.js +26 -21
  28. package/dist/bin/pushary-session-start-hook.js +30 -25
  29. package/dist/bin/pushary-setup.js +145 -37
  30. package/dist/bin/pushary-stats.js +2 -2
  31. package/dist/bin/pushary-status.js +10 -10
  32. package/dist/bin/pushary-stop-hook.js +27 -22
  33. package/dist/bin/pushary-stopfailure-hook.js +26 -21
  34. package/dist/bin/pushary-transcript-register.js +16 -16
  35. package/dist/bin/pushary-transcripts.js +4 -4
  36. package/dist/bin/pushary-upgrade.js +14 -14
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-PD6JY33P.js → chunk-2PS3DTHQ.js} +4 -3
  39. package/dist/{chunk-RALVI7OF.js → chunk-2SL5X7UB.js} +9 -6
  40. package/dist/{chunk-6KLCP7VF.js → chunk-3C4ERARU.js} +3 -3
  41. package/dist/{chunk-LBNAGGLV.js → chunk-3UUAK2AK.js} +3 -2
  42. package/dist/{chunk-V262ATS5.js → chunk-447SBIC7.js} +15 -2
  43. package/dist/{chunk-562UJ2DA.js → chunk-7WMFPYR5.js} +1 -1
  44. package/dist/{chunk-XPPXOKIG.js → chunk-7ZDFVWRR.js} +1 -1
  45. package/dist/{chunk-GCD3SQSV.js → chunk-A3D43VXO.js} +1 -1
  46. package/dist/{chunk-3RVCQDC4.js → chunk-BOZETFXB.js} +1 -1
  47. package/dist/{chunk-4C4XFAGJ.js → chunk-BPD4P3NN.js} +2 -2
  48. package/dist/{chunk-IRLTW6KF.js → chunk-CDTGL7TT.js} +1 -1
  49. package/dist/{chunk-CP34FI4B.js → chunk-D2N2EJH2.js} +2 -2
  50. package/dist/{chunk-6IFUXNFX.js → chunk-DMJTWULU.js} +1 -1
  51. package/dist/{chunk-4PBCNVI3.js → chunk-DOYARIYB.js} +1 -1
  52. package/dist/{chunk-XOOWC62T.js → chunk-DUOG74E7.js} +1 -1
  53. package/dist/{chunk-OPR2BPD4.js → chunk-EMCNSBF3.js} +1 -1
  54. package/dist/{chunk-5KLAETSU.js → chunk-GTR72MIU.js} +1 -1
  55. package/dist/{chunk-NSFNEDCT.js → chunk-HGEI7SVM.js} +6 -6
  56. package/dist/chunk-HS2KEAFD.js +224 -0
  57. package/dist/{chunk-T3JZZUAR.js → chunk-I3TWY7GH.js} +30 -29
  58. package/dist/{chunk-ID5FDKDZ.js → chunk-IBU7UU3R.js} +1 -1
  59. package/dist/{chunk-O3LZQEQP.js → chunk-IWZG2Y4Z.js} +1 -1
  60. package/dist/{chunk-WGV3VY7O.js → chunk-IYA3NCZG.js} +2 -2
  61. package/dist/{chunk-JBJBVERL.js → chunk-LTPQKBJH.js} +1 -1
  62. package/dist/{chunk-64WARXGV.js → chunk-MDV7ZRAT.js} +698 -43
  63. package/dist/{chunk-QS55LDX4.js → chunk-MGI6MLFU.js} +16 -16
  64. package/dist/{chunk-CRFLU455.js → chunk-MSOFZYI3.js} +3 -3
  65. package/dist/{chunk-LWKPMSBB.js → chunk-R744XA2M.js} +5 -5
  66. package/dist/{chunk-3ZAEUPM4.js → chunk-RFNMSC6N.js} +1 -1
  67. package/dist/{chunk-K6BASBQJ.js → chunk-S2W4ZSHE.js} +1 -1
  68. package/dist/{chunk-OUAE3GKO.js → chunk-TTBRBWAK.js} +278 -1
  69. package/dist/{chunk-6C6MS57P.js → chunk-TVY7AT6O.js} +1 -1
  70. package/dist/{chunk-EXHXR2X5.js → chunk-UWQVODHT.js} +1 -1
  71. package/dist/{chunk-JXELRCU5.js → chunk-XK34HTMW.js} +3 -3
  72. package/dist/{chunk-SI7GYBQP.js → chunk-XKDKUFMP.js} +1 -1
  73. package/dist/{chunk-HB2KB7QL.js → chunk-XNXIMIFY.js} +1 -1
  74. package/dist/{chunk-K4XFCEVI.js → chunk-Y55JNI2I.js} +1 -1
  75. package/dist/{chunk-JL33EDN7.js → chunk-ZZIUQVAN.js} +4 -4
  76. package/dist/{reapply-HQ264ZTR.js → reapply-I4NB5SSM.js} +8 -8
  77. package/dist/src/index.js +12 -10
  78. package/package.json +1 -1
  79. package/dist/chunk-A76GNRIK.js +0 -608
@@ -1,28 +1,7 @@
1
- import {
2
- AGENT_IDENTITIES,
3
- TEAMMATE_IDLE_ACTION,
4
- compactionAction,
5
- deriveReceiptMeta,
6
- deriveToolTarget,
7
- describeStopFailure,
8
- describeToolCall,
9
- isToolResultError,
10
- notificationAction,
11
- notificationTypeOf,
12
- parseApplyPatchFiles,
13
- parseApplyPatchOps,
14
- sessionEndAction,
15
- sessionIdentityOf,
16
- sessionStartAction,
17
- subagentStopSummary,
18
- summarizeStop,
19
- taskTitleFrom,
20
- toolResultErrorText
21
- } from "./chunk-A76GNRIK.js";
22
1
  import {
23
2
  NOTIFICATION_PUSH_TYPES,
24
3
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
25
- } from "./chunk-OPR2BPD4.js";
4
+ } from "./chunk-EMCNSBF3.js";
26
5
  import {
27
6
  cancelQuestion,
28
7
  isPolicyConfig,
@@ -31,18 +10,24 @@ import {
31
10
  } from "./chunk-LGXUGYUH.js";
32
11
  import {
33
12
  getMachineId
34
- } from "./chunk-562UJ2DA.js";
13
+ } from "./chunk-7WMFPYR5.js";
35
14
  import {
36
15
  withRetry
37
16
  } from "./chunk-EYRGBYZG.js";
38
17
  import {
39
18
  reportedInstallSource
40
- } from "./chunk-GCD3SQSV.js";
19
+ } from "./chunk-A3D43VXO.js";
41
20
  import {
42
21
  getApiKey,
43
22
  getBaseUrl
44
23
  } from "./chunk-2UMNXADU.js";
45
24
  import {
25
+ ACTION_BODY_MAX,
26
+ AGENT_LABELS_BY_TYPE,
27
+ DECISION_LINE_MAX,
28
+ FILE_TARGET_TOOLS,
29
+ GATE_MAX_TOOL_INPUTS,
30
+ HOOK_COMPATIBLE_PROFILES,
46
31
  HOOK_SOURCES,
47
32
  PENDING_COMMAND_PREFIX,
48
33
  REPO_AWARE_AGENTS,
@@ -52,9 +37,11 @@ import {
52
37
  modeStateFromResponse,
53
38
  normalizeRepoRemote,
54
39
  pendingCommandEvents,
40
+ redactSecrets,
41
+ redactSecretsDeep,
55
42
  resolveAutoResolveOrigin,
56
43
  resolvePolicy
57
- } from "./chunk-OUAE3GKO.js";
44
+ } from "./chunk-TTBRBWAK.js";
58
45
 
59
46
  // src/policy.ts
60
47
  import { createHash } from "crypto";
@@ -196,6 +183,657 @@ var fetchModeState = async (apiKey, sessionId, reuseWithinMs = 0, signal) => {
196
183
  };
197
184
  var fetchModeOverride = async (apiKey) => (await fetchModeState(apiKey)).mode;
198
185
 
186
+ // ../hook-mapping/src/describe.ts
187
+ import { isAbsolute, relative, resolve } from "path";
188
+ var hookPrefixes = {
189
+ Bash: (input) => `bash: ${input.command ?? "(no command)"}`,
190
+ PowerShell: (input) => `powershell: ${input.command ?? "(no command)"}`,
191
+ Monitor: (input) => `monitor: ${input.command ?? "(no command)"}`,
192
+ Write: (input) => `write file: ${input.file_path ?? "(unknown path)"}`,
193
+ Edit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
194
+ MultiEdit: (input) => `edit file: ${input.file_path ?? "(unknown path)"}`,
195
+ Read: (input) => `read file: ${input.file_path ?? "(unknown path)"}`,
196
+ // Without an entry here the fallback stringifies the whole tool input, and for
197
+ // ExitPlanMode that input IS the plan: the action line became a truncated dump of
198
+ // the plan markdown. The plan belongs in the body, not the one-line summary.
199
+ ExitPlanMode: () => "finished planning and wants to start coding",
200
+ // Mirrored on PostToolUse since the matcher was widened. Without entries the
201
+ // fallback prints `Task: done` and `WebSearch: done`, which name the tool and
202
+ // drop the only part a person reads: which subagent, and what it looked for.
203
+ Task: (input) => `subagent: ${describeTaskCall(input)}`,
204
+ WebSearch: (input) => `web search: ${firstString(input.query) ?? "(no query)"}`
205
+ };
206
+ var describeTaskCall = (input) => firstString(input.description) ?? firstString(input.subagent_type) ?? "(unnamed)";
207
+ var describeTodoProgress = (todos) => {
208
+ if (!Array.isArray(todos)) return "updated the plan";
209
+ const items = todos.filter((t) => !!t && typeof t === "object");
210
+ const total = items.length;
211
+ if (total === 0) return "updated the plan";
212
+ const done = items.filter((t) => t.status === "completed").length;
213
+ const current = items.find((t) => t.status === "in_progress");
214
+ const label = current ? [current.activeForm, current.content].find((v) => typeof v === "string" && v.trim().length > 0) : void 0;
215
+ if (done >= total) return `finished all ${total} tasks`;
216
+ if (label) return `${label.trim()} (${done}/${total} done)`;
217
+ return `${done}/${total} tasks done`;
218
+ };
219
+ var eventPrefixes = {
220
+ Bash: (input) => `ran: ${String(input.command ?? "")}`,
221
+ PowerShell: (input) => `ran: ${String(input.command ?? "")}`,
222
+ Monitor: (input) => `ran: ${String(input.command ?? "")}`,
223
+ Write: (input) => `wrote: ${input.file_path ?? "unknown"}`,
224
+ Edit: (input) => `edited: ${input.file_path ?? "unknown"}`,
225
+ MultiEdit: (input) => `edited: ${input.file_path ?? "unknown"}`,
226
+ Read: (input) => `read: ${input.file_path ?? "unknown"}`,
227
+ TodoWrite: (input) => describeTodoProgress(input.todos),
228
+ Task: (input) => `ran subagent: ${describeTaskCall(input)}`,
229
+ WebSearch: (input) => `searched: ${firstString(input.query) ?? "the web"}`
230
+ };
231
+ var MCP_TOOL_PREFIX = "mcp__";
232
+ var parseMcpToolName = (toolName) => {
233
+ if (!toolName.startsWith(MCP_TOOL_PREFIX)) return void 0;
234
+ const rest = toolName.slice(MCP_TOOL_PREFIX.length);
235
+ const split = rest.indexOf("__");
236
+ if (split <= 0 || split + 2 >= rest.length) return void 0;
237
+ return { server: rest.slice(0, split), tool: rest.slice(split + 2) };
238
+ };
239
+ var NETWORK_TARGET_KEYS = ["url", "urls", "href", "host", "hosts", "hostname", "domain", "domains"];
240
+ var ABSOLUTE_HTTP_URL = /^https?:\/\//i;
241
+ var HOSTNAME = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+(?::\d{1,5})?$/i;
242
+ var hostOfUrl = (value) => {
243
+ try {
244
+ return new URL(value).host || void 0;
245
+ } catch {
246
+ return void 0;
247
+ }
248
+ };
249
+ var hostOf = (value, bareHostnames) => {
250
+ if (typeof value !== "string") return void 0;
251
+ const trimmed = value.trim();
252
+ if (ABSOLUTE_HTTP_URL.test(trimmed)) return hostOfUrl(trimmed);
253
+ return bareHostnames && HOSTNAME.test(trimmed) ? trimmed : void 0;
254
+ };
255
+ var firstHost = (value, bareHostnames) => Array.isArray(value) ? value.map((entry) => hostOf(entry, bareHostnames)).find((host) => host !== void 0) : hostOf(value, bareHostnames);
256
+ var deriveNetworkTarget = (toolInput) => {
257
+ for (const key of NETWORK_TARGET_KEYS) {
258
+ const host = firstHost(toolInput[key], true);
259
+ if (host) return host;
260
+ }
261
+ return Object.values(toolInput).map((value) => firstHost(value, false)).find((host) => host !== void 0);
262
+ };
263
+ var describeUnbuiltCall = (toolName, toolInput, format) => {
264
+ const mcp = parseMcpToolName(toolName);
265
+ const host = deriveNetworkTarget(toolInput);
266
+ if (mcp) {
267
+ if (format === "event") return `called ${mcp.tool} on ${mcp.server}`;
268
+ return host ? `call ${mcp.tool} on ${mcp.server} to reach ${host}` : `call ${mcp.tool} on ${mcp.server}`;
269
+ }
270
+ if (host) return format === "event" ? `accessed ${host}` : `network access to ${host}`;
271
+ return void 0;
272
+ };
273
+ var EVENT_ACTION_MAX = 120;
274
+ var HOOK_FALLBACK_MAX = 200;
275
+ var UNNAMED_ACTION = "this request";
276
+ var describeToolCall = (toolName, toolInput, format = "hook") => {
277
+ if (!toolName) return "";
278
+ const prefixes = format === "hook" ? hookPrefixes : eventPrefixes;
279
+ const builder = prefixes[toolName];
280
+ const unbuilt = builder ? void 0 : describeUnbuiltCall(toolName, toolInput ?? {}, format);
281
+ const detail = JSON.stringify(toolInput ?? {});
282
+ const raw = builder ? builder(toolInput) : unbuilt ?? (format === "hook" ? detail === "{}" || detail === void 0 ? toolName : `${toolName}: ${detail}` : `${toolName}: done`);
283
+ const redacted = redactSecrets(raw);
284
+ if (format === "event") return redacted.slice(0, EVENT_ACTION_MAX);
285
+ return builder || unbuilt ? redacted : redacted.slice(0, HOOK_FALLBACK_MAX);
286
+ };
287
+ var TOOL_TARGET_MAX_LENGTH = 80;
288
+ var deriveCommandHead = (command) => {
289
+ if (typeof command !== "string") return void 0;
290
+ const head = command.trim().split(/\s+/).slice(0, 2).join(" ");
291
+ return head ? redactSecrets(head).slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
292
+ };
293
+ var deriveToolTarget = (toolName, toolInput) => {
294
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor") {
295
+ return deriveCommandHead(toolInput.command);
296
+ }
297
+ if (FILE_TARGET_TOOLS.has(toolName)) {
298
+ const filePath = toolInput.file_path;
299
+ if (typeof filePath !== "string") return void 0;
300
+ const separator = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
301
+ const base = filePath.slice(separator + 1);
302
+ const dot = base.lastIndexOf(".");
303
+ if (dot <= 0) return void 0;
304
+ return base.slice(dot).slice(0, TOOL_TARGET_MAX_LENGTH);
305
+ }
306
+ const mcp = parseMcpToolName(toolName);
307
+ if (mcp) return mcp.server.slice(0, TOOL_TARGET_MAX_LENGTH);
308
+ return deriveNetworkTarget(toolInput)?.slice(0, TOOL_TARGET_MAX_LENGTH);
309
+ };
310
+ var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun", "npx", "deno", "rake", "make", "just", "task", "turbo"]);
311
+ var RUN_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "run-script", "exec", "task"]);
312
+ var deriveReceiptCommandHead = (command) => {
313
+ if (typeof command !== "string") return void 0;
314
+ const tokens = command.trim().split(/\s+/);
315
+ const [first, second] = tokens;
316
+ const keep = first && second && SCRIPT_RUNNERS.has(first.toLowerCase()) && RUN_SUBCOMMANDS.has(second.toLowerCase()) ? 3 : 2;
317
+ const head = tokens.slice(0, keep).join(" ");
318
+ return head ? redactSecrets(head).slice(0, TOOL_TARGET_MAX_LENGTH) : void 0;
319
+ };
320
+ var RECEIPT_TARGET_MAX_LENGTH = 256;
321
+ var parseApplyPatchOps = (command) => {
322
+ if (typeof command !== "string") return [];
323
+ const ops = [];
324
+ for (const line of command.split("\n")) {
325
+ const file = line.match(/^\*\*\*\s+(Add|Update|Delete) File:\s+(.+?)\s*$/);
326
+ if (file) {
327
+ ops.push({ op: file[1].toLowerCase(), path: file[2] });
328
+ continue;
329
+ }
330
+ const move = line.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
331
+ if (move) ops.push({ op: "move", path: move[1] });
332
+ }
333
+ return ops;
334
+ };
335
+ var parseApplyPatchFiles = (command) => [...new Set(parseApplyPatchOps(command).map((entry) => entry.path))];
336
+ var OPENCODE_TOOL_NAMES = {
337
+ bash: "Bash",
338
+ // A shell command reaching outside the worktree asks a second time under its
339
+ // own key. It is still the same command, and still Bash.
340
+ external_directory: "Bash",
341
+ edit: "Edit",
342
+ patch: "Edit",
343
+ apply_patch: "Edit",
344
+ write: "Write",
345
+ read: "Read",
346
+ glob: "Glob",
347
+ grep: "Grep",
348
+ webfetch: "WebFetch",
349
+ websearch: "WebSearch",
350
+ task: "Task"
351
+ };
352
+ var canonicalGateInputs = (source, toolName, toolInputs, cwd) => {
353
+ if (source === "codex" && toolName === "apply_patch" || source === "vscode" && ["apply_patch", "applyPatch", "copilot_applyPatch"].includes(toolName)) {
354
+ const paths = [...new Set(toolInputs.flatMap((input) => parseApplyPatchFiles(input.command ?? input.input ?? input.patch)))];
355
+ if (paths.length > GATE_MAX_TOOL_INPUTS) return null;
356
+ return {
357
+ toolName: "Edit",
358
+ toolInputs: paths.length === 0 ? toolInputs.map((input) => ({ file_path: input.command })) : paths.map((path) => ({ file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path }))
359
+ };
360
+ }
361
+ if (source === "opencode") {
362
+ const canonical2 = OPENCODE_TOOL_NAMES[toolName] ?? toolName;
363
+ return {
364
+ toolName: canonical2,
365
+ toolInputs: toolInputs.map((input) => {
366
+ const path = input.file_path;
367
+ return typeof path === "string" && path.length > 0 && cwd && !isAbsolute(path) ? { ...input, file_path: resolve(cwd, path) } : input;
368
+ })
369
+ };
370
+ }
371
+ if (source === "vscode" || source === "cursor") {
372
+ if (source === "vscode" && toolName.startsWith("mcp_pushary_")) return { toolName: `mcp__pushary__${toolName.slice("mcp_pushary_".length)}`, toolInputs };
373
+ const alias = toolName.replace(/^copilot_/, "").toLowerCase().replace(/[^a-z0-9]/g, "");
374
+ const shells = [
375
+ "runterminalcommand",
376
+ "runinterminal",
377
+ "runcommand",
378
+ "terminalcommand",
379
+ "executecommand",
380
+ "runinterminalcommand",
381
+ "bash",
382
+ "shell",
383
+ "terminal",
384
+ "sendtoterminal",
385
+ "powershell",
386
+ "localshell"
387
+ ];
388
+ 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" : [
389
+ "readfile",
390
+ "viewimage",
391
+ "listdir",
392
+ "listdirectory",
393
+ "filesearch",
394
+ "findfiles",
395
+ "grepsearch",
396
+ "findtextinfiles",
397
+ "semanticsearch",
398
+ "searchcodebase",
399
+ "searchworkspacesymbols",
400
+ "geterrors",
401
+ "getchangedfiles",
402
+ "readprojectstructure",
403
+ "getnotebooksummary",
404
+ "readnotebookcelloutput",
405
+ "testsearch",
406
+ "findtestfiles",
407
+ "testfailure",
408
+ "getvscodeapi",
409
+ "getterminaloutput",
410
+ "gettaskoutput",
411
+ "terminalselection",
412
+ "terminallastcommand"
413
+ ].includes(alias) ? "Read" : toolName;
414
+ 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]);
415
+ if (inputs.length > GATE_MAX_TOOL_INPUTS) return null;
416
+ return { toolName: canonical2, toolInputs: inputs.map((input) => {
417
+ 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") };
418
+ const path = input.file_path ?? input.filePath ?? input.path;
419
+ return typeof path === "string" ? { ...input, file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path } : input;
420
+ }) };
421
+ }
422
+ if (source !== "gemini") return { toolName, toolInputs };
423
+ if (toolName === "run_shell_command") {
424
+ return { toolName: "Bash", toolInputs: toolInputs.map((input) => ({ command: input.command })) };
425
+ }
426
+ const canonical = { write_file: "Write", replace: "Edit", read_file: "Read" }[toolName];
427
+ if (!canonical) return { toolName, toolInputs };
428
+ return {
429
+ toolName: canonical,
430
+ toolInputs: toolInputs.map((input) => {
431
+ const path = [input.file_path, input.absolute_path, input.path].find((value) => typeof value === "string" && value.length > 0);
432
+ return path ? { file_path: cwd && !isAbsolute(path) ? resolve(cwd, path) : path } : {};
433
+ })
434
+ };
435
+ };
436
+ var EXIT_CODE_FAILURE = /^Error: Exit code \d+/;
437
+ var isToolResultError = (toolResult) => {
438
+ try {
439
+ if (typeof toolResult === "string") return EXIT_CODE_FAILURE.test(toolResult);
440
+ if (!toolResult || typeof toolResult !== "object") return false;
441
+ return "error" in toolResult || "is_error" in toolResult;
442
+ } catch {
443
+ return false;
444
+ }
445
+ };
446
+ var toolResultErrorText = (toolResult) => {
447
+ try {
448
+ const raw = typeof toolResult === "string" ? toolResult : toolResult && typeof toolResult === "object" ? String(
449
+ toolResult.error ?? toolResult.stderr ?? ""
450
+ ) : "";
451
+ return redactSecrets(raw);
452
+ } catch {
453
+ return "";
454
+ }
455
+ };
456
+ var relativizeReceiptPath = (filePath, cwd) => {
457
+ if (!cwd || !isAbsolute(filePath)) return filePath;
458
+ return relative(cwd, filePath);
459
+ };
460
+ var deriveReceiptMeta = (toolName, toolInput, toolResult, cwd) => {
461
+ try {
462
+ const ok = !isToolResultError(toolResult);
463
+ if (toolName === "apply_patch") {
464
+ const files = parseApplyPatchFiles(toolInput.command);
465
+ const target = files.length === 1 ? files[0] : toolInput.command;
466
+ if (typeof target !== "string" || !target) return void 0;
467
+ return { kind: "edit", target: redactSecrets(target).slice(0, RECEIPT_TARGET_MAX_LENGTH), ok };
468
+ }
469
+ const canonicalTool = {
470
+ run_shell_command: "Bash",
471
+ write_file: "Write",
472
+ replace: "Edit"
473
+ }[toolName] ?? toolName;
474
+ const canonicalInput = canonicalTool === toolName ? toolInput : {
475
+ ...toolInput,
476
+ file_path: toolInput.file_path ?? toolInput.absolute_path ?? toolInput.path
477
+ };
478
+ if (FILE_TARGET_TOOLS.has(canonicalTool)) {
479
+ const filePath = canonicalInput.file_path;
480
+ if (typeof filePath !== "string" || !filePath) return void 0;
481
+ return {
482
+ kind: canonicalTool === "Write" ? "write" : "edit",
483
+ // A path is an unlikely place for a credential but not an impossible one
484
+ // (a token in a temp directory name), and this field is persisted.
485
+ target: redactSecrets(relativizeReceiptPath(filePath, cwd)).slice(0, RECEIPT_TARGET_MAX_LENGTH),
486
+ ok
487
+ };
488
+ }
489
+ if (canonicalTool === "Bash" || canonicalTool === "PowerShell" || canonicalTool === "Monitor") {
490
+ const head = deriveReceiptCommandHead(canonicalInput.command);
491
+ if (!head) return void 0;
492
+ return {
493
+ kind: head === "git commit" ? "commit" : "bash",
494
+ target: head,
495
+ ok
496
+ };
497
+ }
498
+ return void 0;
499
+ } catch {
500
+ return void 0;
501
+ }
502
+ };
503
+ var scopePathFor = (toolName, toolInput, cwd) => {
504
+ const meta = deriveReceiptMeta(toolName, toolInput, void 0, cwd);
505
+ if (!meta) return void 0;
506
+ return meta.kind === "edit" || meta.kind === "write" ? meta.target : void 0;
507
+ };
508
+ var firstString = (...values) => values.find((v) => typeof v === "string" && v.length > 0);
509
+ var ACTION_BODY_TRUNCATION_MARKER = "\n\u2026 [truncated]";
510
+ var capActionBody = (text) => text.length <= ACTION_BODY_MAX ? text : `${text.slice(0, ACTION_BODY_MAX - ACTION_BODY_TRUNCATION_MARKER.length)}${ACTION_BODY_TRUNCATION_MARKER}`;
511
+ var rawActionBody = (toolName, toolInput) => {
512
+ if (toolName === "Edit" || toolName === "replace") {
513
+ const before = firstString(toolInput.old_string);
514
+ const after = firstString(toolInput.new_string);
515
+ if (before === void 0 && after === void 0) return firstString(toolInput.diff);
516
+ return `- ${before ?? ""}
517
+ + ${after ?? ""}`;
518
+ }
519
+ if (toolName === "Write" || toolName === "write_file") {
520
+ return firstString(toolInput.content) ?? firstString(toolInput.diff);
521
+ }
522
+ if (toolName === "ExitPlanMode") return firstString(toolInput.plan);
523
+ if (toolName === "Bash" || toolName === "PowerShell" || toolName === "Monitor" || toolName === "run_shell_command" || toolName === "shell" || toolName === "apply_patch") {
524
+ return firstString(toolInput.command);
525
+ }
526
+ return firstString(toolInput.url, toolInput.href);
527
+ };
528
+ var deriveActionBody = (toolName, toolInput) => {
529
+ try {
530
+ const raw = rawActionBody(toolName, toolInput);
531
+ if (!raw) return void 0;
532
+ return capActionBody(redactSecretsDeep(raw));
533
+ } catch {
534
+ return void 0;
535
+ }
536
+ };
537
+ var deriveAction = (toolName, toolInput) => describeToolCall(toolName, toolInput, "hook").slice(0, DECISION_LINE_MAX);
538
+ var BLOCKER_BY_MODE = {
539
+ push_only: "Approval required before this runs",
540
+ push_first: "Paused for your approval before continuing",
541
+ escalate: "Escalated to you after the approval timeout"
542
+ };
543
+ var deriveBlocker = (mode, reason) => {
544
+ const text = reason?.replace(/\s+/g, " ").trim() || BLOCKER_BY_MODE[mode] || "Waiting for your approval";
545
+ return text.slice(0, DECISION_LINE_MAX);
546
+ };
547
+
548
+ // ../hook-mapping/src/map.ts
549
+ import { basename } from "path";
550
+ var AGENT_IDENTITIES = {
551
+ claude: { type: "claude_code", label: AGENT_LABELS_BY_TYPE.claude_code },
552
+ codex: { type: "codex", label: AGENT_LABELS_BY_TYPE.codex },
553
+ gemini: { type: "gemini_cli", label: AGENT_LABELS_BY_TYPE.gemini_cli },
554
+ cursor: { type: "cursor", label: AGENT_LABELS_BY_TYPE.cursor },
555
+ vscode: { type: "vscode", label: AGENT_LABELS_BY_TYPE.vscode },
556
+ opencode: { type: "opencode", label: AGENT_LABELS_BY_TYPE.opencode },
557
+ qwen: { type: "qwen_code", label: AGENT_LABELS_BY_TYPE.qwen_code },
558
+ codebuddy: { type: "codebuddy", label: AGENT_LABELS_BY_TYPE.codebuddy },
559
+ qoder: { type: "qoder", label: AGENT_LABELS_BY_TYPE.qoder },
560
+ droid: { type: "droid", label: AGENT_LABELS_BY_TYPE.droid },
561
+ devin: { type: "devin", label: AGENT_LABELS_BY_TYPE.devin }
562
+ };
563
+ var TEAMMATE_IDLE_ACTION = "A teammate went idle";
564
+ var CLAUDE_MEANINGS = {
565
+ PostToolUse: { kind: "tool", event: "tool_complete" },
566
+ UserPromptSubmit: { kind: "prompt", event: "user_prompt" },
567
+ Stop: { kind: "stop", event: "session_end" },
568
+ SubagentStop: { kind: "subagent_stop", event: "session_end" },
569
+ PreCompact: { kind: "compaction", event: "compaction" },
570
+ PostCompact: { kind: "compaction", event: "compaction" },
571
+ SessionStart: { kind: "session_start", event: "session_start" },
572
+ SessionEnd: { kind: "session_closed", event: "session_closed" },
573
+ SubagentStart: { kind: "session_start", event: "session_start", action: "Subagent started" },
574
+ Notification: { kind: "notification", event: "notification" },
575
+ TeammateIdle: { kind: "teammate_idle", event: "teammate_idle", action: TEAMMATE_IDLE_ACTION },
576
+ StopFailure: { kind: "stop_failure", event: "turn_error" },
577
+ PermissionDenied: { kind: "permission_denied", event: "permission_denied" }
578
+ };
579
+ var compatibleMeanings = (agentType) => {
580
+ const events = HOOK_COMPATIBLE_PROFILES.find((profile) => profile.agentType === agentType)?.events ?? [];
581
+ return Object.fromEntries(events.flatMap((event) => {
582
+ const meaning = CLAUDE_MEANINGS[event];
583
+ return meaning ? [[event, meaning]] : [];
584
+ }));
585
+ };
586
+ var MEANING_BY_HOOK = {
587
+ claude: CLAUDE_MEANINGS,
588
+ qwen: compatibleMeanings("qwen_code"),
589
+ codebuddy: compatibleMeanings("codebuddy"),
590
+ qoder: compatibleMeanings("qoder"),
591
+ droid: compatibleMeanings("droid"),
592
+ devin: compatibleMeanings("devin"),
593
+ codex: {
594
+ PostToolUse: { kind: "tool", event: "tool_complete" },
595
+ UserPromptSubmit: { kind: "prompt", event: "user_prompt" },
596
+ Stop: { kind: "stop", event: "session_end" },
597
+ SessionStart: { kind: "session_start", event: "session_begin", action: "Session started" },
598
+ SessionEnd: { kind: "session_closed", event: "session_closed" },
599
+ // Codex names all four in its own HookEventName enum, and the CLI registers
600
+ // them. A row missing here is the same defect as the dropped Codex
601
+ // SessionEnd: the CLI reports the event, the Mac app forwards the identical
602
+ // envelope, and only the app's copy vanishes.
603
+ SubagentStart: { kind: "session_start", event: "session_begin", action: "Subagent started" },
604
+ SubagentStop: { kind: "subagent_stop", event: "session_end" },
605
+ PreCompact: { kind: "compaction", event: "compaction" },
606
+ PostCompact: { kind: "compaction", event: "compaction" }
607
+ },
608
+ gemini: {
609
+ AfterTool: { kind: "tool", event: "tool_complete" },
610
+ BeforeAgent: { kind: "prompt", event: "user_prompt" },
611
+ AfterAgent: { kind: "stop", event: "session_end" },
612
+ SessionStart: { kind: "session_start", event: "session_begin", action: "Session started" },
613
+ SessionEnd: { kind: "session_closed", event: "session_closed" }
614
+ },
615
+ cursor: {
616
+ afterShellExecution: { kind: "tool", event: "tool_complete" },
617
+ afterFileEdit: { kind: "tool", event: "tool_complete" },
618
+ postToolUse: { kind: "tool", event: "tool_complete" },
619
+ stop: { kind: "stop", event: "session_end" },
620
+ sessionStart: { kind: "session_start", event: "session_begin" },
621
+ sessionEnd: { kind: "session_closed", event: "session_closed" },
622
+ beforeSubmitPrompt: { kind: "prompt", event: "user_prompt" },
623
+ preCompact: { kind: "compaction", event: "compaction" },
624
+ subagentStart: { kind: "session_start", event: "session_begin", action: "Subagent started" },
625
+ subagentStop: { kind: "subagent_stop", event: "session_end" }
626
+ },
627
+ vscode: {
628
+ PostToolUse: { kind: "tool", event: "tool_complete" },
629
+ Stop: { kind: "stop", event: "session_end" },
630
+ SessionStart: { kind: "session_start", event: "session_begin" },
631
+ UserPromptSubmit: { kind: "prompt", event: "user_prompt" },
632
+ PreCompact: { kind: "compaction", event: "compaction" },
633
+ SubagentStart: { kind: "session_start", event: "session_begin", action: "Subagent started" },
634
+ SubagentStop: { kind: "subagent_stop", event: "session_end" }
635
+ },
636
+ // OpenCode's own names, because the plugin sends the hook it was called from
637
+ // rather than translating. `session.idle` is the turn boundary — its Stop, not
638
+ // its SessionEnd — and there is no session-start entry on purpose: OpenCode
639
+ // publishes `session.created` for every subagent fork too.
640
+ opencode: {
641
+ "tool.execute.after": { kind: "tool", event: "tool_complete" },
642
+ "chat.message": { kind: "prompt", event: "user_prompt" },
643
+ "session.idle": { kind: "stop", event: "session_end" }
644
+ }
645
+ };
646
+ var TASK_TITLE_MAX_LENGTH = 120;
647
+ var STOP_SUMMARY_MAX_LENGTH = 200;
648
+ var SESSION_END_ACTIONS = {
649
+ clear: "Session cleared",
650
+ logout: "Logged out",
651
+ resume: "Session suspended",
652
+ prompt_input_exit: "Session ended",
653
+ bypass_permissions_disabled: "Session ended",
654
+ other: "Session ended"
655
+ };
656
+ var countArray = (value) => Array.isArray(value) ? value.length : 0;
657
+ var summarizeFinalMessage = (message) => {
658
+ if (typeof message !== "string") return "Session ended";
659
+ const collapsed = message.replace(/\s+/g, " ").trim();
660
+ if (!collapsed) return "Session ended";
661
+ return collapsed.slice(0, STOP_SUMMARY_MAX_LENGTH);
662
+ };
663
+ var summarizeStop = (input) => {
664
+ const running = countArray(input.background_tasks);
665
+ if (running > 0) return `Paused with ${running} background task${running === 1 ? "" : "s"} still running`;
666
+ const crons = countArray(input.session_crons);
667
+ if (crons > 0) return `Idle until a scheduled task wakes it (${crons} pending)`;
668
+ return summarizeFinalMessage(input.last_assistant_message ?? input.prompt_response);
669
+ };
670
+ var subagentStopAction = (agentType) => {
671
+ const name = typeof agentType === "string" ? agentType.replace(/\s+/g, " ").trim() : "";
672
+ return name ? `${name} subagent finished` : "Subagent finished";
673
+ };
674
+ var subagentStopSummary = (input) => {
675
+ const message = input.last_assistant_message ?? input.prompt_response;
676
+ if (typeof message === "string" && message.trim()) return summarizeStop(input);
677
+ return subagentStopAction(input.agent_type);
678
+ };
679
+ var COMPACTION_TRIGGERS = {
680
+ manual: "on request",
681
+ auto: "automatically"
682
+ };
683
+ var compactionAction = (hookEvent, trigger) => {
684
+ const how = typeof trigger === "string" ? COMPACTION_TRIGGERS[trigger] : void 0;
685
+ const base = hookEvent === "PostCompact" ? "Compacted context" : "Compacting context";
686
+ return how ? `${base} ${how}` : base;
687
+ };
688
+ var sessionStartAction = (source) => source === "resume" ? "Session resumed" : "Session started";
689
+ var sessionEndAction = (reason) => typeof reason === "string" && SESSION_END_ACTIONS[reason] || "Session ended";
690
+ var taskTitleFrom = (prompt) => typeof prompt === "string" ? prompt.replace(/\s+/g, " ").trim().slice(0, TASK_TITLE_MAX_LENGTH) || void 0 : void 0;
691
+ var asString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
692
+ var NOTIFICATION_ACTION_MAX_LENGTH = 200;
693
+ var STOP_FAILURE_REASON_MAX_LENGTH = 300;
694
+ var notificationTypeOf = (payload) => asString(payload.notification_type) ?? asString(payload.type);
695
+ var notificationAction = (payload) => {
696
+ const text = asString(payload.message) ?? asString(payload.title);
697
+ const collapsed = redactSecrets(text ?? "").replace(/\s+/g, " ").trim();
698
+ return collapsed ? collapsed.slice(0, NOTIFICATION_ACTION_MAX_LENGTH) : "Notification";
699
+ };
700
+ var STOP_FAILURE_REASONS = {
701
+ rate_limit: "Rate limited by the API",
702
+ overloaded: "The API is overloaded right now",
703
+ authentication_failed: "Authentication failed",
704
+ billing_error: "A billing error stopped the turn"
705
+ };
706
+ var STOP_FAILURE_FALLBACK = "The turn ended on an API error (rate limit, billing, or auth).";
707
+ var describeStopFailure = (error) => {
708
+ const fromString = (value) => {
709
+ const trimmed = value.trim();
710
+ if (!trimmed) return void 0;
711
+ return STOP_FAILURE_REASONS[trimmed] ?? redactSecrets(trimmed).slice(0, STOP_FAILURE_REASON_MAX_LENGTH);
712
+ };
713
+ if (typeof error === "string") {
714
+ const described = fromString(error);
715
+ if (described) return described;
716
+ } else if (error && typeof error === "object") {
717
+ for (const key of ["type", "message", "error", "code"]) {
718
+ const value = error[key];
719
+ if (typeof value === "string") {
720
+ const described = fromString(value);
721
+ if (described) return described;
722
+ }
723
+ }
724
+ }
725
+ return STOP_FAILURE_FALLBACK;
726
+ };
727
+ var permissionDeniedAction = (toolName) => {
728
+ const tool = asString(toolName);
729
+ return tool ? `Auto-mode blocked ${tool}` : "Auto-mode blocked a tool call";
730
+ };
731
+ var sessionIdentityOf = (payload) => {
732
+ const sessionId = asString(payload.session_id ?? payload.sessionId ?? payload.conversation_id);
733
+ const agentId = asString(payload.agent_id);
734
+ if (!agentId) return { sessionId };
735
+ return { sessionId: agentId, parentSessionId: sessionId };
736
+ };
737
+
738
+ // ../hook-mapping/src/codex-rollout.ts
739
+ var CODEX_TURN_CONTEXT_TYPE = "turn_context";
740
+ var CODEX_AUTO_REVIEWER = "auto_review";
741
+ var CODEX_GUARDIAN_THREAD_SOURCE = "guardian_review";
742
+ var CODEX_PLANNED_ACTION_MARKER = "Planned action JSON:\n";
743
+ var CODEX_APPROVAL_REQUEST_END = ">>> APPROVAL REQUEST END";
744
+ var CODEX_SHELL_TOOL = "exec_command";
745
+ var CODEX_SHELL_COMMAND_FLAGS = ["-lc", "-c"];
746
+ var asRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
747
+ var parseRecord = (text) => {
748
+ try {
749
+ return asRecord(JSON.parse(text));
750
+ } catch {
751
+ return null;
752
+ }
753
+ };
754
+ var nonEmptyString = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
755
+ var isTimestamp = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
756
+ var readCodexTurnContext = (line) => {
757
+ const entry = parseRecord(line);
758
+ if (entry?.type !== CODEX_TURN_CONTEXT_TYPE) return null;
759
+ const payload = asRecord(entry.payload);
760
+ const turnId = nonEmptyString(payload?.turn_id);
761
+ if (!payload || !turnId) return null;
762
+ return {
763
+ turnId,
764
+ approvalsReviewer: payload.approvals_reviewer === CODEX_AUTO_REVIEWER ? "auto_review" : "user",
765
+ ...isTimestamp(entry.timestamp) ? { startedAt: entry.timestamp } : {}
766
+ };
767
+ };
768
+ var isCodexGuardianRolloutHead = (line, sessionId) => {
769
+ if (!sessionId) return false;
770
+ const entry = parseRecord(line);
771
+ const payload = asRecord(entry?.payload);
772
+ return entry?.type === "session_meta" && payload?.parent_thread_id === sessionId && payload.thread_source === CODEX_GUARDIAN_THREAD_SOURCE;
773
+ };
774
+ var readCodexPlannedAction = (message) => {
775
+ const marker = message.lastIndexOf(CODEX_PLANNED_ACTION_MARKER);
776
+ if (marker < 0) return null;
777
+ const body = message.slice(marker + CODEX_PLANNED_ACTION_MARKER.length);
778
+ const end = body.lastIndexOf(CODEX_APPROVAL_REQUEST_END);
779
+ if (end < 0 || body.slice(end + CODEX_APPROVAL_REQUEST_END.length).trim().length > 0) return null;
780
+ return parseRecord(body.slice(0, end).trim());
781
+ };
782
+ var readCodexReviewVerdict = (message) => {
783
+ const verdict = parseRecord(message.trim());
784
+ if (verdict?.outcome !== "allow" && verdict?.outcome !== "deny") return null;
785
+ const rationale = typeof verdict.rationale === "string" ? verdict.rationale.trim() : "";
786
+ return { outcome: verdict.outcome, ...rationale ? { rationale } : {} };
787
+ };
788
+ var scanCodexGuardianReviews = (lines) => {
789
+ const reviews = [];
790
+ let pending = null;
791
+ for (const line of lines) {
792
+ const entry = parseRecord(line.text);
793
+ const payload = asRecord(entry?.payload);
794
+ if (entry?.type !== "event_msg" || typeof payload?.message !== "string") continue;
795
+ if (payload.type === "user_message") {
796
+ const plannedAction = readCodexPlannedAction(payload.message);
797
+ pending = plannedAction ? { offset: line.offset, plannedAction, ...isTimestamp(entry.timestamp) ? { requestedAt: entry.timestamp } : {} } : null;
798
+ continue;
799
+ }
800
+ if (payload.type !== "agent_message" || !pending) continue;
801
+ const verdict = readCodexReviewVerdict(payload.message);
802
+ if (!verdict) continue;
803
+ reviews.push({ ...pending, verdict });
804
+ pending = null;
805
+ }
806
+ return { reviews, ...pending ? { pendingOffset: pending.offset } : {} };
807
+ };
808
+ var codexPlannedShellCommand = (plannedAction) => {
809
+ if (plannedAction?.tool !== CODEX_SHELL_TOOL) return null;
810
+ const argv = plannedAction.command;
811
+ if (!Array.isArray(argv) || argv.length !== 3) return null;
812
+ const [shell, flag, command] = argv;
813
+ if (typeof shell !== "string" || shell.trim().length === 0) return null;
814
+ if (typeof flag !== "string" || !CODEX_SHELL_COMMAND_FLAGS.includes(flag)) return null;
815
+ if (typeof command !== "string" || command.trim().length === 0) return null;
816
+ const cwd = nonEmptyString(plannedAction.cwd);
817
+ return { command, ...cwd ? { cwd } : {} };
818
+ };
819
+ var codexReviewerDenialsSince = (reviews, since) => {
820
+ const sinceMs = Date.parse(since);
821
+ if (!Number.isFinite(sinceMs)) return [];
822
+ return reviews.flatMap((review) => {
823
+ if (review.verdict.outcome !== "deny" || !review.requestedAt) return [];
824
+ if (Date.parse(review.requestedAt) < sinceMs) return [];
825
+ const shell = codexPlannedShellCommand(review.plannedAction);
826
+ if (!shell) return [];
827
+ return [{
828
+ ...shell,
829
+ offset: review.offset,
830
+ requestedAt: review.requestedAt,
831
+ plannedAction: review.plannedAction,
832
+ ...review.verdict.rationale ? { rationale: review.verdict.rationale } : {}
833
+ }];
834
+ });
835
+ };
836
+
199
837
  // src/pending.ts
200
838
  import { join as join2 } from "path";
201
839
  import { homedir, tmpdir as tmpdir2 } from "os";
@@ -369,11 +1007,11 @@ var removePendingSession = (sessionId) => {
369
1007
 
370
1008
  // src/repo.ts
371
1009
  import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
372
- import { basename, dirname, isAbsolute, join as join3, resolve } from "path";
1010
+ import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
373
1011
  var MAX_PARENT_WALK = 64;
374
1012
  var GIT_FILE_PREFIX = "gitdir:";
375
1013
  var findGitDir = (startDir) => {
376
- let current = resolve(startDir);
1014
+ let current = resolve2(startDir);
377
1015
  for (let depth = 0; depth < MAX_PARENT_WALK; depth += 1) {
378
1016
  const candidate = join3(current, ".git");
379
1017
  try {
@@ -382,7 +1020,7 @@ var findGitDir = (startDir) => {
382
1020
  const pointer = readFileSync3(candidate, "utf-8").trim();
383
1021
  if (pointer.startsWith(GIT_FILE_PREFIX)) {
384
1022
  const target = pointer.slice(GIT_FILE_PREFIX.length).trim();
385
- return isAbsolute(target) ? target : resolve(current, target);
1023
+ return isAbsolute2(target) ? target : resolve2(current, target);
386
1024
  }
387
1025
  }
388
1026
  } catch {
@@ -428,9 +1066,9 @@ var deriveRepoKey = (cwd) => {
428
1066
  const normalized = remote ? normalizeRepoRemote(remote) : void 0;
429
1067
  if (normalized) return normalized;
430
1068
  const root = gitDir.endsWith(".git") ? dirname(gitDir) : dir;
431
- return localRepoKey(basename(root));
1069
+ return localRepoKey(basename2(root));
432
1070
  }
433
- return localRepoKey(basename(resolve(dir)));
1071
+ return localRepoKey(basename2(resolve2(dir)));
434
1072
  } catch {
435
1073
  return void 0;
436
1074
  }
@@ -638,7 +1276,7 @@ var readNewUsage = (transcriptPath, sessionId) => {
638
1276
  };
639
1277
 
640
1278
  // src/codex-adapter.ts
641
- import { resolve as resolve2 } from "path";
1279
+ import { resolve as resolve3 } from "path";
642
1280
  var CODEX_AGENT = { type: "codex", label: "Codex" };
643
1281
  var codexAllow = () => ({ kind: "allow" });
644
1282
  var codexDeny = (reason) => ({ kind: "deny", reason });
@@ -667,7 +1305,7 @@ var toPolicyLookup = (toolName, toolInput, cwd) => {
667
1305
  const command = toolInput.command;
668
1306
  const files = parseApplyPatchFiles(command);
669
1307
  if (files.length === 1) {
670
- return { tool: "Edit", input: { file_path: cwd ? resolve2(cwd, files[0]) : files[0] } };
1308
+ return { tool: "Edit", input: { file_path: cwd ? resolve3(cwd, files[0]) : files[0] } };
671
1309
  }
672
1310
  return { tool: "Edit", input: typeof command === "string" ? { file_path: command } : {} };
673
1311
  };
@@ -683,7 +1321,7 @@ var toPolicyLookups = (toolName, toolInput, cwd) => {
683
1321
  }
684
1322
  return {
685
1323
  tool: "Edit",
686
- inputs: files.map((file) => ({ file_path: cwd ? resolve2(cwd, file) : file }))
1324
+ inputs: files.map((file) => ({ file_path: cwd ? resolve3(cwd, file) : file }))
687
1325
  };
688
1326
  };
689
1327
  var permissionTimeoutDecision = (timeoutAction) => {
@@ -722,7 +1360,7 @@ var throttlePass = (key, windowMs) => {
722
1360
  };
723
1361
 
724
1362
  // src/events.ts
725
- import { basename as basename2, join as join6 } from "path";
1363
+ import { basename as basename3, join as join6 } from "path";
726
1364
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
727
1365
  import { tmpdir as tmpdir5 } from "os";
728
1366
  var INTENT_DIR = join6(tmpdir5(), "pushary-intent");
@@ -1008,7 +1646,7 @@ var handlePostToolUse = async (input, agent = CLAUDE_CODE_AGENT) => {
1008
1646
  const deadline = Date.now() + HOOK_BUDGET_MS;
1009
1647
  const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
1010
1648
  try {
1011
- const projectName = basename2(input.cwd ?? process.cwd());
1649
+ const projectName = basename3(input.cwd ?? process.cwd());
1012
1650
  const action = describeToolCall(input.tool_name, input.tool_input, "event");
1013
1651
  const lookup = toPolicyLookup(input.tool_name, input.tool_input);
1014
1652
  const toolResult = input.tool_response ?? input.tool_result;
@@ -1068,7 +1706,7 @@ var handleUserPrompt = async (input, agent = CLAUDE_CODE_AGENT) => {
1068
1706
  const deadline = Date.now() + HOOK_BUDGET_MS;
1069
1707
  const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
1070
1708
  try {
1071
- const projectName = basename2(input.cwd ?? process.cwd());
1709
+ const projectName = basename3(input.cwd ?? process.cwd());
1072
1710
  const sessionKey = input.session_id || DEFAULT_SESSION;
1073
1711
  const titlesEnabled = process.env.PUSHARY_TASK_TITLES !== "off";
1074
1712
  const taskTitle = titlesEnabled ? taskTitleFrom(input.prompt) : void 0;
@@ -1100,7 +1738,7 @@ var handleStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1100
1738
  const deadline = Date.now() + HOOK_BUDGET_MS;
1101
1739
  const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
1102
1740
  try {
1103
- const projectName = basename2(input.cwd ?? process.cwd());
1741
+ const projectName = basename3(input.cwd ?? process.cwd());
1104
1742
  const sessionKey = input.session_id || DEFAULT_SESSION;
1105
1743
  const late = await reconcilePendingQuestions(sessionKey, void 0, signal);
1106
1744
  const tasks = [
@@ -1135,7 +1773,7 @@ var isSubagentStop = (input) => input.hook_event_name === "SubagentStop" || type
1135
1773
  var SUBAGENT_STOP_REPORT_TIMEOUT_MS = 4e3;
1136
1774
  var handleSubagentStop = async (input, agent = CLAUDE_CODE_AGENT) => {
1137
1775
  try {
1138
- const projectName = basename2(input.cwd ?? process.cwd());
1776
+ const projectName = basename3(input.cwd ?? process.cwd());
1139
1777
  await reportEvent({
1140
1778
  event: "session_end",
1141
1779
  agentType: agent.type,
@@ -1153,7 +1791,7 @@ var isCompactionEvent = (hookEventName) => hookEventName === "PreCompact" || hoo
1153
1791
  var COMPACTION_REPORT_TIMEOUT_MS = 4e3;
1154
1792
  var handleCompaction = async (input, agent = CLAUDE_CODE_AGENT) => {
1155
1793
  try {
1156
- const projectName = basename2(input.cwd ?? process.cwd());
1794
+ const projectName = basename3(input.cwd ?? process.cwd());
1157
1795
  await reportEvent({
1158
1796
  event: "compaction",
1159
1797
  agentType: agent.type,
@@ -1166,7 +1804,7 @@ var handleCompaction = async (input, agent = CLAUDE_CODE_AGENT) => {
1166
1804
  };
1167
1805
  var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
1168
1806
  try {
1169
- const projectName = basename2(input.cwd ?? process.cwd());
1807
+ const projectName = basename3(input.cwd ?? process.cwd());
1170
1808
  const action = input.agent_id ? "Subagent started" : sessionStartAction(input.source);
1171
1809
  await reportEvent({
1172
1810
  event: "session_start",
@@ -1180,7 +1818,7 @@ var handleSessionStart = async (input, agent = CLAUDE_CODE_AGENT) => {
1180
1818
  };
1181
1819
  var handleSessionEnd = async (input, agent = CLAUDE_CODE_AGENT, options = {}) => {
1182
1820
  try {
1183
- const projectName = basename2(input.cwd ?? process.cwd());
1821
+ const projectName = basename3(input.cwd ?? process.cwd());
1184
1822
  const action = sessionEndAction(input.reason);
1185
1823
  const sessionKey = input.session_id || DEFAULT_SESSION;
1186
1824
  if (options.reconcile ?? true) {
@@ -1207,7 +1845,7 @@ var NOTIFICATION_THROTTLE_MS = 60 * 1e3;
1207
1845
  var handleNotification = async (input, agent = CLAUDE_CODE_AGENT) => {
1208
1846
  const signal = AbortSignal.timeout(HOOK_BUDGET_MS);
1209
1847
  try {
1210
- const projectName = basename2(input.cwd ?? process.cwd());
1848
+ const projectName = basename3(input.cwd ?? process.cwd());
1211
1849
  const notifType = notificationTypeOf(input) ?? "";
1212
1850
  const sessionKey = input.session_id || DEFAULT_SESSION;
1213
1851
  const teammateIdle = input.hook_event_name === "TeammateIdle";
@@ -1268,7 +1906,7 @@ var handleStopFailure = async (input, agent = CLAUDE_CODE_AGENT) => {
1268
1906
  } catch {
1269
1907
  return;
1270
1908
  }
1271
- const projectName = basename2(input.cwd ?? process.cwd());
1909
+ const projectName = basename3(input.cwd ?? process.cwd());
1272
1910
  const reason = describeStopFailure(input.error);
1273
1911
  await reportEvent({
1274
1912
  event: "turn_error",
@@ -1303,6 +1941,23 @@ export {
1303
1941
  fetchModeState,
1304
1942
  fetchModeOverride,
1305
1943
  phoneDrivenEnv,
1944
+ HOOK_FALLBACK_MAX,
1945
+ UNNAMED_ACTION,
1946
+ describeToolCall,
1947
+ deriveToolTarget,
1948
+ parseApplyPatchFiles,
1949
+ OPENCODE_TOOL_NAMES,
1950
+ canonicalGateInputs,
1951
+ scopePathFor,
1952
+ deriveActionBody,
1953
+ deriveAction,
1954
+ deriveBlocker,
1955
+ permissionDeniedAction,
1956
+ sessionIdentityOf,
1957
+ readCodexTurnContext,
1958
+ isCodexGuardianRolloutHead,
1959
+ scanCodexGuardianReviews,
1960
+ codexReviewerDenialsSince,
1306
1961
  DEFAULT_SESSION,
1307
1962
  readPendingCommandById,
1308
1963
  readPendingCommand,