Skip to content

Commit 34012b7

Browse files
committed
Review fixes
1 parent 20cfa8a commit 34012b7

7 files changed

Lines changed: 78 additions & 57 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: 46 additions & 31 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,43 @@ 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-
}
405+
public recordActivity(taskRunId: string): void {
406+
if (!this.sessions.has(taskRunId)) return;
408407

409-
private recordActivity(taskRunId: string): void {
410-
const existing = this.idleTimeoutHandles.get(taskRunId);
411-
if (existing) clearTimeout(existing);
408+
const existing = this.idleTimeouts.get(taskRunId);
409+
if (existing) clearTimeout(existing.handle);
412410

411+
const deadline = Date.now() + AgentService.IDLE_TIMEOUT_MS;
413412
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-
});
413+
this.killIdleSession(taskRunId);
425414
}, AgentService.IDLE_TIMEOUT_MS);
426415

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

430444
private getToken(fallback: string): string {
@@ -821,6 +835,7 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
821835
};
822836

823837
this.sessions.set(taskRunId, session);
838+
this.recordActivity(taskRunId);
824839
if (isRetry) {
825840
log.info("Session created after auth retry", { taskRunId });
826841
}
@@ -1176,8 +1191,8 @@ For git operations while detached:
11761191

11771192
@preDestroy()
11781193
async cleanupAll(): Promise<void> {
1179-
for (const handle of this.idleTimeoutHandles.values()) clearTimeout(handle);
1180-
this.idleTimeoutHandles.clear();
1194+
for (const { handle } of this.idleTimeouts.values()) clearTimeout(handle);
1195+
this.idleTimeouts.clear();
11811196
const sessionIds = Array.from(this.sessions.keys());
11821197
log.info("Cleaning up all agent sessions", {
11831198
sessionCount: sessionIds.length,
@@ -1265,10 +1280,10 @@ For git operations while detached:
12651280

12661281
this.sessions.delete(taskRunId);
12671282

1268-
const handle = this.idleTimeoutHandles.get(taskRunId);
1269-
if (handle) {
1270-
clearTimeout(handle);
1271-
this.idleTimeoutHandles.delete(taskRunId);
1283+
const timeout = this.idleTimeouts.get(taskRunId);
1284+
if (timeout) {
1285+
clearTimeout(timeout.handle);
1286+
this.idleTimeouts.delete(taskRunId);
12721287
}
12731288
}
12741289
}

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)