mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 22:03:45 +00:00
Remove hook execution timeouts
- Remove timeout logic from HookRunner - Remove hookTimeout from Settings interface - Remove getHookTimeout/setHookTimeout methods - Update CHANGELOG.md and hooks.md Timeouts were inconsistently applied and caused issues with legitimate slow operations (LLM calls, user prompts). Users can use Ctrl+C to abort hung hooks.
This commit is contained in:
parent
bab343b8bc
commit
88e39471ea
5 changed files with 10 additions and 63 deletions
|
|
@ -25,11 +25,6 @@ import type {
|
|||
ToolResultEventResult,
|
||||
} from "./types.js";
|
||||
|
||||
/**
|
||||
* Default timeout for hook execution (30 seconds).
|
||||
*/
|
||||
const DEFAULT_TIMEOUT = 30000;
|
||||
|
||||
/**
|
||||
* Listener for hook errors.
|
||||
*/
|
||||
|
|
@ -38,20 +33,6 @@ export type HookErrorListener = (error: HookError) => void;
|
|||
// Re-export execCommand for backward compatibility
|
||||
export { execCommand } from "../exec.js";
|
||||
|
||||
/**
|
||||
* Create a promise that rejects after a timeout.
|
||||
*/
|
||||
function createTimeout(ms: number): { promise: Promise<never>; clear: () => void } {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
const promise = new Promise<never>((_, reject) => {
|
||||
timeoutId = setTimeout(() => reject(new Error(`Hook timed out after ${ms}ms`)), ms);
|
||||
});
|
||||
return {
|
||||
promise,
|
||||
clear: () => clearTimeout(timeoutId),
|
||||
};
|
||||
}
|
||||
|
||||
/** No-op UI context used when no UI is available */
|
||||
const noOpUIContext: HookUIContext = {
|
||||
select: async () => undefined,
|
||||
|
|
@ -71,24 +52,16 @@ export class HookRunner {
|
|||
private cwd: string;
|
||||
private sessionManager: SessionManager;
|
||||
private modelRegistry: ModelRegistry;
|
||||
private timeout: number;
|
||||
private errorListeners: Set<HookErrorListener> = new Set();
|
||||
private getModel: () => Model<any> | undefined = () => undefined;
|
||||
|
||||
constructor(
|
||||
hooks: LoadedHook[],
|
||||
cwd: string,
|
||||
sessionManager: SessionManager,
|
||||
modelRegistry: ModelRegistry,
|
||||
timeout: number = DEFAULT_TIMEOUT,
|
||||
) {
|
||||
constructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry) {
|
||||
this.hooks = hooks;
|
||||
this.uiContext = noOpUIContext;
|
||||
this.hasUI = false;
|
||||
this.cwd = cwd;
|
||||
this.sessionManager = sessionManager;
|
||||
this.modelRegistry = modelRegistry;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -262,16 +235,7 @@ export class HookRunner {
|
|||
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
// No timeout for session_before_compact events (like tool_call, they may take a while)
|
||||
let handlerResult: unknown;
|
||||
|
||||
if (event.type === "session_before_compact") {
|
||||
handlerResult = await handler(event, ctx);
|
||||
} else {
|
||||
const timeout = createTimeout(this.timeout);
|
||||
handlerResult = await Promise.race([handler(event, ctx), timeout.promise]);
|
||||
timeout.clear();
|
||||
}
|
||||
const handlerResult = await handler(event, ctx);
|
||||
|
||||
// For session before_* events, capture the result (for cancellation)
|
||||
if (this.isSessionBeforeEvent(event.type) && handlerResult) {
|
||||
|
|
@ -348,9 +312,7 @@ export class HookRunner {
|
|||
for (const handler of handlers) {
|
||||
try {
|
||||
const event: ContextEvent = { type: "context", messages: currentMessages };
|
||||
const timeout = createTimeout(this.timeout);
|
||||
const handlerResult = await Promise.race([handler(event, ctx), timeout.promise]);
|
||||
timeout.clear();
|
||||
const handlerResult = await handler(event, ctx);
|
||||
|
||||
if (handlerResult && (handlerResult as ContextEventResult).messages) {
|
||||
currentMessages = (handlerResult as ContextEventResult).messages!;
|
||||
|
|
@ -387,9 +349,7 @@ export class HookRunner {
|
|||
for (const handler of handlers) {
|
||||
try {
|
||||
const event: BeforeAgentStartEvent = { type: "before_agent_start", prompt, images };
|
||||
const timeout = createTimeout(this.timeout);
|
||||
const handlerResult = await Promise.race([handler(event, ctx), timeout.promise]);
|
||||
timeout.clear();
|
||||
const handlerResult = await handler(event, ctx);
|
||||
|
||||
// Take the first message returned
|
||||
if (handlerResult && (handlerResult as BeforeAgentStartEventResult).message && !result) {
|
||||
|
|
|
|||
|
|
@ -313,7 +313,6 @@ export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|||
shellPath: manager.getShellPath(),
|
||||
collapseChangelog: manager.getCollapseChangelog(),
|
||||
hooks: manager.getHookPaths(),
|
||||
hookTimeout: manager.getHookTimeout(),
|
||||
customTools: manager.getCustomToolPaths(),
|
||||
skills: manager.getSkillsSettings(),
|
||||
terminal: { showImages: manager.getShowImages() },
|
||||
|
|
@ -536,7 +535,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
if (options.hooks !== undefined) {
|
||||
if (options.hooks.length > 0) {
|
||||
const loadedHooks = createLoadedHooksFromDefinitions(options.hooks);
|
||||
hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry, settingsManager.getHookTimeout());
|
||||
hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
|
||||
}
|
||||
} else {
|
||||
// Discover hooks, merging with additional paths
|
||||
|
|
@ -547,7 +546,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
console.error(`Failed to load hook "${path}": ${error}`);
|
||||
}
|
||||
if (hooks.length > 0) {
|
||||
hookRunner = new HookRunner(hooks, cwd, sessionManager, modelRegistry, settingsManager.getHookTimeout());
|
||||
hookRunner = new HookRunner(hooks, cwd, sessionManager, modelRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ export interface Settings {
|
|||
shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)
|
||||
collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)
|
||||
hooks?: string[]; // Array of hook file paths
|
||||
hookTimeout?: number; // Timeout for hook execution in ms (default: 30000)
|
||||
customTools?: string[]; // Array of custom tool file paths
|
||||
skills?: SkillsSettings;
|
||||
terminal?: TerminalSettings;
|
||||
|
|
@ -322,15 +321,6 @@ export class SettingsManager {
|
|||
this.save();
|
||||
}
|
||||
|
||||
getHookTimeout(): number {
|
||||
return this.settings.hookTimeout ?? 30000;
|
||||
}
|
||||
|
||||
setHookTimeout(timeout: number): void {
|
||||
this.globalSettings.hookTimeout = timeout;
|
||||
this.save();
|
||||
}
|
||||
|
||||
getCustomToolPaths(): string[] {
|
||||
return [...(this.settings.customTools ?? [])];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue