sync: Agent Architecture Overhaul - AI SDK 6 + workflow optimization

Synced from private repo (feature/ai-sdk-6-upgrade):
- Upgrade AI SDK 5 → 6 (packages + API changes)
- Add sqliteQuery tool for flexible read-only queries
- New WorkflowExecutor with workflow-specific tools
- 83% token reduction for workflow execution
- Remove mini-rah dead code (simplified architecture)
- Add context management usage endpoint
- Fix Connect workflow instructions
- Clickable node references in workflow UI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
“BeeRad”
2026-01-12 20:59:27 +11:00
co-authored by Claude Opus 4.5
parent f9271aeeb4
commit 3f0426ecf4
27 changed files with 3607 additions and 2279 deletions
+15 -6
View File
@@ -7,6 +7,7 @@ import QuickAddStatus from './QuickAddStatus';
import { Zap, Flame, Minimize2 } from 'lucide-react';
import type { AgentDelegation } from '@/services/agents/delegation';
import { Node } from '@/types/database';
import { parseAndRenderContent } from '@/components/helpers/NodeLabelRenderer';
interface AgentsPanelProps {
openTabsData: Node[];
@@ -509,6 +510,7 @@ export default function AgentsPanel({ openTabsData, activeTabId, activeDimension
return rest;
});
}}
onNodeClick={onNodeClick}
/>
</div>
)}
@@ -522,6 +524,7 @@ export default function AgentsPanel({ openTabsData, activeTabId, activeDimension
<DelegationSummaryView
delegation={selectedDelegation}
onBack={() => setActiveAgentTab('workflows')}
onNodeClick={onNodeClick}
/>
) : (
<DelegationDetailView
@@ -647,7 +650,7 @@ export default function AgentsPanel({ openTabsData, activeTabId, activeDimension
}
// Summary view for completed delegations with no messages
function DelegationSummaryView({ delegation, onBack }: { delegation: AgentDelegation; onBack?: () => void }) {
function DelegationSummaryView({ delegation, onBack, onNodeClick }: { delegation: AgentDelegation; onBack?: () => void; onNodeClick?: (nodeId: number) => void }) {
const isSuccess = delegation.status === 'completed';
const statusColor = isSuccess ? '#22c55e' : '#ff6b6b';
const statusLabel = isSuccess ? 'Completed' : 'Failed';
@@ -748,7 +751,7 @@ function DelegationSummaryView({ delegation, onBack }: { delegation: AgentDelega
lineHeight: '1.6',
whiteSpace: 'pre-wrap'
}}>
{delegation.summary}
{parseAndRenderContent(delegation.summary || '', onNodeClick)}
</div>
</div>
)}
@@ -771,11 +774,13 @@ function DelegationSummaryView({ delegation, onBack }: { delegation: AgentDelega
function WorkflowsListView({
delegations,
onSelectDelegation,
onDeleteDelegation
onDeleteDelegation,
onNodeClick
}: {
delegations: AgentDelegation[];
onSelectDelegation: (sessionId: string) => void;
onDeleteDelegation: (sessionId: string) => void;
onNodeClick?: (nodeId: number) => void;
}) {
const activeDelegations = delegations.filter(d => d.status === 'queued' || d.status === 'in_progress');
const completedDelegations = delegations.filter(d => d.status === 'completed' || d.status === 'failed');
@@ -863,6 +868,7 @@ function WorkflowsListView({
statusLabel={label}
onSelect={() => onSelectDelegation(delegation.sessionId)}
onDelete={() => onDeleteDelegation(delegation.sessionId)}
onNodeClick={onNodeClick}
/>
);
})}
@@ -887,6 +893,7 @@ function WorkflowsListView({
statusLabel={label}
onSelect={() => onSelectDelegation(delegation.sessionId)}
onDelete={() => onDeleteDelegation(delegation.sessionId)}
onNodeClick={onNodeClick}
/>
);
})}
@@ -903,13 +910,15 @@ function WorkflowCard({
statusColor,
statusLabel,
onSelect,
onDelete
onDelete,
onNodeClick
}: {
delegation: AgentDelegation;
statusColor: string;
statusLabel: string;
onSelect: () => void;
onDelete: () => void;
onNodeClick?: (nodeId: number) => void;
}) {
const isActive = delegation.status === 'in_progress' || delegation.status === 'queued';
@@ -1009,7 +1018,7 @@ function WorkflowCard({
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
{delegation.summary}
{parseAndRenderContent(delegation.summary, onNodeClick)}
</div>
)}
</div>
@@ -1084,7 +1093,7 @@ function DelegationDetailView({
</div>
{/* RAHChat for streaming */}
<div style={{ flex: 1 }}>
<div style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
<RAHChat
openTabsData={openTabsData}
activeTabId={activeTabId}
+15 -1
View File
@@ -32,11 +32,17 @@ interface SendParams {
mode: 'easy' | 'hard';
}
interface UsageData {
inputTokens: number;
outputTokens: number;
}
interface UseSSEChatOptions {
getAuthToken?: () => string | null | undefined;
beforeRequest?: () => boolean;
onRequestError?: (error: unknown, response?: Response) => boolean | void;
onStreamComplete?: () => void | Promise<void>;
onUsageUpdate?: (usage: UsageData) => void;
}
export function useSSEChat(
@@ -44,7 +50,7 @@ export function useSSEChat(
setMessages: (updater: (prev: ChatMessage[]) => ChatMessage[]) => void,
options: UseSSEChatOptions = {}
) {
const { getAuthToken, beforeRequest, onRequestError, onStreamComplete } = options;
const { getAuthToken, beforeRequest, onRequestError, onStreamComplete, onUsageUpdate } = options;
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const abortControllerRef = useRef<AbortController | null>(null);
@@ -182,6 +188,14 @@ export function useSSEChat(
setMessages((prev) => [...prev, newAssistantMessage]);
}
}
// Capture usage data from finish event (if AI SDK sends it)
if (data.type === 'finish' && data.usage) {
onUsageUpdate?.({
inputTokens: data.usage.promptTokens ?? data.usage.inputTokens ?? 0,
outputTokens: data.usage.completionTokens ?? data.usage.outputTokens ?? 0
});
}
} catch {
// ignore malformed lines
}
+1 -1
View File
@@ -45,7 +45,7 @@ function NodeLabel({ id, title, dimensions, onNodeClick }: NodeLabelProps) {
verticalAlign: 'baseline'
}}
>
#{id}
{id}
</span>
{/* Non-clickable title - bold and underlined */}
<span style={{