Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,67 @@ jobs:
name: devo-${{ github.ref_name }}-${{ matrix.target }}
path: devo-${{ github.ref_name }}-${{ matrix.target }}.*

# ── Desktop app builds ───────────────────────────────────────────────────
desktop:
name: Build desktop ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-latest
platform: mac
script: package:mac
- os: windows-latest
platform: windows
script: package:win
- os: ubuntu-latest
platform: linux
script: package:linux

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: desktop-${{ matrix.platform }}

- uses: oven-sh/setup-bun@v2

- name: Install Linux packaging dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y rpm

- name: Install desktop dependencies
working-directory: apps/desktop
run: bun install --frozen-lockfile

- name: Build desktop package
working-directory: apps/desktop
run: bun run ${{ matrix.script }}
env:
CSC_IDENTITY_AUTO_DISCOVERY: false

- uses: actions/upload-artifact@v4
with:
name: devo-desktop-${{ github.ref_name }}-${{ matrix.platform }}
path: |
apps/desktop/release/*.AppImage
apps/desktop/release/*.blockmap
apps/desktop/release/*.deb
apps/desktop/release/*.dmg
apps/desktop/release/*.exe
apps/desktop/release/*.rpm
apps/desktop/release/*.yml
apps/desktop/release/*.zip

# ── Create GitHub Release ──────────────────────────────────────────────
release:
name: Create Release
needs: build
needs: [build, desktop]
runs-on: ubuntu-24.04
permissions:
contents: write
Expand Down
19 changes: 10 additions & 9 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@devo/desktop",
"productName": "Devo",
"version": "0.11.0",
"version": "0.1.21",
"description": "AI-powered coding assistant desktop app",
"author": "Devo",
"license": "MIT",
Expand All @@ -15,27 +15,28 @@
"type": "module",
"main": "./out/main/index.js",
"scripts": {
"sync:version": "bun scripts/sync-cargo-version.ts",
"brand:electron-dev": "bun scripts/brand-electron-dev.mjs",
"dev:cli": "cargo build --manifest-path ../../Cargo.toml -p devo-cli --bin devo",
"gen:protocol-types": "cargo run --manifest-path ../../Cargo.toml -p devo-protocol --bin generate-acp-ts -- packages/devo-ai-sdk/src/v2/generated",
"gen:acp-types": "bun run gen:protocol-types",
"gen:protocol-types:check": "bun scripts/check-protocol-types.mjs",
"gen:acp-types:check": "bun run gen:protocol-types:check",
"dev": "bun run gen:protocol-types && bun run dev:cli && bun run brand:electron-dev && electron-vite dev",
"dev:wayland": "bun run gen:protocol-types && bun run dev:cli && bun run brand:electron-dev && electron-vite dev -- --ozone-platform=wayland --enable-features=WaylandWindowDecorations --disable-features=WaylandFractionalScaleV1",
"dev:web": "bun run gen:protocol-types && vite --config src/renderer/vite.web.config.ts",
"build": "bun run gen:protocol-types && electron-vite build",
"preview": "bun run gen:protocol-types && bun run dev:cli && bun run brand:electron-dev && electron-vite preview",
"dev": "bun run sync:version && bun run gen:protocol-types && bun run dev:cli && bun run brand:electron-dev && electron-vite dev",
"dev:wayland": "bun run sync:version && bun run gen:protocol-types && bun run dev:cli && bun run brand:electron-dev && electron-vite dev -- --ozone-platform=wayland --enable-features=WaylandWindowDecorations --disable-features=WaylandFractionalScaleV1",
"dev:web": "bun run sync:version && bun run gen:protocol-types && vite --config src/renderer/vite.web.config.ts",
"build": "bun run sync:version && bun run gen:protocol-types && electron-vite build",
"preview": "bun run sync:version && bun run gen:protocol-types && bun run dev:cli && bun run brand:electron-dev && electron-vite preview",
"package": "bun run build && electron-builder --config electron-builder.yml --publish never",
"package:linux": "bun run build && electron-builder --linux --config electron-builder.yml --publish never",
"package:mac": "bun run build && electron-builder --mac --config electron-builder.yml --publish never",
"package:mac:arm64": "bun run build && electron-builder --mac --arm64 --config electron-builder.yml --publish never",
"package:mac:x64": "bun run build && electron-builder --mac --x64 --config electron-builder.yml --publish never",
"package:win": "bun run build && electron-builder --win --config electron-builder.yml --publish never",
"package:all": "bun run build && electron-builder --linux --mac --win --config electron-builder.yml --publish never",
"eb:linux": "electron-builder --linux --config electron-builder.yml --publish never",
"eb:mac": "electron-builder --mac --config electron-builder.yml --publish never",
"eb:win": "electron-builder --win --config electron-builder.yml --publish never",
"eb:linux": "bun run sync:version && electron-builder --linux --config electron-builder.yml --publish never",
"eb:mac": "bun run sync:version && electron-builder --mac --config electron-builder.yml --publish never",
"eb:win": "bun run sync:version && electron-builder --win --config electron-builder.yml --publish never",
"rebuild": "electron-rebuild",
"lint": "biome check .",
"check-types": "bun run gen:protocol-types && tsc --noEmit",
Expand Down
74 changes: 74 additions & 0 deletions apps/desktop/scripts/sync-cargo-version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, expect, test } from "bun:test";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
readWorkspacePackageVersion,
syncCargoVersion,
} from "./sync-cargo-version";

async function tempDesktopDir(): Promise<string> {
return mkdtemp(join(tmpdir(), "devo-desktop-version-"));
}

describe("desktop Cargo version sync", () => {
test("updates desktop package metadata from the workspace package version", async () => {
const desktopDir = await tempDesktopDir();
const repoRoot = join(desktopDir, "..", "..");
await mkdir(repoRoot, { recursive: true });
await writeFile(
join(repoRoot, "Cargo.toml"),
`
[workspace]
members = ["crates/cli"]

[workspace.package]
edition = "2024"
version = "0.1.21"
`,
);
await writeFile(
join(desktopDir, "package.json"),
`${JSON.stringify(
{
name: "@devo/desktop",
productName: "Devo",
version: "0.11.0",
},
null,
"\t",
)}\n`,
);

const result = await syncCargoVersion({ desktopDir });

const packageJson = JSON.parse(
await readFile(join(desktopDir, "package.json"), "utf8"),
);
expect(result).toEqual({
changed: true,
previousVersion: "0.11.0",
version: "0.1.21",
});
expect(packageJson).toEqual({
name: "@devo/desktop",
productName: "Devo",
version: "0.1.21",
});
});

test("keeps the checked-in desktop package version aligned with Cargo", async () => {
const scriptDir = dirname(fileURLToPath(import.meta.url));
const desktopDir = join(scriptDir, "..");
const cargoToml = await readFile(
join(desktopDir, "..", "..", "Cargo.toml"),
"utf8",
);
const packageJson = JSON.parse(
await readFile(join(desktopDir, "package.json"), "utf8"),
);

expect(packageJson.version).toBe(readWorkspacePackageVersion(cargoToml));
});
});
73 changes: 73 additions & 0 deletions apps/desktop/scripts/sync-cargo-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";

const WORKSPACE_PACKAGE_SECTION = "workspace.package";

type SyncCargoVersionOptions = {
desktopDir?: string;
};

export type SyncCargoVersionResult = {
changed: boolean;
previousVersion: string | null;
version: string;
};

export async function syncCargoVersion({
desktopDir = process.cwd(),
}: SyncCargoVersionOptions = {}): Promise<SyncCargoVersionResult> {
const cargoTomlPath = join(desktopDir, "..", "..", "Cargo.toml");
const packageJsonPath = join(desktopDir, "package.json");
const version = readWorkspacePackageVersion(
await readFile(cargoTomlPath, "utf8"),
);
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
const previousVersion =
typeof packageJson.version === "string" ? packageJson.version : null;
if (previousVersion === version) {
return { changed: false, previousVersion, version };
}

packageJson.version = version;
await writeFile(
packageJsonPath,
`${JSON.stringify(packageJson, null, "\t")}\n`,
);
return { changed: true, previousVersion, version };
}

export function readWorkspacePackageVersion(cargoToml: string): string {
let inWorkspacePackage = false;

for (const line of cargoToml.split(/\r?\n/)) {
const trimmed = line.trim();
const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
if (sectionMatch) {
inWorkspacePackage = sectionMatch[1] === WORKSPACE_PACKAGE_SECTION;
continue;
}

if (!inWorkspacePackage || trimmed.startsWith("#")) continue;

const versionMatch = trimmed.match(/^version\s*=\s*"([^"]+)"\s*(?:#.*)?$/);
if (versionMatch) return versionMatch[1];
}

throw new Error(
`missing [${WORKSPACE_PACKAGE_SECTION}] version in Cargo.toml`,
);
}

if (import.meta.main) {
try {
const result = await syncCargoVersion();
const previous = result.previousVersion ?? "missing";
const message = result.changed
? `Desktop package version: ${previous} -> ${result.version}`
: `Desktop package version already ${result.version}`;
console.log(message);
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}
}
14 changes: 12 additions & 2 deletions apps/desktop/src/renderer/components/agent-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export function AgentDetail({
<>
<SessionPanelHeader
agent={agent}
turns={chatTurns}
isEditingTitle={isEditingTitle}
titleValue={titleValue}
titleInputRef={titleInputRef}
Expand Down Expand Up @@ -298,6 +299,7 @@ export function AgentDetail({

function SessionPanelHeader({
agent,
turns,
isEditingTitle,
titleValue,
titleInputRef,
Expand All @@ -311,6 +313,7 @@ function SessionPanelHeader({
onToggleReviewPanel,
}: {
agent: Agent
turns: ChatTurn[]
isEditingTitle: boolean
titleValue: string
titleInputRef: React.RefObject<HTMLInputElement | null>
Expand All @@ -328,7 +331,10 @@ function SessionPanelHeader({
const toggleReviewPanelShortcut = formatShortcut(["shift", "mod", "D"])

return (
<div className="flex h-[46px] w-full min-w-0 shrink-0 items-center gap-2.5 border-b border-border/50 px-4">
<div
data-slot="session-panel-header"
className="flex h-[46px] w-full min-w-0 shrink-0 items-center gap-2.5 border-b border-border/50 px-4"
>
{/* Breadcrumb: project / [branch badge] / session name */}
<div className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
{/* Project name */}
Expand Down Expand Up @@ -417,7 +423,11 @@ function SessionPanelHeader({

{/* Session metrics bar */}
<div className="hidden min-w-0 shrink lg:block">
<SessionMetricsBar sessionId={agent.sessionId} />
<SessionMetricsBar
sessionId={agent.sessionId}
turns={turns}
isWorking={agent.status === "running"}
/>
</div>

{/* Open in external editor */}
Expand Down
Loading
Loading