diff --git a/src/game/engine/scenes/headless/player_api_main.gd b/src/game/engine/scenes/headless/player_api_main.gd index 7073e925..832a5930 100644 --- a/src/game/engine/scenes/headless/player_api_main.gd +++ b/src/game/engine/scenes/headless/player_api_main.gd @@ -550,9 +550,11 @@ func _pump() -> void: while not _shutdown: var line: String = OS.read_string_from_stdin(4096) if line == "": - # EOF or no line yet — yield to the engine and try again. - await get_tree().process_frame - continue + # A blocking stdin read returns "" only at end-of-stream — the + # client closed stdin. Honour the documented EOF clean-exit instead + # of busy-looping until CP_TIMEOUT_SEC (prior tech debt: this branch + # yielded + retried forever, so `cat reqs | harness` never exited). + break line = line.strip_edges() if line.is_empty(): continue @@ -579,6 +581,15 @@ func _handle_request(req: Dictionary) -> void: "view": var view_json: String = String(_api.view_json(target_slot)) _emit_response_with_view(rid_int, has_id, view_json) + "stop": + # Explicit clean-exit request for interactive clients that hold + # stdin open (Popen drivers, the MCP adapter). Ack, then flag the + # pump loop to break → get_tree().quit(0). Complements EOF exit. + _shutdown = true + if has_id: + _write_line(JSON.stringify({"id": rid_int, "ok": true})) + else: + _write_line(JSON.stringify({"ok": true})) "suggest": # Stage 6.1.6 — read-only "what would the in-box AI do here". # `suggest_json` returns a full `{"ok":...,"actions":[...]}`