mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
* feat(ai): add host coding agent sessions with configured providers * Wait for agent initialization and shutdown before resuming sessions * Keep Pi transcripts outside its configuration migration directory * Cover Pi session directory separation * Update core permission catalog expectations for remote agents * Install remote agent runtimes from verified official sources * Wait for the identity panel credentials request in its test * Add scoped SSH forwarding setup for remote agents * Verify forwarding setup through a fresh SSH connection * Bound the SSH forwarding verification timeout * Return the forwarding verification callback result * fix(ai): support agent send and interrupt keyboard shortcuts * feat(ai): add agent queues attachments worktrees and session recovery * fix(ai): handle deleted workspace paths and duplicate status events * fix(ai): accept file references in root workspaces * fix(ai): refresh paused queues after agent errors * fix(ai): store agent sessions in plugin tables and lock down OpenCode Agent sessions lived in a single ctx.kv value holding up to 2000 events, which passed the 256 KB kv limit within minutes of a normal run. Every save after that failed, so history, nativeId, queue and archive changes were lost and every event wrote a kv audit row. Sessions, events and queued prompts now have their own tables. A running agent appends events in batches at most once a second with a lazy save; session and queue changes save immediately. Prompts, drafts and stored event text are capped in UTF-8 bytes to fit MySQL TEXT. kv:own is no longer requested. The OpenCode server now requires a per-session password, so other local accounts on the host cannot drive the agent through its loopback port. * test(upgrade): include agent session tables in the reviewed upgrade SQL
72 lines
2.7 KiB
TypeScript
72 lines
2.7 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { waitFor } from "@testing-library/react";
|
|
|
|
vi.mock("../../src/frontend/ai-api", async (importOriginal) => ({
|
|
...(await importOriginal<Record<string, unknown>>()),
|
|
getAiStatus: vi.fn(async () => ({ globallyEnabled: true, enabled: true })),
|
|
}));
|
|
import {
|
|
renderWithApp,
|
|
type RenderedPluginApp,
|
|
} from "@termix/plugin-sdk/testing";
|
|
import type { PluginManifest } from "@termix/plugin-sdk/manifest";
|
|
import * as plugin from "../../src/frontend/index";
|
|
import manifestJson from "../../manifest.json";
|
|
import locales from "../../locales/en.json";
|
|
|
|
const manifest = manifestJson as unknown as PluginManifest;
|
|
|
|
const declared = (views?: Array<{ id: string }>) =>
|
|
(views ?? []).map((view) => view.id).sort();
|
|
|
|
let rendered: RenderedPluginApp | null = null;
|
|
|
|
afterEach(async () => {
|
|
await rendered?.deactivate();
|
|
rendered = null;
|
|
});
|
|
|
|
describe(`${manifest.id} activate`, () => {
|
|
it("activates and registers only views its manifest declares", async () => {
|
|
rendered = await renderWithApp(plugin, { manifest, locales });
|
|
const contributes = manifest.contributes as unknown as Record<
|
|
string,
|
|
Array<{ id: string }> | undefined
|
|
>;
|
|
const tabs = declared(contributes.tabs);
|
|
// A rail panel may reuse one of the plugin's tab ids.
|
|
const panels = [...declared(contributes.panels), ...tabs];
|
|
const cards = declared(contributes.dashboardCards);
|
|
for (const id of rendered.registered.tabs()) expect(tabs).toContain(id);
|
|
for (const id of rendered.registered.panels()) expect(panels).toContain(id);
|
|
for (const id of rendered.registered.dashboardCards()) {
|
|
expect(cards).toContain(id);
|
|
}
|
|
});
|
|
|
|
it("removes everything it registered on deactivate", async () => {
|
|
const app = await renderWithApp(plugin, { manifest, locales });
|
|
await app.deactivate();
|
|
expect(app.registered.tabs()).toEqual([]);
|
|
expect(app.registered.panels()).toEqual([]);
|
|
expect(app.registered.railItems()).toEqual([]);
|
|
expect(app.registered.hostActions()).toEqual([]);
|
|
expect(app.registered.hostEditorSections()).toEqual([]);
|
|
expect(app.registered.dashboardCards()).toEqual([]);
|
|
expect(app.registered.settingsComponents()).toEqual([]);
|
|
expect(app.registered.actions()).toEqual([]);
|
|
});
|
|
|
|
it("registers the providers list as its custom settings field", async () => {
|
|
rendered = await renderWithApp(plugin, { manifest, locales });
|
|
expect(rendered.registered.settingsComponents()).toEqual(["providers"]);
|
|
});
|
|
|
|
it("adds the assistant tab once AI is on for the user", async () => {
|
|
rendered = await renderWithApp(plugin, { manifest, locales });
|
|
await waitFor(() =>
|
|
expect(rendered!.registered.tabs()).toEqual(["ai", "ai-agents"]),
|
|
);
|
|
});
|
|
});
|