feat(KB-150): kill active agent sessions on global pause
- Add settings:updated event to TaskStore with previous/new settings payload - Kill all active executor agent sessions when globalPause transitions false→true - Kill all active triage specification sessions on global pause with clean status reset - Track and dispose active merger session on global pause via onSession callback - Add JSDoc documenting global pause behavior on executor and triage constructors
This commit is contained in:
@@ -184,12 +184,36 @@ export class TriageProcessor {
|
||||
private activePollMs: number | null = null;
|
||||
private processing = new Set<string>();
|
||||
private wasGlobalPaused = false;
|
||||
/** Active agent sessions per task, used to terminate on global pause. */
|
||||
private activeSessions = new Map<string, { dispose: () => void }>();
|
||||
/** Tasks that were aborted due to global pause (to avoid reporting as errors). */
|
||||
private globalPauseAborted = new Set<string>();
|
||||
|
||||
/**
|
||||
* @param store — Task store instance (also used to listen for `settings:updated` events)
|
||||
* @param rootDir — Project root directory
|
||||
* @param options — Processor configuration
|
||||
*
|
||||
* Listens for `settings:updated` events: when `globalPause` transitions from
|
||||
* `false` to `true`, all active triage specification sessions are immediately
|
||||
* terminated so the engine acts as a true emergency stop.
|
||||
*/
|
||||
constructor(
|
||||
private store: TaskStore,
|
||||
private rootDir: string,
|
||||
private options: TriageProcessorOptions = {},
|
||||
) {}
|
||||
) {
|
||||
// When globalPause transitions from false → true, terminate all active triage sessions.
|
||||
store.on("settings:updated", ({ settings, previous }) => {
|
||||
if (settings.globalPause && !previous.globalPause) {
|
||||
for (const [taskId, session] of this.activeSessions) {
|
||||
triageLog.log(`Global pause — terminating triage session for ${taskId}`);
|
||||
this.globalPauseAborted.add(taskId);
|
||||
session.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start(): void {
|
||||
if (this.running) return;
|
||||
@@ -305,6 +329,9 @@ export class TriageProcessor {
|
||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||
});
|
||||
|
||||
// Register session so the global pause listener can terminate it
|
||||
this.activeSessions.set(task.id, session);
|
||||
|
||||
try {
|
||||
// Read attachment contents for inlining in prompt
|
||||
const { attachmentContents, imageContents } = await readAttachmentContents(
|
||||
@@ -355,6 +382,7 @@ export class TriageProcessor {
|
||||
this.options.onSpecifyComplete?.(task);
|
||||
}
|
||||
} finally {
|
||||
this.activeSessions.delete(task.id);
|
||||
await agentLogger.flush();
|
||||
session.dispose();
|
||||
}
|
||||
@@ -370,6 +398,11 @@ export class TriageProcessor {
|
||||
// and specifyTask(). The file is gone, so just log and skip — no point retrying.
|
||||
if (err.code === "ENOENT") {
|
||||
triageLog.log(`${task.id} no longer exists — skipping`);
|
||||
} else if (this.globalPauseAborted.has(task.id)) {
|
||||
// Global pause — clear specifying status without reporting an error
|
||||
this.globalPauseAborted.delete(task.id);
|
||||
triageLog.log(`${task.id} aborted by global pause — clearing status`);
|
||||
await this.store.updateTask(task.id, { status: null }).catch(() => {});
|
||||
} else {
|
||||
// Check if the error is a usage-limit error and trigger global pause
|
||||
if (this.options.usageLimitPauser && isUsageLimitError(err.message)) {
|
||||
|
||||
Reference in New Issue
Block a user