Repository navigation
Conversation
Cold sends that open the chat via search take 10-16s on an M4 Mac mini, so the fixed 10s cap (18s with deep recovery) kills sends that are slow but still progressing. The runner only waited for exit and read stderr at the end, so it could not tell slow from stuck. - Read the child's stderr incrementally and track the last write time. - Add an optional idleTimeoutSec to KmsgSubprocessRunner.run(); when set, the run fails after that many seconds without stderr output, under the existing timeoutSec as a hard ceiling. When nil, behavior is unchanged. - kmsg_send always passes --trace-ax so the unbuffered [trace-ax] lines act as a heartbeat, and uses idle 12s / hard 30s. stderr is still only surfaced in the response when trace_ax was requested. Refs channprj#25 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every command that drives the KakaoTalk UI spends 5.5-7.8s before its first output (auth/window probing), so the fixed caps fail cold runs across the board, not just send. Measured with the Homebrew v1.260921.0 MCP server, 5 cold runs each: - kmsg_read: 15/15 PROCESS_TIMEOUT (8s cap, 15s with deep recovery, and 8s with chat_id) - kmsg_send_image: 5/5 PROCESS_TIMEOUT without deep recovery (12s cap); 16.5-18.0s with deep recovery, close to its 20s cap Use the same heartbeat approach as kmsg_send for read (including the CHAT_NOT_FOUND deep-recovery retry) and send-image, and share the limits as KmsgSubprocessRunner.uiIdleTimeoutSec (12s) and uiHardTimeoutSec (30s). Refs channprj#25 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Expanded the scope. After measuring every command, the same problem hits 범위를 넓혔습니다. 모든 명령을 측정해 보니 send뿐 아니라 |
Summary
The MCP tools that drive the KakaoTalk UI (
kmsg_read,kmsg_send,kmsg_send_image) use a single wall-clock cap per call. Every one of these commands spends 5.5–7.8s before its first output (auth/window probing), and a cold run that opens the chat takes 10–18s in total, so the caps fail cold runs across the board. In my testingkmsg_readfailed 15/15 cold runs.This PR replaces the fixed caps for those three tools with an idle timeout (12s without stderr output) plus a hard ceiling (30s).
Fixes #25
Why an idle timeout
The runner only waited for the process to exit and read stderr at the end, so it couldn't tell "slow but progressing" from "stuck".
[trace-ax]lines are written withFileHandle.standardError.write(unbuffered), so they work as a heartbeat. Stdoutprintoutput is buffered on a pipe and can't be used this way.Timing also drifts between runs (it climbs ~0.2–0.3s per run, then drops), so any fixed total cap gets crossed sooner or later. The silent gaps are much more stable: the longest one in any successful run was 7.8s.
Measurements (Homebrew v1.260921.0, before this PR)
MCP server, 5 cold runs each (chat window closed), sent to my own "chat with me" room:
kmsg_read, deep offPROCESS_TIMEOUTkmsg_read, deep onPROCESS_TIMEOUTkmsg_readwithchat_idPROCESS_TIMEOUTkmsg_readwithbackground_safe, window already openkmsg_send_image, deep offPROCESS_TIMEOUTkmsg_send_image, deep onkmsg_send, deep offPROCESS_TIMEOUTat 10.07sCLI timings (no cap), 5 runs each:
readcold 15.1–17.5s,readwith the window already open 10.6–11.0s,read --chat-idcold 15.3–16.5s,send-imagecold 14.2–15.6s,sendcold 10.3–15.9s (30 runs),status10.2–11.4s,chats6.8–7.3s. In every one, the longest silent gap is the 5.5–7.8s before the first[trace-ax]line. Opening a window that's already open, or skipping search with--chat-id, doesn't remove it.Changes (
Sources/kmsg/Commands/MCPServerCommand.swiftonly)MCPDataBuffer: addappend(_:)andlastWriteDate().KmsgSubprocessRunner.run(): new optionalidleTimeoutSec(defaultnil).availableDataloop) instead ofreadDataToEndOfFile().idleTimeoutSecis set, it waits in 0.25s slices and times out if total time >timeoutSecor time since the last stderr output >idleTimeoutSec.nil, behavior is unchanged (--versionand the startupstatuscheck keep their current timeouts).KmsgSubprocessRunner.uiIdleTimeoutSec = 12anduiHardTimeoutSec = 30.kmsg_read(including theCHAT_NOT_FOUNDdeep-recovery retry),kmsg_send,kmsg_send_image: always append--trace-ax(heartbeat) and use the shared limits.trace_axwas requested, so response shapes are unchanged.Testing
No automated suite, so tested manually on an M4 Mac mini (16GB, macOS 27.0.1) with KakaoTalk running, by calling
tools/callover ndjson stdio againstkmsg mcp-serverbuilt from this branch. Same cold conditions as above.kmsg_read, deep offkmsg_read, deep onkmsg_readwithchat_idkmsg_readwithbackground_safekmsg_send_image, deep offkmsg_send_image, deep onkmsg_send, deep offSIGSTOPat 3s / 7sPROCESS_TIMEOUTat 12.25s / 17.8s (last output + 12s); no leftover processtrace_axbackground_safe, latency_ms, layout; send / send-image metalatency_ms, stdout; same as before, no trace outputswift build,swift build -c release, and the Python checks fromci.ymlpass locally.Trade-offs / notes
--trace-axis now always passed for these three tools. That's a bit of extra stderr, but it isn't surfaced unless requested.kmsg_readwithchat_idis the slowest case (20.5–23.0s, climbing across runs), so its margin under the 30s ceiling is the smallest. The ceiling only bounds runs that keep producing output, since stuck runs are caught by the idle timeout, so raising it (e.g. to 45s) would be low-risk if you'd prefer more headroom.WINDOW_NOT_READY, likely read: opening a room by title intermittently clicks the wrong search row (WINDOW_NOT_READY) #24).tools/kmsg-mcp.py(separate Python implementation with the same constants), and the startupstatuscheck (15s cap;statusmeasured 10.2–11.4s).SIGSTOPcases simulate the same "no output" condition.The branch name differs from the one mentioned in #25 (
fix/mcp-send-timeout-scale) because the approach changed from raising the cap to an idle timeout.요약 (한국어)
KakaoTalk UI를 조작하는 MCP 도구(
kmsg_read,kmsg_send,kmsg_send_image)는 호출마다 전체 시간 상한 하나만 둡니다. 이 명령들은 모두 첫 출력 전에 5.57.8초를 쓰고, 채팅창을 여는 cold 실행은 총 1018초가 걸려서 상한에 걸려 실패합니다. 테스트에서kmsg_read는 cold 15/15 실패했습니다.이 PR은 세 도구의 고정 상한을 **idle timeout(stderr 출력 없이 12초) + hard ceiling(30초)**으로 바꿉니다.
[trace-ax]는 stderr로 버퍼링 없이 나오므로 진행 신호로 씁니다.idleTimeoutSec가nil이면 기존 동작 그대로라,--version과 시작 시status확인은 영향이 없습니다.trace_ax를 요청한 경우에만 노출됩니다.chat_idread가 20.5~23.0초로 가장 느려 30초 상한까지의 여유가 가장 작습니다. 필요하면 상한을 45초로 올려도 위험이 적습니다(멈춘 경우는 idle에서 잡히므로).🤖 Generated with Claude Code