feat(t3,views,server,watch): t3 code orchestration protocol 2

This commit is contained in:
hh committed 2026-10-16 01:48:32 +02:00
1 parent 133bc6e915
commit f4c54b6e16
7 files changed
+448 -294

No files matched your search

+138 -104
View File
@@ -8,6 +8,7 @@ from fastmcp.exceptions import ToolError
from t3code_mcp.config import Machine, ModelSpec, load_machines
from t3code_mcp.server import Registry, build_server
from t3code_mcp.t3 import T3Error
class FakeT3:
@@ -30,7 +31,7 @@ class FakeT3:
200,
json={
"label": self.label,
"serverVersion": "0.0.36",
"serverVersion": "0.1.0",
"platform": {"os": "linux"},
},
)
@@ -38,6 +39,8 @@ class FakeT3:
return httpx.Response(
401, json={"code": "auth_invalid", "reason": "missing_credential"}
)
if request.headers.get("x-t3-orchestration-protocol") != "2":
return httpx.Response(400, json={"code": "invalid_request"})
if path == "/api/orchestration/shell":
return httpx.Response(200, json=self._shell())
if path.startswith("/api/orchestration/threads/"):
@@ -48,100 +51,127 @@ class FakeT3:
)
self._tick(thread_id)
return httpx.Response(
200, json={"snapshotSequence": 1, "thread": self.threads[thread_id]}
200, json={"snapshotSequence": 1, "projection": self.threads[thread_id]}
)
if path == "/api/orchestration/dispatch":
command = json.loads(request.content)
self.commands.append(command)
return self._dispatch(command)
return httpx.Response(404, json={"code": "not_found"})
async def rpc(self, tag: str, payload: dict[str, Any]) -> Any:
assert tag == "orchestration.dispatchCommand"
self.commands.append(payload)
return self._dispatch(payload)
def _shell(self) -> dict[str, Any]:
threads = [
{
k: v
for k, v in t.items()
if k not in ("messages", "activities", "checkpoints")
}
| {"hasPendingUserInput": self.pending_input, "hasPendingApprovals": False}
for t in self.threads.values()
]
return {
"snapshotSequence": 1,
"projects": self.projects,
"threads": threads,
"updatedAt": "now",
}
threads = []
for projection in self.threads.values():
thread = projection["thread"]
runs = projection["runs"]
pending = [
r for r in projection["runtimeRequests"] if r["status"] == "pending"
]
threads.append(
{
"id": thread["id"],
"projectId": thread["projectId"],
"title": thread["title"],
"archivedAt": None,
"status": runs[-1]["status"] if runs else "idle",
"lastError": self.fail_with,
"pendingRuntimeRequest": (
{"id": pending[-1]["id"], "kind": pending[-1]["kind"]}
if pending
else None
),
"hasActionableProposedPlan": False,
}
)
return {"snapshotSequence": 1, "projects": self.projects, "threads": threads}
def _tick(self, thread_id: str) -> None:
thread = self.threads[thread_id]
if self.fail_with and thread.get("_pending_turn"):
thread["session"] = {"status": "stopped", "lastError": self.fail_with}
projection = self.threads[thread_id]
if self.pending_input and not projection["runtimeRequests"]:
projection["runtimeRequests"].append(
{"id": "req-0", "kind": "user_input", "status": "pending"}
)
if not projection["runs"]:
return
if thread.get("_pending_turn"):
thread["latestTurn"] = {
"turnId": thread.pop("_pending_turn"),
"state": "running",
}
run = projection["runs"][-1]
if self.fail_with and run["status"] == "running":
run["status"] = "failed"
projection["turnItems"].append(
{
"type": "error",
"runId": run["id"],
"status": "failed",
"failure": {"message": self.fail_with},
}
)
return
latest = thread["latestTurn"]
if latest and latest["state"] == "running":
latest["_polls"] = latest.get("_polls", 0) + 1
if latest["_polls"] > self.polls_until_done:
latest["state"] = "completed"
thread["messages"].append(
{"role": "assistant", "text": "done: report", "streaming": False}
if run["status"] == "running":
run["_polls"] = run.get("_polls", 0) + 1
if run["_polls"] > self.polls_until_done:
run["status"] = "completed"
projection["messages"].append(
{
"role": "assistant",
"text": "done: report",
"streaming": False,
"runId": run["id"],
}
)
thread["session"]["status"] = "ready"
def _dispatch(self, command: dict[str, Any]) -> httpx.Response:
def _dispatch(self, command: dict[str, Any]) -> dict[str, Any]:
kind = command["type"]
if kind == "thread.create":
self.threads[command["threadId"]] = {
"id": command["threadId"],
"projectId": command["projectId"],
"title": command["title"],
"modelSelection": command["modelSelection"],
"runtimeMode": command["runtimeMode"],
"latestTurn": None,
"session": {"status": "idle", "lastError": None},
"thread": {
"id": command["threadId"],
"projectId": command["projectId"],
"title": command["title"],
"modelSelection": command["modelSelection"],
"runtimeMode": command["runtimeMode"],
"archivedAt": None,
},
"runs": [],
"messages": [],
"activities": [],
"turnItems": [],
"runtimeRequests": [],
"checkpoints": [],
"archivedAt": None,
"updatedAt": "now",
"providerSessions": [],
"providerTurns": [],
"plans": [],
}
elif kind == "thread.turn.start":
thread = self.threads[command["threadId"]]
thread["messages"].append(
{"role": "user", "text": command["message"]["text"], "streaming": False}
)
thread["latestTurn"] = None
thread["_pending_turn"] = "turn-" + command["commandId"][:4]
thread["session"] = {"status": "running", "lastError": None}
if "modelSelection" in command:
thread["modelSelection"] = command["modelSelection"]
elif kind == "thread.turn.interrupt":
thread = self.threads[command["threadId"]]
thread.pop("_pending_turn", None)
thread["latestTurn"] = {"turnId": "t", "state": "interrupted"}
elif kind == "thread.user-input.respond":
self.threads[command["threadId"]]["activities"].append(
elif kind == "message.dispatch":
assert command["dispatchMode"] == {"type": "start_immediately"}
projection = self.threads[command["threadId"]]
run_id = f"run-{len(projection['runs']) + 1}"
projection["runs"].append(
{
"kind": "user-input.resolved",
"summary": "User input submitted",
"tone": "info",
"payload": {
"requestId": command["requestId"],
"answers": command["answers"],
},
"id": run_id,
"ordinal": len(projection["runs"]) + 1,
"status": "running",
}
)
else:
return httpx.Response(
400, json={"code": "invalid_request", "reason": "invalid_command"}
projection["messages"].append(
{
"role": "user",
"text": command["text"],
"streaming": False,
"runId": run_id,
}
)
return httpx.Response(200, json={"sequence": len(self.commands)})
if "modelSelection" in command:
projection["thread"]["modelSelection"] = command["modelSelection"]
elif kind == "run.interrupt":
run = self.threads[command["threadId"]]["runs"][-1]
assert run["id"] == command["runId"]
run["status"] = "interrupted"
elif kind == "runtime-request.respond":
for request in self.threads[command["threadId"]]["runtimeRequests"]:
if request["id"] == command["requestId"]:
request["status"] = "resolved"
else:
raise T3Error(0, {"_tag": "OrchestrationV2DispatchCommandError"})
return {"sequence": len(self.commands)}
PROJECTS_MAC = [
@@ -209,7 +239,7 @@ def registry(fakes: dict[str, FakeT3]) -> Registry:
projects=("/root/projects", "/root/projects/*"),
),
}
return Registry(machines, transport=Router(fakes))
return Registry(machines, transport=Router(fakes), rpc=lambda name: fakes[name].rpc)
@pytest.fixture
@@ -254,7 +284,7 @@ async def test_dispatch_wait_and_thread(
assert started["title"] == "write README"
assert started["model"] == "claudeAgent/claude-opus-5 effort=high"
kinds = [c["type"] for c in fakes["mac"].commands]
assert kinds == ["thread.create", "thread.turn.start"]
assert kinds == ["thread.create", "message.dispatch"]
assert fakes["mac"].commands[0]["modelSelection"] == {
"instanceId": "claudeAgent",
"model": "claude-opus-5",
@@ -303,8 +333,8 @@ async def test_follow_up_uses_same_thread(
assert again["thread_id"] == started["thread_id"]
assert [c["type"] for c in fakes["mac"].commands] == [
"thread.create",
"thread.turn.start",
"thread.turn.start",
"message.dispatch",
"message.dispatch",
]
@@ -346,7 +376,7 @@ async def test_wait_timeout_and_interrupt(
await client.call_tool("t3_interrupt", {"thread_id": started["thread_id"]})
)
assert stopped["state"] == "interrupting"
assert fakes["dell"].commands[-1]["type"] == "thread.turn.interrupt"
assert fakes["dell"].commands[-1]["type"] == "run.interrupt"
view = data(
await client.call_tool("t3_thread", {"thread_id": started["thread_id"]})
)
@@ -439,7 +469,7 @@ async def test_wait_returns_on_runtime_failure(
"t3_wait", {"thread_id": started["thread_id"], "timeout": 5}
)
)
assert waited["state"] == "starting"
assert waited["state"] == "failed"
assert waited["error"] == "Claude runtime stream failed."
assert waited["waited"] < 1
@@ -449,10 +479,12 @@ def shell_thread(
) -> dict[str, Any]:
return {
"id": tid,
"latestTurn": {"turnId": "t", "state": state} if state else None,
"session": {"status": "stopped" if error else "running", "lastError": error},
"hasPendingUserInput": user_input,
"hasPendingApprovals": False,
"status": state or "idle",
"lastError": error,
"pendingRuntimeRequest": (
{"id": "req-1", "kind": "user_input"} if user_input else None
),
"hasActionableProposedPlan": False,
}
@@ -533,25 +565,27 @@ async def test_answer_pending_question(
await client.call_tool(
"t3_answer", {"thread_id": tid, "answers": {"q1": "yes"}}
)
fakes["mac"].threads[tid]["activities"].append(
projection = fakes["mac"].threads[tid]
projection["runtimeRequests"].append(
{"id": "req-1", "kind": "user_input", "status": "pending"}
)
projection["turnItems"].append(
{
"kind": "user-input.requested",
"summary": "User input requested",
"tone": "info",
"payload": {
"requestId": "req-1",
"questions": [
{
"id": "q1",
"header": "Deploy",
"question": "Deploy now?",
"options": [
{"label": "yes", "description": "go"},
{"label": "no", "description": "wait"},
],
}
],
},
"type": "user_input_request",
"runId": "run-1",
"status": "waiting",
"requestId": "req-1",
"questions": [
{
"id": "q1",
"header": "Deploy",
"question": "Deploy now?",
"options": [
{"label": "yes", "description": "go"},
{"label": "no", "description": "wait"},
],
}
],
}
)
view = data(await client.call_tool("t3_thread", {"thread_id": tid}))
@@ -569,7 +603,7 @@ async def test_answer_pending_question(
assert answered["answered"] == ["q1"]
cmd = fakes["mac"].commands[-1]
assert (
cmd["type"] == "thread.user-input.respond"
cmd["type"] == "runtime-request.respond"
and cmd["requestId"] == "req-1"
and cmd["answers"] == {"q1": "yes"}
)