hook notifs
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import * as os from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import type { ExtensionAPI, ExtensionContext, ToolResultEvent } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
const HOOK_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
const LOG_FILE = join(os.homedir(), ".pi", "hooks-resolution.log");
|
||||
|
||||
type HookEventName = "PostToolUse" | "PostToolUseFailure";
|
||||
|
||||
@@ -30,18 +28,6 @@ type CommandRunResult = {
|
||||
timedOut: boolean;
|
||||
};
|
||||
|
||||
function logHook(message: string): void {
|
||||
const line = `[${new Date().toISOString()}] ${message}`;
|
||||
console.error(`[hooks-resolution] ${line}`);
|
||||
|
||||
try {
|
||||
mkdirSync(dirname(LOG_FILE), { recursive: true });
|
||||
appendFileSync(LOG_FILE, `${line}\n`);
|
||||
} catch {
|
||||
// ignore logging failures
|
||||
}
|
||||
}
|
||||
|
||||
function isFile(path: string): boolean {
|
||||
try {
|
||||
return statSync(path).isFile();
|
||||
@@ -471,6 +457,23 @@ function hookEventNameForResult(event: ToolResultEvent): HookEventName {
|
||||
return event.isError ? "PostToolUseFailure" : "PostToolUse";
|
||||
}
|
||||
|
||||
function formatDuration(elapsedMs: number): string {
|
||||
if (elapsedMs < 1000) {
|
||||
return `${elapsedMs}ms`;
|
||||
}
|
||||
return `${(elapsedMs / 1000).toFixed(1)}s`;
|
||||
}
|
||||
|
||||
function hookName(command: string): string {
|
||||
const shPathMatch = command.match(/[^\s|;&]+\.sh\b/);
|
||||
if (shPathMatch) {
|
||||
return basename(shPathMatch[0]);
|
||||
}
|
||||
|
||||
const firstToken = command.trim().split(/\s+/)[0] ?? "hook";
|
||||
return basename(firstToken);
|
||||
}
|
||||
|
||||
export default function(pi: ExtensionAPI) {
|
||||
let state: HookState = {
|
||||
projectDir: process.cwd(),
|
||||
@@ -479,13 +482,6 @@ export default function(pi: ExtensionAPI) {
|
||||
|
||||
const refreshHooks = (cwd: string) => {
|
||||
state = loadHooks(cwd);
|
||||
const postCount = state.hooks.filter((hook) => hook.eventName === "PostToolUse").length;
|
||||
const failureCount = state.hooks.filter(
|
||||
(hook) => hook.eventName === "PostToolUseFailure",
|
||||
).length;
|
||||
logHook(
|
||||
`loaded hooks projectDir=${state.projectDir} postToolUse=${postCount} postToolUseFailure=${failureCount}`,
|
||||
);
|
||||
};
|
||||
|
||||
pi.on("session_start", (_event, ctx) => {
|
||||
@@ -514,31 +510,26 @@ export default function(pi: ExtensionAPI) {
|
||||
|
||||
for (const hook of matchingHooks) {
|
||||
if (executedCommands.has(hook.command)) {
|
||||
logHook(
|
||||
`deduped event=${eventName} tool=${event.toolName} source=${hook.source} command=${JSON.stringify(hook.command)}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
executedCommands.add(hook.command);
|
||||
|
||||
logHook(
|
||||
`run event=${eventName} tool=${event.toolName} matcher=${hook.matcherText ?? "*"} source=${hook.source} command=${JSON.stringify(hook.command)}`,
|
||||
);
|
||||
|
||||
const result = await runCommandHook(hook.command, state.projectDir, payload);
|
||||
logHook(
|
||||
`done event=${eventName} tool=${event.toolName} code=${result.code} durationMs=${result.elapsedMs} timedOut=${result.timedOut}`,
|
||||
);
|
||||
const name = hookName(hook.command);
|
||||
const duration = formatDuration(result.elapsedMs);
|
||||
|
||||
if (result.code !== 0) {
|
||||
const matcherLabel = hook.matcherText ?? "*";
|
||||
const errorLine =
|
||||
result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
|
||||
ctx.ui.notify(
|
||||
`Hook failed (${matcherLabel}) from ${hook.source}: ${errorLine}`,
|
||||
"warning",
|
||||
);
|
||||
if (result.code === 0) {
|
||||
ctx.ui.notify(` Hook \`${name}\` executed, took ${duration}`, "info");
|
||||
continue;
|
||||
}
|
||||
|
||||
const matcherLabel = hook.matcherText ?? "*";
|
||||
const errorLine =
|
||||
result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
|
||||
ctx.ui.notify(
|
||||
` Hook \`${name}\` failed after ${duration} (${matcherLabel}) from ${hook.source}: ${errorLine}`,
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user