-
Notifications
You must be signed in to change notification settings - Fork 0
fix: quick fixes 3 — terminal, chat, files, state improvements #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1af8fef
7ffd9bc
92f19d9
4e904c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import { app, BrowserWindow, nativeImage, protocol, shell } from "electron"; | ||
| import path from "node:path"; | ||
| type NodePtyType = typeof import("node-pty"); | ||
| import { registerIpc } from "./services/ipc/registerIpc"; | ||
| import { createFileLogger } from "./services/logging/logger"; | ||
| import { openKvDb } from "./services/state/kvDb"; | ||
|
|
@@ -703,8 +703,11 @@ | |
| const projectInitPromises = new Map<string, Promise<AppContext>>(); | ||
| const closeContextPromises = new Map<string, Promise<void>>(); | ||
| const mcpSocketCleanupByRoot = new Map<string, () => void>(); | ||
| const projectLastActivatedAt = new Map<string, number>(); | ||
| const MAX_WARM_IDLE_PROJECT_CONTEXTS = 1; | ||
| let activeProjectRoot: string | null = null; | ||
| let dormantContext!: AppContext; | ||
| let projectContextRebalancePromise: Promise<void> = Promise.resolve(); | ||
|
|
||
| const emitProjectChanged = (project: ProjectInfo | null): void => { | ||
| broadcast(IPC.appProjectChanged, project); | ||
|
|
@@ -713,6 +716,7 @@ | |
| const setActiveProject = (projectRoot: string | null): void => { | ||
| activeProjectRoot = projectRoot ? normalizeProjectRoot(projectRoot) : null; | ||
| if (activeProjectRoot) { | ||
| projectLastActivatedAt.set(activeProjectRoot, Date.now()); | ||
| try { | ||
| adeArtifactAllowedDir = | ||
| resolveAdeLayout(activeProjectRoot).artifactsDir; | ||
|
|
@@ -743,6 +747,194 @@ | |
| broadcast(channel, payload); | ||
| }; | ||
|
|
||
| const hasActiveProjectWorkloads = async ( | ||
| projectRoot: string, | ||
| ctx: AppContext, | ||
| ): Promise<boolean> => { | ||
| const keepAliveOnProbeFailure = ( | ||
| probe: string, | ||
| error: unknown, | ||
| ): boolean => { | ||
| ctx.logger.warn("project.context_workload_probe_failed", { | ||
| projectRoot, | ||
| probe, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }); | ||
| return true; | ||
| }; | ||
|
|
||
| try { | ||
| if (ctx.sessionService.list({ status: "running", limit: 1 }).length > 0) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("sessions", error); | ||
| } | ||
|
|
||
| try { | ||
| if (ctx.missionService.list({ status: "active", limit: 1 }).length > 0) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("missions", error); | ||
| } | ||
|
|
||
| try { | ||
| if (ctx.testService.hasActiveRuns()) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("tests", error); | ||
| } | ||
|
|
||
| try { | ||
| const lanes = await ctx.laneService.list({ | ||
| includeArchived: false, | ||
| includeStatus: false, | ||
| }); | ||
| for (const lane of lanes) { | ||
| if ( | ||
| ctx.processService.listRuntime(lane.id).some((runtime) => | ||
| runtime.status === "starting" | ||
| || runtime.status === "running" | ||
| || runtime.status === "degraded" | ||
| || runtime.status === "stopping" | ||
| ) | ||
| ) { | ||
| return true; | ||
| } | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("processes", error); | ||
| } | ||
|
|
||
| try { | ||
| if ((ctx.laneProxyService?.getStatus().routes.length ?? 0) > 0) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("proxy_routes", error); | ||
| } | ||
|
|
||
| try { | ||
| if ( | ||
| ctx.oauthRedirectService?.listSessions().some((session) => | ||
| session.status === "pending" || session.status === "active" | ||
| ) ?? false | ||
| ) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("oauth_sessions", error); | ||
| } | ||
|
|
||
| try { | ||
| if ((ctx.getActiveMcpConnectionCount?.() ?? 0) > 0) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("mcp_connections", error); | ||
| } | ||
|
|
||
| try { | ||
| if ((ctx.syncHostService?.getPeerStates().length ?? 0) > 0) { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("sync_peers", error); | ||
| } | ||
|
|
||
| try { | ||
| const syncStatus = await ctx.syncService?.getStatus?.(); | ||
| if (syncStatus?.client.state === "connected") { | ||
| return true; | ||
| } | ||
| } catch (error) { | ||
| return keepAliveOnProbeFailure("sync_client", error); | ||
| } | ||
|
|
||
| return false; | ||
| }; | ||
|
|
||
| const rebalanceProjectContexts = async (): Promise<void> => { | ||
| const currentActiveRoot = activeProjectRoot; | ||
| if (!currentActiveRoot) return; | ||
|
|
||
| const idleRoots: string[] = []; | ||
| for (const [projectRoot, ctx] of projectContexts.entries()) { | ||
| if (projectRoot === currentActiveRoot) continue; | ||
| if (await hasActiveProjectWorkloads(projectRoot, ctx)) { | ||
| ctx.logger.info("project.context_retained", { | ||
| projectRoot, | ||
| policy: "active_workload", | ||
| }); | ||
| continue; | ||
| } | ||
| idleRoots.push(projectRoot); | ||
| } | ||
|
|
||
| idleRoots.sort( | ||
| (left, right) => | ||
| (projectLastActivatedAt.get(right) ?? 0) | ||
| - (projectLastActivatedAt.get(left) ?? 0), | ||
| ); | ||
| const warmRoots = new Set( | ||
| idleRoots.slice(0, MAX_WARM_IDLE_PROJECT_CONTEXTS), | ||
| ); | ||
|
|
||
| for (const projectRoot of idleRoots) { | ||
| if (activeProjectRoot !== currentActiveRoot) { | ||
| return; | ||
| } | ||
| const ctx = projectContexts.get(projectRoot); | ||
| if (!ctx) continue; | ||
| if (projectRoot === activeProjectRoot) continue; | ||
| if (warmRoots.has(projectRoot)) { | ||
| ctx.logger.info("project.context_retained", { | ||
| projectRoot, | ||
| policy: "warm_idle", | ||
| activeProjectRoot: currentActiveRoot, | ||
| }); | ||
| continue; | ||
| } | ||
| // Re-check workloads immediately before eviction to avoid TOCTOU races | ||
| if (await hasActiveProjectWorkloads(projectRoot, ctx)) { | ||
| ctx.logger.info("project.context_retained", { | ||
| projectRoot, | ||
| policy: "became_active_during_rebalance", | ||
| activeProjectRoot: currentActiveRoot, | ||
| }); | ||
| continue; | ||
| } | ||
| ctx.logger.info("project.context_evicted", { | ||
| projectRoot, | ||
| policy: "idle_after_switch", | ||
| activeProjectRoot: currentActiveRoot, | ||
| }); | ||
| await closeProjectContext(projectRoot); | ||
| } | ||
greptile-apps[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| const scheduleProjectContextRebalance = (): void => { | ||
| projectContextRebalancePromise = projectContextRebalancePromise | ||
| .catch(() => { | ||
| // Swallow previous rebalance failures so future rebalances still run. | ||
| }) | ||
| .then(async () => { | ||
| try { | ||
| await rebalanceProjectContexts(); | ||
| } catch (error) { | ||
| const logger = activeProjectRoot | ||
| ? projectContexts.get(activeProjectRoot)?.logger ?? dormantContext.logger | ||
| : dormantContext.logger; | ||
| logger.warn("project.context_rebalance_failed", { | ||
| activeProjectRoot, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }); | ||
| } | ||
| }); | ||
| }; | ||
|
|
||
| const initContextForProjectRoot = async ({ | ||
| projectRoot, | ||
| baseRef, | ||
|
|
@@ -2697,6 +2889,7 @@ | |
| projectId, | ||
| adeDir: adePaths.adeDir, | ||
| hasUserSelectedProject: userSelectedProject, | ||
| getActiveMcpConnectionCount: () => activeMcpConnections.size, | ||
| disposeHeadWatcher, | ||
| keybindingsService, | ||
| agentToolsService, | ||
|
|
@@ -2797,6 +2990,7 @@ | |
| hasUserSelectedProject: false, | ||
| projectId: "", | ||
| adeDir: "", | ||
| getActiveMcpConnectionCount: () => 0, | ||
| disposeHeadWatcher: () => {}, | ||
| keybindingsService: null, | ||
| agentToolsService: null, | ||
|
|
@@ -2951,6 +3145,26 @@ | |
| } catch { | ||
| // ignore | ||
| } | ||
| try { | ||
| ctx.embeddingService?.stopHealthCheck?.(); | ||
| } catch { | ||
| // ignore | ||
| } | ||
| try { | ||
| await ctx.embeddingService?.dispose?.(); | ||
| } catch { | ||
| // ignore | ||
| } | ||
|
Comment on lines
+3148
to
+3157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
worker_file="$(fd 'embeddingWorkerService\.ts$' apps/desktop/src/main/services/memory | head -n1)"
main_file="apps/desktop/src/main/main.ts"
printf '== embedding worker API ==\n'
sed -n '1,260p' "$worker_file" | rg -n -C2 'createEmbeddingWorkerService|return \{|start\(|stop\(|dispose\('
printf '\n== current teardown sites in main.ts ==\n'
rg -n -C3 'embeddingWorkerService|embeddingService\?\.dispose|stopHealthCheck' "$main_file"Repository: arul28/ADE Length of output: 3471 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find and read the complete embeddingWorkerService.ts file
worker_file="$(fd 'embeddingWorkerService\.ts$' apps/desktop/src/main/services/memory | head -n1)"
echo "=== Complete embeddingWorkerService.ts ==="
wc -l "$worker_file"
cat -n "$worker_file"Repository: arul28/ADE Length of output: 11665 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Get the complete teardown section around lines 3139-3148 in main.ts
echo "=== Teardown section in main.ts (lines 3100-3200) ==="
sed -n '3100,3200p' apps/desktop/src/main/main.ts | cat -nRepository: arul28/ADE Length of output: 2654 Wait for embedding worker to complete processing before disposing The worker queue is in-memory and can have pending embedding work. Currently, The 🤖 Prompt for AI Agents |
||
| try { | ||
| await ctx.laneProxyService?.dispose?.(); | ||
| } catch { | ||
| // ignore | ||
| } | ||
| try { | ||
| ctx.oauthRedirectService?.dispose?.(); | ||
| } catch { | ||
| // ignore | ||
| } | ||
| try { | ||
| await ctx.externalMcpService?.dispose?.(); | ||
| } catch { | ||
|
|
@@ -3037,6 +3251,7 @@ | |
| const closePromise = (async () => { | ||
| await disposeContextResources(ctx); | ||
| projectContexts.delete(normalizedRoot); | ||
| projectLastActivatedAt.delete(normalizedRoot); | ||
| if (activeProjectRoot === normalizedRoot) { | ||
| activeProjectRoot = null; | ||
| } | ||
|
|
@@ -3080,6 +3295,7 @@ | |
| recordRecent: false, | ||
| }); | ||
| emitProjectChanged(existing.project); | ||
| scheduleProjectContextRebalance(); | ||
| return existing.project; | ||
| } | ||
|
|
||
|
|
@@ -3111,6 +3327,7 @@ | |
| recordRecent: false, | ||
| }); | ||
| emitProjectChanged(ctx.project); | ||
| scheduleProjectContextRebalance(); | ||
| return ctx.project; | ||
| }; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.