generated from nhcarrigan/template
a4e6788573
## Summary This PR bundles a collection of new features and quality-of-life improvements identified during a Claude CLI 2.1.50 audit. - **Tab status indicator** — Tab stays yellow until the greeting is responded to, then turns green. Fixed disconnect not resetting to grey. Closes #157 - **Auth status display** — New "Account" section in settings sidebar showing login status, email, org, API key source, and Hikari override indicator. Includes login/logout buttons. Closes #153 - **CLI version badge** — New "Supported" badge showing the highest audited CLI version, colour-coded green/amber/red based on installed vs supported version. Closes #154 (bump to 2.1.50) - **Rate limit events** — `rate_limit_event` messages from the stream are now parsed and shown as amber `[rate-limit]` lines in the terminal instead of being silently dropped. Closes #155 - **"Prompt is too long" handling** — Detects this error in assistant messages and shows a ⚡ Compact Conversation button to send `/compact` directly. Closes #158 - **`last_assistant_message` in Agent Monitor** — Extracts the agent's final output from the `ToolResult` content block in the JSON stream and displays it as a snippet on completed agent cards. Closes #156 - **`--worktree` flag** — New "Worktree isolation" toggle in session settings passes `--worktree` to Claude Code. Hook events (`WorktreeCreate`/`WorktreeRemove`) are displayed as green `[worktree]` lines. Closes #152, Closes #150 - **ConfigChange hook events** — `[ConfigChange Hook]` stderr events are now displayed as cyan `[config]` lines instead of errors. Closes #151 - **`CLAUDE_CODE_DISABLE_1M_CONTEXT` toggle** — New "Disable 1M context" setting in session configuration injects this env var into the Claude process. Closes #154 ## Test plan - [ ] Tab status indicator: start a new session and verify the tab stays yellow until Claude responds to the greeting, then turns green - [ ] Auth status: open settings and verify the Account section shows correct login info - [ ] CLI version badge: verify the "Supported 2.1.50" badge shows green when CLI matches - [ ] Rate limit events: unit tests cover parsing; amber `[rate-limit]` lines display correctly - [ ] Compact button: unit tests cover detection; button renders correctly in terminal - [ ] Agent Monitor: use the Task tool and verify completed agent cards show a message snippet - [ ] Worktree: enable toggle, start session, verify `--worktree` flag appears in process args - [ ] ConfigChange: hook events display as `[config]` lines rather than errors - [ ] Disable 1M context: enable toggle, start session, verify `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` in `/proc/<pid>/environ` ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #159 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
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[]>>({});
|
|
|
|
function createAgentStore() {
|
|
return {
|
|
subscribe: agentsByConversation.subscribe,
|
|
|
|
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, characterName: character.name, characterAvatar: character.avatar },
|
|
],
|
|
};
|
|
});
|
|
},
|
|
|
|
updateAgentId(conversationId: string, toolUseId: string, agentId: string) {
|
|
agentsByConversation.update((state) => {
|
|
const agents = state[conversationId];
|
|
if (!agents) return state;
|
|
|
|
const agentIndex = agents.findIndex((a) => a.toolUseId === toolUseId);
|
|
if (agentIndex === -1) return state;
|
|
|
|
const updated = [...agents];
|
|
updated[agentIndex] = {
|
|
...updated[agentIndex],
|
|
agentId,
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
[conversationId]: updated,
|
|
};
|
|
});
|
|
},
|
|
|
|
endAgent(
|
|
conversationId: string,
|
|
toolUseId: string,
|
|
endedAt: number,
|
|
isError: boolean,
|
|
lastAssistantMessage?: string
|
|
) {
|
|
agentsByConversation.update((state) => {
|
|
const agents = state[conversationId];
|
|
if (!agents) return state;
|
|
|
|
const agentIndex = agents.findIndex((a) => a.toolUseId === toolUseId);
|
|
if (agentIndex === -1) return state;
|
|
|
|
const updated = [...agents];
|
|
const agent = updated[agentIndex];
|
|
const durationMs = endedAt - agent.startedAt;
|
|
|
|
updated[agentIndex] = {
|
|
...agent,
|
|
endedAt,
|
|
status: isError ? "errored" : "completed",
|
|
durationMs,
|
|
lastAssistantMessage,
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
[conversationId]: updated,
|
|
};
|
|
});
|
|
},
|
|
|
|
markAllErrored(conversationId: string) {
|
|
agentsByConversation.update((state) => {
|
|
const agents = state[conversationId];
|
|
if (!agents) return state;
|
|
|
|
const now = Date.now();
|
|
const updated = agents.map((agent) =>
|
|
agent.status === "running"
|
|
? { ...agent, endedAt: now, status: "errored" as const }
|
|
: agent
|
|
);
|
|
|
|
return {
|
|
...state,
|
|
[conversationId]: updated,
|
|
};
|
|
});
|
|
},
|
|
|
|
clearCompleted(conversationId: string) {
|
|
agentsByConversation.update((state) => {
|
|
const agents = state[conversationId];
|
|
if (!agents) return state;
|
|
|
|
return {
|
|
...state,
|
|
[conversationId]: agents.filter((a) => a.status === "running"),
|
|
};
|
|
});
|
|
},
|
|
|
|
clearConversation(conversationId: string) {
|
|
agentsByConversation.update((state) => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Unused destructured value
|
|
const { [conversationId]: _, ...rest } = state;
|
|
return rest;
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
export const agentStore = createAgentStore();
|
|
|
|
export function getAgentsForConversation(conversationId: string) {
|
|
return derived(agentsByConversation, ($state) => $state[conversationId] || []);
|
|
}
|
|
|
|
export const runningAgentCount = derived(agentsByConversation, ($state) => {
|
|
let count = 0;
|
|
for (const agents of Object.values($state)) {
|
|
count += agents.filter((a) => a.status === "running").length;
|
|
}
|
|
return count;
|
|
});
|