Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3887a4b
Implement interrupt to cancel active subagents
arul28 Mar 30, 2026
e923182
Add synthetic tool_result and artifact serving
arul28 Mar 30, 2026
62177f8
Add appOpenPath IPC; improve lane import/setup
arul28 Mar 30, 2026
821fe08
Improve branch import handling and media previews
arul28 Mar 30, 2026
8ded7c6
Fix existing-branch import excluding stale baseBranch
arul28 Mar 30, 2026
6314753
Add missing Trash icon import in LaneGitActionsPane
arul28 Mar 30, 2026
e8d140c
Fix missing resolveAdeLayout import and normalizeToFsPath definition
arul28 Mar 30, 2026
21f0f1e
Merge pull request #108 from arul28/proof-drawer-fixes-and-lane-creation
arul28 Mar 31, 2026
4cc354c
Add manual naming support for chat sessions
arul28 Mar 30, 2026
3b3719f
Re-sync permission modes and approval policies
arul28 Mar 30, 2026
ebdf392
Detect rewritten history and recommend force push
arul28 Mar 30, 2026
cde52ba
Fix review issues: manuallyNamed persistence, auto-title race, git di…
arul28 Mar 31, 2026
3b01b15
Fix hooks order, rename guard, summary isolation, and auto-title clob…
arul28 Mar 31, 2026
2ddbe88
chat tool calling (#111)
arul28 Mar 31, 2026
e713479
Fix manual-name reset when title is cleared
arul28 Mar 31, 2026
dcee360
Merge pull request #110 from arul28/ade/chat-title-summaries-30b9f371
arul28 Mar 31, 2026
b42d333
Implement interrupt to cancel active subagents
arul28 Mar 30, 2026
10cc4c5
Merge remote tracking branch and resolve doc conflicts
arul28 Mar 31, 2026
11c4bff
fix: chat interrupt/synthetic tools, lane/git UX, pty titles, artifac…
arul28 Mar 31, 2026
6158e4b
fix: PR review fixes — CSP, permissions, a11y, UX, CI regex
arul28 Mar 31, 2026
797d2ea
fix: PR review — dedup synthetic tools, safer pty errors, UX refinements
arul28 Mar 31, 2026
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
Empty file added .ade/db.sqlite
Empty file.
134 changes: 132 additions & 2 deletions apps/desktop/src/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { app, BrowserWindow, nativeImage, shell } from "electron";
import { app, BrowserWindow, nativeImage, protocol, shell } from "electron";
import path from "node:path";
type NodePtyType = typeof import("node-pty");

Check warning on line 3 in apps/desktop/src/main/main.ts

View workflow job for this annotation

GitHub Actions / lint-desktop

`import()` type annotations are forbidden
import { registerIpc } from "./services/ipc/registerIpc";
import { createFileLogger } from "./services/logging/logger";
import { openKvDb } from "./services/state/kvDb";
Expand Down Expand Up @@ -38,6 +38,7 @@
import { createAdeProjectService } from "./services/projects/adeProjectService";
import { createConfigReloadService } from "./services/projects/configReloadService";
import { IPC } from "../shared/ipc";
import { resolveAdeLayout } from "../shared/adeLayout";
import type { PortLease, ProjectInfo } from "../shared/types";
import type { AppContext } from "./services/ipc/registerIpc";
import fs from "node:fs";
Expand Down Expand Up @@ -228,7 +229,8 @@
`frame-src 'none'`,
`script-src ${cspSources} 'unsafe-inline'`,
`style-src ${cspSources} 'unsafe-inline'`,
`img-src ${cspImageSources} data: blob:`,
`img-src ${cspImageSources} ade-artifact: data: blob:`,
`media-src ade-artifact:`,
`font-src ${cspSources} data:`,
`connect-src ${cspSources}${cspWsSources} https:`,
`worker-src 'self' blob:`,
Expand Down Expand Up @@ -394,7 +396,125 @@
return win;
}

// Register custom protocol for serving local artifact files (images, videos) to the renderer.
// Must be called before app.whenReady().
protocol.registerSchemesAsPrivileged([
{ scheme: "ade-artifact", privileges: { standard: false, supportFetchAPI: true, stream: true } },
]);

app.whenReady().then(async () => {
/** Canonical artifacts dir for the active project; ade-artifact:// only serves under this path. */
let adeArtifactAllowedDir: string | null = null;

const isPathInsideArtifactAllowRoot = (resolvedFile: string, allowedDir: string): boolean => {
let allowed: string;
try {
allowed = fs.realpathSync(allowedDir);
} catch {
return false;
}
const normFile = path.normalize(resolvedFile);
const normAllowed = path.normalize(allowed);
if (process.platform === "win32") {
return normFile.toLowerCase().startsWith(normAllowed.toLowerCase() + path.sep)
|| normFile.toLowerCase() === normAllowed.toLowerCase();
}
return normFile === normAllowed || normFile.startsWith(normAllowed + path.sep);
};

// Handle ade-artifact:// requests — serves local files for proof drawer previews.
// Path is encoded in the URL: ade-artifact:///absolute/path/to/file.png
protocol.handle("ade-artifact", (request) => {
const url = new URL(request.url);
let filePath = decodeURIComponent(url.pathname);
// On Windows, pathname starts with /C:/... — strip leading slash
if (process.platform === "win32" && /^\/[a-zA-Z]:/.test(filePath)) {
filePath = filePath.slice(1);
}
filePath = path.resolve(filePath);
let resolvedFile: string;
try {
resolvedFile = fs.realpathSync(filePath);
} catch {
console.warn("[ade-artifact] realpath failed", { filePath });
return new Response("Not found", { status: 404 });
}
const allowedDir = adeArtifactAllowedDir;
if (!allowedDir || !isPathInsideArtifactAllowRoot(resolvedFile, allowedDir)) {
console.warn("[ade-artifact] rejected path outside artifacts dir", { resolvedFile, allowedDir });
return new Response("Not found", { status: 404 });
}
try {
const stat = fs.statSync(resolvedFile);
if (!stat.isFile()) return new Response("Not found", { status: 404 });
const fileSize = stat.size;
const ext = path.extname(resolvedFile).replace(/^\./, "").toLowerCase();
const mimeMap: Record<string, string> = {
png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", webp: "image/webp",
gif: "image/gif", bmp: "image/bmp", svg: "image/svg+xml",
mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", avi: "video/x-msvideo", mkv: "video/x-matroska",
};
const mime = mimeMap[ext] ?? "application/octet-stream";

// Support Range requests — required for <video> playback and seeking
const rangeHeader = request.headers.get("Range");
if (rangeHeader) {
const match = /bytes=(\d+)-(\d*)/.exec(rangeHeader);
let start = match ? parseInt(match[1], 10) : 0;
let end = match && match[2] !== undefined && match[2] !== "" ? parseInt(match[2], 10) : fileSize - 1;
if (!Number.isFinite(start) || start < 0) start = 0;
if (!Number.isFinite(end)) end = fileSize - 1;
if (end > fileSize - 1) end = fileSize - 1;
if (start >= fileSize || start > end) {
return new Response(null, {
status: 416,
headers: {
"Content-Range": `bytes */${fileSize}`,
},
});
}
const chunkSize = end - start + 1;
const fileStream = fs.createReadStream(resolvedFile, { start, end });
const webStream = new ReadableStream({
start(controller) {
fileStream.on("data", (chunk) => controller.enqueue(typeof chunk === "string" ? Buffer.from(chunk) : chunk));
fileStream.on("end", () => controller.close());
fileStream.on("error", (err) => controller.error(err));
},
cancel() { fileStream.destroy(); },
});
return new Response(webStream, {
status: 206,
headers: {
"Content-Type": mime,
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
"Content-Length": String(chunkSize),
"Accept-Ranges": "bytes",
},
});
}

// Full file response (images, small files)
const fileStream = fs.createReadStream(resolvedFile);
const webStream = new ReadableStream({
start(controller) {
fileStream.on("data", (chunk) => controller.enqueue(typeof chunk === "string" ? Buffer.from(chunk) : chunk));
fileStream.on("end", () => controller.close());
fileStream.on("error", (err) => controller.error(err));
},
cancel() { fileStream.destroy(); },
});
return new Response(webStream, {
headers: {
"Content-Type": mime,
"Content-Length": String(fileSize),
"Accept-Ranges": "bytes",
},
});
} catch {
return new Response("Not found", { status: 404 });
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
console.log("[info] app.hardware_acceleration", {
enabled: !disableHardwareAcceleration,
reason: disableHardwareAcceleration
Expand Down Expand Up @@ -485,6 +605,15 @@

const setActiveProject = (projectRoot: string | null): void => {
activeProjectRoot = projectRoot ? normalizeProjectRoot(projectRoot) : null;
if (activeProjectRoot) {
try {
adeArtifactAllowedDir = resolveAdeLayout(activeProjectRoot).artifactsDir;
} catch {
adeArtifactAllowedDir = null;
}
} else {
adeArtifactAllowedDir = null;
}
};

const getActiveContext = (): AppContext => {
Expand Down Expand Up @@ -903,6 +1032,7 @@
const ptyService = createPtyService({
projectRoot,
transcriptsDir: adePaths.transcriptsDir,
chatSessionsDir: adePaths.chatSessionsDir,
laneService,
sessionService,
aiIntegrationService,
Expand Down
Loading
Loading