summaryrefslogtreecommitdiffhomepage
path: root/packages/frontend/src/lib
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-23 16:59:20 +0900
committerAdam Malczewski <[email protected]>2026-05-23 16:59:20 +0900
commit236beefb708a6cd91b673978ddf4ebf045a9844c (patch)
tree6522c65a0d490b41cc01297f2444f160f8dbb7f8 /packages/frontend/src/lib
parent225d3ea65cfc35d211fc66e30cf05cbc693d37e4 (diff)
downloaddispatch-236beefb708a6cd91b673978ddf4ebf045a9844c.tar.gz
dispatch-236beefb708a6cd91b673978ddf4ebf045a9844c.zip
feat: key fallback using agent models[] hierarchy, background tool modes, copy truncation
- Agent rate-limit fallback now iterates through agent's configured models[] in strict order - Frontend sends agentModels with each /chat request; backend uses buildFallbackSequence() - Emits notice event on fallback so chat shows which key failed and what's being tried next - Child agents inherit parent's agentModels for fallback - Added statusCode propagation from AI SDK errors for programmatic 429 detection - Copy button truncates all tool results at 300 chars (was 200 for 4 specific tools) - run_shell, summon, youtube_transcribe: background mode support - summon: blocking mode by default with getResult callback
Diffstat (limited to 'packages/frontend/src/lib')
-rw-r--r--packages/frontend/src/lib/components/Header.svelte2
-rw-r--r--packages/frontend/src/lib/tabs.svelte.ts47
-rw-r--r--packages/frontend/src/lib/types.ts2
3 files changed, 48 insertions, 3 deletions
diff --git a/packages/frontend/src/lib/components/Header.svelte b/packages/frontend/src/lib/components/Header.svelte
index 06b8ac1..713e916 100644
--- a/packages/frontend/src/lib/components/Header.svelte
+++ b/packages/frontend/src/lib/components/Header.svelte
@@ -26,7 +26,7 @@ async function handleCopy() {
}
</script>
-<header class="navbar bg-base-200 border-b border-base-300 px-4 min-h-14 flex-shrink-0">
+<header class="navbar bg-base-200 px-4 min-h-14 flex-shrink-0">
<div class="navbar-start">
<button class="text-xl font-bold tracking-tight btn btn-ghost px-0" onclick={() => router.navigate("dashboard")}>Dispatch</button>
</div>
diff --git a/packages/frontend/src/lib/tabs.svelte.ts b/packages/frontend/src/lib/tabs.svelte.ts
index c488a92..a48be64 100644
--- a/packages/frontend/src/lib/tabs.svelte.ts
+++ b/packages/frontend/src/lib/tabs.svelte.ts
@@ -50,6 +50,8 @@ export interface Tab {
agentSlug: string | null;
/** Scope of the selected agent */
agentScope: string | null;
+ /** Ordered key+model fallback hierarchy from the selected agent */
+ agentModels: Array<{ key_id: string; model_id: string }> | null;
/** Custom working directory override for this tab */
workingDirectory: string | null;
/** Messages queued to be sent once the agent finishes its current run */
@@ -118,6 +120,7 @@ function createTabStore() {
persistent: true,
agentSlug: null,
agentScope: null,
+ agentModels: null,
workingDirectory: null,
queuedMessages: [],
};
@@ -210,6 +213,7 @@ function createTabStore() {
persistent: true,
agentSlug: null,
agentScope: null,
+ agentModels: null,
workingDirectory: null,
queuedMessages: [],
};
@@ -408,6 +412,25 @@ function createTabStore() {
}
break;
}
+ case "notice": {
+ if (tabId) {
+ const noticeMsg: ChatMessage = {
+ id: generateId(),
+ role: "assistant",
+ content: [{ type: "text", text: event.message }],
+ isStreaming: false,
+ debugInfo: makeDebugInfo({ notice: event.message }),
+ };
+ const tabN = getTabById(tabId);
+ if (tabN) {
+ updateTab(tabId, {
+ messages: [...tabN.messages, noticeMsg],
+ currentAssistantId: null,
+ });
+ }
+ }
+ break;
+ }
case "permission-prompt": {
pendingPermissions = event.pending;
break;
@@ -481,6 +504,7 @@ function createTabStore() {
persistent: newTabEvent.parentTabId == null,
agentSlug: null,
agentScope: null,
+ agentModels: null,
workingDirectory: newTabEvent.workingDirectory ?? null,
queuedMessages: [],
};
@@ -644,6 +668,7 @@ function createTabStore() {
const patch: Partial<Tab> = {
agentSlug: defaultAgent.slug,
agentScope: defaultAgent.scope,
+ agentModels: defaultAgent.models,
workingDirectory: defaultAgent.cwd || null,
};
if (firstModel) {
@@ -805,6 +830,7 @@ function createTabStore() {
message: messageToSend,
...(tab.keyId ? { keyId: tab.keyId } : {}),
...(tab.modelId ? { modelId: tab.modelId } : {}),
+ ...(tab.agentModels ? { agentModels: tab.agentModels } : {}),
reasoningEffort: tab.reasoningEffort,
...(tab.workingDirectory ? { workingDirectory: tab.workingDirectory } : {}),
...(queueId ? { queueId } : {}),
@@ -935,7 +961,12 @@ function createTabStore() {
if (!agent) {
// Switch back to manual mode — clear agent and reset working directory
- updateTab(tab.id, { agentSlug: null, agentScope: null, workingDirectory: null });
+ updateTab(tab.id, {
+ agentSlug: null,
+ agentScope: null,
+ agentModels: null,
+ workingDirectory: null,
+ });
return;
}
@@ -944,6 +975,7 @@ function createTabStore() {
const patch: Partial<Tab> = {
agentSlug: agent.slug,
agentScope: agent.scope,
+ agentModels: agent.models,
workingDirectory: agent.cwd || null,
};
if (firstModel) {
@@ -1039,6 +1071,8 @@ function createTabStore() {
`All tab IDs: ${tabs.map((t) => t.id).join(", ")}`,
"",
];
+ const TOOL_RESULT_MAX = 300;
+
for (const msg of tab.messages) {
const role = msg.role === "user" ? "User" : msg.role === "system" ? "System" : "Assistant";
lines.push(`--- ${role} ---`);
@@ -1047,7 +1081,16 @@ function createTabStore() {
if (seg.type === "text") lines.push(seg.text);
else if (seg.type === "tool-call") {
lines.push(` [Tool: ${seg.name}]`);
- if (seg.result !== undefined) lines.push(` Result: ${seg.result}`);
+ if (seg.result !== undefined) {
+ const result = String(seg.result);
+ if (result.length > TOOL_RESULT_MAX) {
+ lines.push(
+ ` Result: ${result.slice(0, TOOL_RESULT_MAX)}... [truncated, ${result.length} chars total]`,
+ );
+ } else {
+ lines.push(` Result: ${result}`);
+ }
+ }
}
}
lines.push("");
diff --git a/packages/frontend/src/lib/types.ts b/packages/frontend/src/lib/types.ts
index 79b2867..95ffb2b 100644
--- a/packages/frontend/src/lib/types.ts
+++ b/packages/frontend/src/lib/types.ts
@@ -10,6 +10,7 @@ export interface ToolCallDisplay {
export interface DebugInfo {
timestamp: string;
error?: string;
+ notice?: string;
model?: string;
apiBase?: string;
connectionStatus?: string;
@@ -51,6 +52,7 @@ export type AgentEvent =
toolResult: { toolCallId: string; result: string; isError: boolean };
}
| { type: "error"; error: string }
+ | { type: "notice"; message: string }
| { type: "task-list-update"; tasks: TaskItem[] }
| { type: "config-reload" }
| {