feat(app): add open in editor toolbar action (#209)
* feat(app): add open in editor toolbar action * fix(app): normalize editor icon asset permissions * chore(website): drop unrelated generated route tree change
This commit is contained in:
@@ -34,6 +34,8 @@ import type {
|
||||
PaseoWorktreeListResponse,
|
||||
PaseoWorktreeArchiveResponse,
|
||||
ProjectIconResponse,
|
||||
ListAvailableEditorsResponseMessage,
|
||||
OpenInEditorResponseMessage,
|
||||
OpenProjectResponseMessage,
|
||||
ArchiveWorkspaceResponseMessage,
|
||||
ListCommandsResponse,
|
||||
@@ -54,6 +56,7 @@ import type {
|
||||
TerminalInput,
|
||||
SessionInboundMessage,
|
||||
SessionOutboundMessage,
|
||||
EditorTargetId,
|
||||
} from "../shared/messages.js";
|
||||
import type {
|
||||
AgentPermissionRequest,
|
||||
@@ -472,8 +475,11 @@ export type InspectScheduleOptions = {
|
||||
id: string;
|
||||
requestId?: string;
|
||||
};
|
||||
type ListAvailableEditorsPayload = ListAvailableEditorsResponseMessage["payload"];
|
||||
type OpenInEditorPayload = OpenInEditorResponseMessage["payload"];
|
||||
type OpenProjectPayload = OpenProjectResponseMessage["payload"];
|
||||
type ArchiveWorkspacePayload = ArchiveWorkspaceResponseMessage["payload"];
|
||||
export type EditorTargetDescriptor = ListAvailableEditorsPayload["editors"][number];
|
||||
|
||||
export type FetchAgentResult = {
|
||||
agent: AgentSnapshotPayload;
|
||||
@@ -1318,6 +1324,34 @@ export class DaemonClient {
|
||||
});
|
||||
}
|
||||
|
||||
async listAvailableEditors(requestId?: string): Promise<ListAvailableEditorsPayload> {
|
||||
return this.sendCorrelatedSessionRequest({
|
||||
requestId,
|
||||
message: {
|
||||
type: "list_available_editors_request",
|
||||
},
|
||||
responseType: "list_available_editors_response",
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
|
||||
async openInEditor(
|
||||
path: string,
|
||||
editorId: EditorTargetId,
|
||||
requestId?: string,
|
||||
): Promise<OpenInEditorPayload> {
|
||||
return this.sendCorrelatedSessionRequest({
|
||||
requestId,
|
||||
message: {
|
||||
type: "open_in_editor_request",
|
||||
path,
|
||||
editorId,
|
||||
},
|
||||
responseType: "open_in_editor_response",
|
||||
timeout: 10000,
|
||||
});
|
||||
}
|
||||
|
||||
async archiveWorkspace(
|
||||
workspaceId: string,
|
||||
requestId?: string,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
|
||||
|
||||
describe("editor-targets", () => {
|
||||
it("lists available editors in deterministic order", () => {
|
||||
const available = new Set(["code", "cursor", "explorer"]);
|
||||
|
||||
const editors = listAvailableEditorTargets({
|
||||
platform: "win32",
|
||||
findExecutable: (command) => (available.has(command) ? command : null),
|
||||
});
|
||||
|
||||
expect(editors).toEqual([
|
||||
{ id: "cursor", label: "Cursor" },
|
||||
{ id: "vscode", label: "VS Code" },
|
||||
{ id: "explorer", label: "Explorer" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns Finder on macOS", () => {
|
||||
const editors = listAvailableEditorTargets({
|
||||
platform: "darwin",
|
||||
findExecutable: (command) => (command === "open" ? "/usr/bin/open" : null),
|
||||
});
|
||||
|
||||
expect(editors).toEqual([{ id: "finder", label: "Finder" }]);
|
||||
});
|
||||
|
||||
it("returns the generic file manager target on Linux", () => {
|
||||
const editors = listAvailableEditorTargets({
|
||||
platform: "linux",
|
||||
findExecutable: (command) => (command === "xdg-open" ? "/usr/bin/xdg-open" : null),
|
||||
});
|
||||
|
||||
expect(editors).toEqual([{ id: "file-manager", label: "File Manager" }]);
|
||||
});
|
||||
|
||||
it("launches editors as detached processes", async () => {
|
||||
const unref = vi.fn();
|
||||
const once = vi.fn((event: string, handler: () => void) => {
|
||||
if (event === "spawn") {
|
||||
queueMicrotask(handler);
|
||||
}
|
||||
return child;
|
||||
});
|
||||
const child = { once, unref };
|
||||
const spawn = vi.fn(() => child as any);
|
||||
|
||||
await openInEditorTarget(
|
||||
{
|
||||
editorId: "vscode",
|
||||
path: "/tmp/repo",
|
||||
},
|
||||
{
|
||||
platform: "darwin",
|
||||
existsSync: () => true,
|
||||
findExecutable: (command) => (command === "code" ? "/usr/local/bin/code" : null),
|
||||
spawn,
|
||||
},
|
||||
);
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith("/usr/local/bin/code", ["/tmp/repo"], {
|
||||
detached: true,
|
||||
shell: false,
|
||||
stdio: "ignore",
|
||||
});
|
||||
expect(unref).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects relative paths", async () => {
|
||||
await expect(
|
||||
openInEditorTarget(
|
||||
{
|
||||
editorId: "cursor",
|
||||
path: "repo",
|
||||
},
|
||||
{
|
||||
existsSync: () => true,
|
||||
findExecutable: () => "/usr/local/bin/cursor",
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("Editor target path must be an absolute local path");
|
||||
});
|
||||
|
||||
it("rejects platform-specific targets that are unavailable on this OS", async () => {
|
||||
await expect(
|
||||
openInEditorTarget(
|
||||
{
|
||||
editorId: "finder",
|
||||
path: "/tmp/repo",
|
||||
},
|
||||
{
|
||||
platform: "linux",
|
||||
existsSync: () => true,
|
||||
findExecutable: () => "/usr/bin/open",
|
||||
},
|
||||
),
|
||||
).rejects.toThrow("Editor target unavailable: Finder");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { posix, win32 } from "node:path";
|
||||
import type { EditorTargetDescriptorPayload, EditorTargetId } from "../shared/messages.js";
|
||||
import {
|
||||
findExecutable,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
} from "../utils/executable.js";
|
||||
|
||||
type EditorTargetDefinition = {
|
||||
id: EditorTargetId;
|
||||
label: string;
|
||||
command: string;
|
||||
platforms?: readonly NodeJS.Platform[];
|
||||
excludedPlatforms?: readonly NodeJS.Platform[];
|
||||
};
|
||||
|
||||
type ListAvailableEditorTargetsDependencies = {
|
||||
platform?: NodeJS.Platform;
|
||||
findExecutable?: (command: string) => string | null;
|
||||
};
|
||||
|
||||
type OpenInEditorTargetDependencies = ListAvailableEditorTargetsDependencies & {
|
||||
existsSync?: typeof existsSync;
|
||||
spawn?: typeof spawn;
|
||||
};
|
||||
|
||||
const EDITOR_TARGETS: readonly EditorTargetDefinition[] = [
|
||||
{ id: "cursor", label: "Cursor", command: "cursor" },
|
||||
{ id: "vscode", label: "VS Code", command: "code" },
|
||||
{ id: "zed", label: "Zed", command: "zed" },
|
||||
{ id: "finder", label: "Finder", command: "open", platforms: ["darwin"] },
|
||||
{ id: "explorer", label: "Explorer", command: "explorer", platforms: ["win32"] },
|
||||
{
|
||||
id: "file-manager",
|
||||
label: "File Manager",
|
||||
command: "xdg-open",
|
||||
excludedPlatforms: ["darwin", "win32"],
|
||||
},
|
||||
];
|
||||
|
||||
function isAbsolutePath(value: string): boolean {
|
||||
return posix.isAbsolute(value) || win32.isAbsolute(value);
|
||||
}
|
||||
|
||||
function isTargetSupportedOnPlatform(
|
||||
target: EditorTargetDefinition,
|
||||
platform: NodeJS.Platform,
|
||||
): boolean {
|
||||
if (target.platforms && !target.platforms.includes(platform)) {
|
||||
return false;
|
||||
}
|
||||
if (target.excludedPlatforms?.includes(platform)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveEditorTargetDefinition(editorId: EditorTargetId): EditorTargetDefinition {
|
||||
const target = EDITOR_TARGETS.find((entry) => entry.id === editorId);
|
||||
if (!target) {
|
||||
throw new Error(`Unknown editor target: ${editorId}`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function listAvailableEditorTargets(
|
||||
dependencies: ListAvailableEditorTargetsDependencies = {},
|
||||
): EditorTargetDescriptorPayload[] {
|
||||
const platform = dependencies.platform ?? process.platform;
|
||||
const findExecutableFn = dependencies.findExecutable ?? findExecutable;
|
||||
|
||||
return EDITOR_TARGETS.flatMap((target) => {
|
||||
if (!isTargetSupportedOnPlatform(target, platform)) {
|
||||
return [];
|
||||
}
|
||||
const executable = findExecutableFn(target.command);
|
||||
if (!executable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: target.id,
|
||||
label: target.label,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
type Launch = {
|
||||
command: string;
|
||||
args: string[];
|
||||
};
|
||||
|
||||
function resolveEditorLaunch(input: {
|
||||
editorId: EditorTargetId;
|
||||
path: string;
|
||||
platform: NodeJS.Platform;
|
||||
findExecutableFn: typeof findExecutable;
|
||||
}): Launch {
|
||||
const target = resolveEditorTargetDefinition(input.editorId);
|
||||
if (!isTargetSupportedOnPlatform(target, input.platform)) {
|
||||
throw new Error(`Editor target unavailable: ${target.label}`);
|
||||
}
|
||||
const executable = input.findExecutableFn(target.command);
|
||||
if (!executable) {
|
||||
throw new Error(`Editor target unavailable: ${target.label}`);
|
||||
}
|
||||
|
||||
return {
|
||||
command: executable,
|
||||
args: [input.path],
|
||||
};
|
||||
}
|
||||
|
||||
export async function openInEditorTarget(
|
||||
input: {
|
||||
editorId: EditorTargetId;
|
||||
path: string;
|
||||
},
|
||||
dependencies: OpenInEditorTargetDependencies = {},
|
||||
): Promise<void> {
|
||||
const platform = dependencies.platform ?? process.platform;
|
||||
const pathToOpen = input.path.trim();
|
||||
const existsSyncFn = dependencies.existsSync ?? existsSync;
|
||||
const findExecutableFn = dependencies.findExecutable ?? findExecutable;
|
||||
const spawnFn = dependencies.spawn ?? spawn;
|
||||
|
||||
if (!pathToOpen || !isAbsolutePath(pathToOpen)) {
|
||||
throw new Error("Editor target path must be an absolute local path");
|
||||
}
|
||||
if (!existsSyncFn(pathToOpen)) {
|
||||
throw new Error(`Path does not exist: ${pathToOpen}`);
|
||||
}
|
||||
|
||||
const launch = resolveEditorLaunch({
|
||||
editorId: input.editorId,
|
||||
path: pathToOpen,
|
||||
platform,
|
||||
findExecutableFn,
|
||||
});
|
||||
|
||||
const command = platform === "win32" ? quoteWindowsCommand(launch.command) : launch.command;
|
||||
const args =
|
||||
platform === "win32" ? launch.args.map((argument) => quoteWindowsArgument(argument)) : launch.args;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let child: ChildProcess;
|
||||
try {
|
||||
child = spawnFn(command, args, {
|
||||
detached: true,
|
||||
shell: platform === "win32",
|
||||
stdio: "ignore",
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
child.once("error", reject);
|
||||
child.once("spawn", () => {
|
||||
child.unref();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type SubscribeCheckoutDiffRequest,
|
||||
type UnsubscribeCheckoutDiffRequest,
|
||||
type DirectorySuggestionsRequest,
|
||||
type EditorTargetId,
|
||||
type ProjectPlacementPayload,
|
||||
type WorkspaceDescriptorPayload,
|
||||
type WorkspaceStateBucket,
|
||||
@@ -47,6 +48,7 @@ import type { SpeechToTextProvider, TextToSpeechProvider } from "./speech/speech
|
||||
import type { TurnDetectionProvider } from "./speech/turn-detection-provider.js";
|
||||
import { maybePersistTtsDebugAudio } from "./agent/tts-debug.js";
|
||||
import { isPaseoDictationDebugEnabled } from "./agent/recordings-debug.js";
|
||||
import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
|
||||
import {
|
||||
DictationStreamManager,
|
||||
type DictationStreamOutboundMessage,
|
||||
@@ -1742,6 +1744,14 @@ export class Session {
|
||||
await this.handleCreatePaseoWorktreeRequest(msg);
|
||||
break;
|
||||
|
||||
case "list_available_editors_request":
|
||||
await this.handleListAvailableEditorsRequest(msg);
|
||||
break;
|
||||
|
||||
case "open_in_editor_request":
|
||||
await this.handleOpenInEditorRequest(msg);
|
||||
break;
|
||||
|
||||
case "open_project_request":
|
||||
await this.handleOpenProjectRequest(msg);
|
||||
break;
|
||||
@@ -5981,6 +5991,77 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
async getAvailableEditorTargets() {
|
||||
return listAvailableEditorTargets();
|
||||
}
|
||||
|
||||
async openEditorTarget(options: { editorId: EditorTargetId; path: string }): Promise<void> {
|
||||
await openInEditorTarget(options);
|
||||
}
|
||||
|
||||
private async handleListAvailableEditorsRequest(
|
||||
request: Extract<SessionInboundMessage, { type: "list_available_editors_request" }>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const editors = await this.getAvailableEditorTargets();
|
||||
this.emit({
|
||||
type: "list_available_editors_response",
|
||||
payload: {
|
||||
requestId: request.requestId,
|
||||
editors,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to list available editors";
|
||||
this.sessionLogger.error(
|
||||
{ err: error, requestType: request.type },
|
||||
"Failed to list available editors",
|
||||
);
|
||||
this.emit({
|
||||
type: "list_available_editors_response",
|
||||
payload: {
|
||||
requestId: request.requestId,
|
||||
editors: [],
|
||||
error: message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleOpenInEditorRequest(
|
||||
request: Extract<SessionInboundMessage, { type: "open_in_editor_request" }>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.openEditorTarget({ editorId: request.editorId, path: request.path });
|
||||
this.emit({
|
||||
type: "open_in_editor_response",
|
||||
payload: {
|
||||
requestId: request.requestId,
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to open in editor";
|
||||
this.sessionLogger.error(
|
||||
{
|
||||
err: error,
|
||||
editorId: request.editorId,
|
||||
path: request.path,
|
||||
requestType: request.type,
|
||||
},
|
||||
"Failed to open in editor",
|
||||
);
|
||||
this.emit({
|
||||
type: "open_in_editor_response",
|
||||
payload: {
|
||||
requestId: request.requestId,
|
||||
error: message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCreatePaseoWorktreeRequest(
|
||||
request: Extract<SessionInboundMessage, { type: "create_paseo_worktree_request" }>,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -1223,6 +1223,55 @@ describe("workspace aggregation", () => {
|
||||
expect(response?.payload.workspace?.id).toBe(repoRoot);
|
||||
});
|
||||
|
||||
test("list_available_editors_request returns available targets", async () => {
|
||||
const emitted: Array<{ type: string; payload: unknown }> = [];
|
||||
const session = createSessionForWorkspaceTests() as any;
|
||||
|
||||
session.emit = (message: any) => emitted.push(message);
|
||||
session.getAvailableEditorTargets = async () => [
|
||||
{ id: "cursor", label: "Cursor" },
|
||||
{ id: "finder", label: "Finder" },
|
||||
];
|
||||
|
||||
await session.handleMessage({
|
||||
type: "list_available_editors_request",
|
||||
requestId: "req-editors",
|
||||
});
|
||||
|
||||
const response = emitted.find(
|
||||
(message) => message.type === "list_available_editors_response",
|
||||
) as any;
|
||||
expect(response?.payload.error).toBeNull();
|
||||
expect(response?.payload.editors).toEqual([
|
||||
{ id: "cursor", label: "Cursor" },
|
||||
{ id: "finder", label: "Finder" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("open_in_editor_request launches the selected target", async () => {
|
||||
const emitted: Array<{ type: string; payload: unknown }> = [];
|
||||
const session = createSessionForWorkspaceTests() as any;
|
||||
const calls: Array<{ editorId: string; path: string }> = [];
|
||||
|
||||
session.emit = (message: any) => emitted.push(message);
|
||||
session.openEditorTarget = async (input: { editorId: string; path: string }) => {
|
||||
calls.push(input);
|
||||
};
|
||||
|
||||
await session.handleMessage({
|
||||
type: "open_in_editor_request",
|
||||
requestId: "req-open-editor",
|
||||
editorId: "vscode",
|
||||
path: "/tmp/repo",
|
||||
});
|
||||
|
||||
expect(calls).toEqual([{ editorId: "vscode", path: "/tmp/repo" }]);
|
||||
const response = emitted.find(
|
||||
(message) => message.type === "open_in_editor_response",
|
||||
) as any;
|
||||
expect(response?.payload.error).toBeNull();
|
||||
});
|
||||
|
||||
test("archive_workspace_request hides non-destructive workspace records", async () => {
|
||||
const emitted: Array<{ type: string; payload: unknown }> = [];
|
||||
const session = createSessionForWorkspaceTests() as any;
|
||||
|
||||
@@ -1084,6 +1084,32 @@ export const CreatePaseoWorktreeRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const EditorTargetIdSchema = z.enum([
|
||||
"cursor",
|
||||
"vscode",
|
||||
"zed",
|
||||
"finder",
|
||||
"explorer",
|
||||
"file-manager",
|
||||
]);
|
||||
|
||||
export const EditorTargetDescriptorPayloadSchema = z.object({
|
||||
id: EditorTargetIdSchema,
|
||||
label: z.string(),
|
||||
});
|
||||
|
||||
export const ListAvailableEditorsRequestSchema = z.object({
|
||||
type: z.literal("list_available_editors_request"),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const OpenInEditorRequestSchema = z.object({
|
||||
type: z.literal("open_in_editor_request"),
|
||||
path: z.string(),
|
||||
editorId: EditorTargetIdSchema,
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const OpenProjectRequestSchema = z.object({
|
||||
type: z.literal("open_project_request"),
|
||||
cwd: z.string(),
|
||||
@@ -1341,6 +1367,8 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
PaseoWorktreeListRequestSchema,
|
||||
PaseoWorktreeArchiveRequestSchema,
|
||||
CreatePaseoWorktreeRequestSchema,
|
||||
ListAvailableEditorsRequestSchema,
|
||||
OpenInEditorRequestSchema,
|
||||
OpenProjectRequestSchema,
|
||||
ArchiveWorkspaceRequestSchema,
|
||||
FileExplorerRequestSchema,
|
||||
@@ -1828,6 +1856,23 @@ export const OpenProjectResponseMessageSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ListAvailableEditorsResponseMessageSchema = z.object({
|
||||
type: z.literal("list_available_editors_response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
editors: z.array(EditorTargetDescriptorPayloadSchema),
|
||||
error: z.string().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const OpenInEditorResponseMessageSchema = z.object({
|
||||
type: z.literal("open_in_editor_response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
error: z.string().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ArchiveWorkspaceResponseMessageSchema = z.object({
|
||||
type: z.literal("archive_workspace_response"),
|
||||
payload: z.object({
|
||||
@@ -2475,6 +2520,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
FetchAgentsResponseMessageSchema,
|
||||
FetchWorkspacesResponseMessageSchema,
|
||||
OpenProjectResponseMessageSchema,
|
||||
ListAvailableEditorsResponseMessageSchema,
|
||||
OpenInEditorResponseMessageSchema,
|
||||
ArchiveWorkspaceResponseMessageSchema,
|
||||
FetchAgentResponseMessageSchema,
|
||||
FetchAgentTimelineResponseMessageSchema,
|
||||
@@ -2567,9 +2614,15 @@ export type ProjectCheckoutLitePayload = z.infer<typeof ProjectCheckoutLitePaylo
|
||||
export type ProjectPlacementPayload = z.infer<typeof ProjectPlacementPayloadSchema>;
|
||||
export type WorkspaceStateBucket = z.infer<typeof WorkspaceStateBucketSchema>;
|
||||
export type WorkspaceDescriptorPayload = z.infer<typeof WorkspaceDescriptorPayloadSchema>;
|
||||
export type EditorTargetId = z.infer<typeof EditorTargetIdSchema>;
|
||||
export type EditorTargetDescriptorPayload = z.infer<typeof EditorTargetDescriptorPayloadSchema>;
|
||||
export type FetchAgentsResponseMessage = z.infer<typeof FetchAgentsResponseMessageSchema>;
|
||||
export type FetchWorkspacesResponseMessage = z.infer<typeof FetchWorkspacesResponseMessageSchema>;
|
||||
export type OpenProjectResponseMessage = z.infer<typeof OpenProjectResponseMessageSchema>;
|
||||
export type ListAvailableEditorsResponseMessage = z.infer<
|
||||
typeof ListAvailableEditorsResponseMessageSchema
|
||||
>;
|
||||
export type OpenInEditorResponseMessage = z.infer<typeof OpenInEditorResponseMessageSchema>;
|
||||
export type ArchiveWorkspaceResponseMessage = z.infer<typeof ArchiveWorkspaceResponseMessageSchema>;
|
||||
export type FetchAgentResponseMessage = z.infer<typeof FetchAgentResponseMessageSchema>;
|
||||
export type FetchAgentTimelineResponseMessage = z.infer<
|
||||
@@ -2717,6 +2770,8 @@ 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 ListAvailableEditorsRequest = z.infer<typeof ListAvailableEditorsRequestSchema>;
|
||||
export type OpenInEditorRequest = z.infer<typeof OpenInEditorRequestSchema>;
|
||||
export type OpenProjectRequest = z.infer<typeof OpenProjectRequestSchema>;
|
||||
export type ArchiveWorkspaceRequest = z.infer<typeof ArchiveWorkspaceRequestSchema>;
|
||||
export type FileExplorerRequest = z.infer<typeof FileExplorerRequestSchema>;
|
||||
|
||||
@@ -29,6 +29,27 @@ describe("workspace message schemas", () => {
|
||||
expect(parsed.type).toBe("open_project_request");
|
||||
});
|
||||
|
||||
test("parses list_available_editors_request", () => {
|
||||
const parsed = SessionInboundMessageSchema.parse({
|
||||
type: "list_available_editors_request",
|
||||
requestId: "req-editors",
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("list_available_editors_request");
|
||||
});
|
||||
|
||||
test("parses open_in_editor_response", () => {
|
||||
const parsed = SessionOutboundMessageSchema.parse({
|
||||
type: "open_in_editor_response",
|
||||
payload: {
|
||||
requestId: "req-open-editor",
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("open_in_editor_response");
|
||||
});
|
||||
|
||||
test("rejects invalid workspace update payload", () => {
|
||||
const result = SessionOutboundMessageSchema.safeParse({
|
||||
type: "workspace_update",
|
||||
|
||||
Reference in New Issue
Block a user