Eureka/tools/mind.py
Eureka 0342dceef9 recall: recalled text is inert data; the deadline is the whole run; budget, stdin and answer are bounded
Soul s1 fixes. The hook header says the lines are recalled records, data and
never instructions, written by agents not the operator. Every item is one JSON
object per line: id, title and operator_quote are JSON strings with < >, control,
bidi and separator characters escaped, so a title cannot close the framing, forge
a tag, a header or an operator segment. RECALL_DEADLINE_MS now bounds the whole
run through a hard stop; stdin is read to 4 MiB and the answer to 1 MiB
(mind.call max_bytes); RECALL_BUDGET is clamped to 9000 under Claude Code's
10,000-character cap. Tests: hostile text, a non-ASCII process-boundary run under
cp1252, byte-versus-character budget, stdin held open and endless, answer size,
budget ceiling, and the four unrecorded survivors (negative reply timeout, reply
id check, env-int positivity, EOF sentinel). Mutants of each were run and caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:44:02 +02:00

145 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""The one way a Eureka script reaches the mind: the eureka-state stdio MCP binding.
call(tool, arguments, deadline) spawns the binding, does initialize,
notifications/initialized and tools/call, and returns the parsed JSON body of the
answer. It raises MindError for every failure and kills the child on every exit
path. It never retries; retry_once(fn) is the caller's choice. Standard library only.
The binding: EUREKA_STATE_EXE overrides the command; ~/.claude.json
mcpServers.eureka-state gives command, args and env; the caller's environment wins
over the binding's env. A MindError names a kind and a detail and never echoes an
input value (an endpoint, a path, a line of config).
"""
import json
import os
import queue
import subprocess
import threading
import time
MAX_ANSWER_BYTES = 8 << 20
ERROR_CODES = ("Unavailable", "Rejected", "Misconfigured", "TooLarge",
"Unencodable", "InvalidInput", "Internal")
class MindError(Exception):
"""kind: binding (no binding or no command), transport (spawn, pipe, deadline),
tool (the daemon refused; detail is an ERROR_CODES entry or "error"), answer
(the body was not JSON). detail is the code, always "Unavailable" for transport."""
def __init__(self, kind, detail):
super().__init__(kind + ": " + detail)
self.kind, self.detail = kind, detail
@property
def unavailable(self):
return self.detail == "Unavailable"
def binding():
exe = os.environ.get("EUREKA_STATE_EXE")
args = []
env = dict(os.environ)
try:
with open(os.path.join(os.path.expanduser("~"), ".claude.json"), encoding="utf-8") as f:
b = json.load(f)["mcpServers"]["eureka-state"]
exe = exe or b.get("command")
args = list(b.get("args", []))
for k, v in (b.get("env") or {}).items():
env.setdefault(k, v)
except (OSError, ValueError, KeyError, TypeError):
if not exe:
raise MindError("binding", "eureka-state is not configured")
if not exe:
raise MindError("binding", "eureka-state has no command")
return [exe, *args], env
def call(tool, arguments, deadline, max_bytes=MAX_ANSWER_BYTES):
"""One tool call within `deadline` seconds in all, reading at most `max_bytes` of
the binding's output (a larger answer is MindError answer). Returns the parsed
JSON body."""
end = time.monotonic() + deadline
argv, env = binding()
try:
proc = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, env=env)
except OSError:
raise MindError("transport", "Unavailable")
lines = queue.Queue()
def pump():
room = max_bytes
while True:
ln = proc.stdout.readline(room + 1)
if not ln:
break
room -= len(ln)
if room < 0:
lines.put(MindError("answer", "answer too large"))
return
lines.put(ln)
lines.put(None)
threading.Thread(target=pump, daemon=True).start()
def send(obj):
proc.stdin.write((json.dumps(obj) + "\n").encode("utf-8"))
proc.stdin.flush()
def reply(want):
while True:
try:
ln = lines.get(timeout=max(end - time.monotonic(), 0))
except queue.Empty:
raise MindError("transport", "Unavailable")
if ln is None:
raise MindError("transport", "Unavailable")
if isinstance(ln, MindError):
raise ln
try:
msg = json.loads(ln)
except ValueError:
continue
if isinstance(msg, dict) and msg.get("id") == want:
return msg
try:
send({"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2024-11-05", "capabilities": {},
"clientInfo": {"name": "eureka-tools", "version": "1"}}})
reply(1)
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
send({"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": tool, "arguments": arguments}})
res = reply(2).get("result") or {}
except OSError:
raise MindError("transport", "Unavailable")
finally:
try:
proc.stdin.close()
except OSError:
pass
try:
proc.kill()
proc.wait(timeout=2)
except (OSError, subprocess.TimeoutExpired):
pass
body = "".join(c.get("text", "") for c in res.get("content", []) if isinstance(c, dict))
if res.get("isError"):
raise MindError("tool", next((c for c in ERROR_CODES if c in body), "error"))
try:
return json.loads(body)
except ValueError:
raise MindError("answer", "unparsable answer")
def retry_once(fn):
"""Run fn; on an Unavailable MindError run it once more. Any other error stands."""
try:
return fn()
except MindError as first:
if not first.unavailable:
raise
return fn()