feat: auto-delete old conversations, and stop a tool call that repeats (#15)

This commit was merged in pull request #15.
This commit is contained in:
2026-10-05 09:05:15 +00:00
parent 576c5946f7
commit 05fd9ff13b
25 changed files with 2372 additions and 59 deletions
+32
View File
@@ -35,6 +35,19 @@ export interface UseChatOptions {
initialSession?: Session;
}
/** Which tool is running right now, and how stale it is getting. */
export interface ToolActivity {
/** Human-readable label, e.g. "Searching the web". */
label: string;
/**
* How many times this turn has now started this *identical* call.
*
* Counted by the agent over name and arguments together, so two different
* searches are two tools doing their job rather than a repeat.
*/
count: number;
}
export interface UseChat {
session: Session;
isStreaming: boolean;
@@ -42,6 +55,15 @@ export interface UseChat {
streamingThinking: string;
/** Short activity line such as "Searching the web", shown while a turn is in flight. */
status?: string;
/**
* The tool running right now.
*
* Separate from {@link UseChat.status} because `status` goes quiet as soon as
* the answer starts arriving, and the whole point of this one is to stay on
* screen: a model that has started circling looks identical to a model that is
* working, unless the tool and its repeat count stay visible.
*/
activity?: ToolActivity;
error?: string;
ask: (prompt: string, attachment?: PendingAttachment) => void;
cancel: () => void;
@@ -58,6 +80,7 @@ export function useChat(options: UseChatOptions): UseChat {
const [streamingResponse, setStreamingResponse] = useState("");
const [streamingThinking, setStreamingThinking] = useState("");
const [status, setStatus] = useState<string | undefined>(undefined);
const [activity, setActivity] = useState<ToolActivity | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const sessionRef = useRef(session);
@@ -149,6 +172,7 @@ export function useChat(options: UseChatOptions): UseChat {
setStreamingResponse("");
setStreamingThinking("");
setError(undefined);
setActivity(undefined);
setStatusValue("Thinking");
liveRef.current = true;
setIsStreaming(true);
@@ -173,6 +197,10 @@ export function useChat(options: UseChatOptions): UseChat {
apiKey: preferences.apiKey,
model: preferences.model,
think: preferences.think,
// `undefined` is "unlimited" in the preferences, and `null` is what the
// agent reads as "no cap": an omitted option there means the default
// cap, so the two conventions meet here.
maxToolRounds: preferences.maxToolRounds ?? null,
messages,
tools: toolsFor({ webEnabled: preferences.webEnabled }),
toolContext: {
@@ -195,6 +223,7 @@ export function useChat(options: UseChatOptions): UseChat {
scheduleFlush();
},
onStatus: (value) => setStatusValue(value),
onToolStart: (_name, label, count) => setActivity({ label, count }),
onToolCall: (record) => {
toolCalls = [...toolCalls, record];
},
@@ -232,6 +261,7 @@ export function useChat(options: UseChatOptions): UseChat {
commit(updated);
setIsStreaming(false);
setStatusValue(undefined);
setActivity(undefined);
setError(failure);
flushNow();
@@ -249,6 +279,7 @@ export function useChat(options: UseChatOptions): UseChat {
liveRef.current = false;
setIsStreaming(false);
setStatusValue(undefined);
setActivity(undefined);
setError(undefined);
setStreamingResponse("");
setStreamingThinking("");
@@ -274,6 +305,7 @@ export function useChat(options: UseChatOptions): UseChat {
streamingResponse,
streamingThinking,
status,
activity,
error,
ask,
cancel,