Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/hooks/task-session-manager/revived-run-tracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function createHarness(
messages: () => unknown,
prompt = mock(async () => ({})),
assertBound = false,
shouldManageSession?: (sessionID: string) => boolean,
) {
const board = new BackgroundJobBoard();
board.registerLaunch({
Expand Down Expand Up @@ -59,6 +60,7 @@ function createHarness(
notificationRetryDelayMs: 0,
onSettled: settled,
pruneContext: pruned,
shouldManageSession,
});
return {
board,
Expand Down Expand Up @@ -322,6 +324,31 @@ describe('revived run tracker', () => {
expect(harness.prompt).toHaveBeenCalledTimes(1);
});

test('skips parent notification when shouldManageSession returns false', async () => {
const harness = createHarness(
() => ({ data: [] }),
undefined,
false,
() => false,
);
harness.tracker.register({
taskID: harness.run.taskID,
generation: harness.run.generation,
parentSessionID: 'parent',
description: 'inspect the change',
});
const terminal = harness.board.updateStatus({
taskID: harness.run.taskID,
expectedGeneration: harness.run.generation,
state: 'completed',
resultSummary: 'done',
});
if (!terminal) throw new Error('missing terminal record');
harness.tracker.onTerminal(terminal);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(harness.prompt).toHaveBeenCalledTimes(0);
});

test('discards a retry when the task generation is relaunched', async () => {
const prompt = mock(async () => {
throw new Error('parent unavailable');
Expand Down
6 changes: 6 additions & 0 deletions src/hooks/task-session-manager/revived-run-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function createRevivedRunTracker(options: {
onSettled?: (taskID: string) => void;
contextFilesForPrompt?: (taskID: string) => ContextFile[];
pruneContext?: () => void;
shouldManageSession?: (sessionID: string) => boolean;
}): RevivedRunTracker {
const runs = new Map<string, RevivedRun>();
const maxNotificationRetries =
Expand Down Expand Up @@ -230,6 +231,11 @@ export function createRevivedRunTracker(options: {
record: BackgroundJobRecord,
): Promise<void> {
if (disposed || run.notification.sent || run.notification.pending) return;
if (
options.shouldManageSession &&
!options.shouldManageSession(run.parentSessionID)
)
return;
Comment on lines +234 to +238

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing metadata drops notifications

When an idle orchestrator parent is evicted from the bounded session-metadata store while its task runs, shouldManageSession returns false and this new gate permanently drops the task's terminal completion or error notification. Missing metadata is recoverable rather than an authoritative mode switch, as sibling orchestrator gates restore stale mappings through registerSessionAsOrchestrator.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +234 to +238

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Suppressed runs remain tracked

When this gate suppresses a terminal notification, it returns without discarding the completed run, retaining its RevivedRun entry until replacement or plugin disposal and causing unnecessary lifetime memory growth.

Suggested change
if (
options.shouldManageSession &&
!options.shouldManageSession(run.parentSessionID)
)
return;
if (
options.shouldManageSession &&
!options.shouldManageSession(run.parentSessionID)
) {
discardRun(run);
return;
}

run.notification.pending = true;
run.notification.attempts += 1;
try {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ export const OhMyOpenCodeLite: Plugin = async (ctx) => {
onSettled: (taskID) => markRevivedRunSettled(taskID),
contextFilesForPrompt: (taskID) => getRevivedContextFiles(taskID),
pruneContext: () => pruneRevivedContext(),
shouldManageSession: (sessionID) =>
sessionMetadata.getAgent(sessionID) === 'orchestrator',
});
backgroundJobCoordinator.addTerminalOutcomeListener((record) => {
revivedRunTracker.onTerminal(record);
Expand Down
Loading