Skip to content

Commit 1cc0ca2

Browse files
committed
Add TurtleTerm surface and bridge actions to agentd
1 parent 876dac3 commit 1cc0ca2

1 file changed

Lines changed: 138 additions & 8 deletions

File tree

assets/sourceos/bin/turtle-agentd

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
#!/usr/bin/env python3
22
"""TurtleTerm local agent gateway v0.
33
4-
This is the first executable scaffold for TurtleTerm's local agent gateway. It is
5-
intentionally conservative: it supports local JSON requests over stdin/stdout or
6-
a Unix socket, exposes read/inspect/propose flows, and records policy-required
7-
execution requests without executing commands.
4+
Conservative local JSON gateway for TurtleTerm. It supports local requests over
5+
stdin/stdout or a Unix socket, exposes read/inspect/propose flows, and records
6+
policy-required execution requests without executing commands.
87
"""
98

109
from __future__ import annotations
@@ -21,7 +20,6 @@ from typing import Any
2120

2221

2322
SCHEMA = "sourceos.turtle.agentd.response.v0"
24-
REQUEST_SCHEMA = "sourceos.turtle.agentd.request.v0"
2523

2624

2725
def utc_now() -> str:
@@ -64,7 +62,7 @@ def read_ndjson(path: Path, limit: int = 100) -> list[dict[str, Any]]:
6462

6563

6664
def decision_stub(action: str, reason: str, decision: str = "ask") -> dict[str, Any]:
67-
payload = {
65+
return {
6866
"id": f"urn:srcos:exec-decision:{uuid.uuid4().hex}",
6967
"type": "ExecutionDecision",
7068
"specVersion": "2.0.0",
@@ -73,7 +71,6 @@ def decision_stub(action: str, reason: str, decision: str = "ask") -> dict[str,
7371
"issuedAt": utc_now(),
7472
"action": action,
7573
}
76-
return payload
7774

7875

7976
def response(kind: str, data: dict[str, Any] | list[Any] | None = None, *, status: str = "ok") -> dict[str, Any]:
@@ -86,6 +83,80 @@ def response(kind: str, data: dict[str, Any] | list[Any] | None = None, *, statu
8683
}
8784

8885

86+
def default_surfaces() -> list[dict[str, Any]]:
87+
workspace = env("SOURCEOS_WORKSPACE", "default")
88+
cwd = os.getcwd()
89+
return [
90+
{
91+
"surface_id": "local/default",
92+
"label": "Local Workspace",
93+
"kind": "host",
94+
"workspace_id": workspace,
95+
"cwd": cwd,
96+
"execution_domain": "host",
97+
"policy_profile": "standard",
98+
"agent_allowed": False,
99+
"receipt_required": True,
100+
"provider": "turtleterm",
101+
},
102+
{
103+
"surface_id": "tmux/attached",
104+
"label": "Attached tmux Session",
105+
"kind": "tmux",
106+
"workspace_id": workspace,
107+
"cwd": cwd,
108+
"execution_domain": "host",
109+
"policy_profile": "standard",
110+
"agent_allowed": False,
111+
"receipt_required": True,
112+
"provider": "turtle-tmux",
113+
},
114+
{
115+
"surface_id": "neovim/context",
116+
"label": "Neovim Context",
117+
"kind": "editor-context",
118+
"workspace_id": workspace,
119+
"cwd": cwd,
120+
"execution_domain": "host",
121+
"policy_profile": "review-only",
122+
"agent_allowed": False,
123+
"receipt_required": True,
124+
"provider": "turtle.nvim",
125+
},
126+
{
127+
"surface_id": "cloudfog/local-devshell",
128+
"label": "CloudFog Local Devshell",
129+
"kind": "devshell",
130+
"workspace_id": workspace,
131+
"cwd": cwd,
132+
"execution_domain": "devshell",
133+
"policy_profile": "low-risk",
134+
"agent_allowed": False,
135+
"receipt_required": True,
136+
"provider": "cloudfog-shell",
137+
},
138+
{
139+
"surface_id": "agent-machine/local-agentpod",
140+
"label": "Agent Machine Local AgentPod",
141+
"kind": "agentpod",
142+
"workspace_id": workspace,
143+
"cwd": cwd,
144+
"execution_domain": "agent-machine",
145+
"policy_profile": "activation-required",
146+
"agent_allowed": False,
147+
"receipt_required": True,
148+
"provider": "agent-machine",
149+
},
150+
]
151+
152+
153+
def surface_by_id(surface_id: str | None) -> dict[str, Any] | None:
154+
for surface in default_surfaces():
155+
if surface["surface_id"] == surface_id:
156+
return surface
157+
return None
158+
159+
89160
def handle_request(request: dict[str, Any]) -> dict[str, Any]:
90161
action = request.get("action")
91162

@@ -108,6 +179,31 @@ def handle_request(request: dict[str, Any]) -> dict[str, Any]:
108179
})
109180
return response("sessions", {"sessions": list(sessions.values()), "event_stream": str(event_stream_path())})
110181

182+
if action == "surfaces":
183+
return response("surfaces", {"surfaces": default_surfaces()})
184+
185+
if action == "inspect_surface":
186+
surface_id = request.get("surface_id")
187+
surface = surface_by_id(surface_id)
188+
if not surface:
189+
return response("error", {"message": f"unknown surface: {surface_id}", "known_surfaces": [s["surface_id"] for s in default_surfaces()]}, status="error")
190+
return response("surface_inspection", {"surface": surface, "decision": decision_stub("surface.inspect", "Surface inspection is review-only.", "allow")})
191+
192+
if action == "request_surface_execution":
193+
surface_id = request.get("surface_id")
194+
command = request.get("command")
195+
surface = surface_by_id(surface_id)
196+
if not surface:
197+
return response("error", {"message": f"unknown surface: {surface_id}"}, status="error")
198+
if not command:
199+
return response("error", {"message": "command is required"}, status="error")
200+
return response("surface_execution_request", {
201+
"surface": surface,
202+
"command": command,
203+
"executionAllowed": False,
204+
"decision": decision_stub("surface.execute_command", "Surface execution requires Policy Fabric approval; turtle-agentd v0 does not execute commands."),
205+
})
206+
111207
if action == "inspect":
112208
session_id = request.get("session_id")
113209
events = [row for row in read_ndjson(event_stream_path(), limit=500) if not session_id or row.get("session_id") == session_id]
@@ -148,6 +244,40 @@ def handle_request(request: dict[str, Any]) -> dict[str, Any]:
148244
"decision": decision_stub("terminal.execute_command", "Execution requires Policy Fabric approval; turtle-agentd v0 does not execute commands."),
149245
})
150246

247+
if action == "superconscious_observe":
248+
return response("superconscious_observation", {
249+
"status": "recorded",
250+
"observation": request.get("observation", {}),
251+
"traceRef": f"urn:srcos:turtle:superconscious-observation:{uuid.uuid4().hex}",
252+
"decision": decision_stub("superconscious.observe", "Observation handoff is review-only; no shell authority granted.", "allow"),
253+
})
254+
255+
if action == "superconscious_propose":
256+
return response("superconscious_proposal", {
257+
"status": "deferred",
258+
"prompt": request.get("prompt", ""),
259+
"decision": decision_stub("superconscious.propose", "Superconscious proposal requires cognition loop adapter; execution remains gated.", "defer"),
260+
})
261+
262+
if action == "agent_machine_surfaces":
263+
return response("agent_machine_surfaces", {"surfaces": [s for s in default_surfaces() if s["provider"] == "agent-machine"]})
264+
265+
if action == "agent_machine_probe":
266+
return response("agent_machine_probe", {
267+
"status": "deferred",
268+
"decision": decision_stub("agent-machine.probe", "Agent Machine probe adapter is specified but not implemented in turtle-agentd v0.", "defer"),
269+
})
270+
271+
if action == "cloudfog_surfaces":
272+
return response("cloudfog_surfaces", {"surfaces": [s for s in default_surfaces() if s["provider"] == "cloudfog-shell"]})
273+
274+
if action == "cloudfog_inspect":
275+
surface_id = request.get("surface_id", "cloudfog/local-devshell")
276+
surface = surface_by_id(surface_id)
277+
if not surface or surface.get("provider") != "cloudfog-shell":
278+
return response("error", {"message": f"unknown cloudfog surface: {surface_id}"}, status="error")
279+
return response("cloudfog_surface_inspection", {"surface": surface, "decision": decision_stub("cloudfog.inspect", "CloudFog surface inspection is review-only.", "allow")})
280+
151281
if action == "receipts":
152282
session_id = request.get("session_id")
153283
root = receipts_root()
@@ -193,7 +323,7 @@ def serve_socket(path: Path) -> int:
193323
try:
194324
request = json.loads(raw.decode("utf-8"))
195325
result = handle_request(request)
196-
except Exception as exc: # noqa: BLE001 - defensive boundary
326+
except Exception as exc: # noqa: BLE001
197327
result = response("error", {"message": str(exc)}, status="error")
198328
conn.sendall((json.dumps(result, sort_keys=True) + "\n").encode("utf-8"))
199329
finally:

0 commit comments

Comments
 (0)