@pushary/agent-hooks 0.99.0 → 1.1.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 +14 -0
- package/data/SKILL.md +250 -49
- package/data/cursor-plugin/scripts/pushary-gate.mjs +24 -0
- package/data/cursor-plugin/skills/pushary/SKILL.md +250 -49
- package/data/vscode-plugin/scripts/pushary-gate.mjs +24 -0
- package/data/vscode-plugin/skills/pushary/SKILL.md +250 -49
- package/dist/bin/pushary-bell-hook.js +18 -18
- package/dist/bin/pushary-bell.js +6 -6
- package/dist/bin/pushary-claude.js +22 -24
- package/dist/bin/pushary-clean.js +60 -18
- package/dist/bin/pushary-codex-bridge.js +10 -10
- package/dist/bin/pushary-codex-hook.js +33 -35
- package/dist/bin/pushary-codex.js +6 -7
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-cowork.js +3 -3
- package/dist/bin/pushary-daemon.js +22 -22
- package/dist/bin/pushary-disconnect.js +48 -7
- package/dist/bin/pushary-doctor.js +88 -19
- package/dist/bin/pushary-elicitation-hook.js +17 -17
- package/dist/bin/pushary-gemini-bridge.js +9 -9
- package/dist/bin/pushary-gemini-hook.js +25 -27
- package/dist/bin/pushary-hook.js +61 -33
- package/dist/bin/pushary-login.js +7 -7
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +26 -21
- package/dist/bin/pushary-opencode-hook.js +27 -29
- package/dist/bin/pushary-permission-denied-hook.js +24 -23
- package/dist/bin/pushary-permission-hook.js +32 -24
- package/dist/bin/pushary-post-hook.js +30 -22
- package/dist/bin/pushary-prompt-hook.js +26 -21
- package/dist/bin/pushary-session-end-hook.js +26 -21
- package/dist/bin/pushary-session-start-hook.js +30 -25
- package/dist/bin/pushary-setup.js +145 -37
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +10 -10
- package/dist/bin/pushary-stop-hook.js +27 -22
- package/dist/bin/pushary-stopfailure-hook.js +26 -21
- package/dist/bin/pushary-transcript-register.js +16 -16
- package/dist/bin/pushary-transcripts.js +4 -4
- package/dist/bin/pushary-upgrade.js +14 -14
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-ID5FDKDZ.js → chunk-2MOQUNFL.js} +1 -1
- package/dist/{chunk-CRFLU455.js → chunk-6JUCFMSF.js} +3 -3
- package/dist/{chunk-QS55LDX4.js → chunk-6T4OINZB.js} +16 -16
- package/dist/{chunk-6C6MS57P.js → chunk-76ZCJZBK.js} +1 -1
- package/dist/{chunk-JXELRCU5.js → chunk-AH5OO2YZ.js} +3 -3
- package/dist/{chunk-XPPXOKIG.js → chunk-BBUZD7MU.js} +1 -1
- package/dist/{chunk-T3JZZUAR.js → chunk-DKTFZCLQ.js} +30 -29
- package/dist/{chunk-RALVI7OF.js → chunk-DZYIAMKR.js} +9 -6
- package/dist/{chunk-GCD3SQSV.js → chunk-E3WQMDCE.js} +1 -1
- package/dist/{chunk-JBJBVERL.js → chunk-EWQZISYV.js} +1 -1
- package/dist/{chunk-LWKPMSBB.js → chunk-EZ3GX3NS.js} +5 -5
- package/dist/{chunk-5KLAETSU.js → chunk-FO4HHPMO.js} +1 -1
- package/dist/{chunk-6KLCP7VF.js → chunk-IH7YUPFN.js} +3 -3
- package/dist/{chunk-OPR2BPD4.js → chunk-INBTKFT6.js} +1 -1
- package/dist/{chunk-K4XFCEVI.js → chunk-IPDBFJ2M.js} +1 -1
- package/dist/{chunk-6IFUXNFX.js → chunk-ISELLVBP.js} +1 -1
- package/dist/{chunk-562UJ2DA.js → chunk-J7PJALS2.js} +1 -1
- package/dist/{chunk-O3LZQEQP.js → chunk-LQ3BHPPZ.js} +1 -1
- package/dist/{chunk-3RVCQDC4.js → chunk-LUYYB6V2.js} +1 -1
- package/dist/{chunk-V262ATS5.js → chunk-MR2PWE62.js} +17 -3
- package/dist/{chunk-WGV3VY7O.js → chunk-MZUXTN7X.js} +2 -2
- package/dist/{chunk-LBNAGGLV.js → chunk-OYYQHJAO.js} +3 -2
- package/dist/{chunk-4C4XFAGJ.js → chunk-PHJOSAXW.js} +2 -2
- package/dist/{chunk-SI7GYBQP.js → chunk-PQG6NTPT.js} +1 -1
- package/dist/{chunk-3ZAEUPM4.js → chunk-PUDQJ772.js} +1 -1
- package/dist/{chunk-NSFNEDCT.js → chunk-PWIMJP4U.js} +6 -6
- package/dist/{chunk-JL33EDN7.js → chunk-QACXZYDA.js} +4 -4
- package/dist/{chunk-CP34FI4B.js → chunk-RZTH4MIY.js} +2 -2
- package/dist/{chunk-HB2KB7QL.js → chunk-S2BNTXP6.js} +1 -1
- package/dist/{chunk-EXHXR2X5.js → chunk-SA6UB3GD.js} +1 -1
- package/dist/{chunk-IRLTW6KF.js → chunk-SLEWDZOA.js} +1 -1
- package/dist/chunk-SM54HHVY.js +224 -0
- package/dist/{chunk-4PBCNVI3.js → chunk-UP7NZXJU.js} +1 -1
- package/dist/{chunk-XOOWC62T.js → chunk-VACFOGF3.js} +1 -1
- package/dist/{chunk-OUAE3GKO.js → chunk-VQYARC2K.js} +279 -1
- package/dist/{chunk-64WARXGV.js → chunk-WTT24AUA.js} +698 -43
- package/dist/{chunk-K6BASBQJ.js → chunk-XIHKSZHL.js} +1 -1
- package/dist/{chunk-PD6JY33P.js → chunk-YM4G6CFN.js} +4 -3
- package/dist/{reapply-HQ264ZTR.js → reapply-STSGYPF2.js} +8 -8
- package/dist/src/index.js +12 -10
- package/package.json +1 -1
- 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-
|
|
4
|
+
} from "./chunk-INBTKFT6.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-
|
|
13
|
+
} from "./chunk-J7PJALS2.js";
|
|
35
14
|
import {
|
|
36
15
|
withRetry
|
|
37
16
|
} from "./chunk-EYRGBYZG.js";
|
|
38
17
|
import {
|
|
39
18
|
reportedInstallSource
|
|
40
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-E3WQMDCE.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-
|
|
44
|
+
} from "./chunk-VQYARC2K.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 =
|
|
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
|
|
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(
|
|
1069
|
+
return localRepoKey(basename2(root));
|
|
432
1070
|
}
|
|
433
|
-
return localRepoKey(
|
|
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
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|