takt/src/features/tasks/execute/abortHandler.ts
nrslib 47612d9dcc refactor: agent-usecases / schema-loader の移動と pieceExecution の責務分割
- agent-usecases.ts を core/piece/ → agents/ へ移動
- schema-loader.ts を core/piece/ → infra/resources/ へ移動
- interactive-summary-types.ts を分離、shared/types/ ディレクトリを追加
- pieceExecution.ts を abortHandler / analyticsEmitter / iterationLimitHandler / outputFns / runMeta / sessionLogger に分割
- buildMergeFn を async → sync に変更(custom merge の file 戦略を削除)
- cleanupOrphanedClone にパストラバーサル保護を追加
- review-fix / frontend-review-fix ピースの IT テストを追加
2026-03-02 21:20:50 +09:00

92 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* AbortHandler — abortSignal 監視と割り込み処理専用モジュール
*
* 外部 abortSignal並列実行モードまたは ShutdownManagerシングル実行モード
* どちらかを使い、エンジンの中断処理を担う。
* EPIPE エラーの抑制も担当する。
*/
import { interruptAllQueries } from '../../../infra/claude/query-manager.js';
import { ShutdownManager } from './shutdownManager.js';
import { EXIT_SIGINT } from '../../../shared/exitCodes.js';
import type { PieceEngine } from '../../../core/piece/engine/PieceEngine.js';
export interface AbortHandlerOptions {
/** 外部から渡された AbortSignal並列実行モード */
externalSignal?: AbortSignal;
/** 外部シグナルがない場合に使う内部 AbortController */
internalController: AbortController;
/** 中断時に呼び出す PieceEngine インスタンス(遅延参照) */
getEngine: () => PieceEngine | null;
}
export class AbortHandler {
private readonly options: AbortHandlerOptions;
private shutdownManager: ShutdownManager | undefined;
private onAbortSignal: (() => void) | undefined;
private onEpipe: ((err: NodeJS.ErrnoException) => void) | undefined;
constructor(options: AbortHandlerOptions) {
this.options = options;
}
/**
* 中断ハンドラをインストールする。
* エンジン生成後に呼ぶこと。
*/
install(): void {
const { externalSignal, internalController, getEngine } = this.options;
this.onEpipe = (err: NodeJS.ErrnoException) => {
if (err.code === 'EPIPE') return;
throw err;
};
const abortEngine = () => {
const engine = getEngine();
if (!engine || !this.onEpipe) {
throw new Error('Abort handler invoked before PieceEngine initialization');
}
if (!internalController.signal.aborted) {
internalController.abort();
}
process.on('uncaughtException', this.onEpipe);
interruptAllQueries();
engine.abort();
};
if (externalSignal) {
// 並列実行モード: 外部シグナルへ委譲
this.onAbortSignal = abortEngine;
if (externalSignal.aborted) {
abortEngine();
} else {
externalSignal.addEventListener('abort', this.onAbortSignal, { once: true });
}
} else {
// シングル実行モード: SIGINT を自前でハンドリング
this.shutdownManager = new ShutdownManager({
callbacks: {
onGraceful: abortEngine,
onForceKill: () => process.exit(EXIT_SIGINT),
},
});
this.shutdownManager.install();
}
}
/**
* ハンドラをクリーンアップする。
* finally ブロックで必ず呼ぶこと。
*/
cleanup(): void {
this.shutdownManager?.cleanup();
if (this.onAbortSignal && this.options.externalSignal) {
this.options.externalSignal.removeEventListener('abort', this.onAbortSignal);
}
if (this.onEpipe) {
process.removeListener('uncaughtException', this.onEpipe);
}
}
}