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