mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Compare commits
2 Commits
v0.1.63-be
...
add-websto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3876ffe61 | ||
|
|
8f136a1f92 |
BIN
packages/app/assets/images/editor-apps/webstorm.png
Normal file
BIN
packages/app/assets/images/editor-apps/webstorm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
@@ -1,5 +1,10 @@
|
|||||||
|
import { SquareTerminal } from "lucide-react-native";
|
||||||
import { Image, type ImageSourcePropType } from "react-native";
|
import { Image, type ImageSourcePropType } from "react-native";
|
||||||
import type { EditorTargetId } from "@server/shared/messages";
|
import {
|
||||||
|
isKnownEditorTargetId,
|
||||||
|
type EditorTargetId,
|
||||||
|
type KnownEditorTargetId,
|
||||||
|
} from "@server/shared/messages";
|
||||||
|
|
||||||
interface EditorAppIconProps {
|
interface EditorAppIconProps {
|
||||||
editorId: EditorTargetId;
|
editorId: EditorTargetId;
|
||||||
@@ -8,9 +13,10 @@ interface EditorAppIconProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const EDITOR_APP_IMAGES: Record<EditorTargetId, ImageSourcePropType> = {
|
const EDITOR_APP_IMAGES: Record<KnownEditorTargetId, ImageSourcePropType> = {
|
||||||
cursor: require("../../../assets/images/editor-apps/cursor.png"),
|
cursor: require("../../../assets/images/editor-apps/cursor.png"),
|
||||||
vscode: require("../../../assets/images/editor-apps/vscode.png"),
|
vscode: require("../../../assets/images/editor-apps/vscode.png"),
|
||||||
|
webstorm: require("../../../assets/images/editor-apps/webstorm.png"),
|
||||||
zed: require("../../../assets/images/editor-apps/zed.png"),
|
zed: require("../../../assets/images/editor-apps/zed.png"),
|
||||||
finder: require("../../../assets/images/editor-apps/finder.png"),
|
finder: require("../../../assets/images/editor-apps/finder.png"),
|
||||||
explorer: require("../../../assets/images/editor-apps/file-explorer.png"),
|
explorer: require("../../../assets/images/editor-apps/file-explorer.png"),
|
||||||
@@ -18,10 +24,21 @@ const EDITOR_APP_IMAGES: Record<EditorTargetId, ImageSourcePropType> = {
|
|||||||
};
|
};
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
|
export function hasBundledEditorAppIcon(
|
||||||
|
editorId: EditorTargetId,
|
||||||
|
): editorId is KnownEditorTargetId {
|
||||||
|
return isKnownEditorTargetId(editorId);
|
||||||
|
}
|
||||||
|
|
||||||
export function EditorAppIcon({
|
export function EditorAppIcon({
|
||||||
editorId,
|
editorId,
|
||||||
size = 16,
|
size = 16,
|
||||||
|
color,
|
||||||
}: EditorAppIconProps) {
|
}: EditorAppIconProps) {
|
||||||
|
if (!hasBundledEditorAppIcon(editorId)) {
|
||||||
|
return <SquareTerminal size={size} color={color} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
source={EDITOR_APP_IMAGES[editorId]}
|
source={EDITOR_APP_IMAGES[editorId]}
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ describe("resolvePreferredEditorId", () => {
|
|||||||
expect(resolvePreferredEditorId(["explorer", "vscode"], "finder")).toBe("explorer");
|
expect(resolvePreferredEditorId(["explorer", "vscode"], "finder")).toBe("explorer");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps unknown editor ids when they are still available", () => {
|
||||||
|
expect(resolvePreferredEditorId(["unknown-editor", "cursor"], "unknown-editor")).toBe(
|
||||||
|
"unknown-editor",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns null when no editors are available", () => {
|
it("returns null when no editors are available", () => {
|
||||||
expect(resolvePreferredEditorId([], "cursor")).toBeNull();
|
expect(resolvePreferredEditorId([], "cursor")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets
|
|||||||
|
|
||||||
describe("editor-targets", () => {
|
describe("editor-targets", () => {
|
||||||
it("lists available editors in deterministic order", async () => {
|
it("lists available editors in deterministic order", async () => {
|
||||||
const available = new Set(["code", "cursor", "explorer"]);
|
const available = new Set(["code", "cursor", "explorer", "webstorm"]);
|
||||||
|
|
||||||
const editors = await listAvailableEditorTargets({
|
const editors = await listAvailableEditorTargets({
|
||||||
platform: "win32",
|
platform: "win32",
|
||||||
@@ -13,6 +13,7 @@ describe("editor-targets", () => {
|
|||||||
expect(editors).toEqual([
|
expect(editors).toEqual([
|
||||||
{ id: "cursor", label: "Cursor" },
|
{ id: "cursor", label: "Cursor" },
|
||||||
{ id: "vscode", label: "VS Code" },
|
{ id: "vscode", label: "VS Code" },
|
||||||
|
{ id: "webstorm", label: "WebStorm" },
|
||||||
{ id: "explorer", label: "Explorer" },
|
{ id: "explorer", label: "Explorer" },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -97,4 +98,19 @@ describe("editor-targets", () => {
|
|||||||
),
|
),
|
||||||
).rejects.toThrow("Editor target unavailable: Finder");
|
).rejects.toThrow("Editor target unavailable: Finder");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects unknown editor ids", async () => {
|
||||||
|
await expect(
|
||||||
|
openInEditorTarget(
|
||||||
|
{
|
||||||
|
editorId: "unknown-editor",
|
||||||
|
path: "/tmp/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
existsSync: () => true,
|
||||||
|
findExecutable: () => null,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).rejects.toThrow("Unknown editor target: unknown-editor");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { spawn, type ChildProcess } from "node:child_process";
|
import { spawn, type ChildProcess } from "node:child_process";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { posix, win32 } from "node:path";
|
import { posix, win32 } from "node:path";
|
||||||
import type { EditorTargetDescriptorPayload, EditorTargetId } from "../shared/messages.js";
|
import type {
|
||||||
|
EditorTargetDescriptorPayload,
|
||||||
|
EditorTargetId,
|
||||||
|
KnownEditorTargetId,
|
||||||
|
} from "../shared/messages.js";
|
||||||
import {
|
import {
|
||||||
findExecutable,
|
findExecutable,
|
||||||
quoteWindowsArgument,
|
quoteWindowsArgument,
|
||||||
@@ -9,7 +13,7 @@ import {
|
|||||||
} from "../utils/executable.js";
|
} from "../utils/executable.js";
|
||||||
|
|
||||||
type EditorTargetDefinition = {
|
type EditorTargetDefinition = {
|
||||||
id: EditorTargetId;
|
id: KnownEditorTargetId;
|
||||||
label: string;
|
label: string;
|
||||||
command: string;
|
command: string;
|
||||||
platforms?: readonly NodeJS.Platform[];
|
platforms?: readonly NodeJS.Platform[];
|
||||||
@@ -29,6 +33,7 @@ type OpenInEditorTargetDependencies = ListAvailableEditorTargetsDependencies & {
|
|||||||
const EDITOR_TARGETS: readonly EditorTargetDefinition[] = [
|
const EDITOR_TARGETS: readonly EditorTargetDefinition[] = [
|
||||||
{ id: "cursor", label: "Cursor", command: "cursor" },
|
{ id: "cursor", label: "Cursor", command: "cursor" },
|
||||||
{ id: "vscode", label: "VS Code", command: "code" },
|
{ id: "vscode", label: "VS Code", command: "code" },
|
||||||
|
{ id: "webstorm", label: "WebStorm", command: "webstorm" },
|
||||||
{ id: "zed", label: "Zed", command: "zed" },
|
{ id: "zed", label: "Zed", command: "zed" },
|
||||||
{ id: "finder", label: "Finder", command: "open", platforms: ["darwin"] },
|
{ id: "finder", label: "Finder", command: "open", platforms: ["darwin"] },
|
||||||
{ id: "explorer", label: "Explorer", command: "explorer", platforms: ["win32"] },
|
{ id: "explorer", label: "Explorer", command: "explorer", platforms: ["win32"] },
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { homedir } from "node:os";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import type { ToolSet } from "ai";
|
import type { ToolSet } from "ai";
|
||||||
import {
|
import {
|
||||||
|
isLegacyEditorTargetId,
|
||||||
serializeAgentStreamEvent,
|
serializeAgentStreamEvent,
|
||||||
type AgentSnapshotPayload,
|
type AgentSnapshotPayload,
|
||||||
type SessionInboundMessage,
|
type SessionInboundMessage,
|
||||||
@@ -28,6 +29,7 @@ import {
|
|||||||
type SubscribeCheckoutDiffRequest,
|
type SubscribeCheckoutDiffRequest,
|
||||||
type UnsubscribeCheckoutDiffRequest,
|
type UnsubscribeCheckoutDiffRequest,
|
||||||
type DirectorySuggestionsRequest,
|
type DirectorySuggestionsRequest,
|
||||||
|
type EditorTargetDescriptorPayload,
|
||||||
type EditorTargetId,
|
type EditorTargetId,
|
||||||
type ProjectPlacementPayload,
|
type ProjectPlacementPayload,
|
||||||
type WorkspaceDescriptorPayload,
|
type WorkspaceDescriptorPayload,
|
||||||
@@ -202,13 +204,14 @@ const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
|||||||
// the entire session message if they encounter an unknown provider.
|
// the entire session message if they encounter an unknown provider.
|
||||||
const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]);
|
const LEGACY_PROVIDER_IDS = new Set(["claude", "codex", "opencode"]);
|
||||||
const MIN_VERSION_ALL_PROVIDERS = "0.1.45";
|
const MIN_VERSION_ALL_PROVIDERS = "0.1.45";
|
||||||
|
const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50";
|
||||||
|
|
||||||
function clientSupportsAllProviders(appVersion: string | null): boolean {
|
function isAppVersionAtLeast(appVersion: string | null, minVersion: string): boolean {
|
||||||
if (!appVersion) return false;
|
if (!appVersion) return false;
|
||||||
// Strip RC/prerelease suffix: "0.1.45-rc.4" → "0.1.45"
|
// Strip RC/prerelease suffix: "0.1.45-rc.4" → "0.1.45"
|
||||||
const base = appVersion.replace(/-.*$/, "");
|
const base = appVersion.replace(/-.*$/, "");
|
||||||
const parts = base.split(".").map(Number);
|
const parts = base.split(".").map(Number);
|
||||||
const minParts = MIN_VERSION_ALL_PROVIDERS.split(".").map(Number);
|
const minParts = minVersion.split(".").map(Number);
|
||||||
for (let i = 0; i < minParts.length; i++) {
|
for (let i = 0; i < minParts.length; i++) {
|
||||||
const a = parts[i] ?? 0;
|
const a = parts[i] ?? 0;
|
||||||
const b = minParts[i] ?? 0;
|
const b = minParts[i] ?? 0;
|
||||||
@@ -218,6 +221,14 @@ function clientSupportsAllProviders(appVersion: string | null): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clientSupportsAllProviders(appVersion: string | null): boolean {
|
||||||
|
return isAppVersionAtLeast(appVersion, MIN_VERSION_ALL_PROVIDERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientSupportsFlexibleEditorIds(appVersion: string | null): boolean {
|
||||||
|
return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
|
||||||
|
}
|
||||||
|
|
||||||
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
|
const WORKSPACE_GIT_WATCH_DEBOUNCE_MS = 500;
|
||||||
const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
|
const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
|
||||||
const TERMINAL_STREAM_HIGH_WATER_BYTES = 256 * 1024;
|
const TERMINAL_STREAM_HIGH_WATER_BYTES = 256 * 1024;
|
||||||
@@ -1215,6 +1226,15 @@ export class Session {
|
|||||||
return LEGACY_PROVIDER_IDS.has(provider);
|
return LEGACY_PROVIDER_IDS.has(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private filterEditorsForClient(
|
||||||
|
editors: EditorTargetDescriptorPayload[],
|
||||||
|
): EditorTargetDescriptorPayload[] {
|
||||||
|
if (clientSupportsFlexibleEditorIds(this.appVersion)) {
|
||||||
|
return editors;
|
||||||
|
}
|
||||||
|
return editors.filter((editor) => isLegacyEditorTargetId(editor.id));
|
||||||
|
}
|
||||||
|
|
||||||
private matchesAgentFilter(options: {
|
private matchesAgentFilter(options: {
|
||||||
agent: AgentSnapshotPayload;
|
agent: AgentSnapshotPayload;
|
||||||
project: ProjectPlacementPayload;
|
project: ProjectPlacementPayload;
|
||||||
@@ -6095,7 +6115,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAvailableEditorTargets() {
|
async getAvailableEditorTargets() {
|
||||||
return listAvailableEditorTargets();
|
return this.filterEditorsForClient(await listAvailableEditorTargets());
|
||||||
}
|
}
|
||||||
|
|
||||||
async openEditorTarget(options: { editorId: EditorTargetId; path: string }): Promise<void> {
|
async openEditorTarget(options: { editorId: EditorTargetId; path: string }): Promise<void> {
|
||||||
@@ -7523,7 +7543,9 @@ export class Session {
|
|||||||
listStoredAgents: () => this.agentStorage.list(),
|
listStoredAgents: () => this.agentStorage.list(),
|
||||||
listLiveAgents: () => this.agentManager.listAgents(),
|
listLiveAgents: () => this.agentManager.listAgents(),
|
||||||
resolveAgentIdentifier: (identifier) => this.resolveAgentIdentifier(identifier),
|
resolveAgentIdentifier: (identifier) => this.resolveAgentIdentifier(identifier),
|
||||||
sendAgentMessage: (agentId, text) => this.handleSendAgentMessage(agentId, text),
|
sendAgentMessage: async (agentId, text) => {
|
||||||
|
await this.handleSendAgentMessage(agentId, text);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.emitChatRpcError(request, error);
|
this.emitChatRpcError(request, error);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function makeAgent(input: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSessionForWorkspaceTests(): Session {
|
function createSessionForWorkspaceTests(options: { appVersion?: string | null } = {}): Session {
|
||||||
const logger = {
|
const logger = {
|
||||||
child: () => logger,
|
child: () => logger,
|
||||||
trace: vi.fn(),
|
trace: vi.fn(),
|
||||||
@@ -73,6 +73,7 @@ function createSessionForWorkspaceTests(): Session {
|
|||||||
|
|
||||||
const session = new Session({
|
const session = new Session({
|
||||||
clientId: "test-client",
|
clientId: "test-client",
|
||||||
|
appVersion: options.appVersion ?? null,
|
||||||
onMessage: vi.fn(),
|
onMessage: vi.fn(),
|
||||||
logger: logger as any,
|
logger: logger as any,
|
||||||
downloadTokenStore: {} as any,
|
downloadTokenStore: {} as any,
|
||||||
@@ -1246,19 +1247,52 @@ describe("workspace aggregation", () => {
|
|||||||
|
|
||||||
test("list_available_editors_request returns available targets", async () => {
|
test("list_available_editors_request returns available targets", async () => {
|
||||||
const emitted: Array<{ type: string; payload: unknown }> = [];
|
const emitted: Array<{ type: string; payload: unknown }> = [];
|
||||||
const session = createSessionForWorkspaceTests() as any;
|
const session = createSessionForWorkspaceTests({ appVersion: "0.1.50" }) as any;
|
||||||
|
|
||||||
session.emit = (message: any) => emitted.push(message);
|
session.emit = (message: any) => emitted.push(message);
|
||||||
session.getAvailableEditorTargets = async () => [
|
session.getAvailableEditorTargets = async () =>
|
||||||
{ id: "cursor", label: "Cursor" },
|
session.filterEditorsForClient([
|
||||||
{ id: "finder", label: "Finder" },
|
{ id: "cursor", label: "Cursor" },
|
||||||
];
|
{ id: "webstorm", label: "WebStorm" },
|
||||||
|
{ id: "finder", label: "Finder" },
|
||||||
|
{ id: "unknown-editor", label: "Unknown Editor" },
|
||||||
|
]);
|
||||||
|
|
||||||
await session.handleMessage({
|
await session.handleMessage({
|
||||||
type: "list_available_editors_request",
|
type: "list_available_editors_request",
|
||||||
requestId: "req-editors",
|
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: "webstorm", label: "WebStorm" },
|
||||||
|
{ id: "finder", label: "Finder" },
|
||||||
|
{ id: "unknown-editor", label: "Unknown Editor" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("list_available_editors_request filters unsupported ids for legacy clients", async () => {
|
||||||
|
const emitted: Array<{ type: string; payload: unknown }> = [];
|
||||||
|
const session = createSessionForWorkspaceTests({ appVersion: "0.1.49" }) as any;
|
||||||
|
|
||||||
|
session.emit = (message: any) => emitted.push(message);
|
||||||
|
session.getAvailableEditorTargets = async () =>
|
||||||
|
session.filterEditorsForClient([
|
||||||
|
{ id: "cursor", label: "Cursor" },
|
||||||
|
{ id: "webstorm", label: "WebStorm" },
|
||||||
|
{ id: "unknown-editor", label: "Unknown Editor" },
|
||||||
|
{ id: "finder", label: "Finder" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
await session.handleMessage({
|
||||||
|
type: "list_available_editors_request",
|
||||||
|
requestId: "req-editors-legacy",
|
||||||
|
});
|
||||||
|
|
||||||
const response = emitted.find(
|
const response = emitted.find(
|
||||||
(message) => message.type === "list_available_editors_response",
|
(message) => message.type === "list_available_editors_response",
|
||||||
) as any;
|
) as any;
|
||||||
|
|||||||
3
packages/server/src/shared/literal-union.ts
Normal file
3
packages/server/src/shared/literal-union.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Adapted from type-fest's LiteralUnion:
|
||||||
|
// https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts
|
||||||
|
export type LiteralUnion<T extends U, U extends string> = T | (U & Record<never, never>);
|
||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
LoopLogsResponseSchema,
|
LoopLogsResponseSchema,
|
||||||
LoopStopResponseSchema,
|
LoopStopResponseSchema,
|
||||||
} from "../server/loop/rpc-schemas.js";
|
} from "../server/loop/rpc-schemas.js";
|
||||||
|
import type { LiteralUnion } from "./literal-union.js";
|
||||||
import type {
|
import type {
|
||||||
AgentCapabilityFlags,
|
AgentCapabilityFlags,
|
||||||
AgentModelDefinition,
|
AgentModelDefinition,
|
||||||
@@ -1096,14 +1097,32 @@ export const CreatePaseoWorktreeRequestSchema = z.object({
|
|||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const EditorTargetIdSchema = z.enum([
|
// TODO(2026-07): Remove once most clients are on >=0.1.50 and support arbitrary editor ids.
|
||||||
|
export const LEGACY_EDITOR_TARGET_IDS = [
|
||||||
"cursor",
|
"cursor",
|
||||||
"vscode",
|
"vscode",
|
||||||
"zed",
|
"zed",
|
||||||
"finder",
|
"finder",
|
||||||
"explorer",
|
"explorer",
|
||||||
"file-manager",
|
"file-manager",
|
||||||
]);
|
] as const;
|
||||||
|
|
||||||
|
export const KNOWN_EDITOR_TARGET_IDS = [...LEGACY_EDITOR_TARGET_IDS, "webstorm"] as const;
|
||||||
|
|
||||||
|
export const KnownEditorTargetIdSchema = z.enum(KNOWN_EDITOR_TARGET_IDS);
|
||||||
|
export const LegacyEditorTargetIdSchema = z.enum(LEGACY_EDITOR_TARGET_IDS);
|
||||||
|
export const EditorTargetIdSchema = z.string().trim().min(1);
|
||||||
|
|
||||||
|
const KNOWN_EDITOR_TARGET_ID_SET = new Set<string>(KNOWN_EDITOR_TARGET_IDS);
|
||||||
|
const LEGACY_EDITOR_TARGET_ID_SET = new Set<string>(LEGACY_EDITOR_TARGET_IDS);
|
||||||
|
|
||||||
|
export function isKnownEditorTargetId(value: string): value is KnownEditorTargetId {
|
||||||
|
return KNOWN_EDITOR_TARGET_ID_SET.has(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLegacyEditorTargetId(value: string): value is LegacyEditorTargetId {
|
||||||
|
return LEGACY_EDITOR_TARGET_ID_SET.has(value);
|
||||||
|
}
|
||||||
|
|
||||||
export const EditorTargetDescriptorPayloadSchema = z.object({
|
export const EditorTargetDescriptorPayloadSchema = z.object({
|
||||||
id: EditorTargetIdSchema,
|
id: EditorTargetIdSchema,
|
||||||
@@ -2626,7 +2645,9 @@ export type ProjectCheckoutLitePayload = z.infer<typeof ProjectCheckoutLitePaylo
|
|||||||
export type ProjectPlacementPayload = z.infer<typeof ProjectPlacementPayloadSchema>;
|
export type ProjectPlacementPayload = z.infer<typeof ProjectPlacementPayloadSchema>;
|
||||||
export type WorkspaceStateBucket = z.infer<typeof WorkspaceStateBucketSchema>;
|
export type WorkspaceStateBucket = z.infer<typeof WorkspaceStateBucketSchema>;
|
||||||
export type WorkspaceDescriptorPayload = z.infer<typeof WorkspaceDescriptorPayloadSchema>;
|
export type WorkspaceDescriptorPayload = z.infer<typeof WorkspaceDescriptorPayloadSchema>;
|
||||||
export type EditorTargetId = z.infer<typeof EditorTargetIdSchema>;
|
export type KnownEditorTargetId = z.infer<typeof KnownEditorTargetIdSchema>;
|
||||||
|
export type LegacyEditorTargetId = z.infer<typeof LegacyEditorTargetIdSchema>;
|
||||||
|
export type EditorTargetId = LiteralUnion<KnownEditorTargetId, string>;
|
||||||
export type EditorTargetDescriptorPayload = z.infer<typeof EditorTargetDescriptorPayloadSchema>;
|
export type EditorTargetDescriptorPayload = z.infer<typeof EditorTargetDescriptorPayloadSchema>;
|
||||||
export type FetchAgentsResponseMessage = z.infer<typeof FetchAgentsResponseMessageSchema>;
|
export type FetchAgentsResponseMessage = z.infer<typeof FetchAgentsResponseMessageSchema>;
|
||||||
export type FetchWorkspacesResponseMessage = z.infer<typeof FetchWorkspacesResponseMessageSchema>;
|
export type FetchWorkspacesResponseMessage = z.infer<typeof FetchWorkspacesResponseMessageSchema>;
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ describe("workspace message schemas", () => {
|
|||||||
expect(parsed.type).toBe("list_available_editors_request");
|
expect(parsed.type).toBe("list_available_editors_request");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("parses open_in_editor_request with flexible editor ids", () => {
|
||||||
|
const knownEditor = SessionInboundMessageSchema.parse({
|
||||||
|
type: "open_in_editor_request",
|
||||||
|
requestId: "req-open-webstorm",
|
||||||
|
editorId: "webstorm",
|
||||||
|
path: "/tmp/repo",
|
||||||
|
});
|
||||||
|
const unknownEditor = SessionInboundMessageSchema.parse({
|
||||||
|
type: "open_in_editor_request",
|
||||||
|
requestId: "req-open-custom",
|
||||||
|
editorId: "unknown-editor",
|
||||||
|
path: "/tmp/repo",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(knownEditor.type).toBe("open_in_editor_request");
|
||||||
|
expect(unknownEditor.type).toBe("open_in_editor_request");
|
||||||
|
});
|
||||||
|
|
||||||
test("parses open_in_editor_response", () => {
|
test("parses open_in_editor_response", () => {
|
||||||
const parsed = SessionOutboundMessageSchema.parse({
|
const parsed = SessionOutboundMessageSchema.parse({
|
||||||
type: "open_in_editor_response",
|
type: "open_in_editor_response",
|
||||||
@@ -50,6 +68,33 @@ describe("workspace message schemas", () => {
|
|||||||
expect(parsed.type).toBe("open_in_editor_response");
|
expect(parsed.type).toBe("open_in_editor_response");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("parses list_available_editors_response with unknown editor ids", () => {
|
||||||
|
const parsed = SessionOutboundMessageSchema.parse({
|
||||||
|
type: "list_available_editors_response",
|
||||||
|
payload: {
|
||||||
|
requestId: "req-editors",
|
||||||
|
editors: [
|
||||||
|
{ id: "cursor", label: "Cursor" },
|
||||||
|
{ id: "unknown-editor", label: "Unknown Editor" },
|
||||||
|
],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed.type).toBe("list_available_editors_response");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects empty editor ids", () => {
|
||||||
|
const result = SessionInboundMessageSchema.safeParse({
|
||||||
|
type: "open_in_editor_request",
|
||||||
|
requestId: "req-open-empty",
|
||||||
|
editorId: "",
|
||||||
|
path: "/tmp/repo",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
test("rejects invalid workspace update payload", () => {
|
test("rejects invalid workspace update payload", () => {
|
||||||
const result = SessionOutboundMessageSchema.safeParse({
|
const result = SessionOutboundMessageSchema.safeParse({
|
||||||
type: "workspace_update",
|
type: "workspace_update",
|
||||||
|
|||||||
Reference in New Issue
Block a user