Keep new workspaces recent in status groups

This commit is contained in:
Mohamed Boudra
2026-06-16 16:41:07 +07:00
parent 88b48c9d09
commit be3a1ea6c3
3 changed files with 35 additions and 11 deletions
+1 -1
View File
@@ -6736,7 +6736,7 @@ export class Session {
title: result.workspace.title,
archivingAt: null,
status: "done",
statusEnteredAt: null,
statusEnteredAt: result.workspace.createdAt,
activityAt: null,
diffStat: { additions: 0, deletions: 0 },
scripts: [],
@@ -2894,6 +2894,8 @@ test("archiving the last workspace emits a remove carrying the now-empty project
test("create paseo worktree request returns a registered workspace descriptor", async () => {
const emitted: SessionOutboundMessage[] = [];
const createdAt = "2026-05-12T12:00:00.000Z";
vi.setSystemTime(new Date(createdAt));
const tempDir = realpathSync(mkdtempSync(path.join(tmpdir(), "session-worktree-test-")));
const repoDir = path.join(tempDir, "repo");
const paseoHome = path.join(tempDir, "paseo-home");
@@ -2981,6 +2983,7 @@ test("create paseo worktree request returns a registered workspace descriptor",
requestId: "req-worktree",
});
} finally {
vi.useRealTimers();
rmSync(tempDir, { recursive: true, force: true });
}
@@ -2993,6 +2996,7 @@ test("create paseo worktree request returns a registered workspace descriptor",
workspaceKind: "worktree",
name: "worktree-123",
status: "done",
statusEnteredAt: createdAt,
});
expect(response?.payload.workspace?.id).toMatch(/^wks_[0-9a-f]{16}$/);
expect(response?.payload.workspace?.workspaceDirectory).toContain(path.join("worktree-123"));
@@ -4365,7 +4369,7 @@ test("listWorkspaceDescriptorsSnapshot keeps git workspaces on the baseline desc
name: "main",
archivingAt: null,
status: "done",
statusEnteredAt: null,
statusEnteredAt: workspace.createdAt,
activityAt: null,
diffStat: null,
} as const;
@@ -4424,14 +4428,14 @@ test("buildWorkspaceDescriptorMap computes statusEnteredAt from runtime agent fi
return descriptor!;
});
// 1. Empty workspace — no agents contribute. statusEnteredAt must be null
// and the workspace status is "done".
// 1. Empty workspace — no agents contribute. The workspace entered its
// initial "done" bucket when it was created.
{
const { session, workspace } = setupSession();
session.listAgentPayloads = async () => [];
const descriptor = await buildDescriptor(session, workspace.workspaceId);
expect(descriptor.status).toBe("done");
expect(descriptor.statusEnteredAt).toBeNull();
expect(descriptor.statusEnteredAt).toBe(workspace.createdAt);
}
// Agents own the workspace by id; cwd is incidental.
@@ -32,8 +32,9 @@ const FETCH_WORKSPACES_SORT_KEYS = [
* `statusEnteredAt`: when the winning bucket changes from a higher-priority
* mask to a lower-priority bucket, the new entry time is the unmask time
* (i.e., the moment the higher-priority bucket cleared), not when the
* underlying agent originally entered the lower-priority bucket. Cleared when
* the workspace has never had contributing agents.
* underlying agent originally entered the lower-priority bucket. A fresh
* workspace enters its initial `done` bucket at creation time, even before any
* agent or terminal contributes activity.
*/
interface WorkspaceBucketHistoryEntry {
bucket: WorkspaceStateBucket;
@@ -205,6 +206,9 @@ export class WorkspaceDirectory {
const includedWorkspaces = activeRecords.filter(
(workspace) => !workspaceIds || workspaceIds.has(workspace.workspaceId),
);
const activeRecordsByWorkspaceId = new Map(
activeRecords.map((workspace) => [workspace.workspaceId, workspace] as const),
);
const workspaceDescriptors = await Promise.all(
includedWorkspaces.map((workspace) =>
this.deps.buildWorkspaceDescriptor({
@@ -253,6 +257,7 @@ export class WorkspaceDirectory {
contributingAgents,
terminalEntries,
previous: this.bucketHistoryByWorkspaceId.get(workspaceId) ?? null,
workspaceCreatedAt: activeRecordsByWorkspaceId.get(workspaceId)?.createdAt ?? null,
nowIso,
});
descriptor.statusEnteredAt = result.statusEnteredAt;
@@ -360,8 +365,8 @@ export class WorkspaceDirectory {
// - priority unmasking: when the winning bucket transitions (e.g. a
// higher-priority bucket cleared), the new entry time is "now";
// - same-bucket emits reuse the previous entered-at;
// - empty workspaces that never had contributing agents or terminals get
// `statusEnteredAt: null`.
// - empty workspaces that never had contributing agents or terminals use
// their workspace creation time as their initial `done` entry time.
// - when archived agents leave a previously active workspace empty, keep
// the previous done timestamp or stamp the transition to done now.
private resolveStatusEnteredAt(params: {
@@ -370,17 +375,32 @@ export class WorkspaceDirectory {
contributingAgents: AgentSnapshotPayload[];
terminalEntries: Array<{ bucket: WorkspaceStateBucket; changedAtIso: string }>;
previous: WorkspaceBucketHistoryEntry | null;
workspaceCreatedAt: string | null;
nowIso: string;
}): {
statusEnteredAt: string | null;
recordUpdate?: WorkspaceBucketHistoryEntry;
recordDelete?: true;
} {
const { winningBucket, contributingAgents, terminalEntries, previous, nowIso } = params;
const {
winningBucket,
contributingAgents,
terminalEntries,
previous,
workspaceCreatedAt,
nowIso,
} = params;
if (contributingAgents.length === 0 && terminalEntries.length === 0) {
if (!previous) {
return { statusEnteredAt: null };
if (!workspaceCreatedAt) {
return { statusEnteredAt: null };
}
return {
statusEnteredAt: workspaceCreatedAt,
recordUpdate: { bucket: "done", enteredAt: workspaceCreatedAt },
};
}
const enteredAt = previous.bucket === "done" ? previous.enteredAt : nowIso;