fix(server): restore release-blocking lifecycle checks
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
|
||||
function state(overrides: Partial<ClientPresenceState>): ClientPresenceState {
|
||||
return {
|
||||
appVisible: true,
|
||||
focusedAgentId: null,
|
||||
lastActivityAtMs: null,
|
||||
...overrides,
|
||||
@@ -54,6 +55,23 @@ describe("computeNotificationPlan", () => {
|
||||
).toEqual({ inAppRecipientIndex: null, shouldPush: false });
|
||||
});
|
||||
|
||||
it("does not suppress notifications when a focused client is backgrounded", () => {
|
||||
const backgroundFocused = state({
|
||||
appVisible: false,
|
||||
focusedAgentId: "agent-1",
|
||||
lastActivityAtMs: presentAtMs,
|
||||
});
|
||||
|
||||
expect(
|
||||
computeNotificationPlan({
|
||||
allStates: [backgroundFocused],
|
||||
agentId: "agent-1",
|
||||
reason: "finished",
|
||||
nowMs,
|
||||
}),
|
||||
).toEqual({ inAppRecipientIndex: 0, shouldPush: false });
|
||||
});
|
||||
|
||||
it("treats present clients focused on different agents as eligible", () => {
|
||||
expect(
|
||||
computeNotificationPlan({
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { AgentAttentionReason } from "../shared/agent-attention-notificatio
|
||||
export const PRESENCE_THRESHOLD_MS = 180_000;
|
||||
|
||||
export interface ClientPresenceState {
|
||||
appVisible: boolean;
|
||||
lastActivityAtMs: number | null;
|
||||
focusedAgentId: string | null;
|
||||
}
|
||||
@@ -38,7 +39,7 @@ export function computeNotificationPlan({
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state.focusedAgentId === agentId) {
|
||||
if (state.appVisible && state.focusedAgentId === agentId) {
|
||||
return { inAppRecipientIndex: null, shouldPush: false };
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ import { DownloadTokenStore } from "./file-download/token-store.js";
|
||||
import { PushTokenStore } from "./push/token-store.js";
|
||||
import { type WorktreeConfig } from "../utils/worktree.js";
|
||||
import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
|
||||
import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
|
||||
import type { ScriptRouteStore } from "./script-proxy.js";
|
||||
import {
|
||||
checkoutResolvedBranch,
|
||||
@@ -6440,7 +6441,43 @@ export class Session {
|
||||
}
|
||||
|
||||
private async reconcileActiveWorkspaceRecords(): Promise<Set<string>> {
|
||||
return new Set();
|
||||
const service = new WorkspaceReconciliationService({
|
||||
projectRegistry: this.projectRegistry,
|
||||
workspaceRegistry: this.workspaceRegistry,
|
||||
logger: this.sessionLogger,
|
||||
workspaceGitService: this.workspaceGitService,
|
||||
});
|
||||
const result = await service.runOnce();
|
||||
const changedWorkspaceIds = new Set<string>();
|
||||
const changedProjectIds = new Set<string>();
|
||||
|
||||
for (const change of result.changesApplied) {
|
||||
switch (change.kind) {
|
||||
case "workspace_archived":
|
||||
await this.removeWorkspaceGitWatchTarget(change.directory);
|
||||
this.scriptRuntimeStore?.removeForWorkspace(change.directory);
|
||||
this.removeWorkspaceGitSubscription(change.workspaceId);
|
||||
changedWorkspaceIds.add(change.workspaceId);
|
||||
break;
|
||||
case "workspace_updated":
|
||||
changedWorkspaceIds.add(change.workspaceId);
|
||||
break;
|
||||
case "project_archived":
|
||||
case "project_updated":
|
||||
changedProjectIds.add(change.projectId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProjectIds.size > 0) {
|
||||
for (const workspace of await this.workspaceRegistry.list()) {
|
||||
if (changedProjectIds.has(workspace.projectId)) {
|
||||
changedWorkspaceIds.add(workspace.workspaceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changedWorkspaceIds;
|
||||
}
|
||||
|
||||
private async emitWorkspaceUpdatesForWorkspaceIds(
|
||||
|
||||
@@ -311,6 +311,59 @@ function createSessionForWorkspaceTests(
|
||||
}
|
||||
|
||||
describe("workspace aggregation", () => {
|
||||
test("workspace reconciliation reports archived workspaces to subscribed clients", async () => {
|
||||
const missingCwd = path.join(tmpdir(), `paseo-missing-workspace-${Date.now()}`);
|
||||
rmSync(missingCwd, { recursive: true, force: true });
|
||||
const projects = new Map([
|
||||
[
|
||||
"proj-missing",
|
||||
createPersistedProjectRecord({
|
||||
projectId: "proj-missing",
|
||||
rootPath: missingCwd,
|
||||
kind: "non_git",
|
||||
displayName: "missing",
|
||||
createdAt: "2026-03-01T12:00:00.000Z",
|
||||
updatedAt: "2026-03-01T12:00:00.000Z",
|
||||
}),
|
||||
],
|
||||
]);
|
||||
const workspaces = new Map([
|
||||
[
|
||||
"ws-missing",
|
||||
createPersistedWorkspaceRecord({
|
||||
workspaceId: "ws-missing",
|
||||
projectId: "proj-missing",
|
||||
cwd: missingCwd,
|
||||
kind: "directory",
|
||||
displayName: "missing",
|
||||
createdAt: "2026-03-01T12:00:00.000Z",
|
||||
updatedAt: "2026-03-01T12:00:00.000Z",
|
||||
}),
|
||||
],
|
||||
]);
|
||||
const session = createSessionForWorkspaceTests() as any;
|
||||
session.projectRegistry.list = async () => Array.from(projects.values());
|
||||
session.projectRegistry.archive = async (projectId: string, archivedAt: string) => {
|
||||
const project = projects.get(projectId);
|
||||
if (project) {
|
||||
projects.set(projectId, { ...project, archivedAt });
|
||||
}
|
||||
};
|
||||
session.workspaceRegistry.list = async () => Array.from(workspaces.values());
|
||||
session.workspaceRegistry.archive = async (workspaceId: string, archivedAt: string) => {
|
||||
const workspace = workspaces.get(workspaceId);
|
||||
if (workspace) {
|
||||
workspaces.set(workspaceId, { ...workspace, archivedAt });
|
||||
}
|
||||
};
|
||||
|
||||
const changedWorkspaceIds = await session.reconcileActiveWorkspaceRecords();
|
||||
|
||||
expect(changedWorkspaceIds).toEqual(new Set(["ws-missing"]));
|
||||
expect(workspaces.get("ws-missing")?.archivedAt).toBeTruthy();
|
||||
expect(projects.get("proj-missing")?.archivedAt).toBeTruthy();
|
||||
});
|
||||
|
||||
test("agent_update placement does not refresh git snapshots", async () => {
|
||||
const emitted: SessionOutboundMessage[] = [];
|
||||
const getSnapshot = vi.fn(async () => {
|
||||
|
||||
@@ -1524,12 +1524,14 @@ export class VoiceAssistantWebSocketServer {
|
||||
const activity = session.getClientActivity();
|
||||
if (!activity) {
|
||||
return {
|
||||
appVisible: false,
|
||||
focusedAgentId: null,
|
||||
lastActivityAtMs: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
appVisible: activity.appVisible,
|
||||
focusedAgentId: activity.focusedAgentId,
|
||||
lastActivityAtMs: activity.lastActivityAt.getTime(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user