Skip to content

Commit 9ead9ec

Browse files
igorcostaAutohand Evolve
andcommitted
Clear sticky task activity after successful turn completion
Remove completed task and worker rows only after successful final-turn reconciliation, while retaining activity for failed turns. Co-authored-by: Autohand Evolve <code-noreply@autohand.ai>
1 parent a606528 commit 9ead9ec

6 files changed

Lines changed: 64 additions & 7 deletions

File tree

src/core/agent.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ import {
210210
buildAgentSpinnerStatusText,
211211
withPeerLineExtension,
212212
cleanupAgentUI,
213+
clearAgentActivityForCompletedTurn,
213214
clearAgentComposerInput,
214215
ensureAgentSpinnerRunning,
215216
executeAgentImmediateShellCommand,
@@ -1219,6 +1220,10 @@ export class AutohandAgent {
12191220
return this.actionExecutor?.completeTodoActivityForSuccessfulTurn() ?? false;
12201221
}
12211222

1223+
private clearActivityForCompletedTurn(): void {
1224+
return clearAgentActivityForCompletedTurn(this);
1225+
}
1226+
12221227
private createReactLoopHost(): AgentReactLoopHost {
12231228
const agent = this;
12241229

src/core/agent/AgentUIRuntime.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ export function setAgentComposerIdle(host: AgentUIRuntimeHost): void {
376376
host.ui?.setWorking(false);
377377
}
378378

379+
export function clearAgentActivityForCompletedTurn(host: AgentUIRuntimeHost): void {
380+
host.inkRenderer?.clearActivityItems?.();
381+
}
382+
379383
export function clearAgentComposerInput(host: AgentUIRuntimeHost): void {
380384
host.inkRenderer?.clearInput();
381385
host.ui?.clearInput();

src/core/agent/InstructionRunner.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export interface AgentInstructionHost {
160160
printCompletionSummary(regionsStillActive: boolean, succeeded?: boolean): void;
161161
beginTodoActivityTurn?(): void;
162162
completeTodoActivityForSuccessfulTurn?(): Promise<boolean>;
163+
clearActivityForCompletedTurn?(): void;
163164
scheduleTurnMemoryReflection(outcome: TurnMemoryReflectionOutcome): void;
164165
writeDebugLine?(message: string): void;
165166
}
@@ -456,6 +457,7 @@ export class InstructionRunner {
456457
success = await finalizeResearchForTurn(success);
457458
if (success) {
458459
await host.completeTodoActivityForSuccessfulTurn?.();
460+
host.clearActivityForCompletedTurn?.();
459461
}
460462
} catch (error) {
461463
success = false;
@@ -543,6 +545,7 @@ export class InstructionRunner {
543545
success = await finalizeResearchForTurn(success);
544546
if (success) {
545547
await host.completeTodoActivityForSuccessfulTurn?.();
548+
host.clearActivityForCompletedTurn?.();
546549
}
547550
return success;
548551
} catch (retryError) {

tests/core/agent/InstructionRunner.command-mode.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,23 @@ describe('InstructionRunner command mode UI', () => {
223223
expect(host.scheduleTurnMemoryReflection).toHaveBeenCalledWith({ status: 'succeeded' });
224224
});
225225

226+
it('clears sticky activity after a successful turn reaches final completion', async () => {
227+
const host = createHost();
228+
const completeTodoActivityForSuccessfulTurn = vi.fn(async () => true);
229+
const clearActivityForCompletedTurn = vi.fn();
230+
const activityHost = host as AgentInstructionHost & {
231+
completeTodoActivityForSuccessfulTurn: () => Promise<boolean>;
232+
clearActivityForCompletedTurn: () => void;
233+
};
234+
activityHost.completeTodoActivityForSuccessfulTurn = completeTodoActivityForSuccessfulTurn;
235+
activityHost.clearActivityForCompletedTurn = clearActivityForCompletedTurn;
236+
237+
await expect(new InstructionRunner(activityHost).run('finish the active task')).resolves.toBe(true);
238+
239+
expect(completeTodoActivityForSuccessfulTurn).toHaveBeenCalledOnce();
240+
expect(clearActivityForCompletedTurn).toHaveBeenCalledOnce();
241+
});
242+
226243
it('keeps the Ink renderer mounted while running quality checks after an implementation turn', async () => {
227244
const host = createHost();
228245
const inkRenderer = {
@@ -253,6 +270,10 @@ describe('InstructionRunner command mode UI', () => {
253270

254271
it('marks the turn failed when project quality checks fail', async () => {
255272
const host = createHost();
273+
const clearActivityForCompletedTurn = vi.fn();
274+
(host as AgentInstructionHost & {
275+
clearActivityForCompletedTurn: () => void;
276+
}).clearActivityForCompletedTurn = clearActivityForCompletedTurn;
256277
host.runtime = {
257278
...host.runtime,
258279
options: {},
@@ -270,6 +291,7 @@ describe('InstructionRunner command mode UI', () => {
270291

271292
expect(result).toBe(false);
272293
expect(host.stopUI).toHaveBeenCalledWith(true, 'Quality checks failed');
294+
expect(clearActivityForCompletedTurn).not.toHaveBeenCalled();
273295
expect(host.printCompletionSummary).toHaveBeenCalledWith(false, false);
274296
expect(host.scheduleTurnMemoryReflection).toHaveBeenCalledWith({
275297
status: 'failed',

tests/tuistory/task-activity.tuistory.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ afterEach(async () => {
4848
});
4949

5050
describe('interactive task activity', () => {
51-
it('finishes the visible plan and keeps the final summary above the composer', async () => {
51+
it('removes completed task activity when the final turn reaches the composer', async () => {
5252
const server = await createMockOpenRouterSequenceServer([
5353
JSON.stringify({
5454
thought: 'Create a five-step plan and start the validation.',
@@ -103,15 +103,16 @@ describe('interactive task activity', () => {
103103

104104
const viewport = await waitForActiveViewport(
105105
session,
106-
(text) => text.includes('5/5 done')
107-
&& text.includes('TASK_ACTIVITY_FINAL_SUMMARY')
108-
&& text.includes('All 5 tasks completed')
106+
(text) => text.includes('TASK_ACTIVITY_FINAL_SUMMARY')
109107
&& text.includes('Completed in')
110-
&& text.includes('❯'),
108+
&& text.includes('❯')
109+
&& !text.includes('Tasks')
110+
&& !text.includes('All 5 tasks completed'),
111111
);
112112

113-
expect(viewport).toContain('100%');
114-
expect(viewport).not.toContain('in progress · 1');
113+
expect(viewport).not.toContain('5/5 done');
114+
expect(viewport).not.toContain('100%');
115+
expect(viewport).not.toContain('Report the task-view result');
115116

116117
await exitInteractive(session);
117118
}, 60_000);

tests/ui/ink/InkRenderer.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,28 @@ describe('InkRenderer live command blocks', () => {
8686
]);
8787
});
8888

89+
it('clears completed todo and worker activity after final turn completion', () => {
90+
const renderer = new InkRenderer({
91+
onInstruction: () => {},
92+
onEscape: () => {},
93+
onCtrlC: () => {},
94+
});
95+
96+
renderer.setTodoActivityItems([
97+
{ id: 'todo-1', kind: 'todo', label: 'Review the final task', status: 'completed' },
98+
]);
99+
renderer.upsertActivityItem({
100+
id: 'worker-1',
101+
kind: 'subagent',
102+
label: 'reviewer: validate the result',
103+
status: 'completed',
104+
});
105+
106+
renderer.clearActivityItems();
107+
108+
expect(renderer.getState().activityItems).toEqual([]);
109+
});
110+
89111
it('records grouped parallel tool output as a single batch chat message', () => {
90112
const renderer = new InkRenderer({
91113
onInstruction: () => {},

0 commit comments

Comments
 (0)