Files
cline/apps/examples/desktop-app/sidecar/context.test.ts
T
Tomás BarreiroandSaoud Rizwan 220a21bdcf Move sdk/apps/ to apps/ (#11200)
* Move the apps to the root dir

* Update all references from sdk/apps/ to apps/

* Update dependencies

* Install bun types

* Fix types

* Fix types

* Fix linter

* Ingore apps from vscode

* Fix security warning

* Fix windows install

* Enable windows dev mode

* Revert "Enable windows dev mode"

This reverts commit a46c99282e.

* Revert "Ingore apps from vscode"

This reverts commit 47f7b265d2.

* Revert "Fix windows install"

This reverts commit 1dabba1556.

* update the repo root

* fix root dir

* fix path

* fix other path

* Fix unrelated changes

* fix: address apps move follow-up blockers (#11228)

* fix: update root app command paths

* fix: include moved apps in root checks

* fix: clean up moved app path references

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2026-06-03 01:49:57 +02:00

211 lines
5.7 KiB
TypeScript

import type { RuntimeCapabilities } from "@cline/core";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { SidecarContext } from "./types";
const createCoreMock = vi.hoisted(() => vi.fn());
const connectMock = vi.hoisted(() => vi.fn());
const subscribeMock = vi.hoisted(() => vi.fn());
vi.mock("@cline/core", async () => {
const actual =
await vi.importActual<typeof import("@cline/core")>("@cline/core");
return {
...actual,
ClineCore: {
create: createCoreMock,
},
NodeHubClient: class {
connect = connectMock;
subscribe = subscribeMock;
dispose = vi.fn();
},
};
});
function readEvents(ctx: SidecarContext): Array<{
event: { name: string; payload: Record<string, unknown> };
}> {
const [client] = ctx.wsClients;
const send = client?.send;
if (!send || typeof send !== "function" || !("mock" in send)) {
return [];
}
return (send as ReturnType<typeof vi.fn>).mock.calls.map(([raw]) =>
JSON.parse(String(raw)),
);
}
describe("Code sidecar runtime capabilities", () => {
beforeEach(() => {
createCoreMock.mockReset();
connectMock.mockReset();
subscribeMock.mockReset();
connectMock.mockResolvedValue(undefined);
subscribeMock.mockReturnValue(() => {});
createCoreMock.mockResolvedValue({
runtimeAddress: "ws://127.0.0.1:25463/hub",
subscribe: vi.fn(() => () => {}),
dispose: vi.fn(),
});
});
it("registers Code App capability factory with core", async () => {
const { createSidecarContext, initializeSessionManager } = await import(
"./context"
);
const ctx = createSidecarContext("/workspace/project");
await initializeSessionManager(ctx);
expect(createCoreMock).toHaveBeenCalledWith(
expect.objectContaining({
backendMode: "hub",
capabilities: expect.objectContaining({
toolExecutors: expect.objectContaining({
askQuestion: expect.any(Function),
}),
requestToolApproval: expect.any(Function),
}),
hub: expect.objectContaining({
clientType: "code-sidecar",
displayName: "Code App sidecar",
}),
}),
);
});
it("resolves askQuestion through the websocket request/response protocol", async () => {
const { createSidecarContext, initializeSessionManager } = await import(
"./context"
);
const { handleCommand } = await import("./commands");
const ctx = createSidecarContext("/workspace/project");
ctx.wsClients.add({ send: vi.fn() });
await initializeSessionManager(ctx);
const capabilities = createCoreMock.mock.calls[0][0]
.capabilities as RuntimeCapabilities;
const answer = capabilities.toolExecutors?.askQuestion?.(
"Which branch?",
["Keep current", "Create new"],
{
agentId: "agent-1",
conversationId: "conversation-1",
iteration: 3,
},
);
expect(answer).toBeInstanceOf(Promise);
const event = readEvents(ctx).find(
(item) => item.event.name === "ask_question_requested",
);
expect(event?.event.payload).toMatchObject({
question: "Which branch?",
options: ["Keep current", "Create new"],
context: {
agentId: "agent-1",
conversationId: "conversation-1",
iteration: 3,
},
});
const requestId = String(event?.event.payload.requestId ?? "");
expect(requestId.length).toBeGreaterThan(0);
await handleCommand(ctx, "respond_ask_question", {
requestId,
answer: "Create new",
});
await expect(answer).resolves.toBe("Create new");
expect(ctx.pendingQuestions.size).toBe(0);
expect(readEvents(ctx)).toContainEqual(
expect.objectContaining({
event: expect.objectContaining({
name: "ask_question_answered",
payload: { requestId },
}),
}),
);
});
it("resolves approval through websocket state", async () => {
const { createSidecarContext, initializeSessionManager } = await import(
"./context"
);
const { handleCommand } = await import("./commands");
const ctx = createSidecarContext("/workspace/project");
ctx.wsClients.add({ send: vi.fn() });
await initializeSessionManager(ctx);
expect(createCoreMock).toHaveBeenCalledWith(
expect.objectContaining({
backendMode: "hub",
capabilities: expect.objectContaining({
requestToolApproval: expect.any(Function),
}),
hub: expect.objectContaining({
clientType: "code-sidecar",
displayName: "Code App sidecar",
}),
}),
);
const capabilities = createCoreMock.mock.calls[0][0]
.capabilities as RuntimeCapabilities;
const approval = capabilities.requestToolApproval?.({
sessionId: "sess-1",
agentId: "agent-1",
conversationId: "conversation-1",
iteration: 2,
toolCallId: "tool-call-1",
toolName: "run_commands",
input: { commands: ["echo hi"] },
policy: { autoApprove: false },
});
expect(approval).toBeInstanceOf(Promise);
const pending = await handleCommand(ctx, "poll_tool_approvals", {
sessionId: "sess-1",
});
expect(pending).toEqual([
expect.objectContaining({
sessionId: "sess-1",
toolCallId: "tool-call-1",
toolName: "run_commands",
input: { commands: ["echo hi"] },
agentId: "agent-1",
conversationId: "conversation-1",
}),
]);
expect(readEvents(ctx)).toContainEqual(
expect.objectContaining({
event: expect.objectContaining({
name: "tool_approval_state",
payload: expect.objectContaining({
sessionId: "sess-1",
items: expect.arrayContaining([
expect.objectContaining({ toolCallId: "tool-call-1" }),
]),
}),
}),
}),
);
const [{ requestId }] = pending as Array<{ requestId: string }>;
await handleCommand(ctx, "respond_tool_approval", {
sessionId: "sess-1",
requestId,
approved: true,
});
await expect(approval).resolves.toEqual({ approved: true });
expect(
await handleCommand(ctx, "poll_tool_approvals", { sessionId: "sess-1" }),
).toEqual([]);
});
});