fix(@projects/@magic-civilization): 🧹 player-API harness exits on stdin EOF + adds explicit stop request

The pump loop treated an empty stdin read as "EOF or no line yet" and yielded +
retried forever, so `cat reqs | player-api-server.sh` hung until CP_TIMEOUT_SEC
instead of the documented clean EOF exit. Godot 4.6's read_string_from_stdin
blocks, so an empty return is end-of-stream — break and quit(0). Also add a
`{"type":"stop"}` request (acked, sets the loop flag) so interactive clients that
hold stdin open (Popen drivers, the MCP adapter) can exit cleanly without relying
on EOF — the clean-exit request path the docs promised but never implemented.

Verified: 11-request batch pipe now exits 0 in ~1s (was 124/timeout); view+stop
exits 0 in ~4s with both responses acked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-25 02:37:36 -04:00
parent e6be945ff2
commit 19c53a6de1

View file

@ -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":[...]}`