diff --git a/CHANGELOG.md b/CHANGELOG.md index 42df59a3a..65302c7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,33 @@ ## 0.1.60-beta.1 - 2026-04-20 ### Added -- Beta release channel for desktop updates, with a Settings toggle for receiving beta builds before they are promoted to stable. -- Release candidates are now called beta releases, starting with `0.1.60-beta.1`. +- Scripts and services per worktree — define named commands in `paseo.json`, and long-running services get supervised with their own ports and nice proxy URLs like `http://web.my-app.localhost:6767`. See the [worktrees guide](https://paseo.sh/docs/worktrees). +- Launch scripts and services for a worktree directly from the workspace header. +- New Setup tab in every workspace showing setup, teardown, and script progress live. +- GitHub checks and PR reviews in the explorer sidebar, with a hover card for the full breakdown. +- New worktree creation flow lets you pick a base branch or check out an existing GitHub pull request. +- Attach GitHub issues and pull requests to an agent as part of its prompt context. +- Pull request pane in the workspace sidebar. +- Redesigned Settings screen with modular section navigation. +- Per-host provider configuration — set providers, models, and credentials independently on each remote host. +- Direct Pi integration replaces the ACP bridge, with faster streaming and fewer hiccups. +- Beta release channel — opt in from Settings to receive beta desktop builds before they are promoted to stable. +- New-workspace picker ranks branches by recency with fast search. + +### Improved +- Workspace and tab switching are dramatically faster on desktop and mobile — you can keep many workspaces open in parallel without lag. +- Provider refresh is reliable and no longer stalls on transient failures. +- Git and GitHub state stay in sync with local changes like commits, branch switches, and pushes. +- Composer attachments redesigned with a cleaner pill layout and an image lightbox. +- In-app notifications route to whichever surface you're actually looking at. +- Keyboard shortcuts keep working while Settings is open. + +### Fixed +- Composer textarea shrinks back down after sending on web. +- Branch switcher title no longer overflows on narrow rows. +- iOS image picker no longer leaves the screen unresponsive after cancelling. +- Archiving a worktree recovers cleanly if a previous attempt was interrupted. +- Images in agent messages with `~`-prefixed paths load instead of spinning forever. ## 0.1.59 - 2026-04-16 diff --git a/packages/server/src/server/script-health-monitor.test.ts b/packages/server/src/server/script-health-monitor.test.ts index 54ddb790a..9bf9bde4d 100644 --- a/packages/server/src/server/script-health-monitor.test.ts +++ b/packages/server/src/server/script-health-monitor.test.ts @@ -7,7 +7,7 @@ import { scheduler } from "node:timers/promises"; import { afterEach, describe, expect, it, vi } from "vitest"; import { findFreePort, ScriptRouteStore } from "./script-proxy.js"; import { ScriptHealthMonitor, type ScriptHealthEntry } from "./script-health-monitor.js"; -import { spawnWorktreeScripts } from "./worktree-bootstrap.js"; +import { spawnWorkspaceScript } from "./worktree-bootstrap.js"; import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js"; type TcpServerHandle = { @@ -433,15 +433,19 @@ describe("ScriptHealthMonitor", () => { []; try { - await spawnWorktreeScripts({ - repoRoot: workspace.repoDir, - workspaceId: workspace.repoDir, - branchName: null, - daemonPort: null, - routeStore, - runtimeStore, - terminalManager: createStubTerminalManager(createTerminalCalls) as any, - }); + for (const scriptName of ["typecheck", "api"]) { + await spawnWorkspaceScript({ + repoRoot: workspace.repoDir, + workspaceId: workspace.repoDir, + projectSlug: "repo", + branchName: null, + scriptName, + daemonPort: null, + routeStore, + runtimeStore, + terminalManager: createStubTerminalManager(createTerminalCalls) as any, + }); + } expect(createTerminalCalls).toHaveLength(2); expect(routeStore.listRoutes()).toEqual([ diff --git a/packages/server/src/server/worktree-bootstrap.test.ts b/packages/server/src/server/worktree-bootstrap.test.ts index f233dfdea..6631a0376 100644 --- a/packages/server/src/server/worktree-bootstrap.test.ts +++ b/packages/server/src/server/worktree-bootstrap.test.ts @@ -5,11 +5,7 @@ import { join } from "path"; import { tmpdir } from "os"; import type { AgentTimelineItem } from "./agent/agent-sdk-types.js"; -import { - runAsyncWorktreeBootstrap, - spawnWorkspaceScript, - spawnWorktreeScripts, -} from "./worktree-bootstrap.js"; +import { runAsyncWorktreeBootstrap, spawnWorkspaceScript } from "./worktree-bootstrap.js"; import { ensureWorkspaceServicePortPlan } from "./workspace-service-port-registry.js"; import { ScriptRouteStore } from "./script-proxy.js"; import { createBranchChangeRouteHandler } from "./script-route-branch-handler.js"; @@ -717,17 +713,19 @@ describe("runAsyncWorktreeBootstrap", () => { const createTerminalCalls: CreateTerminalCall[] = []; const terminalRecords: StubTerminalRecord[] = []; - const results = await spawnWorktreeScripts({ + const result = await spawnWorkspaceScript({ repoRoot: repoDir, workspaceId: repoDir, + projectSlug: "repo", branchName: "feature-socket-service", + scriptName: "web", daemonPort: null, routeStore, runtimeStore, terminalManager: createStubTerminalManager(createTerminalCalls, terminalRecords), }); - expect(results).toHaveLength(1); + expect(result).toBeDefined(); expect(routeStore.listRoutes()).toEqual([]); expect(createTerminalCalls).toHaveLength(1); expect(createTerminalCalls[0]?.cwd).toBe(repoDir); @@ -1344,15 +1342,19 @@ describe("runAsyncWorktreeBootstrap", () => { const terminalManager = createTerminalManager(); realTerminalManagers.push(terminalManager); - await spawnWorktreeScripts({ - repoRoot: repoDir, - workspaceId: repoDir, - branchName: "feature-peer-env", - daemonPort: 6767, - routeStore, - runtimeStore, - terminalManager, - }); + for (const scriptName of ["api", "web"]) { + await spawnWorkspaceScript({ + repoRoot: repoDir, + workspaceId: repoDir, + projectSlug: "repo", + branchName: "feature-peer-env", + scriptName, + daemonPort: 6767, + routeStore, + runtimeStore, + terminalManager, + }); + } const apiEnvPath = join(repoDir, "api-env.json"); const webEnvPath = join(repoDir, "web-env.json"); @@ -1422,10 +1424,12 @@ describe("runAsyncWorktreeBootstrap", () => { const runtimeStore = new WorkspaceScriptRuntimeStore(); const createTerminalCalls: CreateTerminalCall[] = []; - await spawnWorktreeScripts({ + await spawnWorkspaceScript({ repoRoot: repoDir, workspaceId: repoDir, + projectSlug: "repo", branchName: "feature-remote-service", + scriptName: "web", daemonPort: 6767, daemonListenHost: "100.64.0.20", routeStore, diff --git a/packages/server/src/server/worktree-bootstrap.ts b/packages/server/src/server/worktree-bootstrap.ts index f3b361b91..e78f6d24f 100644 --- a/packages/server/src/server/worktree-bootstrap.ts +++ b/packages/server/src/server/worktree-bootstrap.ts @@ -3,7 +3,6 @@ import type { Logger } from "pino"; import type { TerminalManager } from "../terminal/terminal-manager.js"; import type { TerminalSession } from "../terminal/terminal.js"; import { buildScriptHostname } from "../utils/script-hostname.js"; -import { deriveProjectSlug } from "./workspace-git-metadata.js"; import { getScriptConfigs, getWorktreeTerminalSpecs, @@ -922,39 +921,6 @@ export async function spawnWorkspaceScript( } } -export async function spawnWorktreeScripts(options: { - repoRoot: string; - workspaceId: string; - branchName: string | null; - daemonPort?: number | null; - daemonListenHost?: string | null; - routeStore: ScriptRouteStore; - runtimeStore: WorkspaceScriptRuntimeStore; - terminalManager: TerminalManager; - logger?: Logger; - onLifecycleChanged?: () => void; -}): Promise { - const { repoRoot } = options; - const projectSlug = deriveProjectSlug(repoRoot); - const scriptConfigs = getScriptConfigs(repoRoot); - if (scriptConfigs.size === 0) { - return []; - } - - const results: WorktreeScriptResult[] = []; - for (const scriptName of scriptConfigs.keys()) { - results.push( - await spawnWorkspaceScript({ - ...options, - projectSlug, - scriptName, - }), - ); - } - - return results; -} - export function teardownWorktreeScripts(options: { hostnames: string[]; routeStore: ScriptRouteStore; diff --git a/packages/server/src/server/worktree-session.ts b/packages/server/src/server/worktree-session.ts index 22cc2c069..cb0bbf590 100644 --- a/packages/server/src/server/worktree-session.ts +++ b/packages/server/src/server/worktree-session.ts @@ -20,7 +20,6 @@ import { buildWorktreeSetupDetail, createWorktreeSetupProgressAccumulator, getWorktreeSetupProgressResults, - spawnWorktreeScripts, } from "./worktree-bootstrap.js"; import type { TerminalManager } from "../terminal/terminal-manager.js"; import type { ScriptRouteStore } from "./script-proxy.js"; @@ -738,28 +737,6 @@ export async function runWorktreeSetupInBackground( emitSetupProgress("completed", null); } } - - if ( - options.shouldBootstrap && - dependencies.terminalManager && - dependencies.scriptRouteStore && - dependencies.scriptRuntimeStore - ) { - await spawnWorktreeScripts({ - repoRoot: worktree.worktreePath, - workspaceId: options.workspaceId, - branchName: worktree.branchName, - daemonPort: dependencies.getDaemonTcpPort?.() ?? null, - daemonListenHost: dependencies.getDaemonTcpHost?.() ?? null, - routeStore: dependencies.scriptRouteStore, - runtimeStore: dependencies.scriptRuntimeStore, - terminalManager: dependencies.terminalManager, - logger: dependencies.sessionLogger, - onLifecycleChanged: () => { - dependencies.onScriptsChanged?.(options.workspaceId, worktree.worktreePath); - }, - }); - } } catch (error) { if (error instanceof WorktreeSetupError) { setupResults = error.results;