Skip to content

Commit f6ebf31

Browse files
committed
Review fixes
1 parent 20cfa8a commit f6ebf31

7 files changed

Lines changed: 76 additions & 58 deletions

File tree

apps/code/src/main/services/agent/schemas.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ export const subscribeSessionInput = z.object({
183183
taskRunId: z.string(),
184184
});
185185

186-
// Report activity input — keeps the idle timeout debounce alive for the given task
187-
export const reportActivityInput = z.object({
188-
taskId: z.string().nullable(),
186+
// Record activity input — resets the idle timeout for the given session
187+
export const recordActivityInput = z.object({
188+
taskRunId: z.string(),
189189
});
190190

191191
// Agent events

apps/code/src/main/services/agent/service.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ const mockFetch = vi.hoisted(() => vi.fn());
5151

5252
// --- Module mocks ---
5353

54+
const mockPowerMonitor = vi.hoisted(() => ({
55+
on: vi.fn(),
56+
}));
57+
5458
vi.mock("electron", () => ({
5559
app: mockApp,
60+
powerMonitor: mockPowerMonitor,
5661
}));
5762

5863
vi.mock("../../utils/logger.js", () => ({

apps/code/src/main/services/agent/service.ts

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { getLlmGatewayUrl } from "@posthog/agent/posthog-api";
2323
import type { OnLogCallback } from "@posthog/agent/types";
2424
import { isAuthError } from "@shared/errors.js";
2525
import type { AcpMessage } from "@shared/types/session-events.js";
26-
import { app } from "electron";
26+
import { app, powerMonitor } from "electron";
2727
import { inject, injectable, preDestroy } from "inversify";
2828
import { MAIN_TOKENS } from "../../di/tokens.js";
2929
import { isDevBuild } from "../../utils/env.js";
@@ -258,7 +258,10 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
258258
private currentToken: string | null = null;
259259
private pendingPermissions = new Map<string, PendingPermission>();
260260
private mockNodeReady = false;
261-
private idleTimeoutHandles = new Map<string, ReturnType<typeof setTimeout>>();
261+
private idleTimeouts = new Map<
262+
string,
263+
{ handle: ReturnType<typeof setTimeout>; deadline: number }
264+
>();
262265
private processTracking: ProcessTrackingService;
263266
private sleepService: SleepService;
264267
private fsService: FsService;
@@ -279,6 +282,8 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
279282
this.sleepService = sleepService;
280283
this.fsService = fsService;
281284
this.posthogPluginService = posthogPluginService;
285+
286+
powerMonitor.on("resume", () => this.checkIdleDeadlines());
282287
}
283288

284289
public updateToken(newToken: string): void {
@@ -397,34 +402,40 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
397402
return false;
398403
}
399404

400-
public reportActivity(taskId: string | null): void {
401-
if (!taskId) return;
402-
for (const session of this.sessions.values()) {
403-
if (session.taskId === taskId) {
404-
this.recordActivity(session.taskRunId);
405-
}
406-
}
407-
}
408-
409-
private recordActivity(taskRunId: string): void {
410-
const existing = this.idleTimeoutHandles.get(taskRunId);
411-
if (existing) clearTimeout(existing);
405+
public recordActivity(taskRunId: string): void {
406+
const existing = this.idleTimeouts.get(taskRunId);
407+
if (existing) clearTimeout(existing.handle);
412408

409+
const deadline = Date.now() + AgentService.IDLE_TIMEOUT_MS;
413410
const handle = setTimeout(() => {
414-
this.idleTimeoutHandles.delete(taskRunId);
415-
const session = this.sessions.get(taskRunId);
416-
if (!session || session.promptPending) return;
417-
log.info("Killing idle session", { taskRunId, taskId: session.taskId });
418-
this.emit(AgentServiceEvent.SessionIdleKilled, {
419-
taskRunId,
420-
taskId: session.taskId,
421-
});
422-
this.cleanupSession(taskRunId).catch((err) => {
423-
log.error("Failed to cleanup idle session", { taskRunId, err });
424-
});
411+
this.killIdleSession(taskRunId);
425412
}, AgentService.IDLE_TIMEOUT_MS);
426413

427-
this.idleTimeoutHandles.set(taskRunId, handle);
414+
this.idleTimeouts.set(taskRunId, { handle, deadline });
415+
}
416+
417+
private killIdleSession(taskRunId: string): void {
418+
this.idleTimeouts.delete(taskRunId);
419+
const session = this.sessions.get(taskRunId);
420+
if (!session || session.promptPending) return;
421+
log.info("Killing idle session", { taskRunId, taskId: session.taskId });
422+
this.emit(AgentServiceEvent.SessionIdleKilled, {
423+
taskRunId,
424+
taskId: session.taskId,
425+
});
426+
this.cleanupSession(taskRunId).catch((err) => {
427+
log.error("Failed to cleanup idle session", { taskRunId, err });
428+
});
429+
}
430+
431+
private checkIdleDeadlines(): void {
432+
const now = Date.now();
433+
for (const [taskRunId, { handle, deadline }] of this.idleTimeouts) {
434+
if (now >= deadline) {
435+
clearTimeout(handle);
436+
this.killIdleSession(taskRunId);
437+
}
438+
}
428439
}
429440

430441
private getToken(fallback: string): string {
@@ -821,6 +832,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
821832
};
822833

823834
this.sessions.set(taskRunId, session);
835+
this.recordActivity(taskRunId);
824836
if (isRetry) {
825837
log.info("Session created after auth retry", { taskRunId });
826838
}
@@ -1176,8 +1188,8 @@ For git operations while detached:
11761188

11771189
@preDestroy()
11781190
async cleanupAll(): Promise<void> {
1179-
for (const handle of this.idleTimeoutHandles.values()) clearTimeout(handle);
1180-
this.idleTimeoutHandles.clear();
1191+
for (const { handle } of this.idleTimeouts.values()) clearTimeout(handle);
1192+
this.idleTimeouts.clear();
11811193
const sessionIds = Array.from(this.sessions.keys());
11821194
log.info("Cleaning up all agent sessions", {
11831195
sessionCount: sessionIds.length,
@@ -1265,10 +1277,10 @@ For git operations while detached:
12651277

12661278
this.sessions.delete(taskRunId);
12671279

1268-
const handle = this.idleTimeoutHandles.get(taskRunId);
1269-
if (handle) {
1270-
clearTimeout(handle);
1271-
this.idleTimeoutHandles.delete(taskRunId);
1280+
const timeout = this.idleTimeouts.get(taskRunId);
1281+
if (timeout) {
1282+
clearTimeout(timeout.handle);
1283+
this.idleTimeouts.delete(taskRunId);
12721284
}
12731285
}
12741286
}

apps/code/src/main/trpc/routers/agent.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
promptInput,
1515
promptOutput,
1616
reconnectSessionInput,
17-
reportActivityInput,
17+
recordActivityInput,
1818
respondToPermissionInput,
1919
sessionResponseSchema,
2020
setConfigOptionInput,
@@ -184,9 +184,9 @@ export const agentRouter = router({
184184
log.info("All sessions reset successfully");
185185
}),
186186

187-
reportActivity: publicProcedure
188-
.input(reportActivityInput)
189-
.mutation(({ input }) => getService().reportActivity(input.taskId)),
187+
recordActivity: publicProcedure
188+
.input(recordActivityInput)
189+
.mutation(({ input }) => getService().recordActivity(input.taskRunId)),
190190

191191
onSessionIdleKilled: publicProcedure.subscription(async function* (opts) {
192192
const service = getService();

apps/code/src/renderer/features/sessions/service/service.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,13 @@ export class SessionService {
125125

126126
constructor() {
127127
this.idleKilledSubscription =
128-
trpcVanilla.agent.onSessionIdleKilled.subscribe(undefined, {
129-
onData: (event) => {
130-
const { taskRunId } = event as { taskRunId: string; taskId: string };
128+
trpcClient.agent.onSessionIdleKilled.subscribe(undefined, {
129+
onData: (event: { taskRunId: string }) => {
130+
const { taskRunId } = event;
131131
log.info("Session idle-killed by main process", { taskRunId });
132-
this.unsubscribeFromChannel(taskRunId);
133-
sessionStoreSetters.removeSession(taskRunId);
134-
removePersistedConfigOptions(taskRunId);
132+
this.teardownSession(taskRunId);
135133
},
136-
onError: (err) => {
134+
onError: (err: unknown) => {
137135
log.debug("Idle-killed subscription error", { error: err });
138136
},
139137
});
@@ -1617,9 +1615,7 @@ export class SessionService {
16171615
throw new Error("Unable to reach server. Please check your connection.");
16181616
}
16191617

1620-
const prefetchedLogs = logUrl
1621-
? await this.fetchSessionLogs(logUrl, taskRunId)
1622-
: { rawEntries: [] as StoredLogEntry[], adapter: undefined };
1618+
const prefetchedLogs = await this.fetchSessionLogs(logUrl, taskRunId);
16231619

16241620
// Determine sessionId: undefined = use from logs, null = strip (fresh), string = use as-is
16251621
const sessionId =

apps/code/src/renderer/features/task-detail/components/TaskLogsPanel.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { toast } from "@utils/toast";
3434
import { useCallback, useEffect, useMemo, useRef } from "react";
3535

3636
const log = logger.scope("task-logs-panel");
37+
const ACTIVITY_HEARTBEAT_MS = 5 * 60 * 1000;
3738

3839
interface TaskLogsPanelProps {
3940
taskId: string;
@@ -133,15 +134,14 @@ export function TaskLogsPanel({ taskId, task }: TaskLogsPanelProps) {
133134
}, [taskId, requestFocus]);
134135

135136
useEffect(() => {
136-
trpcVanilla.agent.reportActivity.mutate({ taskId }).catch(() => {});
137-
const heartbeat = setInterval(
138-
() => {
139-
trpcVanilla.agent.reportActivity.mutate({ taskId }).catch(() => {});
140-
},
141-
5 * 60 * 1000,
142-
);
137+
const taskRunId = session?.taskRunId;
138+
if (!taskRunId) return;
139+
trpcClient.agent.recordActivity.mutate({ taskRunId }).catch(() => {});
140+
const heartbeat = setInterval(() => {
141+
trpcClient.agent.recordActivity.mutate({ taskRunId }).catch(() => {});
142+
}, ACTIVITY_HEARTBEAT_MS);
143143
return () => clearInterval(heartbeat);
144-
}, [taskId]);
144+
}, [session?.taskRunId]);
145145

146146
// Keep cloud session title aligned with latest task metadata.
147147
useEffect(() => {

packages/agent/src/adapters/claude/tools.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ export const SEARCH_TOOLS: Set<string> = new Set(["Glob", "Grep", "LS"]);
2626

2727
export const WEB_TOOLS: Set<string> = new Set(["WebSearch", "WebFetch"]);
2828

29-
export const AGENT_TOOLS: Set<string> = new Set(["Task", "TodoWrite", "Skill"]);
29+
export const AGENT_TOOLS: Set<string> = new Set([
30+
"Task",
31+
"Agent",
32+
"TodoWrite",
33+
"Skill",
34+
]);
3035

3136
const BASE_ALLOWED_TOOLS = [
3237
...READ_TOOLS,

0 commit comments

Comments
 (0)