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
132 changes: 132 additions & 0 deletions plugins/docs/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,138 @@ describe("Docs vault operations", () => {
]);
});

it("routes project-backed workspace files through the selected or primary source", async () => {
const host = createFakePluginHost({
pluginId: "simple-notes",
sdk: {
files: {
mkdir: async () => ({ ok: true as const }),
read: async ({ path: openedPath }) => ({
path: openedPath,
content: "# Project plan",
contentEncoding: "utf8" as const,
mimeType: "text/markdown",
sizeBytes: 14,
modifiedAtMs: 1,
sha256: "project-sha",
}),
write: async () => ({
outcome: "written" as const,
sha256: "project-written-sha",
sizeBytes: 22,
}),
createPreview: async () => ({
baseUrl: "/project-preview",
expiresAtMs: Date.now() + 60_000,
}),
},
projects: {
get: async () => ({
sources: [
{
hostId: "host_remote",
path: "/remote/project",
isDefault: true,
type: "local_path",
},
{
hostId: "host_primary",
path: "/primary/project",
isDefault: false,
type: "local_path",
},
],
}),
},
system: {
config: async () => ({ primaryHostId: "host_primary" }),
},
},
});
await simpleNotes(host.bb);
host.harness.sdk.calls.length = 0;
const selectedSource = {
kind: "workspace",
threadId: null,
environmentId: null,
projectId: "project_1",
experimental_hostId: "host_remote",
};

await expect(
host.harness.callRpc("openFile", {
source: selectedSource,
path: "docs/plan.md",
}),
).resolves.toMatchObject({ previewPath: "docs/plan.md" });
await expect(
host.harness.callRpc("saveOpenedFile", {
source: selectedSource,
path: "docs/plan.md",
content: "# Updated project plan",
expectedSha256: "project-sha",
}),
).resolves.toMatchObject({ outcome: "written" });
await expect(
host.harness.callRpc("openFile", {
source: {
kind: "workspace",
threadId: null,
environmentId: null,
projectId: "project_1",
},
path: "docs/plan.md",
}),
).resolves.toMatchObject({
previewPath: "docs/plan.md",
});
await expect(
host.harness.callRpc("openFile", {
source: { ...selectedSource, experimental_hostId: "host_missing" },
path: "docs/plan.md",
}),
).rejects.toThrow("This project has no workspace on the selected host");

expect(host.harness.sdk.callsTo("projects.get")).toEqual([
[{ projectId: "project_1" }],
[{ projectId: "project_1" }],
[{ projectId: "project_1" }],
[{ projectId: "project_1" }],
]);
expect(host.harness.sdk.callsTo("system.config")).toEqual([[]]);
expect(host.harness.sdk.callsTo("files.read")).toEqual([
[
{
hostId: "host_remote",
path: "/remote/project/docs/plan.md",
rootPath: "/remote/project",
},
],
[
{
hostId: "host_primary",
path: "/primary/project/docs/plan.md",
rootPath: "/primary/project",
},
],
]);
expect(host.harness.sdk.callsTo("files.createPreview")).toEqual([
[{ hostId: "host_remote", rootPath: "/remote/project" }],
[{ hostId: "host_primary", rootPath: "/primary/project" }],
]);
expect(host.harness.sdk.callsTo("files.write")).toEqual([
[
{
hostId: "host_remote",
path: "/remote/project/docs/plan.md",
rootPath: "/remote/project",
content: "# Updated project plan",
expectedSha256: "project-sha",
},
],
]);
});

it("opens and saves thread-storage Markdown files on the thread's host", async () => {
const storageRootPath = String.raw`C:\bb\thread-storage\thread_1`;
const openedPath = String.raw`C:\bb\thread-storage\thread_1\reports\plan.md`;
Expand Down
34 changes: 34 additions & 0 deletions plugins/docs/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,40 @@ export default async function plugin(
hostId: environment.hostId,
};
}
if (source.kind === "workspace" && source.projectId) {
const hostId =
source.experimental_hostId ??
(await bb.sdk.system.config()).primaryHostId;
if (!hostId) {
throw new Error("This project has no primary host");
}
const project = await bb.sdk.projects.get({
projectId: source.projectId,
});
const matchingSources = project.sources.filter(
(projectSource) => projectSource.hostId === hostId,
);
const [projectSource] = matchingSources;
if (!projectSource) {
throw new Error(
source.experimental_hostId
? "This project has no workspace on the selected host"
: "This project has no workspace on the primary host",
);
}
if (matchingSources.length > 1) {
throw new Error("This project has multiple workspaces on that host");
}
const rootPath = normalizeHostRoot(projectSource.path);
if (!isAbsoluteHostPath(rootPath)) {
throw new Error("This project has no absolute workspace path");
}
return {
path: hostPathApi(rootPath).join(rootPath, ...filePath.split("/")),
rootPath,
hostId,
};
}
if (source.kind === "thread-storage") {
if (!source.threadId) {
throw new Error("Thread-storage files require a thread ID");
Expand Down
Loading