feat(desktop): add hot view retain/release lifecycle and process metrics

Replace single-shot acquireHotView with retainHotView/releaseHotView
plus a minute-level reaper that closes views idle beyond 5 minutes and
never evicts a view while it still has live retainers. Wrap adapter
calls in try/finally so a failing publish/query still releases the
view. Sample process metrics every minute and expose hot-view policy
and process snapshot via the runtime snapshot for diagnostics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 11:07:58 +08:00
parent a46ed4b9f6
commit ef868f81a1
5 changed files with 261 additions and 43 deletions
@@ -27,7 +27,7 @@ import {
normalizeAccountPlatformUid,
sameAccountPlatformUid,
} from "../shared/account-identity";
import { acquireHotView } from "./view-pool";
import { releaseHotView, retainHotView } from "./view-pool";
import { getLeaseManagerSnapshot, noteLeaseExtended, noteLeaseReleased, setActiveLease } from "./lease-manager";
import {
initScheduler,
@@ -939,25 +939,29 @@ async function executeTaskAdapter(
const viewHandle =
adapter.executionMode === "session" || adapter.executionMode === "playwright"
? null
: acquireHotView(task.accountId || task.id);
: retainHotView(task.accountId || task.id);
const result = await adapter.publish(
{
taskId: task.id,
accountId: task.accountId || task.id,
session: sessionHandle.session,
view: viewHandle?.view ?? null,
signal,
phase: "initial",
article: await loadPublishArticle(task),
reportProgress(stage: string) {
updateTaskProgress(task.id, `publish adapter progress: ${stage}`);
try {
return await adapter.publish(
{
taskId: task.id,
accountId: task.accountId || task.id,
session: sessionHandle.session,
view: viewHandle?.view ?? null,
signal,
phase: "initial",
article: await loadPublishArticle(task),
reportProgress(stage: string) {
updateTaskProgress(task.id, `publish adapter progress: ${stage}`);
},
},
},
payload,
);
return result;
payload,
);
} finally {
if (viewHandle) {
releaseHotView(viewHandle.accountId);
}
}
}
const adapter = selectMonitorAdapter(task.platform);
@@ -968,24 +972,28 @@ async function executeTaskAdapter(
const viewHandle =
adapter.executionMode === "session" || adapter.executionMode === "playwright"
? null
: acquireHotView(task.accountId || task.id);
: retainHotView(task.accountId || task.id);
const result = await adapter.query(
{
taskId: task.id,
accountId: task.accountId || task.id,
session: sessionHandle.session,
view: viewHandle?.view ?? null,
signal,
phase: "initial",
reportProgress(stage: string) {
updateTaskProgress(task.id, `monitor adapter progress: ${stage}`);
try {
return await adapter.query(
{
taskId: task.id,
accountId: task.accountId || task.id,
session: sessionHandle.session,
view: viewHandle?.view ?? null,
signal,
phase: "initial",
reportProgress(stage: string) {
updateTaskProgress(task.id, `monitor adapter progress: ${stage}`);
},
},
},
payload,
);
return result;
payload,
);
} finally {
if (viewHandle) {
releaseHotView(viewHandle.accountId);
}
}
}
function buildScaffoldResult(