"""V14: Auto-continue. Use ASCII-only text for reliable build.""" import os, sys ROOT = "/source/frontend/src" FILES = { "events.ts": ROOT + "/types/events.ts", "sse.ts": ROOT + "/lib/sse-chat-transport.ts", "hook.ts": ROOT + "/hooks/useAgentChat.ts", "input.tsx": ROOT + "/components/Chat/ChatInput.tsx", "sess.tsx": ROOT + "/components/SessionChat.tsx", "agent.py": "/app/agent/core/agent_loop.py", } # 1. events.ts c = open(FILES["events.ts"]).read() if "'task_incomplete'" not in c: c = c.replace(" | 'interrupted'", " | 'interrupted'\n | 'task_incomplete'") open(FILES["events.ts"], 'w').write(c) print("OK: events.ts") # 2. sse.ts c = open(FILES["sse.ts"]).read() if "case 'task_incomplete'" not in c: block = """ case 'task_incomplete': (sideChannel as any).onTaskIncomplete( (event.data?.incomplete_plan as Array<{ id: string; content: string; status: string }>) || [], ); break; default:""" c = c.replace("\n default:", "\n" + block) open(FILES["sse.ts"], 'w').write(c) print("OK: sse.ts") # 3. hook.ts - useAgentChat.ts c = open(FILES["hook.ts"]).read() # useState import if "useState" not in c: c = c.replace( "import { useCallback, useEffect, useMemo, useRef } from 'react';", "import { useCallback, useEffect, useMemo, useRef, useState } from 'react';" ) # State after callbacksRef if "_showAc" not in c: state_block = ( " // Auto-continue state\n" " const [_showAc, _setShowAc] = useState(false);\n" " const _acRef = useRef>([]);\n" ) c = c.replace( "callbacksRef.current = { onReady, onError, onSessionDead };", "callbacksRef.current = { onReady, onError, onSessionDead };\n" + state_block ) # Handler in onSessionUpdate if "ac_plan" not in c: handler = ( " // Auto-continue check\n" " if ((data as any).ac_plan) {\n" " _acRef.current = (data as any).ac_plan;\n" " _setShowAc(true);\n" " return;\n" " }\n" ) c = c.replace("onSessionUpdate: (data) => {", "onSessionUpdate: (data) => {\n" + handler) # Cancel + effect before return if "_acCancel" not in c: cancel = " const _acCancel = useCallback(() => { _setShowAc(false); }, []);\n" effect = ( " useEffect(() => {\n" " if (!_showAc) return;\n" " const plan = _acRef.current;\n" " const timer = setTimeout(() => {\n" " const s = chatActionsRef.current.setMessages;\n" " const m = chatActionsRef.current.messages;\n" " if (s && plan.length > 0) {\n" " const t = plan.map((i: { content: string }) => '- ' + i.content).join('\\\\n');\n" " const nu = { id: 'ac-'+Date.now(), role: 'user' as const, parts: [{ type: 'text' as const, text: '[TIEP TUC] Task chua hoan thanh:\\\\n' + t }], content: '' };\n" " s([...m, nu]);\n" " }\n" " _setShowAc(false);\n" " }, 10000);\n" " return () => clearTimeout(timer);\n" " }, [_showAc]);\n\n" ) c = c.replace( " return {\n messages: chat.messages,", cancel + effect + " return {\n messages: chat.messages," ) # Return values if "_showAc," not in c: c = c.replace("refreshMessages,\n };", "refreshMessages,\n _showAc,\n _acCancel,\n };") open(FILES["hook.ts"], 'w').write(c) print("OK: useAgentChat.ts") # 4. input.tsx - ChatInput.tsx c = open(FILES["input.tsx"]).read() if "Button" not in c: # Match the exact MUI import block c = c.replace("import {\n Alert,\n Box,\n TextField,\n IconButton,\n CircularProgress,\n Typography,\n Menu,\n MenuItem,\n ListItemIcon,\n ListItemText,\n Chip,\n LinearProgress,\n Snackbar,\n Tooltip,\n} from '@mui/material';", "import {\n Alert,\n Box,\n TextField,\n IconButton,\n CircularProgress,\n Typography,\n Menu,\n MenuItem,\n ListItemIcon,\n ListItemText,\n Chip,\n LinearProgress,\n Snackbar,\n Tooltip,\n Button,\n} from '@mui/material';") if "_showAc" not in c: c = c.replace("placeholder?: string;", "placeholder?: string;\n _showAc?: boolean;\n _acCancel?: () => void;") c = c.replace("placeholder = 'Ask anything...' }: ChatInputProps) {", "placeholder = 'Ask anything...', _showAc = false, _acCancel }: ChatInputProps) {") if "TAM_DUNG" not in c: btn = ( " {_showAc && _acCancel && (\n" " \n" " \n" " \n" " )}\n" ) c = c.replace("