feat: add file manager context actions (#3027)
* feat(app): add file manager context actions * fix: reshape file context actions --------- Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
co-authored by
Mohamed Boudra
parent
a98d154b6b
commit
ca2b633286
@@ -1727,6 +1727,181 @@ test("a candidate measurement that times out under a heartbeat tick does not cou
|
||||
expect(session.teardownCount()).toBe(0);
|
||||
});
|
||||
|
||||
test("file context action RPCs correlate success and error responses", async () => {
|
||||
const mock = createMockTransport();
|
||||
const client = new DaemonClient({
|
||||
url: "ws://test",
|
||||
clientId: "clsk_context_actions",
|
||||
logger: createMockLogger(),
|
||||
reconnect: { enabled: false },
|
||||
transportFactory: () => mock.transport,
|
||||
});
|
||||
clients.push(client);
|
||||
|
||||
const connectPromise = client.connect();
|
||||
mock.triggerOpen();
|
||||
await connectPromise;
|
||||
|
||||
const createPromise = client.createFileEntry({
|
||||
cwd: "/tmp/project",
|
||||
parentPath: "src",
|
||||
name: "new.ts",
|
||||
kind: "file",
|
||||
});
|
||||
const createRequest = parseSentFrame(mock.sent.at(-1));
|
||||
expect(createRequest).toEqual({
|
||||
type: "fs.entry.create.request",
|
||||
cwd: "/tmp/project",
|
||||
parentPath: "src",
|
||||
name: "new.ts",
|
||||
kind: "file",
|
||||
requestId: expect.any(String),
|
||||
});
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "fs.entry.create.response",
|
||||
payload: {
|
||||
cwd: "/tmp/project",
|
||||
parentPath: "src",
|
||||
path: "src/new.ts",
|
||||
success: true,
|
||||
error: null,
|
||||
requestId: createRequest.requestId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await expect(createPromise).resolves.toMatchObject({
|
||||
path: "src/new.ts",
|
||||
success: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const renamePromise = client.renameFileEntry({
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
name: "existing.ts",
|
||||
});
|
||||
const renameRequest = parseSentFrame(mock.sent.at(-1));
|
||||
expect(renameRequest).toMatchObject({
|
||||
type: "fs.entry.rename.request",
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
name: "existing.ts",
|
||||
});
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "fs.entry.rename.response",
|
||||
payload: {
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
renamedPath: null,
|
||||
success: false,
|
||||
error: '"existing.ts" already exists',
|
||||
requestId: renameRequest.requestId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await expect(renamePromise).resolves.toMatchObject({
|
||||
renamedPath: null,
|
||||
success: false,
|
||||
error: '"existing.ts" already exists',
|
||||
});
|
||||
|
||||
const duplicatePromise = client.duplicateFileEntry({
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
});
|
||||
const duplicateRequest = parseSentFrame(mock.sent.at(-1));
|
||||
expect(duplicateRequest).toMatchObject({
|
||||
type: "fs.entry.duplicate.request",
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
});
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "fs.entry.duplicate.response",
|
||||
payload: {
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
duplicatedPath: "src/new copy.ts",
|
||||
success: true,
|
||||
error: null,
|
||||
requestId: duplicateRequest.requestId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await expect(duplicatePromise).resolves.toMatchObject({
|
||||
duplicatedPath: "src/new copy.ts",
|
||||
success: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const deletePromise = client.deleteFileEntry({ cwd: "/tmp/project", path: "src/new.ts" });
|
||||
const deleteRequest = parseSentFrame(mock.sent.at(-1));
|
||||
expect(deleteRequest).toMatchObject({
|
||||
type: "fs.entry.delete.request",
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
});
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "fs.entry.delete.response",
|
||||
payload: {
|
||||
cwd: "/tmp/project",
|
||||
path: "src/new.ts",
|
||||
success: true,
|
||||
error: null,
|
||||
requestId: deleteRequest.requestId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await expect(deletePromise).resolves.toMatchObject({ success: true, error: null });
|
||||
|
||||
const discardPromise = client.checkoutDiscardChanges("/tmp/project", { paths: ["src"] });
|
||||
const discardRequest = parseSentFrame(mock.sent.at(-1));
|
||||
expect(discardRequest).toMatchObject({
|
||||
type: "checkout.discard_changes.request",
|
||||
cwd: "/tmp/project",
|
||||
paths: ["src"],
|
||||
});
|
||||
mock.triggerMessage(
|
||||
wrapSessionMessage({
|
||||
type: "checkout.discard_changes.response",
|
||||
payload: {
|
||||
cwd: "/tmp/project",
|
||||
success: false,
|
||||
error: { code: "NOT_GIT_REPO", message: "Not a git repository" },
|
||||
requestId: discardRequest.requestId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
await expect(discardPromise).resolves.toMatchObject({
|
||||
success: false,
|
||||
error: { code: "NOT_GIT_REPO", message: "Not a git repository" },
|
||||
});
|
||||
});
|
||||
|
||||
test("a connection loss rejects an in-flight file context action", async () => {
|
||||
const mock = createMockTransport();
|
||||
const client = new DaemonClient({
|
||||
url: "ws://test",
|
||||
clientId: "clsk_context_disconnect",
|
||||
logger: createMockLogger(),
|
||||
reconnect: { enabled: false },
|
||||
transportFactory: () => mock.transport,
|
||||
});
|
||||
clients.push(client);
|
||||
|
||||
const connectPromise = client.connect();
|
||||
mock.triggerOpen();
|
||||
await connectPromise;
|
||||
|
||||
const pending = client.deleteFileEntry({ cwd: "/tmp/project", path: "src/file.ts" });
|
||||
mock.triggerClose({ code: 1006, reason: "network lost" });
|
||||
|
||||
await expect(pending).rejects.toThrow(/network lost|disconnected|closed/i);
|
||||
});
|
||||
|
||||
test("listDirectory sends a list file explorer request and returns directory entries", async () => {
|
||||
const logger = createMockLogger();
|
||||
const mock = createMockTransport();
|
||||
|
||||
@@ -4329,6 +4329,54 @@ export class DaemonClient {
|
||||
return payload.result;
|
||||
}
|
||||
|
||||
async createFileEntry(input: {
|
||||
cwd: string;
|
||||
parentPath: string;
|
||||
name: string;
|
||||
kind: "file" | "directory";
|
||||
}): Promise<CorrelatedResponsePayload<"fs.entry.create.response">> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"fs.entry.create.response">({
|
||||
message: { type: "fs.entry.create.request", ...input },
|
||||
});
|
||||
}
|
||||
|
||||
async renameFileEntry(input: {
|
||||
cwd: string;
|
||||
path: string;
|
||||
name: string;
|
||||
}): Promise<CorrelatedResponsePayload<"fs.entry.rename.response">> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"fs.entry.rename.response">({
|
||||
message: { type: "fs.entry.rename.request", ...input },
|
||||
});
|
||||
}
|
||||
|
||||
async duplicateFileEntry(input: {
|
||||
cwd: string;
|
||||
path: string;
|
||||
}): Promise<CorrelatedResponsePayload<"fs.entry.duplicate.response">> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"fs.entry.duplicate.response">({
|
||||
message: { type: "fs.entry.duplicate.request", ...input },
|
||||
});
|
||||
}
|
||||
|
||||
async deleteFileEntry(input: {
|
||||
cwd: string;
|
||||
path: string;
|
||||
}): Promise<CorrelatedResponsePayload<"fs.entry.delete.response">> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"fs.entry.delete.response">({
|
||||
message: { type: "fs.entry.delete.request", ...input },
|
||||
});
|
||||
}
|
||||
|
||||
async checkoutDiscardChanges(
|
||||
cwd: string,
|
||||
input: { paths: string[] },
|
||||
): Promise<CorrelatedResponsePayload<"checkout.discard_changes.response">> {
|
||||
return this.sendNamespacedCorrelatedSessionRequest<"checkout.discard_changes.response">({
|
||||
message: { type: "checkout.discard_changes.request", cwd, paths: input.paths },
|
||||
});
|
||||
}
|
||||
|
||||
async uploadFile(input: FileUploadInput): Promise<FileUploadResult> {
|
||||
const bytes = asUint8Array(input.bytes);
|
||||
if (!bytes) {
|
||||
|
||||
Reference in New Issue
Block a user