9381a6a9f7
* fix(scripts): launch dev-isolated backend via __backend subcommand The kandev binary became a bundle launcher with the restart supervisor (ADR 0019) and exits without KANDEV_BUNDLE_DIR when invoked bare; the isolated dev instance must run the backend entrypoint directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(mcp): session spawning, sibling messaging, and renameable tabs - spawn_session_kandev spawns an additional agent session on an existing task (default: the caller's own) via LaunchSession(IntentStart), with optional agent_profile_id (defaults to the spawner's profile) and a session name; the initial prompt carries spawner attribution and reply instructions. - message_task_kandev accepts an optional session_id to target a specific session, enabling same-task sibling messaging. Explicit targets are pinned through dispatch so the idle-path primary-session re-resolution cannot reroute a sibling message back to its sender (regression-tested). - task_sessions gains a name column (migration ordered after the agent_execution_id-drop rebuild), a session.rename WS action, and inline tab rename UI; custom names take precedence over derived agent/model labels across the tab, agents dropdown, mobile list, chat header, and dockview panel titles. - Inter-session messages stamp sender_session_name into metadata so the sender badge shows which session sent the message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): address review findings on session spawn/rename/messaging - include name in task-session DTOs (list + full, no omitempty) so persisted tab names survive list hydration and clears propagate - sanitize sender task/session ids embedded in <kandev-system> blocks - reject cross-workspace spawn targets; require sender_task_id - truncate session names by runes, not bytes - distinguish not-found from internal errors in message target lookup - guard TabRenameInput blur-commit after Enter/Escape resolution - cap optimistic rename length client-side and toast on rename failure - add tests: spawn launch-failure and cross-workspace paths, renameSession API client Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): round-two review fixes for spawn/rename lint and hardening - extract spawn validation/authorization helpers (cyclop/nestif limits) - make stripSystemTag replace until stable to block nested tag evasion - load session before rename so a durable rename can never skip its broadcast; surface lookup failures to the caller instead - drop redundant sessionLauncher nil check (guarded registration) - add maxLength to the rename input matching the server cap - add tests: nested sanitizer evasion, spawn rename-failure tolerance Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const requestMock = vi.fn();
|
|
let clientFactory: () => { request: typeof requestMock } | null = () => ({ request: requestMock });
|
|
|
|
vi.mock("@/lib/ws/connection", () => ({
|
|
getWebSocketClient: () => clientFactory(),
|
|
}));
|
|
|
|
// Imported after the mock so it picks up the mocked module.
|
|
const { renameSession } = await import("./session-api");
|
|
|
|
beforeEach(() => {
|
|
requestMock.mockReset();
|
|
requestMock.mockResolvedValue({ success: true });
|
|
clientFactory = () => ({ request: requestMock });
|
|
});
|
|
|
|
describe("renameSession", () => {
|
|
it("sends session.rename with the session id and name", async () => {
|
|
await renameSession("sess-1", "reviewer");
|
|
expect(requestMock).toHaveBeenCalledWith("session.rename", {
|
|
session_id: "sess-1",
|
|
name: "reviewer",
|
|
});
|
|
});
|
|
|
|
it("passes an empty name through so the custom label can be cleared", async () => {
|
|
await renameSession("sess-1", "");
|
|
expect(requestMock).toHaveBeenCalledWith("session.rename", { session_id: "sess-1", name: "" });
|
|
});
|
|
|
|
it("throws when the WebSocket client is unavailable", async () => {
|
|
clientFactory = () => null;
|
|
await expect(renameSession("sess-1", "reviewer")).rejects.toThrow("WebSocket unavailable");
|
|
expect(requestMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("propagates backend errors to the caller", async () => {
|
|
requestMock.mockRejectedValueOnce(new Error("session not found"));
|
|
await expect(renameSession("missing", "x")).rejects.toThrow("session not found");
|
|
});
|
|
});
|