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:
Dustin Byrne
2026-03-28 02:16:30 -04:00
parent 72a8953e4e
commit d32c9c031f
9 changed files with 443 additions and 5 deletions

View File

@@ -974,4 +974,52 @@ describe("TaskStore", () => {
expect(updated.columnMovedAt).toBe(originalMovedAt);
});
});
describe("settings:updated event", () => {
it("fires on updateSettings with correct old and new values", async () => {
const events: { settings: any; previous: any }[] = [];
store.on("settings:updated", (data) => events.push(data));
await store.updateSettings({ maxConcurrent: 5 });
expect(events).toHaveLength(1);
expect(events[0].previous.maxConcurrent).toBe(2); // DEFAULT_SETTINGS value
expect(events[0].settings.maxConcurrent).toBe(5);
});
it("includes previous globalPause: false → new globalPause: true when toggled", async () => {
const events: { settings: any; previous: any }[] = [];
store.on("settings:updated", (data) => events.push(data));
// Default globalPause is false
await store.updateSettings({ globalPause: true });
expect(events).toHaveLength(1);
expect(events[0].previous.globalPause).toBe(false);
expect(events[0].settings.globalPause).toBe(true);
});
it("includes previous globalPause: true → new globalPause: false when toggled off", async () => {
await store.updateSettings({ globalPause: true });
const events: { settings: any; previous: any }[] = [];
store.on("settings:updated", (data) => events.push(data));
await store.updateSettings({ globalPause: false });
expect(events).toHaveLength(1);
expect(events[0].previous.globalPause).toBe(true);
expect(events[0].settings.globalPause).toBe(false);
});
it("fires on every updateSettings call even when value unchanged", async () => {
const events: { settings: any; previous: any }[] = [];
store.on("settings:updated", (data) => events.push(data));
await store.updateSettings({ maxConcurrent: 2 });
await store.updateSettings({ maxConcurrent: 2 });
expect(events).toHaveLength(2);
});
});
});

View File

@@ -12,6 +12,7 @@ export interface TaskStoreEvents {
"task:updated": [task: Task];
"task:deleted": [task: Task];
"task:merged": [result: MergeResult];
"settings:updated": [data: { settings: Settings; previous: Settings }];
"agent:log": [entry: AgentLogEntry];
}
@@ -130,10 +131,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
async updateSettings(patch: Partial<Settings>): Promise<Settings> {
return this.withConfigLock(async () => {
const config = await this.readConfig();
const current = { ...DEFAULT_SETTINGS, ...config.settings };
const updated = { ...current, ...patch };
const previous = { ...DEFAULT_SETTINGS, ...config.settings };
const updated = { ...previous, ...patch };
config.settings = updated;
await this.writeConfig(config);
this.emit("settings:updated", { settings: updated, previous });
return updated;
});
}