mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
Change a worktree workspace's base from the Changes pane. The choice persists and diffs, counts, commits and Git actions follow it. Supersedes #6872. Co-authored-by: Tianyu Yang <yangtianyu.cl@bytedance.com>
648 lines
20 KiB
TypeScript
648 lines
20 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import { z } from "zod";
|
|
import {
|
|
MutableDaemonConfigSchema,
|
|
MutableDaemonConfigPatchSchema,
|
|
UsageReportEntrySchema,
|
|
AgentSnapshotPayloadSchema,
|
|
AgentTimelineItemPayloadSchema,
|
|
ServerInfoStatusPayloadSchema,
|
|
SessionOutboundMessageSchema,
|
|
WSHelloMessageSchema,
|
|
WorkspaceSetupSnapshotSchema,
|
|
WorkspaceSetupProgressMessageSchema,
|
|
AgentTimelineEntryPayloadSchema,
|
|
CheckoutStatusResponseSchema,
|
|
BranchSuggestionsRequestSchema,
|
|
WorkspaceBaseBranchSetRequestSchema,
|
|
WorkspaceBaseBranchSetResponseSchema,
|
|
} from "./messages.js";
|
|
|
|
test("base selection RPCs are correlated while older checkout and branch messages remain valid", () => {
|
|
const payload = {
|
|
cwd: "/repo",
|
|
requestId: "status",
|
|
error: null,
|
|
isGit: true,
|
|
isPaseoOwnedWorktree: false,
|
|
repoRoot: "/repo",
|
|
currentBranch: "feature",
|
|
isDirty: false,
|
|
baseRef: "release",
|
|
aheadBehind: null,
|
|
aheadOfOrigin: null,
|
|
behindOfOrigin: null,
|
|
hasRemote: false,
|
|
remoteUrl: null,
|
|
};
|
|
expect(
|
|
CheckoutStatusResponseSchema.safeParse({ type: "checkout_status_response", payload }).success,
|
|
).toBe(true);
|
|
expect(
|
|
CheckoutStatusResponseSchema.parse({
|
|
type: "checkout_status_response",
|
|
payload: { ...payload, baseRefExact: "refs/remotes/upstream/release" },
|
|
}).payload.baseRefExact,
|
|
).toBe("refs/remotes/upstream/release");
|
|
expect(
|
|
BranchSuggestionsRequestSchema.safeParse({
|
|
type: "branch_suggestions_request",
|
|
cwd: "/repo",
|
|
requestId: "branches",
|
|
}).success,
|
|
).toBe(true);
|
|
const request = {
|
|
type: "workspace.base_branch.set.request",
|
|
workspaceId: "workspace",
|
|
baseRef: "refs/heads/release",
|
|
requestId: "base",
|
|
};
|
|
expect(WorkspaceBaseBranchSetRequestSchema.parse(request)).toEqual(request);
|
|
const response = {
|
|
type: "workspace.base_branch.set.response",
|
|
payload: {
|
|
workspaceId: "workspace",
|
|
baseRef: "refs/heads/release",
|
|
requestId: "base",
|
|
accepted: true,
|
|
error: null,
|
|
},
|
|
};
|
|
expect(WorkspaceBaseBranchSetResponseSchema.parse(response)).toEqual(response);
|
|
});
|
|
|
|
test("terminal listings accept older rows and retain new per-terminal directories", () => {
|
|
const response = {
|
|
type: "list_terminals_response",
|
|
payload: {
|
|
requestId: "terminal-list",
|
|
cwd: "/workspace",
|
|
terminals: [{ id: "terminal", name: "Shell", workspaceId: "workspace" }],
|
|
},
|
|
};
|
|
expect(SessionOutboundMessageSchema.parse(response)).toEqual(response);
|
|
const withDirectory = {
|
|
...response,
|
|
payload: {
|
|
...response.payload,
|
|
terminals: [{ ...response.payload.terminals[0], cwd: "/workspace/subdirectory" }],
|
|
},
|
|
};
|
|
expect(SessionOutboundMessageSchema.parse(withDirectory)).toEqual(withDirectory);
|
|
});
|
|
|
|
const LegacySubAgentToolCallSchema = z.object({
|
|
type: z.literal("tool_call"),
|
|
callId: z.string(),
|
|
name: z.string(),
|
|
status: z.enum(["running", "completed", "failed", "canceled"]),
|
|
error: z.unknown().nullable(),
|
|
detail: z.object({
|
|
type: z.literal("sub_agent"),
|
|
subAgentType: z.string().optional(),
|
|
description: z.string().optional(),
|
|
log: z.string(),
|
|
// Copied from v0.1.65-beta.3: actions was required even though the UI ignored it.
|
|
actions: z.array(
|
|
z.object({
|
|
index: z.number().int().positive(),
|
|
toolName: z.string(),
|
|
summary: z.string().optional(),
|
|
}),
|
|
),
|
|
}),
|
|
});
|
|
|
|
const LegacyAgentCapabilityFlagsSchema = z.object({
|
|
supportsStreaming: z.boolean(),
|
|
supportsSessionPersistence: z.boolean(),
|
|
supportsDynamicModes: z.boolean(),
|
|
supportsMcpServers: z.boolean(),
|
|
supportsReasoningStream: z.boolean(),
|
|
supportsToolInvocations: z.boolean(),
|
|
});
|
|
|
|
const LegacyAgentSnapshotPayloadSchema = AgentSnapshotPayloadSchema.extend({
|
|
capabilities: LegacyAgentCapabilityFlagsSchema,
|
|
});
|
|
|
|
describe("wire schema compatibility", () => {
|
|
test("hello parses with and without the project update capability", () => {
|
|
const legacy = WSHelloMessageSchema.parse({
|
|
type: "hello",
|
|
clientId: "legacy-client",
|
|
clientType: "mobile",
|
|
protocolVersion: 1,
|
|
});
|
|
const capable = WSHelloMessageSchema.parse({
|
|
type: "hello",
|
|
clientId: "capable-client",
|
|
clientType: "mobile",
|
|
protocolVersion: 1,
|
|
capabilities: { project_updates: true },
|
|
});
|
|
|
|
expect([legacy, capable]).toEqual([
|
|
{
|
|
type: "hello",
|
|
clientId: "legacy-client",
|
|
clientType: "mobile",
|
|
protocolVersion: 1,
|
|
},
|
|
{
|
|
type: "hello",
|
|
clientId: "capable-client",
|
|
clientType: "mobile",
|
|
protocolVersion: 1,
|
|
capabilities: { project_updates: true },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("timeline replacement invalidation is opt-in and carries no timeline rows", () => {
|
|
expect(
|
|
WSHelloMessageSchema.parse({
|
|
type: "hello",
|
|
clientId: "capable-client",
|
|
clientType: "mobile",
|
|
protocolVersion: 1,
|
|
capabilities: { timeline_replacement_invalidation: true },
|
|
}).capabilities,
|
|
).toEqual({ timeline_replacement_invalidation: true });
|
|
|
|
expect(
|
|
SessionOutboundMessageSchema.parse({
|
|
type: "agent.timeline.replacement",
|
|
payload: { agentId: "agent-1", epoch: "epoch-2" },
|
|
}),
|
|
).toEqual({
|
|
type: "agent.timeline.replacement",
|
|
payload: { agentId: "agent-1", epoch: "epoch-2" },
|
|
});
|
|
});
|
|
|
|
test("server info strips unknown legacy features while accepting former turn identity", () => {
|
|
const parsed = ServerInfoStatusPayloadSchema.parse({
|
|
status: "server_info",
|
|
serverId: "legacy-server",
|
|
features: {
|
|
workspaceGithubClone: true,
|
|
agentTurnIdentity: true,
|
|
},
|
|
});
|
|
|
|
expect(parsed).toEqual({
|
|
status: "server_info",
|
|
serverId: "legacy-server",
|
|
hostname: null,
|
|
version: null,
|
|
features: { agentTurnIdentity: true },
|
|
});
|
|
});
|
|
|
|
test("assistant timeline message ids are optional on the wire", () => {
|
|
expect(
|
|
AgentTimelineItemPayloadSchema.parse({
|
|
type: "assistant_message",
|
|
text: "old daemon shape",
|
|
}),
|
|
).toEqual({
|
|
type: "assistant_message",
|
|
text: "old daemon shape",
|
|
});
|
|
expect(
|
|
AgentTimelineItemPayloadSchema.parse({
|
|
type: "assistant_message",
|
|
text: "new daemon shape",
|
|
messageId: "msg-1",
|
|
}),
|
|
).toEqual({
|
|
type: "assistant_message",
|
|
text: "new daemon shape",
|
|
messageId: "msg-1",
|
|
});
|
|
});
|
|
|
|
test("task progress fields are optional on the wire", () => {
|
|
expect(
|
|
AgentTimelineItemPayloadSchema.parse({
|
|
type: "todo",
|
|
items: [{ text: "Legacy task", completed: false }],
|
|
}),
|
|
).toEqual({ type: "todo", items: [{ text: "Legacy task", completed: false }] });
|
|
expect(
|
|
AgentTimelineItemPayloadSchema.parse({
|
|
type: "todo",
|
|
items: [
|
|
{
|
|
id: "task-1",
|
|
text: "Current task",
|
|
activeForm: "Working on current task",
|
|
status: "in_progress",
|
|
completed: false,
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
type: "todo",
|
|
items: [
|
|
{
|
|
id: "task-1",
|
|
text: "Current task",
|
|
activeForm: "Working on current task",
|
|
status: "in_progress",
|
|
completed: false,
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
test("a sub-agent launch marker still parses against the v0.1.65-beta.3 schema", () => {
|
|
const card = {
|
|
type: "tool_call",
|
|
callId: "call-sub-agent-1",
|
|
name: "Task",
|
|
status: "completed",
|
|
error: null,
|
|
detail: {
|
|
type: "sub_agent",
|
|
subAgentType: "Explore",
|
|
description: "Inspect repository structure",
|
|
childSessionId: "child-session-1",
|
|
log: "",
|
|
actions: [],
|
|
},
|
|
} as const;
|
|
|
|
const parsed = LegacySubAgentToolCallSchema.parse(JSON.parse(JSON.stringify(card)));
|
|
expect(parsed.detail).toMatchObject({ log: "", actions: [] });
|
|
const { log: _log, ...withoutLog } = card.detail;
|
|
expect(LegacySubAgentToolCallSchema.safeParse({ ...card, detail: withoutLog }).success).toBe(
|
|
false,
|
|
);
|
|
expect(AgentTimelineItemPayloadSchema.parse({ ...card, detail: withoutLog })).toMatchObject({
|
|
detail: { type: "sub_agent", description: "Inspect repository structure" },
|
|
});
|
|
expect(
|
|
AgentTimelineItemPayloadSchema.parse({
|
|
...card,
|
|
detail: { ...card.detail, log: "[Read] README.md" },
|
|
}),
|
|
).toMatchObject({ detail: { log: "[Read] README.md" } });
|
|
});
|
|
|
|
test("old clients parse agent snapshots with rewind capabilities", () => {
|
|
const parsed = LegacyAgentSnapshotPayloadSchema.parse({
|
|
id: "agent-1",
|
|
provider: "claude",
|
|
cwd: "/tmp/project",
|
|
model: null,
|
|
thinkingOptionId: null,
|
|
effectiveThinkingOptionId: null,
|
|
createdAt: "2026-05-23T00:00:00.000Z",
|
|
updatedAt: "2026-05-23T00:00:00.000Z",
|
|
lastUserMessageAt: null,
|
|
status: "idle",
|
|
capabilities: {
|
|
supportsStreaming: true,
|
|
supportsSessionPersistence: true,
|
|
supportsDynamicModes: true,
|
|
supportsMcpServers: true,
|
|
supportsReasoningStream: true,
|
|
supportsToolInvocations: true,
|
|
supportsRewindConversation: true,
|
|
supportsRewindFiles: true,
|
|
supportsRewindBoth: true,
|
|
},
|
|
currentModeId: null,
|
|
availableModes: [],
|
|
pendingPermissions: [],
|
|
persistence: null,
|
|
title: null,
|
|
labels: {},
|
|
});
|
|
|
|
expect(parsed.capabilities).toEqual({
|
|
supportsStreaming: true,
|
|
supportsSessionPersistence: true,
|
|
supportsDynamicModes: true,
|
|
supportsMcpServers: true,
|
|
supportsReasoningStream: true,
|
|
supportsToolInvocations: true,
|
|
});
|
|
});
|
|
|
|
test("new clients parse agent snapshots without rewind capabilities", () => {
|
|
const parsed = AgentSnapshotPayloadSchema.parse({
|
|
id: "agent-1",
|
|
provider: "claude",
|
|
cwd: "/tmp/project",
|
|
model: null,
|
|
thinkingOptionId: null,
|
|
effectiveThinkingOptionId: null,
|
|
createdAt: "2026-05-23T00:00:00.000Z",
|
|
updatedAt: "2026-05-23T00:00:00.000Z",
|
|
lastUserMessageAt: null,
|
|
status: "idle",
|
|
capabilities: {
|
|
supportsStreaming: true,
|
|
supportsSessionPersistence: true,
|
|
supportsDynamicModes: true,
|
|
supportsMcpServers: true,
|
|
supportsReasoningStream: true,
|
|
supportsToolInvocations: true,
|
|
},
|
|
currentModeId: null,
|
|
availableModes: [],
|
|
pendingPermissions: [],
|
|
persistence: null,
|
|
title: null,
|
|
labels: {},
|
|
});
|
|
|
|
expect(parsed.capabilities.supportsRewindConversation).toBe(false);
|
|
expect(parsed.capabilities.supportsRewindFiles).toBe(false);
|
|
expect(parsed.capabilities.supportsRewindBoth).toBe(false);
|
|
});
|
|
|
|
test("notification timeline items parse their level and message", () => {
|
|
expect(
|
|
AgentTimelineItemPayloadSchema.parse({
|
|
type: "notification",
|
|
level: "warning",
|
|
message: "Command blocked by user",
|
|
}),
|
|
).toEqual({
|
|
type: "notification",
|
|
level: "warning",
|
|
message: "Command blocked by user",
|
|
});
|
|
});
|
|
});
|
|
|
|
test("0.8 timeline and setup capabilities remain optional in the hello", () => {
|
|
const hello = { type: "hello", clientId: "compat", clientType: "mobile", protocolVersion: 1 };
|
|
expect(WSHelloMessageSchema.safeParse(hello).success).toBe(true);
|
|
expect(
|
|
WSHelloMessageSchema.parse({
|
|
...hello,
|
|
capabilities: { plugin_timeline_items: true, workspace_setup_blocked: true },
|
|
}).capabilities,
|
|
).toEqual({ plugin_timeline_items: true, workspace_setup_blocked: true });
|
|
});
|
|
|
|
test("plugin rows and identity merges require a capable receiver", () => {
|
|
const item = {
|
|
type: "plugin",
|
|
id: "task",
|
|
pluginId: "tasks",
|
|
kind: "tasks",
|
|
version: 1,
|
|
data: { text: "Working" },
|
|
};
|
|
expect(AgentTimelineItemPayloadSchema.parse(item)).toEqual(item);
|
|
const legacyItems = z.object({
|
|
type: z.enum([
|
|
"user_message",
|
|
"assistant_message",
|
|
"reasoning",
|
|
"tool_call",
|
|
"todo",
|
|
"error",
|
|
"notification",
|
|
"compaction",
|
|
]),
|
|
});
|
|
expect(legacyItems.safeParse(item).success).toBe(false);
|
|
const entry = {
|
|
provider: "codex",
|
|
item,
|
|
timestamp: "2026-09-07T00:00:00.000Z",
|
|
seqStart: 1,
|
|
seqEnd: 2,
|
|
sourceSeqRanges: [{ startSeq: 1, endSeq: 2 }],
|
|
collapsed: ["identity"],
|
|
};
|
|
expect(AgentTimelineEntryPayloadSchema.parse(entry)).toEqual(entry);
|
|
const legacyCollapsed = z.array(z.enum(["assistant_merge", "reasoning_merge", "tool_lifecycle"]));
|
|
expect(legacyCollapsed.safeParse(entry.collapsed).success).toBe(false);
|
|
});
|
|
|
|
test("blocked setup preserves the legacy failed shape and optional provenance", () => {
|
|
const snapshot = {
|
|
status: "blocked",
|
|
error: null,
|
|
detail: {
|
|
type: "worktree_setup",
|
|
worktreePath: "/workspace",
|
|
branchName: "fork",
|
|
log: "",
|
|
commands: [],
|
|
},
|
|
blockedSource: {
|
|
kind: "change_request",
|
|
forge: "github",
|
|
number: 42,
|
|
headRepository: "contributor/project",
|
|
},
|
|
};
|
|
expect(WorkspaceSetupSnapshotSchema.parse(snapshot)).toEqual(snapshot);
|
|
const legacyStatus = z.enum(["running", "completed", "failed"]);
|
|
const legacySnapshot = WorkspaceSetupSnapshotSchema.omit({ blockedSource: true }).extend({
|
|
status: legacyStatus,
|
|
});
|
|
expect(legacySnapshot.safeParse(snapshot).success).toBe(false);
|
|
const failed = { ...snapshot, status: "failed", error: "Update Paseo to review and run setup." };
|
|
expect(legacySnapshot.safeParse(failed).success).toBe(true);
|
|
const progress = {
|
|
type: "workspace_setup_progress",
|
|
payload: { ...failed, workspaceId: "workspace" },
|
|
};
|
|
expect(WorkspaceSetupProgressMessageSchema.parse(progress)).toEqual(progress);
|
|
expect(WorkspaceSetupSnapshotSchema.parse(legacySnapshot.parse(failed))).toEqual(
|
|
legacySnapshot.parse(failed),
|
|
);
|
|
});
|
|
|
|
test("usage login errors are additive and older reports still parse", () => {
|
|
const entry = {
|
|
id: "codex:account",
|
|
account: {},
|
|
fetchedAt: "2026-10-05T00:00:00.000Z",
|
|
sourceId: "codex",
|
|
sourceLabel: "Codex",
|
|
report: { status: "error", error: "Usage API returned 500" },
|
|
};
|
|
const legacy = z.object({
|
|
type: z.literal("usage.list_reports.update"),
|
|
payload: z.object({
|
|
requestId: z.string(),
|
|
report: z.object({
|
|
id: z.string(),
|
|
account: z.object({ label: z.string().optional() }),
|
|
fetchedAt: z.string(),
|
|
sourceId: z.string(),
|
|
sourceLabel: z.string(),
|
|
icon: z.string().optional(),
|
|
report: z.discriminatedUnion("status", [
|
|
z.object({ status: z.literal("available"), windows: z.array(z.unknown()) }),
|
|
z.object({ status: z.literal("unavailable"), problem: z.unknown() }),
|
|
z.object({ status: z.literal("error"), error: z.string() }),
|
|
]),
|
|
}),
|
|
}),
|
|
});
|
|
const oldMessage = {
|
|
type: "usage.list_reports.update",
|
|
payload: { requestId: "usage", report: entry },
|
|
};
|
|
const newMessage = {
|
|
...oldMessage,
|
|
payload: {
|
|
...oldMessage.payload,
|
|
report: {
|
|
...entry,
|
|
loginErrors: [{ harness: "Codex", report: entry.report }],
|
|
},
|
|
},
|
|
};
|
|
expect(SessionOutboundMessageSchema.parse(oldMessage)).toEqual(oldMessage);
|
|
expect(SessionOutboundMessageSchema.parse(newMessage)).toEqual(newMessage);
|
|
expect(legacy.parse(newMessage)).toEqual(oldMessage);
|
|
});
|
|
|
|
test("usage entries accept older failures and additive successful snapshots", () => {
|
|
const oldEntry = {
|
|
id: "source:account",
|
|
sourceId: "source",
|
|
sourceLabel: "Source",
|
|
account: {},
|
|
fetchedAt: "2026-10-17T10:06:00Z",
|
|
report: { status: "error", error: "Rate limited" },
|
|
};
|
|
expect(UsageReportEntrySchema.parse(oldEntry)).toEqual(oldEntry);
|
|
const retained = {
|
|
...oldEntry,
|
|
lastSuccessful: {
|
|
fetchedAt: "2026-10-17T10:00:00Z",
|
|
report: { status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct: 42 }] },
|
|
},
|
|
};
|
|
expect(UsageReportEntrySchema.parse(retained)).toEqual(retained);
|
|
const available = {
|
|
...oldEntry,
|
|
report: retained.lastSuccessful.report,
|
|
successfulAt: "2026-10-17T10:00:15Z",
|
|
};
|
|
expect(UsageReportEntrySchema.parse(available)).toEqual(available);
|
|
expect(
|
|
UsageReportEntrySchema.omit({ lastSuccessful: true, successfulAt: true }).parse(available),
|
|
).toEqual({ ...oldEntry, report: available.report });
|
|
// An older client's entry shape strips the additive snapshot and still sees the failure.
|
|
expect(UsageReportEntrySchema.omit({ lastSuccessful: true }).parse(retained)).toEqual(oldEntry);
|
|
expect(
|
|
UsageReportEntrySchema.safeParse({
|
|
...oldEntry,
|
|
lastSuccessful: { fetchedAt: oldEntry.fetchedAt, report: oldEntry.report },
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
// Frozen setup detail shape from v0.8.0: log is required, command output is optional.
|
|
const ReleasedSetupDetailSchema = z.object({
|
|
type: z.literal("worktree_setup"),
|
|
worktreePath: z.string(),
|
|
branchName: z.string(),
|
|
log: z.string(),
|
|
commands: z.array(
|
|
z.object({
|
|
index: z.number().int().positive(),
|
|
command: z.string(),
|
|
cwd: z.string(),
|
|
log: z.string().optional().default(""),
|
|
status: z.enum(["running", "completed", "failed"]),
|
|
exitCode: z.number().nullable(),
|
|
durationMs: z.number().nonnegative().optional(),
|
|
}),
|
|
),
|
|
truncated: z.boolean().optional(),
|
|
});
|
|
|
|
test("released setup clients parse empty combined logs and retain command output", () => {
|
|
const detail = {
|
|
type: "worktree_setup",
|
|
worktreePath: "/workspace",
|
|
branchName: "feature",
|
|
log: "",
|
|
commands: [
|
|
{
|
|
index: 1,
|
|
command: "echo ready",
|
|
cwd: "/workspace",
|
|
log: "ready\n",
|
|
status: "completed",
|
|
exitCode: 0,
|
|
},
|
|
],
|
|
};
|
|
const parsed = ReleasedSetupDetailSchema.parse(JSON.parse(JSON.stringify(detail)));
|
|
expect(parsed).toEqual(detail);
|
|
expect(parsed.commands[0]?.log).toBe("ready\n");
|
|
const { log: _log, ...withoutLegacyField } = detail;
|
|
expect(ReleasedSetupDetailSchema.safeParse(withoutLegacyField).success).toBe(false);
|
|
expect(
|
|
WorkspaceSetupSnapshotSchema.parse({
|
|
status: "completed",
|
|
detail: { ...detail, log: "old daemon combined output" },
|
|
error: null,
|
|
}).detail.log,
|
|
).toBe("old daemon combined output");
|
|
});
|
|
|
|
test("local STT language survives wire reads and patches", () => {
|
|
const patch = {
|
|
features: {
|
|
dictation: { stt: { provider: "local", language: "fr" } },
|
|
voiceMode: { stt: { provider: "local", language: null } },
|
|
},
|
|
};
|
|
expect(MutableDaemonConfigPatchSchema.parse(patch)).toEqual(patch);
|
|
});
|
|
|
|
test("speech config reads remain compatible with clients before features and future providers", () => {
|
|
const oldSchema = MutableDaemonConfigSchema.omit({ features: true, envOverrides: true });
|
|
const oldRead = oldSchema.parse({
|
|
providers: {},
|
|
relay: { enabled: false },
|
|
mcp: { enabled: true, injectIntoAgents: true },
|
|
});
|
|
const read = {
|
|
...oldRead,
|
|
features: {
|
|
dictation: { enabled: true, stt: { provider: "local", language: "fr" } },
|
|
voiceMode: {
|
|
enabled: false,
|
|
stt: { provider: "future", model: "future-model", region: "eu" },
|
|
tts: { provider: "openai", hasApiKey: false },
|
|
},
|
|
},
|
|
envOverrides: { "features.voiceMode.stt.provider": "PASEO_VOICE_STT_PROVIDER" },
|
|
};
|
|
expect(oldSchema.parse(read)).toEqual(read);
|
|
expect(MutableDaemonConfigSchema.parse(oldRead)).toEqual(oldRead);
|
|
expect(MutableDaemonConfigSchema.parse(read).features?.dictation.stt).toEqual(
|
|
read.features.dictation.stt,
|
|
);
|
|
expect(MutableDaemonConfigSchema.parse(read).features?.voiceMode.stt).toEqual(
|
|
read.features.voiceMode.stt,
|
|
);
|
|
const oldPatchSchema = MutableDaemonConfigPatchSchema.omit({ features: true });
|
|
const oldPatch = oldPatchSchema.parse({ relay: { enabled: true } });
|
|
expect(MutableDaemonConfigPatchSchema.parse(oldPatch)).toEqual(oldPatch);
|
|
expect(
|
|
MutableDaemonConfigPatchSchema.parse({
|
|
features: { voiceMode: { stt: { provider: "future", region: "eu" } } },
|
|
}).features?.voiceMode?.stt,
|
|
).toEqual({ provider: "future", region: "eu" });
|
|
});
|