feat: service proxy, health monitor, hover card, and homepage section
Built-in service proxy with branch-based URLs, service health monitoring, workspace hover card with service status, and "Forget about ports" homepage section.
This commit is contained in:
@@ -199,6 +199,7 @@ const WorktreeSetupCommandSnapshotSchema = z.object({
|
||||
index: z.number().int().positive(),
|
||||
command: z.string(),
|
||||
cwd: z.string(),
|
||||
log: z.string(),
|
||||
status: z.enum(["running", "completed", "failed"]),
|
||||
exitCode: z.number().nullable(),
|
||||
durationMs: z.number().nonnegative().optional(),
|
||||
@@ -985,6 +986,12 @@ export const CreatePaseoWorktreeRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const WorkspaceSetupStatusRequestSchema = z.object({
|
||||
type: z.literal("workspace_setup_status_request"),
|
||||
workspaceId: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const OpenProjectRequestSchema = z.object({
|
||||
type: z.literal("open_project_request"),
|
||||
cwd: z.string(),
|
||||
@@ -1228,6 +1235,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
PaseoWorktreeListRequestSchema,
|
||||
PaseoWorktreeArchiveRequestSchema,
|
||||
CreatePaseoWorktreeRequestSchema,
|
||||
WorkspaceSetupStatusRequestSchema,
|
||||
OpenProjectRequestSchema,
|
||||
ArchiveWorkspaceRequestSchema,
|
||||
FileExplorerRequestSchema,
|
||||
@@ -1573,6 +1581,14 @@ export const ProjectPlacementPayloadSchema = z.object({
|
||||
checkout: ProjectCheckoutLitePayloadSchema,
|
||||
});
|
||||
|
||||
export const WorkspaceServicePayloadSchema = z.object({
|
||||
serviceName: z.string(),
|
||||
hostname: z.string(),
|
||||
port: z.number().int().positive(),
|
||||
url: z.string().nullable(),
|
||||
status: z.enum(["running", "stopped"]),
|
||||
});
|
||||
|
||||
export const WorkspaceDescriptorPayloadSchema = z.object({
|
||||
id: z.string(),
|
||||
projectId: z.string(),
|
||||
@@ -1590,6 +1606,7 @@ export const WorkspaceDescriptorPayloadSchema = z.object({
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
services: z.array(WorkspaceServicePayloadSchema).default([]),
|
||||
});
|
||||
|
||||
export const AgentUpdateMessageSchema = z.object({
|
||||
@@ -1682,6 +1699,14 @@ export const WorkspaceUpdateMessageSchema = z.object({
|
||||
]),
|
||||
});
|
||||
|
||||
export const ServiceStatusUpdateMessageSchema = z.object({
|
||||
type: z.literal("service_status_update"),
|
||||
payload: z.object({
|
||||
workspaceId: z.string(),
|
||||
services: z.array(WorkspaceServicePayloadSchema),
|
||||
}),
|
||||
});
|
||||
|
||||
export const WorkspaceSetupProgressMessageSchema = z.object({
|
||||
type: z.literal("workspace_setup_progress"),
|
||||
payload: z.object({
|
||||
@@ -1692,6 +1717,21 @@ export const WorkspaceSetupProgressMessageSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const WorkspaceSetupSnapshotSchema = z.object({
|
||||
status: z.enum(["running", "completed", "failed"]),
|
||||
detail: WorktreeSetupDetailPayloadSchema,
|
||||
error: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const WorkspaceSetupStatusResponseMessageSchema = z.object({
|
||||
type: z.literal("workspace_setup_status_response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
workspaceId: z.string(),
|
||||
snapshot: WorkspaceSetupSnapshotSchema.nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const OpenProjectResponseMessageSchema = z.object({
|
||||
type: z.literal("open_project_response"),
|
||||
payload: z.object({
|
||||
@@ -2263,7 +2303,9 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
ArtifactMessageSchema,
|
||||
AgentUpdateMessageSchema,
|
||||
WorkspaceUpdateMessageSchema,
|
||||
ServiceStatusUpdateMessageSchema,
|
||||
WorkspaceSetupProgressMessageSchema,
|
||||
WorkspaceSetupStatusResponseMessageSchema,
|
||||
AgentStreamMessageSchema,
|
||||
AgentStatusMessageSchema,
|
||||
FetchAgentsResponseMessageSchema,
|
||||
@@ -2348,14 +2390,20 @@ export type RpcErrorMessage = z.infer<typeof RpcErrorMessageSchema>;
|
||||
export type ArtifactMessage = z.infer<typeof ArtifactMessageSchema>;
|
||||
export type AgentUpdateMessage = z.infer<typeof AgentUpdateMessageSchema>;
|
||||
export type WorkspaceSetupProgressMessage = z.infer<typeof WorkspaceSetupProgressMessageSchema>;
|
||||
export type WorkspaceSetupSnapshot = z.infer<typeof WorkspaceSetupSnapshotSchema>;
|
||||
export type WorkspaceSetupStatusResponseMessage = z.infer<
|
||||
typeof WorkspaceSetupStatusResponseMessageSchema
|
||||
>;
|
||||
export type AgentStreamMessage = z.infer<typeof AgentStreamMessageSchema>;
|
||||
export type AgentStatusMessage = z.infer<typeof AgentStatusMessageSchema>;
|
||||
export type ProjectCheckoutLitePayload = z.infer<typeof ProjectCheckoutLitePayloadSchema>;
|
||||
export type ProjectPlacementPayload = z.infer<typeof ProjectPlacementPayloadSchema>;
|
||||
export type WorkspaceStateBucket = z.infer<typeof WorkspaceStateBucketSchema>;
|
||||
export type WorkspaceDescriptorPayload = z.infer<typeof WorkspaceDescriptorPayloadSchema>;
|
||||
export type WorkspaceServicePayload = z.infer<typeof WorkspaceServicePayloadSchema>;
|
||||
export type FetchAgentsResponseMessage = z.infer<typeof FetchAgentsResponseMessageSchema>;
|
||||
export type FetchWorkspacesResponseMessage = z.infer<typeof FetchWorkspacesResponseMessageSchema>;
|
||||
export type ServiceStatusUpdateMessage = z.infer<typeof ServiceStatusUpdateMessageSchema>;
|
||||
export type OpenProjectResponseMessage = z.infer<typeof OpenProjectResponseMessageSchema>;
|
||||
export type ArchiveWorkspaceResponseMessage = z.infer<typeof ArchiveWorkspaceResponseMessageSchema>;
|
||||
export type FetchAgentResponseMessage = z.infer<typeof FetchAgentResponseMessageSchema>;
|
||||
@@ -2468,6 +2516,7 @@ export type PaseoWorktreeListRequest = z.infer<typeof PaseoWorktreeListRequestSc
|
||||
export type PaseoWorktreeListResponse = z.infer<typeof PaseoWorktreeListResponseSchema>;
|
||||
export type PaseoWorktreeArchiveRequest = z.infer<typeof PaseoWorktreeArchiveRequestSchema>;
|
||||
export type PaseoWorktreeArchiveResponse = z.infer<typeof PaseoWorktreeArchiveResponseSchema>;
|
||||
export type WorkspaceSetupStatusRequest = z.infer<typeof WorkspaceSetupStatusRequestSchema>;
|
||||
export type OpenProjectRequest = z.infer<typeof OpenProjectRequestSchema>;
|
||||
export type ArchiveWorkspaceRequest = z.infer<typeof ArchiveWorkspaceRequestSchema>;
|
||||
export type FileExplorerRequest = z.infer<typeof FileExplorerRequestSchema>;
|
||||
|
||||
@@ -44,6 +44,7 @@ describe("workspace message schemas", () => {
|
||||
name: "",
|
||||
status: "not-a-bucket",
|
||||
activityAt: null,
|
||||
services: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -51,6 +52,70 @@ describe("workspace message schemas", () => {
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
test("parses workspace descriptors with services", () => {
|
||||
const parsed = SessionOutboundMessageSchema.parse({
|
||||
type: "workspace_update",
|
||||
payload: {
|
||||
kind: "upsert",
|
||||
workspace: {
|
||||
id: "/repo",
|
||||
projectId: "/repo",
|
||||
projectDisplayName: "repo",
|
||||
projectRootPath: "/repo",
|
||||
projectKind: "non_git",
|
||||
workspaceKind: "directory",
|
||||
name: "repo",
|
||||
status: "done",
|
||||
activityAt: null,
|
||||
services: [
|
||||
{
|
||||
serviceName: "web",
|
||||
hostname: "web.localhost",
|
||||
port: 3000,
|
||||
url: "http://web.localhost:6767",
|
||||
status: "running",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("workspace_update");
|
||||
if (parsed.type !== "workspace_update" || parsed.payload.kind !== "upsert") {
|
||||
throw new Error("Expected workspace_update upsert payload");
|
||||
}
|
||||
expect(parsed.payload.workspace.services).toEqual([
|
||||
{
|
||||
serviceName: "web",
|
||||
hostname: "web.localhost",
|
||||
port: 3000,
|
||||
url: "http://web.localhost:6767",
|
||||
status: "running",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("parses service_status_update payload", () => {
|
||||
const parsed = SessionOutboundMessageSchema.parse({
|
||||
type: "service_status_update",
|
||||
payload: {
|
||||
workspaceId: "/repo",
|
||||
services: [
|
||||
{
|
||||
serviceName: "web",
|
||||
hostname: "web.localhost",
|
||||
port: 3000,
|
||||
url: null,
|
||||
status: "stopped",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("service_status_update");
|
||||
expect(parsed.payload.workspaceId).toBe("/repo");
|
||||
});
|
||||
|
||||
test("parses workspace_setup_progress payload", () => {
|
||||
const parsed = SessionOutboundMessageSchema.parse({
|
||||
type: "workspace_setup_progress",
|
||||
@@ -67,6 +132,7 @@ describe("workspace message schemas", () => {
|
||||
index: 1,
|
||||
command: "npm install",
|
||||
cwd: "/repo/.paseo/worktrees/feature-a",
|
||||
log: "done",
|
||||
status: "completed",
|
||||
exitCode: 0,
|
||||
durationMs: 100,
|
||||
@@ -79,4 +145,37 @@ describe("workspace message schemas", () => {
|
||||
|
||||
expect(parsed.type).toBe("workspace_setup_progress");
|
||||
});
|
||||
|
||||
test("parses workspace_setup_status_request", () => {
|
||||
const parsed = SessionInboundMessageSchema.parse({
|
||||
type: "workspace_setup_status_request",
|
||||
workspaceId: "/repo/.paseo/worktrees/feature-a",
|
||||
requestId: "req-status",
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("workspace_setup_status_request");
|
||||
});
|
||||
|
||||
test("parses workspace_setup_status_response payload", () => {
|
||||
const parsed = SessionOutboundMessageSchema.parse({
|
||||
type: "workspace_setup_status_response",
|
||||
payload: {
|
||||
requestId: "req-status",
|
||||
workspaceId: "/repo/.paseo/worktrees/feature-a",
|
||||
snapshot: {
|
||||
status: "completed",
|
||||
detail: {
|
||||
type: "worktree_setup",
|
||||
worktreePath: "/repo/.paseo/worktrees/feature-a",
|
||||
branchName: "feature-a",
|
||||
log: "done",
|
||||
commands: [],
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("workspace_setup_status_response");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,6 +79,7 @@ describe("shared tool-call display mapping", () => {
|
||||
index: 1,
|
||||
command: "npm install",
|
||||
cwd: "/tmp/repo/.paseo/worktrees/repo/branch",
|
||||
log: "==> [1/1] Running: npm install\n",
|
||||
status: "running",
|
||||
exitCode: null,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user