Skip to content
Closed
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
16 changes: 5 additions & 11 deletions src/bundled/goose/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @vitest-environment jsdom
import { afterEach, expect, it, vi } from "vitest";
import type { AgentType } from "../../features/agents/service";
import { GooseRuntime } from "./runtime";
import { apply } from "./index";
import * as goose from "./index";
import { appliedType } from "../../features/agents/plugin-testing";

vi.mock("@tauri-apps/api/core", () => ({ isTauri: () => true }));
afterEach(() => vi.restoreAllMocks());
Expand All @@ -22,18 +22,12 @@ it("observes saved conversations with the status of open sessions", () => {
// A one-off turn joins no conversation.
{ key: "timer/x #1", busy: true },
]);
let type: AgentType | undefined;
apply({
const type = appliedType(goose, {
pluginOwner: { id: "buzz.goose", revision: "r1" },
host: { request: vi.fn() },
agents: {
register: (registered: AgentType) => {
type = registered;
},
relay: () => undefined,
},
agents: { relay: () => undefined },
effect: vi.fn(),
} as never);
});
// A reload stops its turns, so the owner is asked to resume them.
expect(type?.resumes).toBe(true);
expect(
Expand Down
80 changes: 46 additions & 34 deletions src/bundled/goose/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,52 @@ import { readUsage } from "./usage";

export const TYPE_ID = "goose";

export const inject = ["agents", "host"];
/** What the type's UI reaches of its runtime. */
type GooseExposed = Parameters<typeof createTabs>[0];

export const inject = ["agents"];
export const apply: PluginModule["apply"] = (ctx) => {
// Only the desktop app can start `goose-acp`; no unusable type elsewhere.
if (!isTauri()) return;
ctx.agents.present<GooseExposed, Config>(TYPE_ID, (exposed) => {
const {
Create,
CreateModel,
SettingsTab,
ActivityTab,
DefaultControl,
ProviderDialogBody,
} = createTabs(exposed);
return {
icon: GooseLogoIcon,
Create,
CreateModel,
provider: { DialogBody: ProviderDialogBody, DefaultControl },
tabs: [
{
id: "settings",
title: "Settings",
role: "settings",
splitSettings: true,
component: SettingsTab,
},
{
id: "activity",
title: "Activity",
role: "activity",
component: ActivityTab,
},
],
};
});
};

// The runtime, which runs where the agents do.
export const agent: NonNullable<PluginModule["agent"]> = {
inject: ["agents", "host"],
apply: (ctx) => applyAgent(ctx),
};
function applyAgent(ctx: Parameters<PluginModule["apply"]>[0]) {
if (!isTauri()) return;
const { agents, host } = ctx;
const typeKey = `${ctx.pluginOwner?.id ?? "buzz.goose"}/${TYPE_ID}`;
Expand All @@ -38,18 +81,6 @@ export const apply: PluginModule["apply"] = (ctx) => {
: undefined;
const setup = new GooseSetup(desktop, () => runtime.defaultsChanged());
const history = new GooseHistory(desktop);
const {
Create,
CreateModel,
SettingsTab,
ActivityTab,
DefaultControl,
ProviderDialogBody,
} = createTabs({
setup,
runtime,
history,
});
const sync = () => {
const snapshot = agents.snapshot();
if (snapshot.status === "loading") return;
Expand All @@ -67,7 +98,6 @@ export const apply: PluginModule["apply"] = (ctx) => {
agents.register<Config>({
id: TYPE_ID,
title: "Goose",
icon: GooseLogoIcon,
description:
"goose on this computer, with any provider goose supports. Each thread it is mentioned in is one continuing session.",
defaults: () => ({ config: DEFAULT_CONFIG }),
Expand All @@ -79,14 +109,11 @@ export const apply: PluginModule["apply"] = (ctx) => {
provider: source.provider ?? "",
}
: undefined,
Create,
CreateModel,
expose: { setup, runtime, history } satisfies GooseExposed,
provider: {
snapshot: setup.snapshot,
subscribe: setup.subscribe,
check: setup.check,
DialogBody: ProviderDialogBody,
DefaultControl,
},
adopt: (carry, saved) => ({
...saved,
Expand Down Expand Up @@ -141,22 +168,7 @@ export const apply: PluginModule["apply"] = (ctx) => {
signal,
),
}),
tabs: [
{
id: "settings",
title: "Settings",
role: "settings",
splitSettings: true,
component: SettingsTab,
},
{
id: "activity",
title: "Activity",
role: "activity",
component: ActivityTab,
},
],
models: (agent) => setup.picks(config(agent.config)),
...gooseRun(runtime),
});
};
}
22 changes: 19 additions & 3 deletions src/bundled/goose/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,30 @@ const placeOf = (key: string): Place => {
const titled = (name: string, title: string) =>
title.startsWith(`${name} · `) ? title.slice(name.length + 3) : title;

// What the tabs reach of the runtime: stores and calls returning promises,
// as an agent type's `expose` allows.
type SetupHandle = Pick<
GooseSetup,
| "snapshot"
| "subscribe"
| "check"
| "providers"
| "models"
| "efforts"
| "saved"
| "save"
| "saveDefaults"
| "test"
>;

export function createTabs({
setup,
runtime,
history,
}: Readonly<{
setup: GooseSetup;
runtime: GooseRuntime;
history: GooseHistory;
setup: SetupHandle;
runtime: Pick<GooseRuntime, "subscribe" | "sessions">;
history: Pick<GooseHistory, "find" | "recent" | "transcript">;
}>) {
function DefaultControl({ onConfigure }: { onConfigure(): void }) {
const state = useSyncExternalStore(setup.subscribe, setup.snapshot);
Expand Down
Loading