Skip to content

Commit ea86954

Browse files
feat: auto prune
1 parent 0ae469d commit ea86954

File tree

3 files changed

+107
-60
lines changed

3 files changed

+107
-60
lines changed

index.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import {
88
createChatMessageTransformHandler,
99
createEventHandler,
1010
} from "./lib/hooks";
11-
import { getPendingPrune, setPendingPrune } from "./lib/ui/confirmation";
11+
import {
12+
getPendingPrune,
13+
setPendingPrune,
14+
setAutoConfirm,
15+
} from "./lib/ui/confirmation";
1216

1317
const plugin: Plugin = (async (ctx) => {
1418
const config = getConfig(ctx);
@@ -103,6 +107,13 @@ const plugin: Plugin = (async (ctx) => {
103107
bg: "backgroundPanel",
104108
onConfirm: "cancel-prune",
105109
},
110+
{
111+
type: "confirm-button",
112+
label: " Auto ",
113+
fg: "background",
114+
bg: "warning",
115+
onConfirm: "auto-prune",
116+
},
106117
{
107118
type: "confirm-button",
108119
label: " Confirm ",
@@ -137,6 +148,14 @@ const plugin: Plugin = (async (ctx) => {
137148
} else if (event.event === "cancel-prune" && pending) {
138149
pending.resolve([]);
139150
setPendingPrune(null);
151+
} else if (event.event === "auto-prune" && pending) {
152+
// Enable auto-confirm and confirm this one
153+
setAutoConfirm(true);
154+
const confirmed = pending.items
155+
.filter((i: { checked: boolean }) => i.checked)
156+
.map((i: { id: string }) => i.id);
157+
pending.resolve(confirmed);
158+
setPendingPrune(null);
140159
}
141160
}
142161
},
@@ -179,7 +198,14 @@ const plugin: Plugin = (async (ctx) => {
179198
);
180199
}
181200
},
182-
event: createEventHandler(ctx.client, config, state, logger, ctx.directory),
201+
event: createEventHandler(
202+
ctx.client,
203+
config,
204+
state,
205+
logger,
206+
ctx.directory,
207+
() => setAutoConfirm(false),
208+
),
183209
};
184210
}) satisfies Plugin;
185211

lib/hooks.ts

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,77 @@
1-
import type { SessionState, WithParts } from "./state"
2-
import type { Logger } from "./logger"
3-
import type { PluginConfig } from "./config"
4-
import { syncToolCache } from "./state/tool-cache"
5-
import { deduplicate, supersedeWrites } from "./strategies"
6-
import { prune, insertPruneToolContext } from "./messages"
7-
import { checkSession } from "./state"
8-
import { runOnIdle } from "./strategies/on-idle"
9-
1+
import type { SessionState, WithParts } from "./state";
2+
import type { Logger } from "./logger";
3+
import type { PluginConfig } from "./config";
4+
import { syncToolCache } from "./state/tool-cache";
5+
import { deduplicate, supersedeWrites } from "./strategies";
6+
import { prune, insertPruneToolContext } from "./messages";
7+
import { checkSession } from "./state";
8+
import { runOnIdle } from "./strategies/on-idle";
109

1110
export function createChatMessageTransformHandler(
12-
client: any,
13-
state: SessionState,
14-
logger: Logger,
15-
config: PluginConfig
11+
client: any,
12+
state: SessionState,
13+
logger: Logger,
14+
config: PluginConfig,
1615
) {
17-
return async (
18-
input: {},
19-
output: { messages: WithParts[] }
20-
) => {
21-
await checkSession(client, state, logger, output.messages)
16+
return async (input: {}, output: { messages: WithParts[] }) => {
17+
await checkSession(client, state, logger, output.messages);
2218

23-
if (state.isSubAgent) {
24-
return
25-
}
19+
if (state.isSubAgent) {
20+
return;
21+
}
2622

27-
syncToolCache(state, config, logger, output.messages);
23+
syncToolCache(state, config, logger, output.messages);
2824

29-
deduplicate(state, logger, config, output.messages)
30-
supersedeWrites(state, logger, config, output.messages)
25+
deduplicate(state, logger, config, output.messages);
26+
supersedeWrites(state, logger, config, output.messages);
3127

32-
prune(state, logger, config, output.messages)
28+
prune(state, logger, config, output.messages);
3329

34-
insertPruneToolContext(state, config, logger, output.messages)
35-
}
30+
insertPruneToolContext(state, config, logger, output.messages);
31+
};
3632
}
3733

3834
export function createEventHandler(
39-
client: any,
40-
config: PluginConfig,
41-
state: SessionState,
42-
logger: Logger,
43-
workingDirectory?: string
35+
client: any,
36+
config: PluginConfig,
37+
state: SessionState,
38+
logger: Logger,
39+
workingDirectory?: string,
40+
onUserMessage?: () => void,
4441
) {
45-
return async (
46-
{ event }: { event: any }
47-
) => {
48-
if (state.sessionId === null || state.isSubAgent) {
49-
return
50-
}
42+
return async ({ event }: { event: any }) => {
43+
if (state.sessionId === null || state.isSubAgent) {
44+
return;
45+
}
46+
47+
// Reset auto-confirm on user message
48+
if (
49+
event.type === "message.part.added" &&
50+
event.properties?.part?.type === "text"
51+
) {
52+
const role = event.properties?.role;
53+
if (role === "user" && onUserMessage) {
54+
onUserMessage();
55+
}
56+
}
5157

52-
if (event.type === "session.status" && event.properties.status.type === "idle") {
53-
if (!config.strategies.onIdle.enabled) {
54-
return
55-
}
56-
if (state.lastToolPrune) {
57-
logger.info("Skipping OnIdle pruning - last tool was prune")
58-
return
59-
}
58+
if (
59+
event.type === "session.status" &&
60+
event.properties.status.type === "idle"
61+
) {
62+
if (!config.strategies.onIdle.enabled) {
63+
return;
64+
}
65+
if (state.lastToolPrune) {
66+
logger.info("Skipping OnIdle pruning - last tool was prune");
67+
return;
68+
}
6069

61-
try {
62-
await runOnIdle(
63-
client,
64-
state,
65-
logger,
66-
config,
67-
workingDirectory
68-
)
69-
} catch (err: any) {
70-
logger.error("OnIdle pruning failed", { error: err.message })
71-
}
72-
}
70+
try {
71+
await runOnIdle(client, state, logger, config, workingDirectory);
72+
} catch (err: any) {
73+
logger.error("OnIdle pruning failed", { error: err.message });
74+
}
7375
}
76+
};
7477
}

lib/ui/confirmation.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export interface PendingConfirmation {
1111
// Shared state for pending confirmations
1212
let pendingPrune: PendingConfirmation | null = null;
1313

14+
// Auto-confirm mode - when true, automatically confirms all prunes
15+
let autoConfirmEnabled = false;
16+
1417
export function getPendingPrune(): PendingConfirmation | null {
1518
return pendingPrune;
1619
}
@@ -19,6 +22,14 @@ export function setPendingPrune(pending: PendingConfirmation | null): void {
1922
pendingPrune = pending;
2023
}
2124

25+
export function isAutoConfirmEnabled(): boolean {
26+
return autoConfirmEnabled;
27+
}
28+
29+
export function setAutoConfirm(enabled: boolean): void {
30+
autoConfirmEnabled = enabled;
31+
}
32+
2233
export function resolvePendingPrune(confirmedIds: string[]): void {
2334
if (pendingPrune) {
2435
pendingPrune.resolve(confirmedIds);
@@ -29,6 +40,7 @@ export function resolvePendingPrune(confirmedIds: string[]): void {
2940
/**
3041
* Shows a confirmation UI for pruning and returns a Promise that resolves
3142
* with the list of confirmed tool IDs (or empty array if cancelled).
43+
* If auto-confirm is enabled, immediately returns all IDs without showing UI.
3244
*/
3345
export async function requestPruneConfirmation(
3446
client: any,
@@ -39,6 +51,12 @@ export async function requestPruneConfirmation(
3951
logger: Logger,
4052
workingDirectory: string,
4153
): Promise<string[]> {
54+
// If auto-confirm is enabled, immediately return all IDs
55+
if (autoConfirmEnabled) {
56+
logger.info("Auto-confirming prune", { itemCount: pruneToolIds.length });
57+
return pruneToolIds;
58+
}
59+
4260
// Build checklist items from the tool metadata
4361
const items = pruneToolIds.map((id) => {
4462
const meta = toolMetadata.get(id);

0 commit comments

Comments
 (0)