"""V17: Auto-continue - perfect Python, exact marker matching""" import os EVENTS = "/source/frontend/src/types/events.ts" SSE = "/source/frontend/src/lib/sse-chat-transport.ts" HOOK = "/source/frontend/src/hooks/useAgentChat.ts" INPUT = "/source/frontend/src/components/Chat/ChatInput.tsx" SESS = "/source/frontend/src/components/SessionChat.tsx" AGENT = "/app/agent/core/agent_loop.py" print("Patching auto-continue V17...") # --- events.ts --- with open(EVENTS) as f: c = f.read() if "'task_incomplete'" not in c: c = c.replace(" | 'interrupted'", " | 'interrupted'\n | 'task_incomplete'") with open(EVENTS, 'w') as f: f.write(c) print("OK: events.ts") # --- sse.ts --- with open(SSE) as f: c = f.read() if "case 'task_incomplete'" not in c: block = ( " case 'task_incomplete':\n" " (sideChannel as any).onTaskIncomplete(\n" " (event.data?.incomplete_plan as Array<{ id: string; content: string; status: string }>) || [],\n" " );\n" " break;\n\n" " default:" ) c = c.replace("\n default:", "\n" + block) with open(SSE, 'w') as f: f.write(c) print("OK: sse.ts") # --- useAgentChat.ts --- with open(HOOK) as f: content = f.read() # Add useState import if "useState" not in content: content = content.replace( "import { useCallback, useEffect, useMemo, useRef } from 'react';", "import { useCallback, useEffect, useMemo, useRef, useState } from 'react';" ) # Add state after callbacksRef if "_showAc" not in content: state_code = ( " // Auto-continue state\n" " const [_showAc, _setShowAc] = useState(false);\n" " const _acRef = useRef>([]);\n" ) content = content.replace( "callbacksRef.current = { onReady, onError, onSessionDead };", "callbacksRef.current = { onReady, onError, onSessionDead };\n" + state_code ) # Add handler in onSessionUpdate if "ac_plan" not in content: h = ( " // 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" ) content = content.replace("onSessionUpdate: (data) => {", "onSessionUpdate: (data) => {\n" + h) # ONLY match the unique two-line pattern: " return {\n messages: chat.messages," UNIQUE_MARKER = " return {\n messages: chat.messages," if UNIQUE_MARKER in content and "_acCancel" not in content: insert = ( " const _acCancel = useCallback(() => { _setShowAc(false); }, []);\n" " 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" ) content = content.replace(UNIQUE_MARKER, insert + UNIQUE_MARKER) # Add to return content = content.replace( "refreshMessages,\n };", "refreshMessages,\n _showAc,\n _acCancel,\n };" ) with open(HOOK, 'w') as f: f.write(content) print("OK: useAgentChat.ts") # --- ChatInput.tsx --- with open(INPUT) as f: content = f.read() # Add Button (check exact substring, not "IconButton") if " Button,\n" not in content and " Button\n" not in content: content = content.replace( " Tooltip,", " Tooltip,\n Button," ) if "_showAc" not in content: content = content.replace( "placeholder?: string;", "placeholder?: string;\n _showAc?: boolean;\n _acCancel?: () => void;" ) content = content.replace( "placeholder = 'Ask anything...' }: ChatInputProps) {", "placeholder = 'Ask anything...', _showAc = false, _acCancel }: ChatInputProps) {" ) btn = ( " {_showAc && _acCancel && (\n" " \n" " \n" " \n" " )}\n" ) content = content.replace("