Merge branch 'main' into dev

Resolve 38 conflicts preserving both sides' work:
- Server: DB-based registries (dev) + WorkspaceGitService/DaemonConfigStore (main)
- App: script proxy/voice MCP (dev) + pin/hide tabs/bootstrap boundary (main)
- Shared: WorkspaceScriptPayload (dev) + git/GitHub runtime schemas (main)
- Deleted workspace-registry-bootstrap.ts (dev's DB approach supersedes)
This commit is contained in:
Mohamed Boudra
2026-04-14 03:05:45 +07:00
333 changed files with 13490 additions and 6317 deletions
+113 -3
View File
@@ -47,6 +47,29 @@ import {
LoopLogsResponseSchema,
LoopStopResponseSchema,
} from "../server/loop/rpc-schemas.js";
// ---------------------------------------------------------------------------
// Mutable daemon config schemas (shared between server store and client)
// ---------------------------------------------------------------------------
export const MutableDaemonConfigSchema = z
.object({
mcp: z
.object({
injectIntoAgents: z.boolean(),
})
.passthrough(),
})
.passthrough();
export const MutableDaemonConfigPatchSchema = z
.object({
mcp: MutableDaemonConfigSchema.shape.mcp.partial().optional(),
})
.partial()
.passthrough();
export type MutableDaemonConfig = z.infer<typeof MutableDaemonConfigSchema>;
export type MutableDaemonConfigPatch = z.infer<typeof MutableDaemonConfigPatchSchema>;
import type { LiteralUnion } from "./literal-union.js";
import type {
AgentCapabilityFlags,
@@ -762,6 +785,17 @@ export const WaitForFinishRequestSchema = z.object({
timeoutMs: z.number().int().positive().optional(),
});
export const GetDaemonConfigRequestMessageSchema = z.object({
type: z.literal("get_daemon_config_request"),
requestId: z.string(),
});
export const SetDaemonConfigRequestMessageSchema = z.object({
type: z.literal("set_daemon_config_request"),
requestId: z.string(),
config: MutableDaemonConfigPatchSchema,
});
// ============================================================================
// Dictation Streaming (lossless, resumable)
// ============================================================================
@@ -1472,6 +1506,8 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
SetVoiceModeMessageSchema,
SendAgentMessageRequestSchema,
WaitForFinishRequestSchema,
GetDaemonConfigRequestMessageSchema,
SetDaemonConfigRequestMessageSchema,
DictationStreamStartMessageSchema,
DictationStreamChunkMessageSchema,
DictationStreamFinishMessageSchema,
@@ -1810,6 +1846,13 @@ export const ShutdownRequestedStatusPayloadSchema = z.object({
requestId: z.string(),
});
export const DaemonConfigChangedStatusPayloadSchema = z
.object({
status: z.literal("daemon_config_changed"),
config: MutableDaemonConfigSchema,
})
.passthrough();
export const KnownStatusPayloadSchema = z.discriminatedUnion("status", [
AgentCreatedStatusPayloadSchema,
AgentCreateFailedStatusPayloadSchema,
@@ -1817,6 +1860,7 @@ export const KnownStatusPayloadSchema = z.discriminatedUnion("status", [
AgentRefreshedStatusPayloadSchema,
ShutdownRequestedStatusPayloadSchema,
RestartRequestedStatusPayloadSchema,
DaemonConfigChangedStatusPayloadSchema,
]);
export type KnownStatusPayload = z.infer<typeof KnownStatusPayloadSchema>;
@@ -1903,6 +1947,50 @@ export const WorkspaceScriptPayloadSchema = z.object({
exitCode: z.number().nullable().optional().default(null),
});
const WorkspaceGitRuntimePayloadSchema = z
.object({
currentBranch: z.string().nullable().optional(),
remoteUrl: z.string().nullable().optional(),
isPaseoOwnedWorktree: z.boolean().optional(),
isDirty: z.boolean().nullable().optional(),
aheadBehind: z
.object({
ahead: z.number(),
behind: z.number(),
})
.nullable()
.optional(),
aheadOfOrigin: z.number().nullable().optional(),
behindOfOrigin: z.number().nullable().optional(),
})
.optional()
.nullable();
const WorkspaceGitHubRuntimePayloadSchema = z
.object({
featuresEnabled: z.boolean().optional(),
pullRequest: z
.object({
url: z.string(),
title: z.string(),
state: z.string(),
baseRefName: z.string(),
headRefName: z.string(),
isMerged: z.boolean(),
})
.nullable()
.optional(),
error: z
.object({
message: z.string(),
})
.nullable()
.optional(),
refreshedAt: z.string().nullable().optional(),
})
.optional()
.nullable();
export const WorkspaceDescriptorPayloadSchema = z.object({
id: z.union([z.string(), z.number()]).transform(String),
projectId: z.union([z.string(), z.number()]).transform(String),
@@ -1923,6 +2011,8 @@ export const WorkspaceDescriptorPayloadSchema = z.object({
.nullable()
.optional(),
scripts: z.array(WorkspaceScriptPayloadSchema).default([]),
gitRuntime: WorkspaceGitRuntimePayloadSchema,
githubRuntime: WorkspaceGitHubRuntimePayloadSchema,
});
export const AgentUpdateMessageSchema = z.object({
@@ -2199,6 +2289,26 @@ export const WaitForFinishResponseMessageSchema = z.object({
}),
});
export const GetDaemonConfigResponseMessageSchema = z.object({
type: z.literal("get_daemon_config_response"),
payload: z
.object({
requestId: z.string(),
config: MutableDaemonConfigSchema,
})
.passthrough(),
});
export const SetDaemonConfigResponseMessageSchema = z.object({
type: z.literal("set_daemon_config_response"),
payload: z
.object({
requestId: z.string(),
config: MutableDaemonConfigSchema,
})
.passthrough(),
});
export const AgentPermissionRequestMessageSchema = z.object({
type: z.literal("agent_permission_request"),
payload: z.object({
@@ -2856,6 +2966,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
FetchAgentTimelineResponseMessageSchema,
SendAgentMessageResponseMessageSchema,
SetVoiceModeResponseMessageSchema,
GetDaemonConfigResponseMessageSchema,
SetDaemonConfigResponseMessageSchema,
SetAgentModeResponseMessageSchema,
SetAgentModelResponseMessageSchema,
SetAgentThinkingResponseMessageSchema,
@@ -3047,9 +3159,7 @@ export type AgentAttachment = z.infer<typeof AgentAttachmentSchema>;
export type ListProviderModelsRequestMessage = z.infer<
typeof ListProviderModelsRequestMessageSchema
>;
export type ListProviderModesRequestMessage = z.infer<
typeof ListProviderModesRequestMessageSchema
>;
export type ListProviderModesRequestMessage = z.infer<typeof ListProviderModesRequestMessageSchema>;
export type ListProviderFeaturesRequestMessage = z.infer<
typeof ListProviderFeaturesRequestMessageSchema
>;
@@ -1,3 +1,4 @@
import { z } from "zod";
import { describe, expect, test } from "vitest";
import { SessionInboundMessageSchema, SessionOutboundMessageSchema } from "./messages.js";
@@ -284,6 +285,162 @@ describe("workspace message schemas", () => {
expect(parsed.type).toBe("workspace_setup_status_response");
});
test("parses fetch_workspaces_response with optional runtime fields", () => {
const parsed = SessionOutboundMessageSchema.parse({
type: "fetch_workspaces_response",
payload: {
requestId: "req-workspaces",
entries: [
{
id: "/tmp/repo",
projectId: "remote:github.com/acme/repo",
projectDisplayName: "acme/repo",
projectRootPath: "/tmp/repo",
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
status: "done",
activityAt: null,
diffStat: {
additions: 3,
deletions: 1,
},
gitRuntime: {
currentBranch: "main",
remoteUrl: "https://github.com/acme/repo.git",
isPaseoOwnedWorktree: false,
isDirty: true,
aheadBehind: {
ahead: 2,
behind: 1,
},
aheadOfOrigin: 2,
behindOfOrigin: 1,
},
githubRuntime: {
featuresEnabled: true,
pullRequest: {
url: "https://github.com/acme/repo/pull/123",
title: "Runtime payloads",
state: "open",
baseRefName: "main",
headRefName: "workspace-git-service",
isMerged: false,
},
error: null,
refreshedAt: "2026-04-12T00:00:00.000Z",
},
},
],
pageInfo: {
nextCursor: null,
prevCursor: null,
hasMore: false,
},
},
});
expect(parsed.type).toBe("fetch_workspaces_response");
expect(parsed.payload.entries[0]?.gitRuntime).toMatchObject({
currentBranch: "main",
isDirty: true,
aheadOfOrigin: 2,
});
expect(parsed.payload.entries[0]?.githubRuntime?.pullRequest?.title).toBe("Runtime payloads");
});
test("older workspace parsers ignore additive runtime fields", () => {
const message = {
type: "fetch_workspaces_response",
payload: {
requestId: "req-workspaces",
entries: [
{
id: "/tmp/repo",
projectId: "remote:github.com/acme/repo",
projectDisplayName: "acme/repo",
projectRootPath: "/tmp/repo",
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
status: "done",
activityAt: null,
diffStat: null,
gitRuntime: {
currentBranch: "main",
remoteUrl: "https://github.com/acme/repo.git",
isPaseoOwnedWorktree: false,
isDirty: false,
aheadBehind: {
ahead: 0,
behind: 0,
},
aheadOfOrigin: 0,
behindOfOrigin: 0,
},
githubRuntime: {
featuresEnabled: true,
pullRequest: null,
error: null,
refreshedAt: "2026-04-12T00:00:00.000Z",
},
},
],
pageInfo: {
nextCursor: null,
prevCursor: null,
hasMore: false,
},
},
};
const legacyWorkspaceSchema = z.object({
id: z.string(),
projectId: z.string(),
projectDisplayName: z.string(),
projectRootPath: z.string(),
projectKind: z.enum(["git", "non_git"]),
workspaceKind: z.enum(["local_checkout", "worktree", "directory"]),
name: z.string(),
status: z.enum(["needs_input", "failed", "running", "attention", "done"]),
activityAt: z.string().nullable(),
diffStat: z
.object({
additions: z.number(),
deletions: z.number(),
})
.nullable()
.optional(),
});
const legacyMessageSchema = z.object({
type: z.literal("fetch_workspaces_response"),
payload: z.object({
requestId: z.string(),
entries: z.array(legacyWorkspaceSchema),
pageInfo: z.object({
nextCursor: z.string().nullable(),
prevCursor: z.string().nullable(),
hasMore: z.boolean(),
}),
}),
});
const parsed = legacyMessageSchema.parse(message);
expect(parsed.payload.entries[0]).toEqual({
id: "/tmp/repo",
projectId: "remote:github.com/acme/repo",
projectDisplayName: "acme/repo",
projectRootPath: "/tmp/repo",
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
status: "done",
activityAt: null,
diffStat: null,
});
});
test("parses legacy fetch_agents_response checkout payloads without worktreeRoot", () => {
const result = SessionOutboundMessageSchema.safeParse({
type: "fetch_agents_response",
@@ -142,6 +142,46 @@ describe("shared tool-call display mapping", () => {
});
});
it("humanizes Paseo MCP tool names (Claude Code format)", () => {
const display = buildToolCallDisplayModel({
name: "mcp__paseo__create_agent",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("Create Agent");
});
it("humanizes Paseo MCP tool names (Codex format)", () => {
const display = buildToolCallDisplayModel({
name: "paseo.create_agent",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("Create Agent");
});
it("humanizes list_agents Paseo tool", () => {
const display = buildToolCallDisplayModel({
name: "mcp__paseo__list_agents",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("List Agents");
});
it("does not override speak tool display name", () => {
const display = buildToolCallDisplayModel({
name: "speak",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("Speak");
});
it("labels plan detail rows as Plan", () => {
const display = buildToolCallDisplayModel({
name: "plan",
@@ -1,4 +1,5 @@
import type { ToolCallTimelineItem } from "../server/agent/agent-sdk-types.js";
import { getPaseoToolLeafName, isPaseoToolName } from "../server/agent/tool-name-normalization.js";
import { stripCwdPrefix } from "./path-utils.js";
export type ToolCallDisplayInput = Pick<
@@ -32,6 +33,12 @@ function humanizeToolName(name: string): string {
if (!trimmed) {
return name;
}
if (isPaseoToolName(trimmed)) {
const leaf = getPaseoToolLeafName(trimmed);
if (leaf) {
return humanizeToolName(leaf);
}
}
if (/[:./]/.test(trimmed) || trimmed.includes("__")) {
return trimmed;
}