generated from nhcarrigan/template
feat: agent monitor characters, cast panel, WSL fixes, and Sonnet 4.6 (#149)
## Summary ### New Features - **Claude Sonnet 4.6 support** — added `claude-sonnet-4-6` as a selectable model in the config sidebar - **Anime girl characters for subagents** — each subagent in the agent monitor is automatically assigned one of six characters (Amari, Keiko, Minori, Reina, Tatsumi, Yumiko) with a unique name, CDN avatar, title, and lore-flavoured description; assignment avoids duplicates when possible - **"Meet the Team" cast panel** — a new modal accessible from the status bar introduces the full cast: Naomi (Chief hEx-ecutive Officer), Hikari (Chief Operating Officer), and the six subagent girls with their C-suite titles and character bios ### Bug Fixes - **"Already running" error on invalid working directory** — if a spawned Claude process exits unexpectedly (e.g. because the working directory doesn't exist), `try_wait()` now detects the stale handle and clears it before allowing a restart - **Working directory pre-validation** — on Windows, the app now runs `wsl -e test -d <dir>` before launching Claude; invalid directories surface a clear error immediately - **WSL binary detection** — on Windows, `wsl -e bash -lc "which claude"` is used to probe for the Claude binary inside WSL; on Linux/WSLg, `bash -lc "which claude"` is used as a login-shell fallback so GUI apps find the binary even without shell PATH - **WSL detection fix for production builds** — `detect_wsl()` now short-circuits at compile time on Windows targets, preventing inherited `WSL_DISTRO_NAME` env vars from misrouting native Windows binaries through the Linux code path ✨ This PR was crafted with love by Hikari~ 🌸 Reviewed-on: #149 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #149.
This commit is contained in:
@@ -2,12 +2,15 @@ import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { agentStore, getAgentsForConversation, runningAgentCount } from "./agents";
|
||||
import { get } from "svelte/store";
|
||||
import type { AgentInfo } from "$lib/types/agents";
|
||||
import { CHARACTER_POOL } from "$lib/utils/agentCharacters";
|
||||
|
||||
describe("agents store", () => {
|
||||
const conversationId = "test-conversation-1";
|
||||
const otherConversationId = "test-conversation-2";
|
||||
|
||||
const createMockAgent = (overrides?: Partial<AgentInfo>): AgentInfo => ({
|
||||
type AgentInput = Omit<AgentInfo, "characterName" | "characterAvatar">;
|
||||
|
||||
const createMockAgent = (overrides?: Partial<AgentInput>): AgentInput => ({
|
||||
toolUseId: "toolu_test123",
|
||||
description: "Test agent",
|
||||
subagentType: "Explore",
|
||||
@@ -37,7 +40,29 @@ describe("agents store", () => {
|
||||
|
||||
const agents = get(getAgentsForConversation(conversationId));
|
||||
expect(agents).toHaveLength(1);
|
||||
expect(agents[0]).toEqual(agent);
|
||||
expect(agents[0]).toMatchObject(agent);
|
||||
});
|
||||
|
||||
it("assigns a character name and avatar to added agents", () => {
|
||||
const agent = createMockAgent();
|
||||
agentStore.addAgent(conversationId, agent);
|
||||
|
||||
const agents = get(getAgentsForConversation(conversationId));
|
||||
const validNames = CHARACTER_POOL.map((c) => c.name);
|
||||
expect(validNames).toContain(agents[0].characterName);
|
||||
expect(agents[0].characterAvatar).toMatch(/^https:\/\//u);
|
||||
});
|
||||
|
||||
it("avoids duplicate character names across agents when possible", () => {
|
||||
// Add 6 agents - each should ideally get a unique character
|
||||
for (let i = 0; i < 6; i++) {
|
||||
agentStore.addAgent(conversationId, createMockAgent({ toolUseId: `tool${i.toString()}` }));
|
||||
}
|
||||
|
||||
const agents = get(getAgentsForConversation(conversationId));
|
||||
const names = agents.map((a) => a.characterName);
|
||||
const uniqueNames = new Set(names);
|
||||
expect(uniqueNames.size).toBe(6);
|
||||
});
|
||||
|
||||
it("adds multiple agents to the same conversation", () => {
|
||||
@@ -49,8 +74,8 @@ describe("agents store", () => {
|
||||
|
||||
const agents = get(getAgentsForConversation(conversationId));
|
||||
expect(agents).toHaveLength(2);
|
||||
expect(agents[0]).toEqual(agent1);
|
||||
expect(agents[1]).toEqual(agent2);
|
||||
expect(agents[0]).toMatchObject(agent1);
|
||||
expect(agents[1]).toMatchObject(agent2);
|
||||
});
|
||||
|
||||
it("keeps agents in different conversations separate", () => {
|
||||
@@ -65,8 +90,8 @@ describe("agents store", () => {
|
||||
|
||||
expect(agents1).toHaveLength(1);
|
||||
expect(agents2).toHaveLength(1);
|
||||
expect(agents1[0]).toEqual(agent1);
|
||||
expect(agents2[0]).toEqual(agent2);
|
||||
expect(agents1[0]).toMatchObject(agent1);
|
||||
expect(agents2[0]).toMatchObject(agent2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,7 +281,7 @@ describe("agents store", () => {
|
||||
|
||||
expect(agents1).toHaveLength(0);
|
||||
expect(agents2).toHaveLength(1);
|
||||
expect(agents2[0]).toEqual(agent2);
|
||||
expect(agents2[0]).toMatchObject(agent2);
|
||||
});
|
||||
|
||||
it("does nothing if conversation doesn't exist", () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { writable, derived } from "svelte/store";
|
||||
import type { AgentInfo } from "$lib/types/agents";
|
||||
import { assignCharacter } from "$lib/utils/agentCharacters";
|
||||
|
||||
// Map of conversation ID -> agents in that conversation
|
||||
const agentsByConversation = writable<Record<string, AgentInfo[]>>({});
|
||||
@@ -8,12 +9,17 @@ function createAgentStore() {
|
||||
return {
|
||||
subscribe: agentsByConversation.subscribe,
|
||||
|
||||
addAgent(conversationId: string, agent: AgentInfo) {
|
||||
addAgent(conversationId: string, agent: Omit<AgentInfo, "characterName" | "characterAvatar">) {
|
||||
agentsByConversation.update((state) => {
|
||||
const existing = state[conversationId] || [];
|
||||
const activeNames = existing.map((a) => a.characterName);
|
||||
const character = assignCharacter(activeNames);
|
||||
return {
|
||||
...state,
|
||||
[conversationId]: [...existing, agent],
|
||||
[conversationId]: [
|
||||
...existing,
|
||||
{ ...agent, characterName: character.name, characterAvatar: character.avatar },
|
||||
],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ export type BudgetType = "token" | "cost";
|
||||
export const MODEL_PRICING: Record<string, { input: number; output: number }> = {
|
||||
// Current generation (Claude 4.6)
|
||||
"claude-opus-4-6": { input: 5.0, output: 25.0 },
|
||||
"claude-sonnet-4-6": { input: 3.0, output: 15.0 },
|
||||
// Previous generation (Claude 4.5)
|
||||
"claude-opus-4-5-20251101": { input: 5.0, output: 25.0 },
|
||||
"claude-sonnet-4-5-20250929": { input: 3.0, output: 15.0 },
|
||||
|
||||
Reference in New Issue
Block a user