mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 04:01:56 +00:00
Refactor: shared exec utility, rename CustomMessageRenderer to HookMessageRenderer
- Extract execCommand to src/core/exec.ts, shared by hooks and custom-tools - Rename CustomMessageRenderer -> HookMessageRenderer - Rename registerCustomMessageRenderer -> registerMessageRenderer - Renderer now receives HookMessage instead of CustomMessageEntry - CustomMessageComponent now has setExpanded() and responds to Ctrl+E toggle - Re-export ExecOptions/ExecResult from exec.ts for backward compatibility
This commit is contained in:
parent
5fee9005b7
commit
a8866d7a83
12 changed files with 199 additions and 261 deletions
|
|
@ -2,17 +2,14 @@
|
|||
* Hook runner - executes hooks and manages their lifecycle.
|
||||
*/
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import type { ModelRegistry } from "../model-registry.js";
|
||||
import type { SessionManager } from "../session-manager.js";
|
||||
import type { AppendEntryHandler, LoadedHook, SendMessageHandler } from "./loader.js";
|
||||
import type {
|
||||
CustomMessageRenderer,
|
||||
ExecOptions,
|
||||
ExecResult,
|
||||
HookError,
|
||||
HookEvent,
|
||||
HookEventContext,
|
||||
HookMessageRenderer,
|
||||
HookUIContext,
|
||||
RegisteredCommand,
|
||||
SessionEvent,
|
||||
|
|
@ -32,78 +29,8 @@ const DEFAULT_TIMEOUT = 30000;
|
|||
*/
|
||||
export type HookErrorListener = (error: HookError) => void;
|
||||
|
||||
/**
|
||||
* Execute a command and return stdout/stderr/code.
|
||||
* Supports cancellation via AbortSignal and timeout.
|
||||
*/
|
||||
export async function execCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
cwd: string,
|
||||
options?: ExecOptions,
|
||||
): Promise<ExecResult> {
|
||||
return new Promise((resolve) => {
|
||||
const proc = spawn(command, args, { cwd, shell: false });
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let killed = false;
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
|
||||
const killProcess = () => {
|
||||
if (!killed) {
|
||||
killed = true;
|
||||
proc.kill("SIGTERM");
|
||||
// Force kill after 5 seconds if SIGTERM doesn't work
|
||||
setTimeout(() => {
|
||||
if (!proc.killed) {
|
||||
proc.kill("SIGKILL");
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle abort signal
|
||||
if (options?.signal) {
|
||||
if (options.signal.aborted) {
|
||||
killProcess();
|
||||
} else {
|
||||
options.signal.addEventListener("abort", killProcess, { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
// Handle timeout
|
||||
if (options?.timeout && options.timeout > 0) {
|
||||
timeoutId = setTimeout(() => {
|
||||
killProcess();
|
||||
}, options.timeout);
|
||||
}
|
||||
|
||||
proc.stdout?.on("data", (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
proc.stderr?.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
proc.on("close", (code) => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
if (options?.signal) {
|
||||
options.signal.removeEventListener("abort", killProcess);
|
||||
}
|
||||
resolve({ stdout, stderr, code: code ?? 0, killed });
|
||||
});
|
||||
|
||||
proc.on("error", (_err) => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
if (options?.signal) {
|
||||
options.signal.removeEventListener("abort", killProcess);
|
||||
}
|
||||
resolve({ stdout, stderr, code: 1, killed });
|
||||
});
|
||||
});
|
||||
}
|
||||
// Re-export execCommand for backward compatibility
|
||||
export { execCommand } from "../exec.js";
|
||||
|
||||
/**
|
||||
* Create a promise that rejects after a timeout.
|
||||
|
|
@ -241,12 +168,12 @@ export class HookRunner {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a custom message renderer for the given customType.
|
||||
* Get a message renderer for the given customType.
|
||||
* Returns the first renderer found across all hooks, or undefined if none.
|
||||
*/
|
||||
getCustomMessageRenderer(customType: string): CustomMessageRenderer | undefined {
|
||||
getMessageRenderer(customType: string): HookMessageRenderer | undefined {
|
||||
for (const hook of this.hooks) {
|
||||
const renderer = hook.customMessageRenderers.get(customType);
|
||||
const renderer = hook.messageRenderers.get(customType);
|
||||
if (renderer) {
|
||||
return renderer;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue