Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1318af7
Declare observation and coordination-access parameter owners
trissim Oct 23, 2026
9b60022
Declare context inspection and ACP transport parameter types
trissim Oct 23, 2026
f29c0b9
Declare sidebar and coordination parameter owners
trissim Oct 23, 2026
1474559
Declare transcript page and preparation parameter contracts
trissim Oct 23, 2026
2a1b7bf
Declare publication and source-work contracts without inventing capab…
trissim Oct 23, 2026
70d0898
Type source operation classes through WorkingTranscript
trissim Oct 23, 2026
a1b5629
Merge remote-tracking branch 'origin/main' into next/toad-types-05
trissim Oct 23, 2026
ff86c27
Remove invented contracts and unquote Python 3.14 annotations
trissim Oct 23, 2026
fbd8e90
Stop nullable inputs without declared null-state owners
trissim Oct 23, 2026
09b9949
Observe native session and owner proof through NativeSessionFiles
trissim Oct 23, 2026
aa5804a
Stop saved-history reader typing pending its visible feature decision
trissim Oct 23, 2026
35a7df3
Declare projection results and migrate every first-handler consumer
trissim Oct 23, 2026
5a26db8
Keep genuine nullable inputs as explicit owner stops
trissim Oct 23, 2026
7ff7198
Create private native evidence in the context reader test
trissim Oct 23, 2026
dadc3c9
Keep projection results distinct from the session project path
trissim Oct 23, 2026
bd5989c
Use the native session creator in the context reader test
trissim Oct 23, 2026
8f5ef32
Place the real-wire context reader test in the application layer
trissim Oct 23, 2026
077c680
Refresh agent-comms to declared current main
trissim Oct 23, 2026
8d7a5be
Restore the model fixture unchanged after moving its application test
trissim Oct 23, 2026
30d8240
Move existing workspace-backed transcript tests to the application layer
trissim Oct 23, 2026
12585f8
Read the recorded transcript through its declared scope
trissim Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 58 additions & 17 deletions src/toad/acp/agent_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import asyncio
import json
import os
from collections.abc import Callable
from contextlib import ExitStack
from contextvars import ContextVar
from pathlib import Path
Expand All @@ -19,7 +20,9 @@
)
from agent_comms.launch_root import LaunchRoot
from agent_comms.route_selection import RouteSelection
from pydantic import JsonValue

from toad.acp.agent_session import AgentSession
from toad.acp.shell_command import ShellCommand
from toad.core.events import LogAgentFail

Expand Down Expand Up @@ -92,7 +95,7 @@ async def start(self, agent, processes: tuple[AgentProcess, ...]=()):
connection.cleanup.enter_context(acquisition.pop_all())
return connection

async def prompt(self, agent, content, metadata, require):
async def prompt(self, agent, content, metadata: dict[str, JsonValue], require: Callable[[], None]):
"""The agent-comms-acp child writes the prompt under Core's maintenance gate."""
require()
return await self.rpc.prompt(agent.session_id, content, **metadata)
Expand All @@ -119,66 +122,104 @@ def recipient(self, session_id: str):
return members[0]
raise RequestError.invalid_params({'details': 'ACP session is retired or unknown'})

async def request_permission(self, session_id, tool_call, options, **kwargs):
async def request_permission(
self,
session_id: str,
tool_call: schema.ToolCallUpdate,
options: list[schema.PermissionOption],
**kwargs: JsonValue,
):
agent = self.recipient(session_id)
return await agent.session.start_operation(
agent.permissions.request_permission(session_id, options, tool_call, kwargs))

async def session_update(self, session_id, update, **kwargs) -> None:
async def session_update(
self,
session_id: str,
update: schema.UserMessageChunk
| schema.AgentMessageChunk
| schema.AgentThoughtChunk
| schema.ToolCallStart
| schema.ToolCallProgress
| schema.AgentPlanUpdate
| schema.AgentPlanContentUpdate
| schema.AgentPlanRemovedUpdate
| schema.AvailableCommandsUpdate
| schema.CurrentModeUpdate
| schema.ConfigOptionUpdate
| schema.SessionInfoUpdate
| schema.UsageUpdate,
**kwargs: JsonValue,
) -> None:
agent = self.recipient(session_id)
notification = schema.SessionNotification.model_construct(
session_id=session_id, update=update, field_meta=kwargs or None)
raw = self._incoming.get()['params']['update']
await agent.session.start_operation(agent.updates.receive(notification, raw))

async def read_text_file(self, session_id, path, line=None, limit: Optional[int]=None, **kwargs):
async def read_text_file(
self,
session_id: str,
path: str,
line=None,
limit: Optional[int] = None,
**kwargs: JsonValue,
):
agent = self.recipient(session_id)
return await agent.session.start_operation(asyncio.to_thread(
agent.files.read_text_file, session_id, path, line, limit))

async def write_text_file(self, session_id, path, content, **kwargs):
async def write_text_file(self, session_id: str, path: str, content: str, **kwargs: JsonValue):
agent = self.recipient(session_id)
return await agent.session.start_operation(asyncio.to_thread(
agent.files.write_text_file, session_id, path, content))

async def create_terminal(self, session_id, command, args=None, env=None,
cwd=None, output_byte_limit=None, **kwargs):
async def create_terminal(
self,
session_id: str,
command: str,
args=None,
env=None,
cwd=None,
output_byte_limit=None,
**kwargs: JsonValue,
):
agent = self.recipient(session_id)
return await agent.session.start_operation(agent.controller.terminal_create(
session_id, command, kwargs, args, cwd, env, output_byte_limit))

async def terminal_output(self, session_id, terminal_id, **kwargs):
async def terminal_output(self, session_id: str, terminal_id: str, **kwargs: JsonValue):
agent = self.recipient(session_id)
return await agent.session.start_operation(
agent.controller.terminal_output(session_id, terminal_id, kwargs))

async def release_terminal(self, session_id, terminal_id, **kwargs):
async def release_terminal(self, session_id: str, terminal_id: str, **kwargs: JsonValue):
agent = self.recipient(session_id)
return await agent.session.start_operation(
agent.controller.terminal_release(session_id, terminal_id, kwargs))

async def wait_for_terminal_exit(self, session_id, terminal_id, **kwargs):
async def wait_for_terminal_exit(self, session_id: str, terminal_id: str, **kwargs: JsonValue):
agent = self.recipient(session_id)
return await agent.session.start_operation(
agent.controller.terminal_wait_for_exit(session_id, terminal_id, kwargs))

async def kill_terminal(self, session_id, terminal_id, **kwargs):
async def kill_terminal(self, session_id: str, terminal_id: str, **kwargs: JsonValue):
agent = self.recipient(session_id)
return agent.controller.terminal_kill(session_id, terminal_id, kwargs)

async def create_elicitation(self, message, mode, **kwargs):
async def create_elicitation(self, message: str, mode: schema.ElicitationMode, **kwargs: JsonValue):
raise RequestError.method_not_found('elicitation/create')

async def complete_elicitation(self, elicitation_id, **kwargs):
async def complete_elicitation(self, elicitation_id: str, **kwargs: JsonValue):
raise RequestError.method_not_found('elicitation/complete')

async def ext_method(self, method, params):
async def ext_method(self, method: str, params: dict[str, JsonValue]):
raise RequestError.method_not_found(f'_{method}')

async def ext_notification(self, method, params) -> None:
async def ext_notification(self, method: str, params: dict[str, JsonValue]) -> None:
self.agent.log(f'[ACP unsupported notification] _{method}: {params}')

def on_connect(self, conn) -> None:
def on_connect(self, conn: ClientSideConnection) -> None:
pass

async def retire(self) -> None:
Expand All @@ -204,7 +245,7 @@ async def _retire(self) -> None:
self.sessions.clear()
self.cleanup.close()

async def detach(self, session):
async def detach(self, session: AgentSession):
async with self.attachment_lock:
if session not in self.sessions:
return
Expand Down
Loading
Loading