feat(rah-light): remove agent delegation system
- Delete src/services/agents/delegation.ts (AgentDelegationService) - Delete app/api/rah/delegations/ directory (all routes) - Delete src/components/agents/DelegationIndicator.tsx - Update workflowExecutor.ts to work without delegation streaming - Update executeWorkflow.ts tool to execute workflows directly - Update quickAdd.ts to execute directly without delegation tracking - Add stub AgentDelegation types to UI components for compatibility - Add stub voice hooks to RAHChat.tsx (will be removed in story 2) Files deleted: - src/services/agents/delegation.ts - app/api/rah/delegations/route.ts - app/api/rah/delegations/stream/route.ts - app/api/rah/delegations/[sessionId]/route.ts - app/api/rah/delegations/[sessionId]/summary/route.ts - src/components/agents/DelegationIndicator.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
08012a8e5a
commit
6b7bb5a5f9
@@ -1,70 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
_request: NextRequest,
|
|
||||||
{ params }: { params: Promise<{ sessionId: string }> }
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const { sessionId } = await params;
|
|
||||||
const delegation = AgentDelegationService.getBySessionId(sessionId);
|
|
||||||
if (!delegation) {
|
|
||||||
return NextResponse.json({ error: 'Delegation not found' }, { status: 404 });
|
|
||||||
}
|
|
||||||
return NextResponse.json({ delegation });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch delegation:', error);
|
|
||||||
return NextResponse.json({ error: 'Failed to fetch delegation' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function PATCH(
|
|
||||||
request: NextRequest,
|
|
||||||
{ params }: { params: Promise<{ sessionId: string }> }
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const { sessionId } = await params;
|
|
||||||
const body = await request.json();
|
|
||||||
const summary: string | undefined = body?.summary;
|
|
||||||
const status: string | undefined = body?.status;
|
|
||||||
|
|
||||||
if (!summary && !status) {
|
|
||||||
return NextResponse.json({ error: 'Nothing to update' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedStatus = status && ['queued', 'in_progress', 'completed', 'failed'].includes(status)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
? (status as any)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const delegation = summary
|
|
||||||
? AgentDelegationService.completeDelegation(sessionId, summary, normalizedStatus ?? 'completed')
|
|
||||||
: AgentDelegationService.markInProgress(sessionId);
|
|
||||||
|
|
||||||
if (!delegation) {
|
|
||||||
return NextResponse.json({ error: 'Delegation not found' }, { status: 404 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ delegation });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to update delegation:', error);
|
|
||||||
return NextResponse.json({ error: 'Failed to update delegation' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function DELETE(
|
|
||||||
_request: NextRequest,
|
|
||||||
{ params }: { params: Promise<{ sessionId: string }> }
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const { sessionId } = await params;
|
|
||||||
const deleted = AgentDelegationService.deleteDelegation(sessionId);
|
|
||||||
if (!deleted) {
|
|
||||||
return NextResponse.json({ error: 'Delegation not found' }, { status: 404 });
|
|
||||||
}
|
|
||||||
return NextResponse.json({ success: true });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to delete delegation:', error);
|
|
||||||
return NextResponse.json({ error: 'Failed to delete delegation' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
|
|
||||||
export async function POST(
|
|
||||||
request: NextRequest,
|
|
||||||
{ params }: { params: Promise<{ sessionId: string }> }
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const { sessionId } = await params;
|
|
||||||
const body = await request.json();
|
|
||||||
const summary: string | undefined = body?.summary;
|
|
||||||
const status: string | undefined = body?.status;
|
|
||||||
|
|
||||||
if (!summary) {
|
|
||||||
return NextResponse.json({ error: 'Summary is required' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedStatus = status && ['queued', 'in_progress', 'completed', 'failed'].includes(status)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
? (status as any)
|
|
||||||
: 'completed';
|
|
||||||
|
|
||||||
const delegation = AgentDelegationService.completeDelegation(sessionId, summary, normalizedStatus);
|
|
||||||
if (!delegation) {
|
|
||||||
return NextResponse.json({ error: 'Delegation not found' }, { status: 404 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ delegation });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to store delegation summary:', error);
|
|
||||||
return NextResponse.json({ error: 'Failed to store delegation summary' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const { searchParams } = new URL(request.url);
|
|
||||||
const statusFilter = searchParams.get('status');
|
|
||||||
|
|
||||||
const includeCompleted = searchParams.get('includeCompleted') === 'true'
|
|
||||||
|| statusFilter !== 'active';
|
|
||||||
const delegations = statusFilter === 'active'
|
|
||||||
? AgentDelegationService.listActive({ includeCompleted })
|
|
||||||
: AgentDelegationService.listRecent();
|
|
||||||
|
|
||||||
return NextResponse.json({ delegations });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to list delegations:', error);
|
|
||||||
return NextResponse.json({ error: 'Failed to load delegations' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
import { NextRequest } from 'next/server';
|
|
||||||
|
|
||||||
export const runtime = 'nodejs';
|
|
||||||
export const maxDuration = 900;
|
|
||||||
|
|
||||||
class DelegationStreamBroadcaster {
|
|
||||||
private connections = new Map<string, Set<ReadableStreamDefaultController>>();
|
|
||||||
private pendingMessages = new Map<string, any[]>();
|
|
||||||
private encoder = new TextEncoder();
|
|
||||||
|
|
||||||
private encode(message: any) {
|
|
||||||
return `data: ${JSON.stringify({ ...message, timestamp: Date.now() })}\n\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private send(controller: ReadableStreamDefaultController, encoded: string) {
|
|
||||||
try {
|
|
||||||
controller.enqueue(this.encoder.encode(encoded));
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('[DelegationStream] Removing dead connection', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addConnection(sessionId: string, controller: ReadableStreamDefaultController) {
|
|
||||||
if (!this.connections.has(sessionId)) {
|
|
||||||
this.connections.set(sessionId, new Set());
|
|
||||||
}
|
|
||||||
this.connections.get(sessionId)!.add(controller);
|
|
||||||
console.log(`[DelegationStream] Connection added for ${sessionId}, total: ${this.connections.get(sessionId)!.size}`);
|
|
||||||
|
|
||||||
const backlog = this.pendingMessages.get(sessionId);
|
|
||||||
if (backlog && backlog.length > 0) {
|
|
||||||
console.log(`[DelegationStream] Flushing ${backlog.length} queued events for ${sessionId}`);
|
|
||||||
for (const message of backlog) {
|
|
||||||
const encoded = this.encode(message);
|
|
||||||
const delivered = this.send(controller, encoded);
|
|
||||||
if (!delivered) {
|
|
||||||
this.removeConnection(sessionId, controller);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((this.connections.get(sessionId)?.size || 0) > 0) {
|
|
||||||
this.pendingMessages.delete(sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeConnection(sessionId: string, controller: ReadableStreamDefaultController) {
|
|
||||||
const sessionConns = this.connections.get(sessionId);
|
|
||||||
if (sessionConns) {
|
|
||||||
sessionConns.delete(controller);
|
|
||||||
console.log(`[DelegationStream] Connection removed from ${sessionId}, remaining: ${sessionConns.size}`);
|
|
||||||
if (sessionConns.size === 0) {
|
|
||||||
this.connections.delete(sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(sessionId: string, message: any) {
|
|
||||||
const sessionConns = this.connections.get(sessionId);
|
|
||||||
if (!sessionConns || sessionConns.size === 0) {
|
|
||||||
const queue = this.pendingMessages.get(sessionId) ?? [];
|
|
||||||
queue.push(message);
|
|
||||||
// Prevent unbounded growth by keeping the latest 200 events
|
|
||||||
if (queue.length > 200) {
|
|
||||||
queue.splice(0, queue.length - 200);
|
|
||||||
}
|
|
||||||
this.pendingMessages.set(sessionId, queue);
|
|
||||||
console.log(`[DelegationStream] Queued event for ${sessionId}, pending=${queue.length}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const encoded = this.encode(message);
|
|
||||||
|
|
||||||
let successCount = 0;
|
|
||||||
const staleControllers: ReadableStreamDefaultController[] = [];
|
|
||||||
for (const controller of sessionConns) {
|
|
||||||
if (this.send(controller, encoded)) {
|
|
||||||
successCount++;
|
|
||||||
} else {
|
|
||||||
staleControllers.push(controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (staleControllers.length > 0) {
|
|
||||||
staleControllers.forEach((controller) => sessionConns.delete(controller));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[DelegationStream] Broadcasted to ${successCount}/${sessionConns.size} connections for ${sessionId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendKeepAlive(sessionId: string) {
|
|
||||||
const sessionConns = this.connections.get(sessionId);
|
|
||||||
if (!sessionConns) return;
|
|
||||||
|
|
||||||
const ping = this.encoder.encode(`: keep-alive\n\n`);
|
|
||||||
|
|
||||||
for (const controller of sessionConns) {
|
|
||||||
try {
|
|
||||||
controller.enqueue(ping);
|
|
||||||
} catch {
|
|
||||||
sessionConns.delete(controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var delegationStreamBroadcaster: DelegationStreamBroadcaster | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const delegationStreamBroadcaster =
|
|
||||||
globalThis.delegationStreamBroadcaster ?? new DelegationStreamBroadcaster();
|
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
globalThis.delegationStreamBroadcaster = delegationStreamBroadcaster;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
const { searchParams } = new URL(request.url);
|
|
||||||
const sessionId = searchParams.get('sessionId');
|
|
||||||
|
|
||||||
if (!sessionId) {
|
|
||||||
return new Response('Missing sessionId', { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
start(controller) {
|
|
||||||
const state = this as unknown as { cleanup?: () => void; abortHandler?: () => void };
|
|
||||||
delegationStreamBroadcaster.addConnection(sessionId, controller);
|
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'CONNECTION_ESTABLISHED', timestamp: Date.now() })}\n\n`));
|
|
||||||
|
|
||||||
const keepAliveInterval = setInterval(() => {
|
|
||||||
delegationStreamBroadcaster.sendKeepAlive(sessionId);
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
const cleanup = () => {
|
|
||||||
clearInterval(keepAliveInterval);
|
|
||||||
delegationStreamBroadcaster.removeConnection(sessionId, controller);
|
|
||||||
state.cleanup = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const abortHandler = () => {
|
|
||||||
cleanup();
|
|
||||||
request.signal.removeEventListener('abort', abortHandler);
|
|
||||||
state.abortHandler = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
request.signal.addEventListener('abort', abortHandler);
|
|
||||||
|
|
||||||
state.cleanup = cleanup;
|
|
||||||
state.abortHandler = abortHandler;
|
|
||||||
},
|
|
||||||
cancel() {
|
|
||||||
console.log(`[DelegationStream] Stream cancelled for ${sessionId}`);
|
|
||||||
const state = this as unknown as { cleanup?: () => void; abortHandler?: () => void };
|
|
||||||
if (state.abortHandler) {
|
|
||||||
request.signal.removeEventListener('abort', state.abortHandler);
|
|
||||||
state.abortHandler = undefined;
|
|
||||||
}
|
|
||||||
if (state.cleanup) {
|
|
||||||
state.cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(stream, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/event-stream',
|
|
||||||
'Cache-Control': 'no-cache, no-transform',
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'X-Accel-Buffering': 'no',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { WorkflowRegistry } from '@/services/workflows/registry';
|
import { WorkflowRegistry } from '@/services/workflows/registry';
|
||||||
import { getSQLiteClient } from '@/services/database/sqlite-client';
|
import { getSQLiteClient } from '@/services/database/sqlite-client';
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
import { WorkflowExecutor } from '@/services/agents/workflowExecutor';
|
import { WorkflowExecutor } from '@/services/agents/workflowExecutor';
|
||||||
import { getAutoContextSummaries } from '@/services/context/autoContext';
|
import { getAutoContextSummaries } from '@/services/context/autoContext';
|
||||||
|
|
||||||
@@ -107,35 +106,36 @@ ${workflow.instructions}
|
|||||||
|
|
||||||
${nodeId ? `Target Node ID: ${nodeId}` : 'No specific node targeted (general workflow)'}`;
|
${nodeId ? `Target Node ID: ${nodeId}` : 'No specific node targeted (general workflow)'}`;
|
||||||
|
|
||||||
// Create delegation
|
const sessionId = `workflow_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
const delegation = AgentDelegationService.createDelegation({
|
|
||||||
task,
|
|
||||||
context: contextLines,
|
|
||||||
expectedOutcome: workflow.expectedOutcome,
|
|
||||||
agentType: 'workflow',
|
|
||||||
supabaseToken: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fire-and-forget execution
|
try {
|
||||||
void WorkflowExecutor.execute({
|
// Execute workflow synchronously
|
||||||
sessionId: delegation.sessionId,
|
const result = await WorkflowExecutor.execute({
|
||||||
|
sessionId,
|
||||||
task,
|
task,
|
||||||
context: contextLines,
|
context: contextLines,
|
||||||
expectedOutcome: workflow.expectedOutcome,
|
expectedOutcome: workflow.expectedOutcome,
|
||||||
workflowKey,
|
workflowKey,
|
||||||
workflowNodeId: nodeId,
|
workflowNodeId: nodeId,
|
||||||
}).catch((error) => {
|
|
||||||
console.error('[/api/workflows/execute] Execution failed:', error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
delegationId: delegation.sessionId,
|
sessionId,
|
||||||
workflowKey,
|
workflowKey,
|
||||||
nodeId: nodeId || null,
|
nodeId: nodeId || null,
|
||||||
status: 'executing',
|
status: 'completed',
|
||||||
message: `Workflow '${workflow.displayName}' started`,
|
summary: result.summary,
|
||||||
|
message: `Workflow '${workflow.displayName}' completed`,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[/api/workflows/execute] Execution failed:', error);
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: `Workflow execution failed: ${errorMessage}` },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error executing workflow:', error);
|
console.error('Error executing workflow:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -5,10 +5,21 @@ import RAHChat from './RAHChat';
|
|||||||
import QuickAddInput from './QuickAddInput';
|
import QuickAddInput from './QuickAddInput';
|
||||||
import QuickAddStatus from './QuickAddStatus';
|
import QuickAddStatus from './QuickAddStatus';
|
||||||
import { Zap, Flame, Minimize2 } from 'lucide-react';
|
import { Zap, Flame, Minimize2 } from 'lucide-react';
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
import { Node } from '@/types/database';
|
import { Node } from '@/types/database';
|
||||||
import { parseAndRenderContent } from '@/components/helpers/NodeLabelRenderer';
|
import { parseAndRenderContent } from '@/components/helpers/NodeLabelRenderer';
|
||||||
|
|
||||||
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
|
type AgentDelegation = {
|
||||||
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
agentType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface AgentsPanelProps {
|
interface AgentsPanelProps {
|
||||||
openTabsData: Node[];
|
openTabsData: Node[];
|
||||||
activeTabId: number | null;
|
activeTabId: number | null;
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
|
|
||||||
interface DelegationIndicatorProps {
|
|
||||||
delegations: AgentDelegation[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DelegationIndicator({ delegations }: DelegationIndicatorProps) {
|
|
||||||
if (!delegations.length) return null;
|
|
||||||
|
|
||||||
const activeCount = delegations.filter((d) => d.status === 'queued' || d.status === 'in_progress').length;
|
|
||||||
|
|
||||||
if (activeCount === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
padding: '4px 8px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
background: '#0f0f0f',
|
|
||||||
border: '1px solid #1a1a1a',
|
|
||||||
fontSize: '10px',
|
|
||||||
fontFamily: "'JetBrains Mono', ui-monospace",
|
|
||||||
color: '#6b6b6b'
|
|
||||||
}}>
|
|
||||||
<span style={{
|
|
||||||
width: '6px',
|
|
||||||
height: '6px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: '#22c55e'
|
|
||||||
}} />
|
|
||||||
<span>{activeCount} active</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
|
type AgentDelegation = {
|
||||||
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
context: string[];
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
agentType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface MiniRAHPanelProps {
|
interface MiniRAHPanelProps {
|
||||||
delegation: AgentDelegation;
|
delegation: AgentDelegation;
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
|
type AgentDelegation = {
|
||||||
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
interface QuickAddStatusProps {
|
interface QuickAddStatusProps {
|
||||||
delegations: AgentDelegation[];
|
delegations: AgentDelegation[];
|
||||||
|
|||||||
@@ -6,15 +6,60 @@ import AsciiBanner from './AsciiBanner';
|
|||||||
import TerminalMessage from './TerminalMessage';
|
import TerminalMessage from './TerminalMessage';
|
||||||
import TerminalInput from './TerminalInput';
|
import TerminalInput from './TerminalInput';
|
||||||
import { Zap, Flame } from 'lucide-react';
|
import { Zap, Flame } from 'lucide-react';
|
||||||
import DelegationIndicator from './DelegationIndicator';
|
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
import { useSSEChat, ChatMessage, MessageRole } from './hooks/useSSEChat';
|
import { useSSEChat, ChatMessage, MessageRole } from './hooks/useSSEChat';
|
||||||
import { useQuotaHandler } from '@/hooks/useQuotaHandler';
|
import { useQuotaHandler } from '@/hooks/useQuotaHandler';
|
||||||
import { apiKeyService } from '@/services/storage/apiKeys';
|
import { apiKeyService } from '@/services/storage/apiKeys';
|
||||||
import { useVoiceSession } from './hooks/useVoiceSession';
|
|
||||||
import { useAssistantTTS } from './hooks/useAssistantTTS';
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
import { useRealtimeVoiceClient } from './hooks/useRealtimeVoiceClient';
|
type AgentDelegation = {
|
||||||
import { useVoiceInterruption } from './hooks/useVoiceInterruption';
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
agentType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stub DelegationIndicator component (delegation system removed)
|
||||||
|
function DelegationIndicator({ delegations }: { delegations: AgentDelegation[] }) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stub voice hooks (voice system will be removed in story 2)
|
||||||
|
function useVoiceSession() {
|
||||||
|
return {
|
||||||
|
isActive: false,
|
||||||
|
amplitude: 0,
|
||||||
|
startSession: () => {},
|
||||||
|
stopSession: () => {},
|
||||||
|
resetTranscript: () => {},
|
||||||
|
setStatus: (_status: string) => {},
|
||||||
|
setAmplitude: (_amp: number) => {},
|
||||||
|
setInterimTranscript: (_text: string) => {},
|
||||||
|
appendFinalTranscript: (_text: string) => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function useAssistantTTS(_options: { onSpeechStart?: () => void; onSpeechComplete?: () => void; onError?: (e: Error) => void }) {
|
||||||
|
return {
|
||||||
|
speak: (_text: string, _options?: { flush?: boolean; metadata?: Record<string, string> }) => {},
|
||||||
|
stop: () => {},
|
||||||
|
status: 'idle' as const,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function useVoiceInterruption(_options: { amplitude: number; isVoiceActive: boolean; ttsStatus: string; onInterruption: () => void }) {}
|
||||||
|
|
||||||
|
function useRealtimeVoiceClient(_handlers: { onStatusChange?: (status: string) => void; onInterimTranscript?: (text: string) => void; onFinalTranscript?: (text: string) => void; onAmplitude?: (amp: number) => void; onError?: (e: Error) => void }, _options: { getAuthToken: () => string | null }) {
|
||||||
|
return {
|
||||||
|
connect: () => {},
|
||||||
|
disconnect: () => {},
|
||||||
|
start: () => {},
|
||||||
|
stop: () => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const createSessionId = () => `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
const createSessionId = () => `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
const createVoiceRequestId = () =>
|
const createVoiceRequestId = () =>
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import { Fragment, ReactNode, useMemo } from 'react';
|
import { Fragment, ReactNode, useMemo } from 'react';
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
|
type AgentDelegation = {
|
||||||
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
context: string[];
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
agentType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
const statusPalette: Record<string, { border: string; badge: string }> = {
|
const statusPalette: Record<string, { border: string; badge: string }> = {
|
||||||
queued: { border: '#3a2f5f', badge: '#a78bfa' },
|
queued: { border: '#3a2f5f', badge: '#a78bfa' },
|
||||||
|
|||||||
@@ -6,9 +6,21 @@ import SearchModal from '../nodes/SearchModal';
|
|||||||
import { Node } from '@/types/database';
|
import { Node } from '@/types/database';
|
||||||
import { DatabaseEvent } from '@/services/events';
|
import { DatabaseEvent } from '@/services/events';
|
||||||
import { usePersistentState } from '@/hooks/usePersistentState';
|
import { usePersistentState } from '@/hooks/usePersistentState';
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
import type { ChatMessage } from '@/components/agents/hooks/useSSEChat';
|
import type { ChatMessage } from '@/components/agents/hooks/useSSEChat';
|
||||||
|
|
||||||
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
|
type AgentDelegation = {
|
||||||
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
context: string[];
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
agentType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
// Layout components
|
// Layout components
|
||||||
import LeftToolbar from './LeftToolbar';
|
import LeftToolbar from './LeftToolbar';
|
||||||
import SplitHandle from './SplitHandle';
|
import SplitHandle from './SplitHandle';
|
||||||
@@ -66,8 +78,8 @@ export default function ThreePanelLayout() {
|
|||||||
// Active dimension tracking (for chat context)
|
// Active dimension tracking (for chat context)
|
||||||
const [activeDimension, setActiveDimension] = usePersistentState<string | null>('ui.focus.activeDimension', null);
|
const [activeDimension, setActiveDimension] = usePersistentState<string | null>('ui.focus.activeDimension', null);
|
||||||
|
|
||||||
// Delegations state (shared between chat and workflows panes)
|
// Delegations state (deprecated - kept for component compatibility)
|
||||||
const [delegationsMap, setDelegationsMap] = useState<Record<string, AgentDelegation>>({});
|
const [delegationsMap] = useState<Record<string, AgentDelegation>>({});
|
||||||
const delegations = useMemo(() => Object.values(delegationsMap), [delegationsMap]);
|
const delegations = useMemo(() => Object.values(delegationsMap), [delegationsMap]);
|
||||||
|
|
||||||
// Chat state (lifted to persist across pane type changes)
|
// Chat state (lifted to persist across pane type changes)
|
||||||
@@ -161,32 +173,7 @@ export default function ThreePanelLayout() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [openTabsKey]);
|
}, [openTabsKey]);
|
||||||
|
|
||||||
// Load delegations on mount
|
// Delegations loading removed (delegation system removed in rah-light)
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false;
|
|
||||||
const loadExisting = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/rah/delegations?status=active&includeCompleted=true');
|
|
||||||
if (!response.ok) return;
|
|
||||||
const data = await response.json();
|
|
||||||
if (!Array.isArray(data.delegations)) return;
|
|
||||||
|
|
||||||
setDelegationsMap((prev) => {
|
|
||||||
if (cancelled) return prev;
|
|
||||||
const next = { ...prev };
|
|
||||||
for (const delegation of data.delegations as AgentDelegation[]) {
|
|
||||||
next[delegation.sessionId] = delegation;
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ThreePanelLayout] Failed to load delegations:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadExisting();
|
|
||||||
return () => { cancelled = true; };
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Keyboard shortcut handler
|
// Keyboard shortcut handler
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -278,27 +265,8 @@ export default function ThreePanelLayout() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'AGENT_DELEGATION_CREATED':
|
case 'AGENT_DELEGATION_CREATED':
|
||||||
if (data.data?.delegation) {
|
|
||||||
setDelegationsMap(prev => ({
|
|
||||||
...prev,
|
|
||||||
[data.data.delegation.sessionId]: data.data.delegation
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.dispatchEvent(new CustomEvent('delegations:created', { detail: data.data }));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'AGENT_DELEGATION_UPDATED':
|
case 'AGENT_DELEGATION_UPDATED':
|
||||||
if (data.data?.delegation) {
|
// Delegation events ignored (delegation system removed in rah-light)
|
||||||
setDelegationsMap(prev => ({
|
|
||||||
...prev,
|
|
||||||
[data.data.delegation.sessionId]: data.data.delegation
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.dispatchEvent(new CustomEvent('delegations:updated', { detail: data.data }));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'WORKFLOW_PROGRESS':
|
case 'WORKFLOW_PROGRESS':
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
import { useState, useCallback, useEffect, useMemo } from 'react';
|
import { useState, useCallback, useEffect, useMemo } from 'react';
|
||||||
import RAHChat from '@/components/agents/RAHChat';
|
import RAHChat from '@/components/agents/RAHChat';
|
||||||
import PaneHeader from './PaneHeader';
|
import PaneHeader from './PaneHeader';
|
||||||
import { WorkflowsPaneProps, PaneType } from './types';
|
import { WorkflowsPaneProps, PaneType, AgentDelegation } from './types';
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
import { parseAndRenderContent } from '@/components/helpers/NodeLabelRenderer';
|
import { parseAndRenderContent } from '@/components/helpers/NodeLabelRenderer';
|
||||||
import type { ChatMessage } from '@/components/agents/hooks/useSSEChat';
|
import type { ChatMessage } from '@/components/agents/hooks/useSSEChat';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Node } from '@/types/database';
|
import { Node } from '@/types/database';
|
||||||
import type { AgentDelegation } from '@/services/agents/delegation';
|
|
||||||
|
// Stub type for delegation (delegation system removed in rah-light)
|
||||||
|
export type AgentDelegation = {
|
||||||
|
id: number;
|
||||||
|
sessionId: string;
|
||||||
|
task: string;
|
||||||
|
context: string[];
|
||||||
|
status: 'queued' | 'in_progress' | 'completed' | 'failed';
|
||||||
|
summary?: string | null;
|
||||||
|
agentType: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
// The six pane types
|
// The six pane types
|
||||||
export type PaneType = 'node' | 'chat' | 'workflows' | 'dimensions' | 'map' | 'views';
|
export type PaneType = 'node' | 'chat' | 'workflows' | 'dimensions' | 'map' | 'views';
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
import { getSQLiteClient } from '@/services/database/sqlite-client';
|
|
||||||
import { eventBroadcaster } from '@/services/events';
|
|
||||||
|
|
||||||
export type DelegationStatus = 'queued' | 'in_progress' | 'completed' | 'failed';
|
|
||||||
export type DelegationAgentType = 'workflow' | 'wise-rah';
|
|
||||||
|
|
||||||
export interface AgentDelegation {
|
|
||||||
id: number;
|
|
||||||
sessionId: string;
|
|
||||||
task: string;
|
|
||||||
context: string[];
|
|
||||||
expectedOutcome?: string | null;
|
|
||||||
status: DelegationStatus;
|
|
||||||
summary?: string | null;
|
|
||||||
agentType: DelegationAgentType;
|
|
||||||
supabaseToken?: string | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rowToDelegation(row: any): AgentDelegation {
|
|
||||||
return {
|
|
||||||
id: row.id,
|
|
||||||
sessionId: row.session_id,
|
|
||||||
task: row.task,
|
|
||||||
context: (() => {
|
|
||||||
try {
|
|
||||||
return row.context ? JSON.parse(row.context) : [];
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
expectedOutcome: row.expected_outcome,
|
|
||||||
status: row.status as DelegationStatus,
|
|
||||||
summary: row.summary,
|
|
||||||
agentType: (row.agent_type || 'mini') as DelegationAgentType,
|
|
||||||
supabaseToken: row.supabase_token ?? null,
|
|
||||||
// SQLite CURRENT_TIMESTAMP is UTC, append 'Z' to parse correctly as UTC
|
|
||||||
createdAt: row.created_at ? row.created_at.replace(' ', 'T') + 'Z' : row.created_at,
|
|
||||||
updatedAt: row.updated_at ? row.updated_at.replace(' ', 'T') + 'Z' : row.updated_at,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureTable() {
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
db.query(`
|
|
||||||
CREATE TABLE IF NOT EXISTS agent_delegations (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
session_id TEXT UNIQUE NOT NULL,
|
|
||||||
task TEXT NOT NULL,
|
|
||||||
context TEXT,
|
|
||||||
expected_outcome TEXT,
|
|
||||||
status TEXT NOT NULL DEFAULT 'queued',
|
|
||||||
summary TEXT,
|
|
||||||
agent_type TEXT NOT NULL DEFAULT 'mini',
|
|
||||||
supabase_token TEXT,
|
|
||||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Add agent_type column if it doesn't exist (migration)
|
|
||||||
try {
|
|
||||||
const stmt = db.prepare('SELECT 1 FROM agent_delegations LIMIT 0');
|
|
||||||
const tableExists = stmt !== null;
|
|
||||||
|
|
||||||
if (tableExists) {
|
|
||||||
// Try to add the column, ignore if it already exists
|
|
||||||
try {
|
|
||||||
db.prepare(`ALTER TABLE agent_delegations ADD COLUMN agent_type TEXT NOT NULL DEFAULT 'mini'`).run();
|
|
||||||
console.log('✅ Added agent_type column to agent_delegations table');
|
|
||||||
} catch (alterError: any) {
|
|
||||||
// Column already exists, ignore
|
|
||||||
if (!alterError.message?.includes('duplicate column')) {
|
|
||||||
console.warn('Migration warning:', alterError.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
db.prepare(`ALTER TABLE agent_delegations ADD COLUMN supabase_token TEXT`).run();
|
|
||||||
console.log('✅ Added supabase_token column to agent_delegations table');
|
|
||||||
} catch (alterError: any) {
|
|
||||||
if (!alterError.message?.includes('duplicate column')) {
|
|
||||||
console.warn('Migration warning:', alterError.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
// Table doesn't exist yet or other error - it will be created with the columns
|
|
||||||
console.log('Table creation will include agent_type and supabase_token columns');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AgentDelegationService {
|
|
||||||
static createDelegation(input: {
|
|
||||||
task: string;
|
|
||||||
context?: string[];
|
|
||||||
expectedOutcome?: string | null;
|
|
||||||
agentType?: DelegationAgentType;
|
|
||||||
supabaseToken?: string | null;
|
|
||||||
}): AgentDelegation {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
const sessionId = `delegation_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
||||||
const contextJson = JSON.stringify(input.context ?? []);
|
|
||||||
const agentType = input.agentType || 'mini';
|
|
||||||
db.prepare(
|
|
||||||
`INSERT INTO agent_delegations (session_id, task, context, expected_outcome, status, agent_type, supabase_token)
|
|
||||||
VALUES (?, ?, ?, ?, 'queued', ?, ?)`
|
|
||||||
).run(
|
|
||||||
sessionId,
|
|
||||||
input.task,
|
|
||||||
contextJson,
|
|
||||||
input.expectedOutcome ?? null,
|
|
||||||
agentType,
|
|
||||||
input.supabaseToken ?? null
|
|
||||||
);
|
|
||||||
|
|
||||||
const row = db
|
|
||||||
.prepare('SELECT * FROM agent_delegations WHERE session_id = ?')
|
|
||||||
.get(sessionId);
|
|
||||||
|
|
||||||
const delegation = rowToDelegation(row);
|
|
||||||
eventBroadcaster.broadcast({ type: 'AGENT_DELEGATION_CREATED', data: { delegation } });
|
|
||||||
return delegation;
|
|
||||||
}
|
|
||||||
|
|
||||||
static markInProgress(sessionId: string): AgentDelegation | null {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
db.prepare(
|
|
||||||
`UPDATE agent_delegations
|
|
||||||
SET status = 'in_progress', updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE session_id = ? AND status = 'queued'`
|
|
||||||
).run(sessionId);
|
|
||||||
const row = db.prepare('SELECT * FROM agent_delegations WHERE session_id = ?').get(sessionId);
|
|
||||||
if (!row) return null;
|
|
||||||
const delegation = rowToDelegation(row);
|
|
||||||
eventBroadcaster.broadcast({ type: 'AGENT_DELEGATION_UPDATED', data: { delegation } });
|
|
||||||
return delegation;
|
|
||||||
}
|
|
||||||
|
|
||||||
static touchDelegation(sessionId: string): void {
|
|
||||||
// Update the timestamp to prevent cleanup from killing active delegations
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
db.prepare(
|
|
||||||
`UPDATE agent_delegations SET updated_at = CURRENT_TIMESTAMP WHERE session_id = ? AND status = 'in_progress'`
|
|
||||||
).run(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static completeDelegation(sessionId: string, summary: string, status: DelegationStatus = 'completed'): AgentDelegation | null {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
db.prepare(
|
|
||||||
`UPDATE agent_delegations
|
|
||||||
SET status = ?, summary = ?, updated_at = CURRENT_TIMESTAMP, supabase_token = NULL
|
|
||||||
WHERE session_id = ?`
|
|
||||||
).run(status, summary, sessionId);
|
|
||||||
const row = db.prepare('SELECT * FROM agent_delegations WHERE session_id = ?').get(sessionId);
|
|
||||||
if (!row) return null;
|
|
||||||
const delegation = rowToDelegation(row);
|
|
||||||
eventBroadcaster.broadcast({ type: 'AGENT_DELEGATION_UPDATED', data: { delegation } });
|
|
||||||
return delegation;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getBySessionId(sessionId: string): AgentDelegation | null {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
const row = db.prepare('SELECT * FROM agent_delegations WHERE session_id = ?').get(sessionId);
|
|
||||||
return row ? rowToDelegation(row) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getDelegation(sessionId: string): AgentDelegation | null {
|
|
||||||
return this.getBySessionId(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static listActive({ includeCompleted = true, limit = 100 }: { includeCompleted?: boolean; limit?: number } = {}): AgentDelegation[] {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
// Load all delegations - user closes them manually from UI
|
|
||||||
const rows = includeCompleted
|
|
||||||
? db.prepare(
|
|
||||||
`SELECT * FROM agent_delegations
|
|
||||||
ORDER BY updated_at DESC
|
|
||||||
LIMIT ?`
|
|
||||||
).all(limit)
|
|
||||||
: db.prepare(
|
|
||||||
`SELECT * FROM agent_delegations
|
|
||||||
WHERE status IN ('queued','in_progress')
|
|
||||||
ORDER BY updated_at DESC
|
|
||||||
LIMIT ?`
|
|
||||||
).all(limit);
|
|
||||||
return rows.map(rowToDelegation);
|
|
||||||
}
|
|
||||||
|
|
||||||
static listRecent(limit = 20): AgentDelegation[] {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
const rows = db.prepare(
|
|
||||||
`SELECT * FROM agent_delegations
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT ?`
|
|
||||||
).all(limit);
|
|
||||||
return rows.map(rowToDelegation);
|
|
||||||
}
|
|
||||||
|
|
||||||
static deleteDelegation(sessionId: string): boolean {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
const result = db.prepare('DELETE FROM agent_delegations WHERE session_id = ?').run(sessionId);
|
|
||||||
return result.changes > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static cleanupStaleDelegations(timeoutMinutes = 15): number {
|
|
||||||
ensureTable();
|
|
||||||
const db = getSQLiteClient();
|
|
||||||
const cutoffTime = new Date(Date.now() - timeoutMinutes * 60 * 1000).toISOString();
|
|
||||||
|
|
||||||
const result = db.prepare(`
|
|
||||||
UPDATE agent_delegations
|
|
||||||
SET status = 'failed',
|
|
||||||
summary = 'Task timed out (exceeded ${timeoutMinutes} minutes)',
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE status = 'in_progress'
|
|
||||||
AND updated_at < ?
|
|
||||||
`).run(cutoffTime);
|
|
||||||
|
|
||||||
const affectedCount = result.changes || 0;
|
|
||||||
|
|
||||||
if (affectedCount > 0) {
|
|
||||||
const rows = db.prepare(
|
|
||||||
`SELECT * FROM agent_delegations WHERE status = 'failed' AND summary LIKE 'Task timed out%'`
|
|
||||||
).all();
|
|
||||||
rows.forEach(row => {
|
|
||||||
const delegation = rowToDelegation(row);
|
|
||||||
eventBroadcaster.broadcast({ type: 'AGENT_DELEGATION_UPDATED', data: { delegation } });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return affectedCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { AgentDelegationService } from './delegation';
|
|
||||||
import { summarizeToolExecution } from './toolResultUtils';
|
import { summarizeToolExecution } from './toolResultUtils';
|
||||||
import { youtubeExtractTool } from '@/tools/other/youtubeExtract';
|
import { youtubeExtractTool } from '@/tools/other/youtubeExtract';
|
||||||
import { websiteExtractTool } from '@/tools/other/websiteExtract';
|
import { websiteExtractTool } from '@/tools/other/websiteExtract';
|
||||||
import { paperExtractTool } from '@/tools/other/paperExtract';
|
import { paperExtractTool } from '@/tools/other/paperExtract';
|
||||||
import { formatNodeForChat } from '@/tools/infrastructure/nodeFormatter';
|
import { formatNodeForChat } from '@/tools/infrastructure/nodeFormatter';
|
||||||
import { summarizeTranscript } from './transcriptSummarizer';
|
import { summarizeTranscript } from './transcriptSummarizer';
|
||||||
|
import { eventBroadcaster } from '@/services/events';
|
||||||
|
|
||||||
export type QuickAddMode = 'link' | 'note' | 'chat';
|
export type QuickAddMode = 'link' | 'note' | 'chat';
|
||||||
|
|
||||||
@@ -340,22 +340,18 @@ async function handleChatTranscriptQuickAdd(rawInput: string, task: string): Pro
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function enqueueQuickAdd({ rawInput, mode, description }: QuickAddInput) {
|
export interface QuickAddResult {
|
||||||
|
success: boolean;
|
||||||
|
summary?: string;
|
||||||
|
error?: string;
|
||||||
|
nodeId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function enqueueQuickAdd({ rawInput, mode, description }: QuickAddInput): Promise<QuickAddResult> {
|
||||||
const inputType = detectInputType(rawInput, mode);
|
const inputType = detectInputType(rawInput, mode);
|
||||||
const context: string[] = (inputType === 'note' || inputType === 'chat') ? [] : [rawInput];
|
|
||||||
const task = buildTaskPrompt(inputType, rawInput);
|
const task = buildTaskPrompt(inputType, rawInput);
|
||||||
const expectedOutcome = buildExpectedOutcome(inputType);
|
|
||||||
|
|
||||||
const delegation = AgentDelegationService.createDelegation({
|
|
||||||
task,
|
|
||||||
context,
|
|
||||||
expectedOutcome,
|
|
||||||
});
|
|
||||||
|
|
||||||
setImmediate(async () => {
|
|
||||||
try {
|
try {
|
||||||
AgentDelegationService.markInProgress(delegation.sessionId);
|
|
||||||
|
|
||||||
let summary: string;
|
let summary: string;
|
||||||
if (inputType === 'note') {
|
if (inputType === 'note') {
|
||||||
summary = await handleNoteQuickAdd(rawInput, task, description);
|
summary = await handleNoteQuickAdd(rawInput, task, description);
|
||||||
@@ -365,16 +361,21 @@ export async function enqueueQuickAdd({ rawInput, mode, description }: QuickAddI
|
|||||||
summary = await handleExtractionQuickAdd(inputType as ExtractionQuickAddType, rawInput, task);
|
summary = await handleExtractionQuickAdd(inputType as ExtractionQuickAddType, rawInput, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentDelegationService.completeDelegation(delegation.sessionId, summary, 'completed');
|
// Extract node ID from summary if present
|
||||||
|
const nodeIdMatch = summary.match(/\[NODE:(\d+)/);
|
||||||
|
const nodeId = nodeIdMatch ? parseInt(nodeIdMatch[1], 10) : undefined;
|
||||||
|
|
||||||
|
// Broadcast node created event
|
||||||
|
if (nodeId) {
|
||||||
|
eventBroadcaster.broadcast({
|
||||||
|
type: 'NODE_CREATED',
|
||||||
|
data: { node: { id: nodeId, title: 'Quick Add' } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, summary, nodeId };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'unknown error';
|
const message = error instanceof Error ? error.message : 'unknown error';
|
||||||
AgentDelegationService.completeDelegation(
|
return { success: false, error: `Quick Add failed: ${message}` };
|
||||||
delegation.sessionId,
|
|
||||||
`Quick Add failed: ${message}`,
|
|
||||||
'failed'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return delegation;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { streamText, ModelMessage } from 'ai';
|
import { streamText, ModelMessage } from 'ai';
|
||||||
import { createOpenAI } from '@ai-sdk/openai';
|
import { createOpenAI } from '@ai-sdk/openai';
|
||||||
import type { LanguageModelV2ToolResultOutput } from '@ai-sdk/provider';
|
import type { LanguageModelV2ToolResultOutput } from '@ai-sdk/provider';
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
import { WORKFLOW_EXECUTOR_SYSTEM_PROMPT } from '@/config/prompts/workflow-executor';
|
import { WORKFLOW_EXECUTOR_SYSTEM_PROMPT } from '@/config/prompts/workflow-executor';
|
||||||
import { getToolsByNames } from '@/tools/infrastructure/registry';
|
import { getToolsByNames } from '@/tools/infrastructure/registry';
|
||||||
import { WorkflowRegistry } from '@/services/workflows/registry';
|
import { WorkflowRegistry } from '@/services/workflows/registry';
|
||||||
@@ -10,7 +9,6 @@ import { calculateCost } from '@/services/analytics/pricing';
|
|||||||
import { UsageData } from '@/types/analytics';
|
import { UsageData } from '@/types/analytics';
|
||||||
import { summarizeToolExecution } from '@/services/agents/toolResultUtils';
|
import { summarizeToolExecution } from '@/services/agents/toolResultUtils';
|
||||||
import { edgeService } from '@/services/database/edges';
|
import { edgeService } from '@/services/database/edges';
|
||||||
import { delegationStreamBroadcaster } from '@/app/api/rah/delegations/stream/route';
|
|
||||||
import { RequestContext } from '@/services/context/requestContext';
|
import { RequestContext } from '@/services/context/requestContext';
|
||||||
|
|
||||||
export interface WorkflowExecutionInput {
|
export interface WorkflowExecutionInput {
|
||||||
@@ -38,8 +36,7 @@ export class WorkflowExecutor {
|
|||||||
throw new Error('OPENAI_API_KEY is not set for workflow execution.');
|
throw new Error('OPENAI_API_KEY is not set for workflow execution.');
|
||||||
}
|
}
|
||||||
|
|
||||||
AgentDelegationService.markInProgress(sessionId);
|
console.log('✅ [WorkflowExecutor] Starting workflow execution');
|
||||||
console.log('✅ [WorkflowExecutor] Delegation marked in progress');
|
|
||||||
|
|
||||||
// Get workflow definition if available
|
// Get workflow definition if available
|
||||||
const workflow = workflowKey ? await WorkflowRegistry.getWorkflowByKey(workflowKey) : null;
|
const workflow = workflowKey ? await WorkflowRegistry.getWorkflowByKey(workflowKey) : null;
|
||||||
@@ -165,28 +162,9 @@ export class WorkflowExecutor {
|
|||||||
|
|
||||||
const ensureString = (value: unknown) => (typeof value === 'string' ? value.trim() : '');
|
const ensureString = (value: unknown) => (typeof value === 'string' ? value.trim() : '');
|
||||||
|
|
||||||
const sanitizeForBroadcast = (value: unknown) => {
|
// Logging helpers (no-op for lite version without delegation streaming)
|
||||||
if (value === undefined) return undefined;
|
|
||||||
try {
|
|
||||||
return JSON.parse(JSON.stringify(value));
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[WorkflowExecutor] Failed to serialize delegation payload', error);
|
|
||||||
if (typeof value === 'string') return value;
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const emitDelegationEvent = (payload: Record<string, unknown>) => {
|
|
||||||
delegationStreamBroadcaster.broadcast(sessionId, payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const emitToolStart = (toolCallId: string, toolName: string, input: unknown) => {
|
const emitToolStart = (toolCallId: string, toolName: string, input: unknown) => {
|
||||||
emitDelegationEvent({
|
console.log(`🔧 [WorkflowExecutor] Tool start: ${toolName}`);
|
||||||
type: 'tool-input-start',
|
|
||||||
toolCallId,
|
|
||||||
toolName,
|
|
||||||
input: sanitizeForBroadcast(input),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const emitToolCompletion = (
|
const emitToolCompletion = (
|
||||||
@@ -197,15 +175,7 @@ export class WorkflowExecutor {
|
|||||||
status: 'complete' | 'error' = 'complete',
|
status: 'complete' | 'error' = 'complete',
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
) => {
|
) => {
|
||||||
emitDelegationEvent({
|
console.log(`✅ [WorkflowExecutor] Tool complete: ${toolName} - ${status}`);
|
||||||
type: 'tool-output-available',
|
|
||||||
toolCallId,
|
|
||||||
toolName,
|
|
||||||
result: sanitizeForBroadcast(rawResult),
|
|
||||||
summary,
|
|
||||||
status,
|
|
||||||
error: errorMessage,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildToolOutput = (toolName: string, summary: string, rawResult: any): LanguageModelV2ToolResultOutput => {
|
const buildToolOutput = (toolName: string, summary: string, rawResult: any): LanguageModelV2ToolResultOutput => {
|
||||||
@@ -280,9 +250,6 @@ export class WorkflowExecutor {
|
|||||||
for (let i = 0; i < maxIterations; i++) {
|
for (let i = 0; i < maxIterations; i++) {
|
||||||
console.log(`🔄 [WorkflowExecutor] Iteration ${i + 1}/${maxIterations}`);
|
console.log(`🔄 [WorkflowExecutor] Iteration ${i + 1}/${maxIterations}`);
|
||||||
|
|
||||||
// Touch delegation every iteration to prevent cleanup from killing it
|
|
||||||
AgentDelegationService.touchDelegation(sessionId);
|
|
||||||
|
|
||||||
const streamResult = await streamText({
|
const streamResult = await streamText({
|
||||||
model: openaiProvider('gpt-5-mini'),
|
model: openaiProvider('gpt-5-mini'),
|
||||||
messages,
|
messages,
|
||||||
@@ -308,12 +275,9 @@ export class WorkflowExecutor {
|
|||||||
|
|
||||||
console.log(`📊 [WorkflowExecutor] Step ${i + 1} finishReason:`, response.finishReason);
|
console.log(`📊 [WorkflowExecutor] Step ${i + 1} finishReason:`, response.finishReason);
|
||||||
|
|
||||||
// Stream text response to delegation chat
|
// Log text response
|
||||||
if (response.text && response.text.trim()) {
|
if (response.text && response.text.trim()) {
|
||||||
emitDelegationEvent({
|
console.log(`📝 [WorkflowExecutor] Response text: ${response.text.substring(0, 100)}...`);
|
||||||
type: 'text-delta',
|
|
||||||
delta: response.text,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.finishReason !== 'tool-calls') {
|
if (response.finishReason !== 'tool-calls') {
|
||||||
@@ -325,9 +289,9 @@ export class WorkflowExecutor {
|
|||||||
const toolCalls = response.toolCalls || [];
|
const toolCalls = response.toolCalls || [];
|
||||||
console.log(`🔧 [WorkflowExecutor] Executing ${toolCalls.length} tool calls`);
|
console.log(`🔧 [WorkflowExecutor] Executing ${toolCalls.length} tool calls`);
|
||||||
|
|
||||||
// Broadcast new assistant message for next iteration
|
// Log tool calls
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
emitDelegationEvent({ type: 'assistant-message' });
|
console.log(`🔧 [WorkflowExecutor] Processing ${toolCalls.length} tool calls`);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages.push({
|
messages.push({
|
||||||
@@ -441,25 +405,12 @@ export class WorkflowExecutor {
|
|||||||
console.log('📏 [WorkflowExecutor] Summary length:', summary.length);
|
console.log('📏 [WorkflowExecutor] Summary length:', summary.length);
|
||||||
|
|
||||||
if (!summary) {
|
if (!summary) {
|
||||||
emitDelegationEvent({
|
console.warn('[WorkflowExecutor] Empty summary received');
|
||||||
type: 'assistant-message',
|
|
||||||
});
|
|
||||||
emitDelegationEvent({
|
|
||||||
type: 'text-delta',
|
|
||||||
delta: 'Workflow executor attempted to summarise but the response was empty. Check tool logs above for context.',
|
|
||||||
});
|
|
||||||
throw new Error('Workflow executor returned empty summary');
|
throw new Error('Workflow executor returned empty summary');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[WorkflowExecutor] summary:', summary);
|
console.log('[WorkflowExecutor] summary:', summary);
|
||||||
|
|
||||||
// Emit final summary to the stream so it appears in the UI
|
|
||||||
emitDelegationEvent({ type: 'assistant-message' });
|
|
||||||
emitDelegationEvent({
|
|
||||||
type: 'text-delta',
|
|
||||||
delta: summary,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Calculate cost and log to chats table
|
// Calculate cost and log to chats table
|
||||||
if (usage) {
|
if (usage) {
|
||||||
const inputTokens = (usage as any).promptTokens || usage.inputTokens || 0;
|
const inputTokens = (usage as any).promptTokens || usage.inputTokens || 0;
|
||||||
@@ -487,16 +438,13 @@ export class WorkflowExecutor {
|
|||||||
workflowNodeId,
|
workflowNodeId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const delegation = AgentDelegationService.getDelegation(sessionId);
|
|
||||||
const delegationId = delegation?.id;
|
|
||||||
|
|
||||||
await ChatLoggingMiddleware.logChatInteraction(
|
await ChatLoggingMiddleware.logChatInteraction(
|
||||||
task,
|
task,
|
||||||
summary,
|
summary,
|
||||||
{
|
{
|
||||||
helperName: 'workflow-agent',
|
helperName: 'workflow-agent',
|
||||||
agentType: 'planner',
|
agentType: 'planner',
|
||||||
delegationId: delegationId ?? null,
|
delegationId: null,
|
||||||
sessionId,
|
sessionId,
|
||||||
usageData,
|
usageData,
|
||||||
traceId,
|
traceId,
|
||||||
@@ -511,23 +459,12 @@ export class WorkflowExecutor {
|
|||||||
console.log(`💰 [WorkflowExecutor] Cost: $${costResult.totalCostUsd.toFixed(6)} (${totalTokens} tokens)`);
|
console.log(`💰 [WorkflowExecutor] Cost: $${costResult.totalCostUsd.toFixed(6)} (${totalTokens} tokens)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [WorkflowExecutor] Completing delegation with summary');
|
console.log('✅ [WorkflowExecutor] Workflow execution complete');
|
||||||
return AgentDelegationService.completeDelegation(sessionId, summary);
|
return { sessionId, summary, status: 'completed' as const };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [WorkflowExecutor] Error during execution:', error);
|
console.error('❌ [WorkflowExecutor] Error during execution:', error);
|
||||||
console.error('❌ [WorkflowExecutor] Error stack:', error instanceof Error ? error.stack : 'No stack');
|
console.error('❌ [WorkflowExecutor] Error stack:', error instanceof Error ? error.stack : 'No stack');
|
||||||
const message = error instanceof Error ? error.message : 'Unknown delegation error';
|
const message = error instanceof Error ? error.message : 'Unknown workflow error';
|
||||||
|
|
||||||
// Broadcast error to delegation stream
|
|
||||||
delegationStreamBroadcaster.broadcast(sessionId, {
|
|
||||||
type: 'assistant-message',
|
|
||||||
});
|
|
||||||
delegationStreamBroadcaster.broadcast(sessionId, {
|
|
||||||
type: 'text-delta',
|
|
||||||
delta: `Workflow executor failed: ${message}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
AgentDelegationService.completeDelegation(sessionId, `Workflow executor failed: ${message}`, 'failed');
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { tool } from 'ai';
|
import { tool } from 'ai';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
import { WorkflowExecutor } from '@/services/agents/workflowExecutor';
|
import { WorkflowExecutor } from '@/services/agents/workflowExecutor';
|
||||||
import { RequestContext } from '@/services/context/requestContext';
|
import { RequestContext } from '@/services/context/requestContext';
|
||||||
|
|
||||||
export const delegateToWiseRAHTool = tool({
|
export const delegateToWiseRAHTool = tool({
|
||||||
description: 'Delegate complex workflows to wise ra-h (GPT-5)',
|
description: 'Delegate complex workflows to workflow executor',
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
task: z.string().describe('Complex workflow description: what needs to be planned and executed'),
|
task: z.string().describe('Complex workflow description: what needs to be planned and executed'),
|
||||||
context: z.array(z.string()).max(8).default([]).describe('Optional context: node IDs, URLs, or key information the planner needs'),
|
context: z.array(z.string()).max(8).default([]).describe('Optional context: node IDs, URLs, or key information the planner needs'),
|
||||||
@@ -17,16 +16,10 @@ export const delegateToWiseRAHTool = tool({
|
|||||||
const requestContext = RequestContext.get();
|
const requestContext = RequestContext.get();
|
||||||
console.log('[delegateToWiseRAH] Current traceId:', requestContext.traceId);
|
console.log('[delegateToWiseRAH] Current traceId:', requestContext.traceId);
|
||||||
|
|
||||||
const delegation = AgentDelegationService.createDelegation({
|
const sessionId = `workflow_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
task,
|
|
||||||
context,
|
|
||||||
expectedOutcome,
|
|
||||||
agentType: 'wise-rah',
|
|
||||||
supabaseToken: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const execution = await WorkflowExecutor.execute({
|
const execution = await WorkflowExecutor.execute({
|
||||||
sessionId: delegation.sessionId,
|
sessionId,
|
||||||
task,
|
task,
|
||||||
context,
|
context,
|
||||||
expectedOutcome,
|
expectedOutcome,
|
||||||
@@ -37,7 +30,7 @@ export const delegateToWiseRAHTool = tool({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Return a simple string that Claude can directly use in conversation
|
// Return a simple string that Claude can directly use in conversation
|
||||||
const summary = execution?.summary || 'Wise ra-h delegated but no summary returned.';
|
const summary = execution?.summary || 'Workflow completed but no summary returned.';
|
||||||
return `Wise ra-h (session ${delegation.sessionId.split('_').pop()}) completed the workflow:\n\n${summary}`;
|
return `Workflow (session ${sessionId.split('_').pop()}) completed:\n\n${summary}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { tool } from 'ai';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { WorkflowRegistry } from '@/services/workflows/registry';
|
import { WorkflowRegistry } from '@/services/workflows/registry';
|
||||||
import { getSQLiteClient } from '@/services/database/sqlite-client';
|
import { getSQLiteClient } from '@/services/database/sqlite-client';
|
||||||
import { AgentDelegationService } from '@/services/agents/delegation';
|
|
||||||
import { WorkflowExecutor } from '@/services/agents/workflowExecutor';
|
import { WorkflowExecutor } from '@/services/agents/workflowExecutor';
|
||||||
import { RequestContext } from '@/services/context/requestContext';
|
import { RequestContext } from '@/services/context/requestContext';
|
||||||
import { getAutoContextSummaries } from '@/services/context/autoContext';
|
import { getAutoContextSummaries } from '@/services/context/autoContext';
|
||||||
@@ -92,21 +91,15 @@ ${workflow.instructions}
|
|||||||
|
|
||||||
${nodeId ? `Target Node ID: ${nodeId}` : 'No specific node targeted (general workflow)'}`;
|
${nodeId ? `Target Node ID: ${nodeId}` : 'No specific node targeted (general workflow)'}`;
|
||||||
|
|
||||||
// 5. Delegate to wise ra-h oracle with workflow metadata
|
// 5. Execute workflow directly
|
||||||
const requestContext = RequestContext.get();
|
const requestContext = RequestContext.get();
|
||||||
console.log('[executeWorkflowTool] Current traceId:', requestContext.traceId);
|
console.log('[executeWorkflowTool] Current traceId:', requestContext.traceId);
|
||||||
|
|
||||||
const delegation = AgentDelegationService.createDelegation({
|
const sessionId = `workflow_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||||
task,
|
|
||||||
context: contextLines,
|
|
||||||
expectedOutcome: workflow.expectedOutcome,
|
|
||||||
agentType: 'wise-rah',
|
|
||||||
supabaseToken: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fire-and-forget execution so main ra-h stays responsive while workflow runs
|
try {
|
||||||
void WorkflowExecutor.execute({
|
const result = await WorkflowExecutor.execute({
|
||||||
sessionId: delegation.sessionId,
|
sessionId,
|
||||||
task,
|
task,
|
||||||
context: contextLines,
|
context: contextLines,
|
||||||
expectedOutcome: workflow.expectedOutcome,
|
expectedOutcome: workflow.expectedOutcome,
|
||||||
@@ -114,17 +107,21 @@ ${nodeId ? `Target Node ID: ${nodeId}` : 'No specific node targeted (general wor
|
|||||||
parentChatId: requestContext.parentChatId,
|
parentChatId: requestContext.parentChatId,
|
||||||
workflowKey,
|
workflowKey,
|
||||||
workflowNodeId: nodeId,
|
workflowNodeId: nodeId,
|
||||||
}).catch((error) => {
|
|
||||||
console.error('[executeWorkflowTool] Wise ra-h delegation failed', error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
RequestContext.set({ workflowKey: undefined, workflowNodeId: undefined });
|
RequestContext.set({ workflowKey: undefined, workflowNodeId: undefined });
|
||||||
|
|
||||||
const shortSessionId = delegation.sessionId.split('_').pop();
|
|
||||||
const workflowLabel = workflow.displayName || workflowKey;
|
const workflowLabel = workflow.displayName || workflowKey;
|
||||||
return [
|
return {
|
||||||
`Delegated **${workflowLabel}** to wise ra-h (session ${shortSessionId}).`,
|
success: true,
|
||||||
'Keep working here—wise ra-h will stream every step inside its tab and post the final summary when complete.',
|
message: `Workflow **${workflowLabel}** completed.`,
|
||||||
].join('\n');
|
summary: result.summary,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[executeWorkflowTool] Workflow execution failed', error);
|
||||||
|
RequestContext.set({ workflowKey: undefined, workflowNodeId: undefined });
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
return { success: false, error: `Workflow execution failed: ${errorMessage}` };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user