Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
bedaccb
test(session): stabilize force-kill timing
guicybercode Aug 30, 2026
c69be7b
fix(session): keep process snapshots monotonic
guicybercode Sep 3, 2026
b843c19
fix(session): advance process snapshot floor
guicybercode Sep 3, 2026
7693468
refactor(session): separate worktree preparation from process launch
guicybercode Sep 6, 2026
3c25a85
feat(daemon): expose isolated session and safe worktree lifecycle ove…
guicybercode Sep 6, 2026
72b4d4d
docs(runtime): record parity evidence and integration contracts
guicybercode Sep 6, 2026
910ed7d
docs(xirp): record local scope and integration contracts
guicybercode Sep 6, 2026
1a74bbb
feat(knowledge): persist scoped prompts and context over IPC
guicybercode Sep 6, 2026
b271825
docs(architecture): define local file editor contracts and safe saves
guicybercode Sep 6, 2026
45d817a
merge: preserve current canvas and native browser integration
guicybercode Sep 6, 2026
c98cf25
fix(agents): retry busy executable probes and retain safe error codes
guicybercode Sep 6, 2026
1225931
feat(files): expose scoped file listing and revision-checked atomic s…
guicybercode Sep 6, 2026
2499b0b
docs(runtime): publish file integration evidence and organization own…
guicybercode Sep 6, 2026
968b393
merge: reconcile local file services with current canvas integration
guicybercode Sep 6, 2026
e3c371d
merge: reconcile PR42 process snapshot fixes with current main
guicybercode Sep 6, 2026
0bf0f70
merge: reconcile PR44 files with current canvas and session fixes
guicybercode Sep 6, 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
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/daemon",
"crates/e2e",
"crates/fake-agent",
"crates/file-metadata",
"crates/git",
"crates/session",
"crates/storage",
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src/app/AppShell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,7 @@ describe("AppShell canvas workflows", () => {
sessionId: stoppedSession.id,
path: stoppedSession.worktreePath,
});
let registeredWorktrees = [worktree];
const client = createMockIpcClient({
bootstrap: createBootstrap({
projects: [project],
Expand All @@ -1438,7 +1439,7 @@ describe("AppShell canvas workflows", () => {
worktrees: [worktree],
}),
handlers: {
listWorktrees: async () => [],
listWorktrees: async () => registeredWorktrees,
stopSession: async () => ({
...runningSession,
status: "exited",
Expand All @@ -1451,7 +1452,9 @@ describe("AppShell canvas workflows", () => {
worktreeId: worktree.id,
expiresAtMs: TEST_TIME + 60_000,
}),
removeWorktree: async () => undefined,
removeWorktree: async () => {
registeredWorktrees = [];
},
},
});
const user = await renderApp(client);
Expand Down
32 changes: 32 additions & 0 deletions apps/desktop/src/ipc/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { decodeKnowledgeEntry, decodeKnowledgePage } from "./knowledge-schema";
import { decodeKnowledgeDiscoverResponse, decodeKnowledgeReadResponse } from "./discovery-schema";
import type { KnowledgeEntry, KnowledgeListRequest, KnowledgeListResponse, KnowledgeSaveRequest, KnowledgeDeleteRequest, KnowledgeDiscoverRequest, KnowledgeDiscoverResponse, KnowledgeReadRequest, KnowledgeReadResponse, WorktreeListRequest } from "./domain";
import type {
FileListRequest,
FileListResponse,
FileReadRequest,
FileReadResponse,
FileWriteRequest,
FileWriteResponse,
} from "./domain";
import { decodeFileListResponse, decodeFileReadResponse, decodeFileWriteResponse } from "./file-schema";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { openPath } from "@tauri-apps/plugin-opener";
Expand Down Expand Up @@ -64,6 +73,9 @@ export interface TerminalResizeInput {
/** The sole frontend interface to daemon and native desktop capabilities. */
export interface IpcClient {
readonly platform: AppPlatform;
listFiles(input: FileListRequest): Promise<FileListResponse>;
readFile(input: FileReadRequest): Promise<FileReadResponse>;
writeFile(input: FileWriteRequest): Promise<FileWriteResponse>;
listKnowledge(input: KnowledgeListRequest): Promise<KnowledgeListResponse>;
saveKnowledge(input: KnowledgeSaveRequest): Promise<KnowledgeEntry>;
deleteKnowledge(input: KnowledgeDeleteRequest): Promise<void>;
Expand Down Expand Up @@ -146,6 +158,18 @@ export function toIpcError(error: unknown): IpcError {
class TauriIpcClient implements IpcClient {
readonly platform = detectPlatform();

async listFiles(input: FileListRequest): Promise<FileListResponse> {
return this.fileRequest("file.list", input, (value) => decodeFileListResponse(value, input));
}

async readFile(input: FileReadRequest): Promise<FileReadResponse> {
return this.fileRequest("file.read", input, (value) => decodeFileReadResponse(value, input));
}

async writeFile(input: FileWriteRequest): Promise<FileWriteResponse> {
return this.fileRequest("file.write", input, (value) => decodeFileWriteResponse(value, input));
}

async listKnowledge(input: KnowledgeListRequest): Promise<KnowledgeListResponse> {
return decodeKnowledgePage(await this.request("knowledge.list", input));
}
Expand Down Expand Up @@ -335,6 +359,14 @@ class TauriIpcClient implements IpcClient {
}
}

private async fileRequest<T>(method: string, payload: unknown, decode: (value: unknown) => T): Promise<T> {
try {
return decode(await this.request(method, payload));
} catch (error) {
throw toIpcError(error);
}
}

private async request(method: string, payload: unknown): Promise<unknown> {
const request = createRequestEnvelope(method, payload);
try {
Expand Down
64 changes: 63 additions & 1 deletion apps/desktop/src/ipc/domain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** Additive daemon contracts. Existing UI DTOs remain owned by types.ts. */
export type * from "./types";
import type { Worktree } from "./types";
import type { GitTarget, Worktree } from "./types";

/** List managed worktrees; omission includes every registered project. */
export interface WorktreeListRequest {
Expand Down Expand Up @@ -56,6 +56,68 @@ export interface KnowledgeDeleteRequest {
readonly expectedRevision: number;
}

/** Registered project directory, session cwd, or managed worktree root. */
export type FileTarget = GitTarget | {
readonly kind: "worktree";
readonly worktreeId: string;
};
/** Canonical padded base64 of relative Unix path bytes; empty means list root. */
export type FilePath = string;
/** Opaque v1 SHA-256 revision; preserve exactly as returned by the daemon. */
export type FileRevision = string;

export interface FileEntry {
readonly pathBase64: FilePath;
readonly displayName: string;
readonly kind: "file" | "directory" | "symlink" | "other";
readonly sizeBytes?: number;
readonly modifiedAtMs?: number;
}

export interface FileListRequest {
readonly target: FileTarget;
readonly pathBase64: FilePath;
readonly limit?: number;
readonly afterNameBase64?: string;
}

export interface FileListResponse {
readonly entries: readonly FileEntry[];
readonly nextAfterNameBase64?: string;
readonly observedAtMs: number;
}

export interface FileReadRequest {
readonly target: FileTarget;
readonly pathBase64: FilePath;
}

/** Text is bounded to 128 KiB UTF-8 including any BOM; line endings are preserved. */
export interface FileReadResponse {
readonly pathBase64: FilePath;
readonly text: string;
readonly revision: FileRevision;
readonly sizeBytes: number;
readonly modifiedAtMs?: number;
readonly observedAtMs: number;
}

/** Existing regular text files only; stale revisions leave the buffer unsaved. */
export interface FileWriteRequest {
readonly target: FileTarget;
readonly pathBase64: FilePath;
readonly text: string;
readonly expectedRevision: FileRevision;
}

export interface FileWriteResponse {
readonly pathBase64: FilePath;
readonly revision: FileRevision;
readonly sizeBytes: number;
readonly modifiedAtMs?: number;
readonly writtenAtMs: number;
}

/** Mirrors core::knowledge::discovery; discovery never establishes native activation. */
export type KnowledgeSourceKind = "rule" | "skill";
export type KnowledgeProvider = "codex" | "claude" | "cursor";
Expand Down
Loading
Loading