Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
76f7eb4
docs: add jcode design contract
Jay1 Jul 1, 2026
0c720b1
fix(server): harden opencode resume handling
Jay1 Jul 1, 2026
b67482e
feat(contracts): add chat markdown wrap setting
Jay1 Jul 1, 2026
50042ac
feat(web): add chat markdown wrap controls
Jay1 Jul 1, 2026
a75972f
feat(server): add provider update check opt out
Jay1 Jul 1, 2026
3e29c0e
fix(web): stabilize composer provider state
Jay1 Jul 1, 2026
0c0e43a
feat(web): normalize activity details
Jay1 Jul 1, 2026
c411af7
feat(web): show expandable timeline activity rows
Jay1 Jul 1, 2026
59579f0
feat(web): add timeline minimap affordance
Jay1 Jul 1, 2026
e98c118
feat(web): surface provider usage status
Jay1 Jul 1, 2026
30516aa
refactor(web): split branch toolbar controls
Jay1 Jul 1, 2026
f432fb7
feat(server): add managed worktree cleanup
Jay1 Jul 1, 2026
1c3e805
feat(web): add worktree cleanup controls
Jay1 Jul 1, 2026
98a476d
feat(contracts): add vcs status rpc shape
Jay1 Jul 1, 2026
31f9353
feat(server): expose vcs status over websocket
Jay1 Jul 1, 2026
da7cf06
feat(web): add vcs command center panel
Jay1 Jul 1, 2026
6230471
docs: record copilot provider entry path
Jay1 Jul 1, 2026
4b5884c
feat(server): add offline copilot provider spike
Jay1 Jul 1, 2026
3044016
feat(contracts): add backend discovery contracts
Jay1 Jul 1, 2026
0d61a2d
feat(server): add backend discovery seams
Jay1 Jul 1, 2026
efa1f9a
fix(server): address worktree review feedback
Jay1 Jul 1, 2026
24349dd
fix(server): address provider health review feedback
Jay1 Jul 1, 2026
d1e847c
fix(web): preserve multi-file diff actions
Jay1 Jul 1, 2026
457b7ef
refactor(web): extract activity detail logic
Jay1 Jul 1, 2026
9a8ce2a
fix(web): address markdown control review feedback
Jay1 Jul 1, 2026
ed2a2e5
fix(contracts): constrain backend connection variants
Jay1 Jul 1, 2026
57eec4a
docs: refresh adr index review date
Jay1 Jul 1, 2026
2078d4c
fix(web): preserve raw command detection
Jay1 Jul 1, 2026
2f90a67
merge: resolve main conflicts
Jay1 Jul 1, 2026
9f69757
docs: format design system document
Jay1 Jul 1, 2026
1ef41ac
test(web): stabilize browser harnesses
Jay1 Jul 1, 2026
925223c
test(web): isolate activity details browser test
Jay1 Jul 1, 2026
0167283
test(web): fix activity details assertions
Jay1 Jul 1, 2026
6911992
test(web): narrow command detail assertion
Jay1 Jul 1, 2026
e7ef71a
fix(server): harden websocket review paths
Jay1 Jul 2, 2026
9203bed
fix(server): handle stale opencode update race
Jay1 Jul 2, 2026
49fef9f
fix(web): use derived chat thread selector
Jay1 Jul 2, 2026
d3f8ad0
test(web): exercise real activity row wiring
Jay1 Jul 2, 2026
4250662
test(web): narrow activity file path locator
Jay1 Jul 2, 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
150 changes: 150 additions & 0 deletions DESIGN.md

Large diffs are not rendered by default.

72 changes: 72 additions & 0 deletions apps/server/src/backend/backendPathResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Backend } from "@jcode/contracts";

export type ResolveBackendPathInput = {
readonly backend: Backend;
readonly path: string;
};

export type ResolvedBackendPath = {
readonly hostPath: string;
readonly backendPath: string;
};

export type WslUncPath = {
readonly distro: string;
readonly linuxPath: string;
};

type WindowsDrivePath = {
readonly drive: string;
readonly tail: string;
};

export function parseWslUncPath(path: string): WslUncPath | null {
const normalized = path.replace(/\//g, "\\");
const prefix = "\\\\wsl$\\";
if (!normalized.toLowerCase().startsWith(prefix)) return null;
const remainder = normalized.slice(prefix.length);
const firstSeparator = remainder.indexOf("\\");
if (firstSeparator <= 0) return null;
const distro = remainder.slice(0, firstSeparator);
const tail = remainder.slice(firstSeparator + 1).replace(/\\/g, "/");
return { distro, linuxPath: `/${tail}`.replace(/\/+/g, "/") };
}

function parseWindowsDrivePath(path: string): WindowsDrivePath | null {
const match = /^([A-Za-z]):[\\/]*(.*)$/.exec(path);
if (match === null) return null;
const drive = match[1];
const tail = match[2];
if (drive === undefined || tail === undefined) return null;
return { drive: drive.toLowerCase(), tail: tail.replace(/\\/g, "/") };
}

function hostPathFromMntPath(path: string): string | null {
const match = /^\/mnt\/([A-Za-z])(?:\/(.*))?$/.exec(path);
if (match === null) return null;
const drive = match[1];
const tail = match[2] ?? "";
if (drive === undefined) return null;
const suffix = tail.length > 0 ? `\\${tail.replace(/\//g, "\\")}` : "\\";
return `${drive.toUpperCase()}:${suffix}`;
}

function backendPathForWsl(path: string): string {
const unc = parseWslUncPath(path);
if (unc !== null) return unc.linuxPath;
const drive = parseWindowsDrivePath(path);
if (drive !== null) return `/mnt/${drive.drive}/${drive.tail}`.replace(/\/+/g, "/");
return path;
}

function hostPathForBackend(path: string): string {
const hostPath = hostPathFromMntPath(path);
return hostPath ?? path;
}

export function resolveBackendPath(input: ResolveBackendPathInput): ResolvedBackendPath {
if (input.backend.connection.kind === "wsl-exe") {
return { hostPath: input.path, backendPath: backendPathForWsl(input.path) };
}
return { hostPath: hostPathForBackend(input.path), backendPath: input.path };
}
133 changes: 133 additions & 0 deletions apps/server/src/backend/backendRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { describe, expect, it } from "vitest";

import { makeBackendId } from "@jcode/contracts";
import { discoverBackends, resolveBackendPath, resolveProjectBackend } from "./backendRegistry";

const host = {
environmentId: "host-env",
label: "Windows host",
platform: { os: "windows", arch: "x64" },
serverVersion: "0.0.50",
} as const;

describe("backend registry discovery", () => {
it("discovers host and mocked WSL distro backend states from Windows fixtures", async () => {
const calls: readonly string[][] = [];
const mutableCalls: string[][] = [];
const registry = await discoverBackends({
host,
wsl: {
enabled: true,
run: async (args) => {
mutableCalls.push([...args]);
if (args[0] === "--list") {
return { ok: true, stdout: "\uFEFFUbuntu\u0000\r\n\r\nDebian\r\n", stderr: "" };
}
if (args[1] === "Ubuntu") {
return { ok: true, stdout: "x86_64\n", stderr: "" };
}
return { ok: false, stdout: "", stderr: "The specified distribution was not found." };
},
},
});
calls.concat(mutableCalls);

expect(registry.host.connection.kind).toBe("local");
expect(registry.backends.map((backend) => backend.id)).toEqual([
"host",
"wsl-ubuntu",
"wsl-debian",
]);
expect(registry.backends[1]?.state.kind).toBe("healthy");
expect(registry.backends[2]?.state.kind).toBe("degraded");
expect(mutableCalls).toEqual([
["--list", "--quiet"],
["-d", "Ubuntu", "--", "uname", "-m"],
["-d", "Debian", "--", "uname", "-m"],
]);
});

it("keeps discovery host-only when WSL probing is disabled", async () => {
const registry = await discoverBackends({
host,
wsl: { enabled: false },
});

expect(registry.backends).toHaveLength(1);
expect(registry.backends[0]?.id).toBe("host");
});
});

describe("backend path resolution", () => {
it("resolves project backends without routing live operations", async () => {
const registry = await discoverBackends({
host,
wsl: {
enabled: true,
run: async (args) => {
if (args[0] === "--list") return { ok: true, stdout: "Ubuntu\n", stderr: "" };
return { ok: true, stdout: "x86_64\n", stderr: "" };
},
},
});

const hostResolution = resolveProjectBackend({
registry,
workspaceRoot: "C:\\Users\\Jay\\project",
});
const wslResolution = resolveProjectBackend({
registry,
workspaceRoot: "\\\\wsl$\\Ubuntu\\home\\jay\\project",
});
const overrideResolution = resolveProjectBackend({
registry,
workspaceRoot: "C:\\Users\\Jay\\project",
overrideBackendId: makeBackendId("wsl-ubuntu"),
});
const removedResolution = resolveProjectBackend({
registry,
workspaceRoot: "\\\\wsl$\\Ghost\\home\\jay\\project",
});

expect(hostResolution.backend.id).toBe("host");
expect(hostResolution.backendPath).toBe("C:\\Users\\Jay\\project");
expect(wslResolution.backend.id).toBe("wsl-ubuntu");
expect(wslResolution.backendPath).toBe("/home/jay/project");
expect(overrideResolution.backend.id).toBe("wsl-ubuntu");
expect(overrideResolution.backendPath).toBe("/mnt/c/Users/Jay/project");
expect(removedResolution.backend.state.kind).toBe("removed");
expect(removedResolution.backendPath).toBe("/home/jay/project");
});

it("translates host and WSL path edge cases as pure data", async () => {
const registry = await discoverBackends({
host,
wsl: {
enabled: true,
run: async (args) => {
if (args[0] === "--list") return { ok: true, stdout: "Ubuntu\n", stderr: "" };
return { ok: true, stdout: "aarch64\n", stderr: "" };
},
},
});
const ubuntu = registry.backends.find((backend) => backend.id === "wsl-ubuntu");

expect(ubuntu).toBeDefined();
if (ubuntu === undefined) return;

expect(resolveBackendPath({ backend: ubuntu, path: "D:\\Work Dir\\repo" })).toEqual({
hostPath: "D:\\Work Dir\\repo",
backendPath: "/mnt/d/Work Dir/repo",
});
expect(
resolveBackendPath({ backend: ubuntu, path: "\\\\wsl$\\Ubuntu\\home\\jay\\repo" }),
).toEqual({
hostPath: "\\\\wsl$\\Ubuntu\\home\\jay\\repo",
backendPath: "/home/jay/repo",
});
expect(resolveBackendPath({ backend: registry.host, path: "/mnt/c/Users/Jay/repo" })).toEqual({
hostPath: "C:\\Users\\Jay\\repo",
backendPath: "/mnt/c/Users/Jay/repo",
});
});
});
Loading
Loading