Skip to content

Declare caller-owned parameter types in eight more Toad modules - #1153

Merged
trissim merged 10 commits into
mainfrom
next/toad-types-06
Oct 23, 2026
Merged

trissim merged 10 commits into
mainfrom
next/toad-types-06

Conversation

@trissim

@trissim trissim commented Oct 23, 2026 •

Copy link
Copy Markdown

Declare 249 previously bare parameters from the existing caller-owned types in the next eight heaviest modules. Nullable inputs and missing family capabilities remain explicit stops below. Base 58f0f8f0b47ead92ca1b94e152cf43739278d027; head aade2453ab79204b793ddf385a44383c332b4c34.

The initial draft recorded this same ranking at main 58f0f8f0b, excluding #1148's original eight. No parameters, functions or tests were deleted to lower the count. No ratchet, baseline, dependency lock or guard changes.

Module Main bare parameters Head bare parameters
wire_history.py 43 31
agent_binding.py 42 4
conversation_kind.py 41 2
conversation_submission.py 41 4
widgets/context_explorer.py 40 13
agent_surface.py 38 3
frame_presentation.py 37 3
acp/agent_controller.py 34 7
Selected eight 316 67
All src/toad 1577 1328

Explicit Any: 10 → 10. All 287 production Python modules parsed; zero omissions. Parameter deletions: 0. The new abstract method declarations are fully typed and do not remove existing parameters.

Source relationships checked by AST across Toad/Core/Textual: 1,003 modules; zero parse omissions. The retained annotations use ConversationSessionBinding for shared lifecycle calls, Conversation/CommsChatView for concrete views, Comms/RegistrySnapshot/SidebarModel for conversations, RenderTask[Result]/Renderer for work, ClientSessionRequest/QueueScope for ACP, and frame/effect/node classes for their actual callers. Annotation-only imports keep the runtime graph unchanged; no quoted annotations, Any/object escape, casts, Protocol, ignores, invented nullable inputs or Callable ellipses were added.

Defects fixed in separate commits:

  • cf147a292: acp/prompt.py advertised list[object] although its final SDK validation returns ContentBlock models. The declaration now uses the SDK's existing content type.
  • 25b387487: Conversation.new_terminal constructs ShellTerminal but declared Terminal. Its owner now declares the actual result.
  • 542f851d0: AgentConnection's concrete members already supplied configuration, presentation, begin_submission and accepts_failed_input, while the family omitted those capabilities. Added declarations on that existing family; both concrete branches remain concrete.
  • 5de3715ee: acp/prompt.py still called deleted ImageInput.to_rpc. It now uses FieldCodec.encode_record(image), deriving mimeType/type from Core's declared fields. Existing image tests cover this path; no new encoder or mapping roster.

Defects stopped: the event publisher/event correlation, native/wire loader/cursor/page/fragment correlations, ContextTree node payload presence, ShellOperationalSource sizing/directory capability, and frame running-driver lifetime are named individually below. These are source owner targets, not invented annotations.

Checks:

  • All ten changed production modules import with python -I, explicitly loading this checkout's src and the locked Core wheel. Compileall succeeds; NoAgentConnection and ACP Agent are concrete after the family declarations.
  • AST comparison confirms bodies/defaults/bases unchanged except the single intentional image encoder call migration; new abstract declarations are the existing family contract. Dynamic DeclaredFamily/MRO/singledispatch targets were read semantically; AST inventory alone does not resolve those dispatches, and unresolved correlations are stopped below.
  • Focused tests: six unique cases pass (submission family declaration, clipboard bound, and four image/reference cases). Initial image setup had a missing owned scratch parent, corrected before execution; the two image executions then found the removed to_rpc caller and pass after its repair. No full suite.
  • Mypy comparison adds no new diagnostic messages in the touched files. Hosted Debt ratchet passed on aade245, including production comparison and surface guards: https://github.com/OpenHCSDev/toad/actions/runs/39989650250. The workflow-dispatch-only full suite is skipped as configured.
Stopped parameter Caller evidence and existing owner to fix
wire_history.py:188 WireFragment._unhandled_message.message Dispatch declaration stop: this fallback is deliberately a singledispatch function in the class namespace, not an instance method; annotating its first argument makes Mypy treat it as self. WireFragment._roles needs a truthful static callable declaration.
wire_history.py:188 WireFragment._unhandled_message.fields Fragment keyword contract stop: of forwards heterogeneous fragment constructor fields. WireFragment should own the declared construction shape.
wire_history.py:193 WireFragment.__init_subclass__.roles Role-family stop: Membership, ConversationRole and NoticeRole markers lack a shared meaningful marker parent; their common object is banned.
wire_history.py:193 WireFragment.__init_subclass__.kwargs Subclass keyword construction contract has no declared owner; forwarded to the existing parent.
wire_history.py:199 WireFragment.of.fields IDEN-3/keyword shape: WireSource.read supplies shown plus page.evidence_for(message), which explicitly permits None (Core message_page.py:43–46). WireFragment construction needs one declared field/presence owner.
wire_history.py:203 WireFragment.from_message.message Core dataclass declaration stop: Mypy treats IncomingTranscript as requiring position/timestamp from TranscriptEvent despite IncomingTranscript owning them as ClassVar-backed properties. Do not restate those values at the caller; Core IncomingTranscript owns the constructor contract.
wire_history.py:203 WireFragment.from_message.reader Same Core IncomingTranscript constructor contract; incoming reader/message are known, but a partial annotation activates the contradictory dataclass declaration.
wire_history.py:203 WireFragment.from_message.fields Same heterogeneous fragment construction fields forwarded from of.
wire_history.py:231 WireFragment.blocks.shown Shown/MessageShown correlation stop: LineHistory._show accepts Shown but wire rendering needs MessageShown. The fragment/render parent must preserve the shown member type.
wire_history.py:256 MessageFragment.from_message.message Same Core IncomingTranscript constructor declaration stop; runtime properties own position/timestamp, not this caller.
wire_history.py:256 MessageFragment.from_message.reader Same Core IncomingTranscript constructor declaration stop.
wire_history.py:256 MessageFragment.from_message.fields Same fragment keyword construction contract from WireFragment.of.
wire_history.py:346 RestyledViewportRequest.source.owner History/source correlation stop: concrete caller is WireHistory, while ReplacementViewportRequest accepts ConversationHistory whose source lacks revision. ReplacementViewportRequest must preserve its source member.
wire_history.py:356 ObservedViewportRequest.source.owner Same history/source correlation; replacement requires the wire revision member.
wire_history.py:374 GrowingWireObservation.apply.history Cursor-domain stop: LineHistory.extend_source accepts native TranscriptCursor, while this caller carries wire AfterSequence. LineHistory/PreparedPageSource must preserve its native/wire cursor type.
wire_history.py:374 GrowingWireObservation.apply.source WireHistory.page_source can be WirePageBuffer or ClosedHistory; both have revision, common ReplaceablePageSource does not. That parent owns the missing capability.
wire_history.py:393 WirePageWork.execute.runtime Loader contract stop: inherited TranscriptPageWork.loader is a native-page Callable; WirePageWork needs WireSource.read. TranscriptPageWork/SavedHistory must preserve loader type alongside cursor.
wire_history.py:393 WirePageWork.execute.key Same loader contract stop; adding either parameter checks the body against the false native-callable loader declaration.
wire_history.py:407 WirePageBuffer.__init__.args SavedHistory constructor is native-loader/native-cursor typed, while actual wire callers pass WireSource/WireCut/runtime. SavedHistory must own the correlated loader/cursor constructor.
wire_history.py:407 WirePageBuffer.__init__.kwargs Same constructor contract; no invented keyword record.
wire_history.py:411 WirePageBuffer.replacement.through Shared replacement callers carry native and wire cursor domains (transcript_state.py). PreparedPageSource/ReplacementViewportRequest must retain cursor member identity.
wire_history.py:422 WirePageBuffer.status_thread.access Inherited SavedHistory.loader is a native callable, but this method requires WireSource.conversation/comms. SavedHistory must preserve the actual loader member.
wire_history.py:425 WirePageBuffer.command_context.view Same loader correlation; TargetContext also needs WireSource.me/project. No guessed narrower consumer annotation.
wire_history.py:467 WirePageBuffer.observe_changes.revision Same loader correlation; this path needs WireSource.observe, not native Callable. SavedHistory owns loader identity.
wire_history.py:470 WirePageBuffer.accept_page.history Caller contract is TranscriptSourcePreparation (transcript_source_preparation.py); implementation needs ConversationHistory.show_newest. Preparation owner must declare that capability.
wire_history.py:470 WirePageBuffer.accept_page.edge PreparedTranscriptPage.page is declared native TranscriptPage, but this method needs WirePage.accepts/source. PreparedTranscriptPage/TranscriptPageView must retain page member type.
wire_history.py:470 WirePageBuffer.accept_page.prepared Same native/wire page correlation; no union or broad base invented.
wire_history.py:480 WirePageBuffer.seen_page.history Inherited native callable loader cannot supply conversation; SavedHistory must retain WireSource. Native/wire history correlation remains open.
wire_history.py:480 WirePageBuffer.seen_page.seen widgets/wire_history.py filters TranscriptFragment.evidence before forwarding; TranscriptFragment lacks evidence/message, owned by WireFragment. History must preserve its fragment member.
wire_history.py:497 WirePageBuffer.status_text.read_only Boolean caller is known; declaration stopped on the method because its inherited native-callable loader lacks conversation. Fix the loader family rather than lie about its usable contract.
wire_history.py:500 WirePageBuffer.accepts.through No production caller found; argument is unused. No invented input type or parameter deletion for a count win.
agent_binding.py:81 AgentConnection.matches_agent.agent Publisher/agent correlation stop: core_event_carrier.py:29 exposes publisher as object; callers agent_surface.py:48 and conversation.py:1390 compare that publisher. CoreEventStream/CoreEventMessage must preserve its publisher type.
agent_binding.py:137 NoAgentConnection.matches_agent.agent Same publisher contract as AgentConnection.matches_agent; no guessed AgentBase narrowing.
agent_binding.py:152 NoAgentConnection.compact_context.instructions IDEN-3: conversation.py:1653–1657 forwards its existing nullable instructions. The instructions owner needs an absent member. Concrete absence caller/result: src/toad/widgets/conversation.py:1653–1657; the default value is None.
agent_binding.py:191 NoAgentConnection.request_compaction.instructions IDEN-3: slash_command.py:204–218 declares instructions=None and forwards it. CompactCommand needs an explicit absent-instructions member. Concrete absence caller/result: src/toad/slash_command.py:204–218; CompactCommand.instructions defaults None.
conversation_kind.py:136 ConversationKind.seen_page.seen Fragment correlation stop: widgets/wire_history.py:131–132 filters read_fragments(), declared tuple[TranscriptFragment,...] at transcript_history.py:577. Only WireFragment declares evidence/message. History/fragment parent must preserve the wire capability.
conversation_kind.py:289 DmConversation.compose_activity.view CommsChatView.conversation is ConversationKind (widgets/comms_chat.py:67), while this method needs peer from DmConversation. CommsChatView must preserve its conversation member type.
conversation_submission.py:92 ShellInputSubmission.execute.owner Scheduling-result stop: owner.view.run_worker receives partial(input_histories.shell.record), whose coroutine returns bool; Textual DOMNode.run_worker inference rejects that partial result. The scheduler/partial result contract must remain typed without a forwarder.
conversation_submission.py:255 SubmissionExecution.complete.reason IDEN-3: send initializes reason=None, then call_later(self.complete, reason); Agent.send_prompt may also return None. The stop-reason owner needs an absent member. Concrete absence caller/result: src/toad/conversation_submission.py:229 initializes None; :253 forwards it.
conversation_submission.py:263 ConversationSubmissions.__init__.view ConversationSessionBinding.init constructs this with self (conversation.py); that parent lacks concrete Conversation methods used through owner.view. ConversationSessionBinding must own the shared submission-view capability.
conversation_submission.py:346 ConversationSubmissions._send_now.session IDEN-3: send_bound_now captures agent.session_id and passes it to this method; acp/agent.py:87 permits None. SessionBinding must own the absent-session result. Concrete absence caller/result: src/toad/conversation_submission.py:340–343 captures/forwards session_id; src/toad/acp/agent.py:87 permits None.
widgets/context_explorer.py:59 ContextTreeIntent.search_current.selected IDEN-3: ContextExplorer._present passes intent.selected to _search, then _searching forwards it here; ContextTreeIntent.selected initializes to None. Concrete absence caller/result: src/toad/widgets/context_explorer.py:48 initializes None, :446 forwards intent.selected, :522 forwards it here.
widgets/context_explorer.py:66 ContextTreeIntent.restore_detail.resolve Nullable callback-result stop: ContextTree.restore_detail supplies resolve_reader; its lookup/return permits None. ContextTree must own an explicit missing-reader result. Concrete absence caller/result: src/toad/widgets/context_explorer.py:163 supplies resolve_reader; :168–179 performs a possibly missing lookup and returns it.
widgets/context_explorer.py:131 ContextTree._reconcile.parent Native-node payload stop: TreeNode[ContextNode].data is nullable and label is str
widgets/context_explorer.py:155 ContextTree._retire.node Same data-bearing native-node capability; owns_node is not a declared narrowing of node.data. Callers present/_reconcile pass native group or model nodes.
widgets/context_explorer.py:181 ContextTree._add.before IDEN-3: search_results and _expand omit before, retaining its None default. Native insertion-position owner needs an absent member. Concrete absence caller/result: src/toad/widgets/context_explorer.py:128 and :207 omit before, retaining :181 None default.
widgets/context_explorer.py:194 ContextTree.owns_node.node IDEN-3: selected passes context_nodes.get(...); ContextExplorer actions pass cursor_node, which can be None. Native node presence needs an explicit owner. Concrete absence caller/result: src/toad/widgets/context_explorer.py:229–230 forwards dictionary lookup; :528–529 forwards cursor_node.
widgets/context_explorer.py:201 ContextTree._expand.node Native-node payload stop: _add/node_expanded pass TreeNodes; the owns_node predicate has no declared data-bearing return contract. ContextTree must own the present payload capability.
widgets/context_explorer.py:218 ContextTree.node_expanded.event Native-node payload stop: Textual Tree.NodeExpanded.data remains nullable after owns_node. Same ContextTree capability owner, not a fabricated always-present payload.
widgets/context_explorer.py:224 ContextTree.node_collapsed.event Native-node payload stop: Textual Tree.NodeCollapsed.data remains nullable after owns_node. Same ContextTree capability owner.
widgets/context_explorer.py:228 ContextTree.selected.model Selection/node correlation stop: _selected forwards native node.data; Textual declares it nullable and context_nodes.get can miss. ContextTree must own the present selected model result.
widgets/context_explorer.py:308 ContextExplorer._observed._value IDEN-3: watch_thread_name, watch_thread_view and watch_project_path call _observed() without a value, keeping its None default. Concrete absence caller/result: src/toad/widgets/context_explorer.py:292, :306 and :321 call without the argument, retaining :308 None default.
widgets/context_explorer.py:518 ContextExplorer._searching.selected IDEN-3: _search forwards selected from intent.selected, whose initial value is None. ContextTreeIntent owns selection presence. Concrete absence caller/result: src/toad/widgets/context_explorer.py:48 → :446 → :512 → :518; initial selection is None.
widgets/context_explorer.py:577 ContextExplorer._selected.model Native-node payload stop: action_read_full/action_copy forward cursor_node.data after the untyped owns_node predicate. ContextTree must expose a present-model result.
agent_surface.py:45 AttachedSurfaceBinding.owns_publication.message Carrier capability stop: CoreEventMessage.publisher is object and event is CoreEvent, but this consumer requires agent controller/tools. CoreEventMessage/CoreEventStream must retain publisher and event member types.
agent_surface.py:67 AttachedSurfaceBinding.present_tool.message Same carrier capability stop; this method also needs ToolCall/ToolUpdate-specific event fields. No narrowing to a guessed event/publisher pair.
agent_surface.py:117 AttachedSurfaceBinding.prepare_shell.source ShellOperationalSource.attach passes self (shell_source.py:40), but working_directory/update_size are declared only on Shell. ShellOperationalSource should own those capabilities.
frame_presentation.py:95 FrameState.displayed.frame Running-driver lifetime stop: FramePresentation.present calls state.displayed(self), which needs app._driver.call_after_flush. Textual App declares _driver as Driver
frame_presentation.py:297 AnimationEffects.__init__.args Constructor forwarding stop: ConversationBlock and ToneStatic compose this mixin with different native Widget/Static constructors (block_navigation.py:294, frame_presentation.py). No shared constructor argument owner.
frame_presentation.py:297 AnimationEffects.__init__.kwargs Same native constructor keyword contract; no untyped passthrough disguised as object/Any.
acp/agent_controller.py:105 AgentController.bind_session.session_id IDEN-3: Agent.init assigns nullable session_id (acp/agent.py:87,106), forwarded by its setter :169. SessionBinding should own the absent session member. Concrete absence caller/result: src/toad/acp/agent.py:106 assigns the nullable constructor input (:87); :169 forwards it.
acp/agent_controller.py:128 AgentController.detach.target IDEN-3: attach forwards previous surface.target; DetachedSurfaceBinding.target is None (surface_binding.py:8). SurfaceBinding should own detach behavior without nullable target forwarding. Concrete absence caller/result: src/toad/acp/agent_controller.py:115–119 forwards previous target; src/toad/surface_binding.py:8 declares target=None.
acp/agent_controller.py:278 AgentController._prompt.content Transport content stop: _submit passes validated SDK ContentBlock models, while submit_blocks passes raw dictionaries. AgentProcess/RPC input boundary must own one decoded content contract. Prompt build return declaration is fixed separately.
acp/agent_controller.py:278 AgentController._prompt.command IDEN-3: submit_blocks forwards command, whose existing default is None. PromptRequest needs a meaningful absent-command member. Concrete absence caller/result: src/toad/acp/agent_controller.py:269–274 forwards its existing command=None default.
acp/agent_controller.py:310 AgentController._prompt_failed.command IDEN-3: _prompt forwards the same nullable command to its failure path. Same request-family owner. Concrete absence caller/result: src/toad/acp/agent_controller.py:305–307 forwards the same nullable command.
acp/agent_controller.py:352 AgentController.compact_context.instructions IDEN-3: AgentBase.compact_context forwards Conversation.compact_context instructions (conversation.py:1653), declared nullable. Concrete absence caller/result: src/toad/agent.py:89–90 forwards instructions from conversation.py:1653–1657, default None.
acp/agent_controller.py:358 AgentController._compact_context.instructions IDEN-3: compact_context forwards the same nullable instructions into the operation. The compact request owns absence. Concrete absence caller/result: src/toad/acp/agent_controller.py:352–355 forwards the existing None default.

Assumed:

  • Source callers and installed dependency declarations define the annotation contract; no runtime type filtering is added.
  • Genuine nullable inputs retain their existing defaults/behavior until their owning family supplies an absent member.
  • Annotations do not establish UI usability. This change makes no installed-runtime activation or live-message claim.

@trissim
trissim marked this pull request as ready for review October 23, 2026 16:51
Copilot AI balanced review requested due to automatic review settings October 23, 2026 16:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim
trissim merged commit eee930e into main Oct 23, 2026
2 checks passed
@trissim

trissim commented Oct 23, 2026

Copy link
Copy Markdown
Author

Post-merge review: clean (commander review fork; this PR was merged by its author with no review, which is a process strike, not a code defect).

  • 10 files, about 2,400 diff lines: no added Any, cast, type: ignore or new isinstance; the 3 | None lines were already there; no name deleted.
  • Two real fixes ride along: prompt.py now encodes images through FieldCodec.encode_record (agent-comms 41206be3d deleted ImageInput.to_rpc, so image prompts had been crashing; same {type, data, mimeType} wire shape), and AgentConnection declares begin_submission/accepts_failed_input abstract, which both concrete subclasses (AgentBase agent.py:57, NoAgentConnection agent_binding.py:158) already implement.
  • except ScreenStackError, UnknownModeError: is valid on the declared floor (requires-python >=3.14, PEP 758).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants