mirror of
https://github.com/cline/cline.git
synced 2026-10-29 17:42:34 +08:00
* 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 commita46c99282e. * Revert "Ingore apps from vscode" This reverts commit47f7b265d2. * Revert "Fix windows install" This reverts commit1dabba1556. * 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>
211 lines
5.7 KiB
TypeScript
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([]);
|
|
});
|
|
});
|