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>
111 lines
3.2 KiB
TypeScript
111 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { resolveSessionTabTitle } from "./session-tab-title";
|
|
|
|
const SPARK_MODEL_ID = "gpt-5.3-codex-spark";
|
|
const SPARK_MODEL_NAME = "GPT-5.3-Codex-Spark";
|
|
const PROFILE_LABEL = "GPT-5.5 (medium)";
|
|
|
|
const baseArgs = {
|
|
agentLabel: PROFILE_LABEL,
|
|
activeModelId: null,
|
|
currentModelId: null,
|
|
snapshotModel: null,
|
|
modelOptions: [],
|
|
configOptions: [],
|
|
};
|
|
|
|
describe("resolveSessionTabTitle", () => {
|
|
it("prefers the user-supplied custom name over every derived title", () => {
|
|
expect(
|
|
resolveSessionTabTitle({
|
|
...baseArgs,
|
|
customName: "reviewer",
|
|
activeModelId: SPARK_MODEL_ID,
|
|
modelOptions: [{ id: SPARK_MODEL_ID, name: SPARK_MODEL_NAME }],
|
|
}),
|
|
).toBe("reviewer");
|
|
});
|
|
|
|
it("falls back to derived titles when the custom name is empty or absent", () => {
|
|
expect(resolveSessionTabTitle({ ...baseArgs, customName: "" })).toBe(PROFILE_LABEL);
|
|
expect(resolveSessionTabTitle({ ...baseArgs, customName: null })).toBe(PROFILE_LABEL);
|
|
});
|
|
|
|
it("uses the agent label over live model state when a profile label is available", () => {
|
|
expect(
|
|
resolveSessionTabTitle({
|
|
...baseArgs,
|
|
activeModelId: SPARK_MODEL_ID,
|
|
modelOptions: [{ id: SPARK_MODEL_ID, name: SPARK_MODEL_NAME }],
|
|
}),
|
|
).toBe(PROFILE_LABEL);
|
|
});
|
|
|
|
it("includes non-model config selections in the title", () => {
|
|
expect(
|
|
resolveSessionTabTitle({
|
|
...baseArgs,
|
|
agentLabel: null,
|
|
activeModelId: SPARK_MODEL_ID,
|
|
configOptions: [
|
|
{
|
|
type: "select",
|
|
id: "model",
|
|
name: "Model",
|
|
currentValue: "gpt-5.5",
|
|
options: [
|
|
{ value: "gpt-5.5", name: "GPT-5.5" },
|
|
{ value: SPARK_MODEL_ID, name: SPARK_MODEL_NAME },
|
|
],
|
|
},
|
|
{
|
|
type: "select",
|
|
id: "effort",
|
|
name: "Effort",
|
|
currentValue: "high",
|
|
options: [
|
|
{ value: "medium", name: "Medium" },
|
|
{ value: "high", name: "High" },
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
).toBe(`${SPARK_MODEL_NAME} / High`);
|
|
});
|
|
|
|
it("falls back to the agent label when live model state is unavailable", () => {
|
|
expect(resolveSessionTabTitle(baseArgs)).toBe(PROFILE_LABEL);
|
|
});
|
|
|
|
it("falls back to currentModelId when active model id is missing", () => {
|
|
expect(
|
|
resolveSessionTabTitle({
|
|
...baseArgs,
|
|
agentLabel: null,
|
|
currentModelId: SPARK_MODEL_ID,
|
|
modelOptions: [{ id: SPARK_MODEL_ID, name: SPARK_MODEL_NAME }],
|
|
}),
|
|
).toBe(SPARK_MODEL_NAME);
|
|
});
|
|
|
|
it("falls back to snapshot model when both agent and live model states are unavailable", () => {
|
|
expect(
|
|
resolveSessionTabTitle({
|
|
...baseArgs,
|
|
agentLabel: null,
|
|
snapshotModel: SPARK_MODEL_ID,
|
|
modelOptions: [{ id: SPARK_MODEL_ID, name: SPARK_MODEL_NAME }],
|
|
}),
|
|
).toBe(SPARK_MODEL_NAME);
|
|
});
|
|
|
|
it("keeps the agent label over the start-time snapshot model", () => {
|
|
expect(
|
|
resolveSessionTabTitle({
|
|
...baseArgs,
|
|
snapshotModel: "gpt-5.5",
|
|
}),
|
|
).toBe(PROFILE_LABEL);
|
|
});
|
|
});
|