import { assert, assertEquals, assertMatch, assertRejects, assertStringIncludes, } from "@std/assert"; import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context"; import { type ImageContent, type Message, type ToolResultMessage, Type, } from "@earendil-works/pi-ai"; import { createModels } from "@earendil-works/pi-ai/models"; import { fauxAssistantMessage, fauxProvider, fauxToolCall, } from "@earendil-works/pi-ai/providers/faux"; import { MemoryStorage } from "@earendil-works/pi-durable"; import { defineExtension } from "@berry/api"; import { mkdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { userInfo } from "node:os"; import code from "../extensions/code/mod.ts"; import { BerryError, Conversations, openBerry, Settings } from "./berry.ts"; import { dir, lastToolResult, sectionsSeen, setup, toolsOffered, turn } from "./fixture.ts"; import { TID_PATTERN } from "./tid.ts"; import { localOnly } from "./environment.ts"; import { tempDir } from "@berry/util"; Deno.test("configured and emitted state reaches the prompt, as an extension's section and as named ones", async () => { await using harness = await setup(); const { berry, model } = harness; await assertRejects( () => berry.create({ extensions: { dir: { cwd: 5 } }, model }), BerryError, "dir: cwd:", ); await assertRejects( () => berry.create({ extensions: { dir: { cwd: "here" } }, model }), BerryError, "dir: not absolute: here", ); assertEquals(Object.keys(berry.configurationParameters("dir")?.properties ?? {}), ["cwd"]); assertEquals(berry.configurationParameters("gate"), null); const tid = await berry.create({ extensions: { dir: { cwd: "/seed" } }, model }); assertMatch(tid, TID_PATTERN); const seen = await turn(harness, tid, [ fauxAssistantMessage(fauxToolCall("cd", { path: "/next" }), { stopReason: "toolUse" }), ]); assertEquals(sectionsSeen(seen[0])[0].dir, "\ncwd: /seed\n"); assertEquals(sectionsSeen(seen[1]).at(-1)?.dir, "\ncwd: /next\n"); assertEquals(await berry.state(tid, "dir"), { cwd: "/next" }); await berry.install(defineExtension({ name: "sections", initial: () => ({ text: "first" }), apply: (_state, event: { text: string }) => event, setup(ext) { ext.prompt("extra", ({ state }) => state.text || undefined); return (conv) => conv.prompt(() => "stable"); }, })); const named = await berry.create({ extensions: { sections: {} }, model }); const [initial] = await turn(harness, named, []); const shown = sectionsSeen(initial)[0]; assertEquals(shown.sections, "\nstable\n"); assertEquals(shown["sections_extra"], "\nfirst\n"); for (const text of ["second", ""]) { await (await berry.emit(named, "sections", { text })).wait(context); const [seen] = await turn(harness, named, []); assertEquals(sectionsSeen(seen).at(-1), { "sections_extra": text ? `\n${text}\n` : null, }); } }); Deno.test("global prompt edits reach existing conversations and code prose follows the code extension", async () => { await using harness = await setup(); const { berry, model } = harness; await berry.install(code); const coding = await berry.create({ extensions: { code: {} }, model }); const plain = await berry.create({ extensions: {}, model }); await turn(harness, coding, []); await turn(harness, plain, []); await berry.setSystemPrompt({ userName: "Alex", generalGuidelines: "Ask $USER before deleting anything.", promptProse: { code: "Keep $USER's APIs small." }, }); const [changed] = await turn(harness, coding, []); const patch = sectionsSeen(changed).at(-1)!; assertStringIncludes(patch.lede!, "(Alex)"); assertStringIncludes(patch.guidelines!, "Ask Alex before deleting anything."); assertStringIncludes(patch.code!, "Keep Alex's APIs small."); assertStringIncludes(patch.code!, "current working directory:"); assert(!Object.hasOwn(patch, "code_guidelines")); assert(!Object.hasOwn(patch, "code_project") || patch["code_project"] === null); const [withoutCode] = await turn(harness, plain, []); const plainSections = Object.assign({}, ...sectionsSeen(withoutCode)); assertStringIncludes(plainSections.guidelines!, "Ask Alex before deleting anything."); assert(!plainSections.code); await berry.setSystemPrompt({ generalGuidelines: "", promptProse: { code: "" } }); const [cleared] = await turn(harness, coding, []); const removed = sectionsSeen(cleared).at(-1)!; assertEquals(removed.guidelines, null); // blank prose leaves the dynamic part of the section behind assertStringIncludes(removed.code!, "current working directory:"); assert(!removed.code!.includes("Keep")); assert(!Object.hasOwn(removed, "lede")); }); Deno.test("startup fills missing prompt settings without replacing saved fields or other settings", async () => { await using directory = await tempDir(); const path = join(directory.path, "settings.sqlite"); const { openNodeSqliteStorage } = await import("@earendil-works/pi-durable/storage/sqlite/node"); const models = createModels(); const berry = await openBerry(await openNodeSqliteStorage(path), models); assertEquals( (await berry.harness.snapshot(Settings, context))?.systemPrompt?.userName, userInfo().username, ); await berry.harness.commit(async (tx) => { const settings = await tx.doc(Settings); settings.models = [{ provider: "offline", modelId: "kept" }]; settings.environments = { remote: { ssh: "host" } }; settings.systemPrompt = { userName: "Sam", generalGuidelines: "" } as NonNullable< typeof settings.systemPrompt >; }, context); await berry.close(); const reopened = await openBerry(await openNodeSqliteStorage(path), models); try { const settings = (await reopened.harness.snapshot(Settings, context))!; assertEquals(settings.systemPrompt?.userName, "Sam"); assertEquals(settings.systemPrompt?.generalGuidelines, ""); assertEquals(settings.systemPrompt?.promptProse ?? {}, {}); assertEquals(settings.models, [{ provider: "offline", modelId: "kept" }]); assertEquals(settings.environments, { remote: { ssh: "host" } }); } finally { await reopened.close(); } }); Deno.test("AGENTS.md edits update the project section without repeating code guidelines", async () => { await using rootDir = await tempDir(); const root = rootDir.path; await using harness = await setup(); const { berry, model } = harness; await mkdir(join(root, "inner")); await writeFile(join(root, "AGENTS.md"), "outer rules"); await writeFile(join(root, "inner", "AGENTS.md"), "inner rules"); await berry.install(code); const tid = await berry.create({ extensions: { environment: { cwd: root }, code: {} }, model }); const [initial] = await turn(harness, tid, []); const shown = sectionsSeen(initial)[0]; assertStringIncludes(shown.code!, `current working directory: ${root}`); assertStringIncludes(shown.code!, "guidelines:"); assert(!shown.code!.includes("outer rules")); assertStringIncludes(shown["code_project"]!, "outer rules"); assert(!shown["code_project"]!.includes("current working directory:")); await writeFile(join(root, "AGENTS.md"), "revised rules"); const [edited] = await turn(harness, tid, []); const patch = sectionsSeen(edited).at(-1)!; assertEquals(Object.keys(patch), ["code_project"]); assertStringIncludes(patch["code_project"]!, "revised rules"); await berry.command(tid, "cd", "inner"); const [moved] = await turn(harness, tid, []); const changed = sectionsSeen(moved).at(-1)!; assertStringIncludes(changed.code!, `current working directory: ${join(root, "inner")}`); assertStringIncludes(changed["code_project"]!, "inner rules"); }); Deno.test("direct tools are offered beside codemode, and the model reads their text or their images", async () => { const image: ImageContent = { type: "image", data: "bytes", mimeType: "image/png" }; const values = ["plain text", { ...image, id: "capture" }, { type: "image", data: "bytes" }]; await using harness = await setup(); const { berry, model } = harness; await berry.install(defineExtension({ name: "results", initial: () => null, setup(ext) { ext.tool({ name: "result", description: "Return a value", parameters: Type.Object({ index: Type.Integer() }), expose: "model", execute: ({ index }) => values[index], }); }, })); const tid = await berry.create({ extensions: { dir: {}, results: {} }, model }); const call = (name: string, args = {}) => fauxAssistantMessage(fauxToolCall(name, args), { stopReason: "toolUse" }); const seen = await turn(harness, tid, [ call("shout"), ...values.map((_, index) => call("result", { index })), ]); assertEquals(toolsOffered(seen[0]).sort(), ["cd", "codemode", "result", "shout"]); assertEquals(lastToolResult(seen[1]).text, "HEY!"); const expected: ToolResultMessage["content"][] = [ [{ type: "text", text: "plain text" }], [image], [{ type: "text", text: JSON.stringify(values[2], null, 2) }], ]; for (const [index, content] of expected.entries()) { const result = seen[index + 2].findLast((message) => message.role === "toolResult"); assert(result?.role === "toolResult"); assertEquals(result.content, content); } }); Deno.test("an extension installs whole or not at all, and a new reducer re-derives state from history", async () => { await using harness = await setup(); const { berry, model } = harness; for (const name of ["berry", "environment", "subagent"]) { await assertRejects( () => berry.install(defineExtension({ name, initial: () => null, setup() {} })), BerryError, ); } let disposed = false; const shadow = (expose: "model" | "codemode") => defineExtension({ name: "shadow", initial: () => null, setup(ext) { ext.tool({ name: "codemode", description: "", parameters: Type.Object({}), expose, execute: () => "", }); ext.onDispose(() => void (disposed = true)); }, }); await assertRejects(() => berry.install(shadow("model")), BerryError); assert(disposed, "a refused extension is disposed"); assertEquals(berry.extensions().map((extension) => extension.name), ["dir", "gate"]); // scripts may have a tool by that name; it cannot reach the model await berry.install(shadow("codemode")); const tid = await berry.create({ extensions: { dir: { cwd: "/a" } }, model }); await (await berry.emit(tid, "dir", { type: "cd", cwd: "b" })).wait(context); await assertRejects(() => berry.install(defineExtension({ name: "dir", initial: () => ({}), setup() { throw new Error("broken"); }, })) ); assertEquals(await berry.state(tid, "dir"), { cwd: "b" }); // v2 resolves relative paths against the current cwd await berry.install( dir((s, e) => ({ cwd: e.cwd.startsWith("/") ? e.cwd : `${s.cwd}/${e.cwd}` })), ); assertEquals(await berry.state(tid, "dir"), { cwd: "/a/b" }); }); Deno.test("a conversation has its chosen extensions, the always-on ones, and the harness's own, under any name they have had", async () => { await using harness = await setup(); const { berry, model } = harness; const names = async (id: string) => (await (await berry.conversation(id))!.agent(context)).extensions.map((e) => e.name); // naming the harness's own changes nothing: every conversation has it const tid = await berry.create({ extensions: { berry: {}, dir: { cwd: "/a" } }, model }); assertEquals(await names(tid), ["berry", "environment", "dir"]); await berry.install(defineExtension({ name: "always", alwaysOn: true, initial: () => null, setup() { return (conv) => { conv.command({ name: "ping", description: "always there", execute: () => "pong" }); }; }, })); assertEquals(await names(tid), ["berry", "environment", "always", "dir"]); const named = await berry.create({ extensions: { always: {}, dir: {} }, model }); assertEquals(await names(named), ["berry", "environment", "always", "dir"]); assertEquals((await berry.commands(tid)).map(({ name }) => name), ["cd", "ping", "go"]); assertEquals(await berry.command(tid, "ping", ""), "pong"); await berry.configure(named, { extensions: [] }); assertEquals(await names(named), ["berry", "environment", "always"]); const conversation = (await berry.conversation(tid))!; const [, seed] = (await conversation.entries({ order: "ascending" }, 10, undefined, context)).items; await (await berry.emit(tid, "dir", { type: "cd", cwd: "/b" })).wait(context); berry.uninstall("dir"); await berry.install({ ...dir(), name: "place", formerly: ["dir"] }); assertEquals(await names(tid), ["berry", "environment", "always", "place"]); assertEquals(await berry.state(tid, "place"), { cwd: "/b" }); // a fork from before the rename, and a conversation asked for by the old name const fork = await conversation.fork(seed.id, { ownership: { kind: "ownerless" } }, context); const forkTid = await berry.tidOf(fork.id); assertEquals(await names(forkTid), ["berry", "environment", "always", "place"]); assertEquals(await berry.state(forkTid, "place"), { cwd: "/a" }); const asked = await berry.create({ extensions: { dir: { cwd: "/c" } }, model }); assertEquals(await berry.state(asked, "place"), { cwd: "/c" }); }); Deno.test("a replaced load closes its channels and is disposed once the calls it started end", async () => { const { faux, berry, model } = await setup(); const held = { entered: Promise.withResolvers(), release: Promise.withResolvers() }; const opened: number[] = []; let disposed = 0; const version = (mark: number) => defineExtension({ name: "slow", alwaysOn: true, initial: () => null, setup(ext) { ext.channel({ name: "line", parameters: Type.Object({}), open: () => void opened.push(mark), }); ext.tool({ name: "hold", description: "wait", parameters: Type.Object({}), expose: "model", execute: () => { held.entered.resolve(); return held.release.promise; }, }); ext.onDispose(() => void (disposed += mark)); }, }); await berry.install(version(1)); const tid = await berry.create({ extensions: {}, model }); let closed = 0; const channel = { send() {}, messages: new ReadableStream(), close: () => void closed++ }; await berry.channel(tid, "slow", "line", {}, channel, new AbortController().signal); faux.setResponses([ fauxAssistantMessage(fauxToolCall("hold", {}), { stopReason: "toolUse" }), fauxAssistantMessage("done"), ]); const conversation = (await berry.conversation(tid))!; const submission = await conversation.submit({ type: "input", content: "go" }, context); await held.entered.promise; await berry.install(version(10)); assertEquals(disposed, 0); held.release.resolve("released"); await submission.wait(context); await conversation.waitForIdle(context); assertEquals({ opened, closed, disposed }, { opened: [1], closed: 1, disposed: 1 }); await berry.close(); assertEquals(disposed, 11); }); Deno.test("handlers answer only in conversations that have their extension", async () => { await using harness = await setup(); const { berry, model } = harness; await berry.install(defineExtension({ name: "asked", initial: () => "folded", setup(ext) { ext.handle({ name: "echo", parameters: Type.Object({ times: Type.Integer({ default: 1 }) }), execute: ({ times }, ctx) => ({ state: ctx.state, times, aborted: ctx.signal.aborted }), }); ext.handle({ name: "fail", parameters: Type.Object({}), execute: () => { throw new Error("no such thing"); }, }); }, })); const tid = await berry.create({ extensions: { asked: {} }, model }); assertEquals(await berry.request(tid, "asked", "echo", undefined), { state: "folded", times: 1, aborted: false, }); assertEquals( (await berry.request(tid, "asked", "echo", { times: 3 }) as { times: number }).times, 3, ); await assertRejects( () => berry.request(tid, "asked", "echo", { times: "3" }), BerryError, "times", ); await assertRejects(() => berry.request(tid, "asked", "fail", {}), BerryError, "no such thing"); await assertRejects(() => berry.request(tid, "asked", "nope", {}), BerryError, "no handler"); const without = await berry.create({ extensions: {}, model }); await assertRejects( () => berry.request(without, "asked", "echo", {}), BerryError, "not in this conversation", ); }); Deno.test("forks and branches start from the state at their entry, and a branch keeps the conversation's tid, agent, and name", async () => { await using harness = await setup(); const { faux, berry, model, gate } = harness; const tid = await berry.create({ extensions: { dir: { cwd: "/a" } }, model }); const conversation = (await berry.conversation(tid))!; const [start, seed] = (await conversation.entries({ order: "ascending" }, 10, undefined, context)).items; assertEquals([start.kind, seed.kind], ["berry.start", "berry.event"]); await (await berry.emit(tid, "dir", { type: "cd", cwd: "/b" })).wait(context); const fork = await conversation.fork(seed.id, { ownership: { kind: "ownerless" } }, context); const forkTid = await berry.tidOf(fork.id); assert(forkTid !== tid); assertEquals(await berry.state(forkTid, "dir"), { cwd: "/a" }); assertEquals(await berry.state(tid, "dir"), { cwd: "/b" }); await berry.configure(tid, { extensions: ["dir", "gate"] }); faux.setResponses([ fauxAssistantMessage(fauxToolCall("hold", {}), { stopReason: "toolUse" }), fauxAssistantMessage("done"), ]); const submission = await conversation.submit({ type: "input", content: "go" }, context); await gate.entered.promise; await assertRejects(() => berry.branch(tid, seed.id), BerryError, "busy"); gate.release.resolve("released"); await submission.wait(context); await conversation.waitForIdle(context); const tip = (await conversation.context(context)).entries.at(-1)!.id; await berry.markRead(tid, tip); await berry.rename(tid, " a custom name "); const feed = (await berry.watchState(tid, "dir"))!; const moved = Promise.withResolvers(); feed.start((value) => Promise.resolve(moved.resolve(value))); await berry.branch(tid, seed.id); const branch = (await berry.conversation(tid))!; assert(branch.id !== conversation.id); assertEquals(await berry.tidOf(branch.id), tid); const index = (await berry.harness.snapshot(Conversations, context))!; assertEquals(Object.keys(index).sort(), [tid, forkTid].sort()); assertEquals([index[tid].title, index[tid].readThrough], ["a custom name", tip]); assertEquals(await berry.state(tid, "dir"), { cwd: "/a" }); assertEquals(await moved.promise, { cwd: "/a" }); assertEquals( (await branch.agent(context)).extensions.map((extension) => extension.name), ["berry", "environment", "dir", "gate"], ); await feed.stop(); await berry.branch(tid, tip); assertEquals((await berry.harness.snapshot(Conversations, context))![tid].title, "a custom name"); await berry.rename(tid, " "); assertEquals((await berry.harness.snapshot(Conversations, context))![tid].title, undefined); await assertRejects(() => berry.rename("missing", "name"), BerryError, "No conversation"); }); Deno.test("commands are listed per conversation and run as the user, landing at once while busy", async () => { await using harness = await setup(); const { faux, berry, model, gate } = harness; const tid = await berry.create({ extensions: { dir: {}, gate: {} }, model }); const conversation = (await berry.conversation(tid))!; assertEquals((await berry.commands(tid)).map(({ name }) => name), ["cd", "go"]); assertEquals(await berry.command(tid, "go", "/cmd"), "/cmd"); await conversation.waitForIdle(context); assertEquals(await berry.state(tid, "dir"), { cwd: "/cmd" }); await assertRejects(() => berry.command(tid, "go", "relative"), BerryError, "not absolute"); await assertRejects(() => berry.command(tid, "nope", ""), BerryError, "No command /nope"); const other = await berry.create({ extensions: {}, model }); assertEquals((await berry.commands(other)).map(({ name }) => name), ["cd"]); let next: readonly Message[] = []; faux.setResponses([ fauxAssistantMessage(fauxToolCall("hold", {}), { stopReason: "toolUse" }), (ctx) => (next = ctx.messages, fauxAssistantMessage("done")), ]); const submission = await conversation.submit({ type: "input", content: "go" }, context); await gate.entered.promise; assertEquals(await berry.command(tid, "go", "/mid"), "/mid"); assertEquals(await berry.state(tid, "dir"), { cwd: "/mid" }); gate.release.resolve("released"); await submission.wait(context); await conversation.waitForIdle(context); assertEquals(await berry.state(tid, "dir"), { cwd: "/mid" }); // and the model hears of it at the next request, not only on the next input assertEquals(sectionsSeen(next).at(-1)?.dir, "\ncwd: /mid\n"); }); Deno.test("conversations use only models enabled in settings, at a thinking level the model supports", async () => { await using harness = await setup(undefined, { models: [{ id: "thinker", reasoning: true }, { id: "plain", reasoning: false }], }); const { berry, model } = harness; const plain = { provider: "faux", modelId: "plain" }; await assertRejects( () => berry.create({ extensions: {}, model: plain }), BerryError, "not enabled", ); const tid = await berry.create({ extensions: {}, model, thinkingLevel: "high" }); await assertRejects(() => berry.configure(tid, { model: plain }), BerryError, "not enabled"); await berry.enableModels([model, plain, model]); assertEquals((await berry.harness.snapshot(Settings, context))?.models, [model, plain]); const agent = async (id: string) => await (await berry.conversation(id))!.agent(context); assertEquals((await agent(tid)).thinkingLevel, "high"); await berry.configure(tid, { model: plain }); assertEquals([(await agent(tid)).model, (await agent(tid)).thinkingLevel], [plain, "off"]); await berry.configure(tid, { model, thinkingLevel: "max" }); assertEquals((await agent(tid)).thinkingLevel, "high"); const created = await berry.create({ extensions: {}, model: plain, thinkingLevel: "high" }); assertEquals((await agent(created)).thinkingLevel, "off"); }); Deno.test("extension prose defaults, overrides, blanks, selection, and $USER", async () => { await using harness = await setup(); const { berry, model } = harness; await berry.install(defineExtension({ name: "notes", initial: () => null, setup(ext) { ext.promptProse({ default: "Greet $USER warmly." }); return (conv) => conv.prompt(() => "dynamic content"); }, })); assertEquals(berry.promptProseDefault("notes"), "Greet $USER warmly."); assertEquals(berry.promptProseDefault("dir"), null); const withNotes = await berry.create({ extensions: { notes: {} }, model }); const withoutNotes = await berry.create({ extensions: {}, model }); const [first] = await turn(harness, withNotes, []); const shown = sectionsSeen(first)[0]; assertStringIncludes(shown.notes!, "Greet"); assert(!shown.notes!.includes("$USER")); assert(!shown.notes!.includes("guidelines:")); assertStringIncludes(shown.notes!, "dynamic content"); const [absent] = await turn(harness, withoutNotes, []); assert(!Object.assign({}, ...sectionsSeen(absent)).notes); await berry.setSystemPrompt({ userName: "Sam", promptProse: { notes: "Bid $USER farewell." } }); const [overrode] = await turn(harness, withNotes, []); assertStringIncludes(sectionsSeen(overrode).at(-1)!.notes!, "Bid Sam farewell."); assertStringIncludes(sectionsSeen(overrode).at(-1)!.notes!, "dynamic content"); await berry.setSystemPrompt({ promptProse: { notes: "" } }); const [blanked] = await turn(harness, withNotes, []); assertStringIncludes(sectionsSeen(blanked).at(-1)!.notes!, "dynamic content"); assert(!sectionsSeen(blanked).at(-1)!.notes!.includes("Bid")); const stored = (await berry.harness.snapshot(Settings, context))?.systemPrompt; assertEquals(stored?.promptProse?.notes, ""); }); Deno.test("startup migrates legacy code prose, preserving blank values and newer overrides", async () => { const { openNodeSqliteStorage } = await import("@earendil-works/pi-durable/storage/sqlite/node"); for ( const { legacy, override, expected } of [ { legacy: "Legacy for $USER.", override: undefined, expected: "guidelines:\nLegacy for $USER.", }, { legacy: "", override: undefined, expected: "" }, { legacy: "Stale legacy.", override: "", expected: "" }, ] ) { await using directory = await tempDir(); const path = join(directory.path, "settings.sqlite"); const models = createModels(); const seed = await openBerry(await openNodeSqliteStorage(path), models); try { await seed.harness.commit(async (tx) => { const settings = await tx.doc(Settings); const stored = { userName: "Sam", generalGuidelines: "", codingGuidelines: legacy, promptProse: { other: "untouched", ...(override === undefined ? {} : { code: override }), }, }; settings.systemPrompt = stored; }, context); } finally { await seed.close(); } const berry = await openBerry(await openNodeSqliteStorage(path), models); try { const stored = (await berry.harness.snapshot(Settings, context))?.systemPrompt; assertEquals(stored?.promptProse, { code: expected, other: "untouched" }); assertEquals(stored?.userName, "Sam"); assertEquals(stored?.generalGuidelines, ""); } finally { await berry.close(); } } }); Deno.test("prompt prose overrides survive a missing extension", async () => { await using harness = await setup(); const { berry } = harness; await berry.install(defineExtension({ name: "ephemeral", initial: () => null, setup(ext) { ext.promptProse({ default: "default prose" }); }, })); await berry.setSystemPrompt({ promptProse: { ephemeral: "kept override" } }); berry.uninstall("ephemeral"); assertEquals(berry.promptProseDefault("ephemeral"), null); assertEquals( (await berry.harness.snapshot(Settings, context))?.systemPrompt?.promptProse?.ephemeral, "kept override", ); await berry.install(defineExtension({ name: "ephemeral", initial: () => null, setup(ext) { ext.promptProse({ default: "default prose" }); }, })); assertEquals(berry.promptProseDefault("ephemeral"), "default prose"); assertEquals( (await berry.harness.snapshot(Settings, context))?.systemPrompt?.promptProse?.ephemeral, "kept override", ); }); Deno.test("a host lends /reload its extensions, and the prompt a guide to berry's source", async () => { const faux = fauxProvider(); const models = createModels(); models.setProvider(faux.provider); const model = { provider: "faux", modelId: faux.getModel().id }; const reloaded: string[] = []; const loaded = (name: string) => ({ name, description: null, status: "loaded", error: null }) as const; const failed = { ...loaded("two"), status: "failed", error: "no mod.ts" } as const; const berry = await openBerry(new MemoryStorage(), models, { root: "/somewhere/berry", check: () => Promise.resolve(null), restart: () => {}, extensions: () => [loaded("one"), failed], reload: (name) => { reloaded.push(name); return Promise.resolve(name === "two" ? failed : loaded(name)); }, }, { async open(name, cwd, during) { return Object.assign(await localOnly.open("local", cwd, during), { name }); }, close: () => Promise.resolve(), }); try { await berry.enableModels([model]); const tid = await berry.create({ extensions: {}, model }); const seen = await turn({ faux, berry }, tid, []); assertStringIncludes(sectionsSeen(seen[0])[0].berry ?? "", "/somewhere/berry"); assertEquals(toolsOffered(seen[0]), ["codemode"]); assertEquals(await berry.command(tid, "reload", " one "), "reloaded one"); await assertRejects(() => berry.command(tid, "reload", ""), BerryError, "two: no mod.ts"); assertEquals(reloaded, ["one", "one", "two"]); await berry.configureExtension(tid, "environment", { environment: "remote" }); const [remote] = await turn({ faux, berry }, tid, []); assertEquals(sectionsSeen(remote).at(-1)?.berry, null); const fresh = await berry.create({ extensions: { environment: { environment: "remote" } }, model, }); const [firstRemote] = await turn({ faux, berry }, fresh, []); assertEquals(sectionsSeen(firstRemote).at(-1)?.berry, undefined); await berry.configureExtension(tid, "environment", { environment: "local" }); const [local] = await turn({ faux, berry }, tid, []); assertStringIncludes(sectionsSeen(local).at(-1)?.berry ?? "", "/somewhere/berry"); } finally { await berry.close(); } });