feat(cli): manage workspace scripts (#1992)

* feat(cli): manage workspace scripts

* docs: document workspace script management

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
Matt Cowger
2026-07-24 06:12:14 -07:00
committed by GitHub
parent 21597bdc1b
commit 21404fbdec
22 changed files with 800 additions and 55 deletions

View File

@@ -2328,6 +2328,26 @@ export const StartWorkspaceScriptRequestSchema = z.object({
requestId: z.string(),
});
export const WorkspaceScriptListRequestSchema = z.object({
type: z.literal("workspace.script.list.request"),
workspaceId: z.string(),
requestId: z.string(),
});
export const WorkspaceScriptStartRequestSchema = z.object({
type: z.literal("workspace.script.start.request"),
workspaceId: z.string(),
scriptName: z.string(),
requestId: z.string(),
});
export const WorkspaceScriptStopRequestSchema = z.object({
type: z.literal("workspace.script.stop.request"),
workspaceId: z.string(),
scriptName: z.string(),
requestId: z.string(),
});
export const SubscribeTerminalRequestSchema = z.object({
type: z.literal("subscribe_terminal_request"),
terminalId: z.string(),
@@ -2530,6 +2550,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
CreateTerminalRequestSchema,
RenameTerminalRequestSchema,
StartWorkspaceScriptRequestSchema,
WorkspaceScriptListRequestSchema,
WorkspaceScriptStartRequestSchema,
WorkspaceScriptStopRequestSchema,
SubscribeTerminalRequestSchema,
UnsubscribeTerminalRequestSchema,
TerminalInputSchema,
@@ -2793,6 +2816,8 @@ export const ServerInfoStatusPayloadSchema = z
selectiveAgentTimeline: z.boolean().optional(),
// COMPAT(stableProjectIdentity): added in v0.1.109, remove gate after 2027-01-15.
stableProjectIdentity: z.boolean().optional(),
// COMPAT(workspaceScriptManagement): added in v0.1.105, remove gate after 2027-01-10.
workspaceScriptManagement: z.boolean().optional(),
})
.optional(),
})
@@ -3382,6 +3407,30 @@ export const StartWorkspaceScriptResponseMessageSchema = z.object({
}),
});
const WorkspaceScriptOperationPayloadSchema = z.object({
requestId: z.string(),
workspaceId: z.string(),
scriptName: z.string().optional(),
script: WorkspaceScriptPayloadSchema.nullable().optional(),
scripts: z.array(WorkspaceScriptPayloadSchema).optional(),
error: z.string().nullable(),
});
export const WorkspaceScriptListResponseMessageSchema = z.object({
type: z.literal("workspace.script.list.response"),
payload: WorkspaceScriptOperationPayloadSchema,
});
export const WorkspaceScriptStartResponseMessageSchema = z.object({
type: z.literal("workspace.script.start.response"),
payload: WorkspaceScriptOperationPayloadSchema,
});
export const WorkspaceScriptStopResponseMessageSchema = z.object({
type: z.literal("workspace.script.stop.response"),
payload: WorkspaceScriptOperationPayloadSchema,
});
// COMPAT(desktopEditorBridge): added in v0.1.88, remove after 2026-12-03 once old clients no longer parse daemon editor RPC responses.
export const LegacyListAvailableEditorsResponseMessageSchema = z.object({
type: z.literal("list_available_editors_response"),
@@ -5121,6 +5170,9 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
WorkspaceGithubSearchRepositoriesResponseSchema,
ProjectGithubCloneResponseSchema,
StartWorkspaceScriptResponseMessageSchema,
WorkspaceScriptListResponseMessageSchema,
WorkspaceScriptStartResponseMessageSchema,
WorkspaceScriptStopResponseMessageSchema,
LegacyListAvailableEditorsResponseMessageSchema,
LegacyOpenInEditorResponseMessageSchema,
ArchiveWorkspaceResponseMessageSchema,
@@ -5304,6 +5356,18 @@ export type ProjectGithubCloneResponse = z.infer<typeof ProjectGithubCloneRespon
export type StartWorkspaceScriptResponseMessage = z.infer<
typeof StartWorkspaceScriptResponseMessageSchema
>;
export type WorkspaceScriptListRequest = z.infer<typeof WorkspaceScriptListRequestSchema>;
export type WorkspaceScriptStartRequest = z.infer<typeof WorkspaceScriptStartRequestSchema>;
export type WorkspaceScriptStopRequest = z.infer<typeof WorkspaceScriptStopRequestSchema>;
export type WorkspaceScriptListResponseMessage = z.infer<
typeof WorkspaceScriptListResponseMessageSchema
>;
export type WorkspaceScriptStartResponseMessage = z.infer<
typeof WorkspaceScriptStartResponseMessageSchema
>;
export type WorkspaceScriptStopResponseMessage = z.infer<
typeof WorkspaceScriptStopResponseMessageSchema
>;
export type LegacyListAvailableEditorsResponseMessage = z.infer<
typeof LegacyListAvailableEditorsResponseMessageSchema
>;

View File

@@ -622,6 +622,42 @@ describe("workspace message schemas", () => {
expect(parsed.payload.workspace.workspaceKind).toBe("directory");
});
test("parses workspace script management request and response payloads", () => {
expect(
SessionInboundMessageSchema.parse({
type: "workspace.script.stop.request",
requestId: "req-script-stop",
workspaceId: "ws-repo",
scriptName: "web",
}),
).toMatchObject({ type: "workspace.script.stop.request", scriptName: "web" });
expect(
SessionOutboundMessageSchema.parse({
type: "workspace.script.start.response",
payload: {
requestId: "req-script-start",
workspaceId: "ws-repo",
scriptName: "web",
script: {
scriptName: "web",
type: "service",
hostname: "web--repo.localhost",
port: 3000,
proxyUrl: "http://web--repo.localhost:6767",
lifecycle: "running",
health: "healthy",
terminalId: "terminal-1",
},
error: null,
},
}),
).toMatchObject({
type: "workspace.script.start.response",
payload: { script: { terminalId: "terminal-1", lifecycle: "running" } },
});
});
test("parses script_status_update payload", () => {
const parsed = SessionOutboundMessageSchema.parse({
type: "script_status_update",