Textual UI adapter for agent execution.
The tool.result hook payload for an answered prompt that never completed.
Reported by _dispatch_terminal_tool_result_hooks when a teardown closes out a
row carrying a deferred success — the agent crashed, the stream ended, or the
user cancelled the turn. The guard there matches an already-settled row too, not
only one still awaiting its ToolMessage; see
ToolCallMessage.deferred_success_output.
The status stays "success" because the answers did reach the graph and only the
tool's own completion was lost, and ask_user results double as authorization
records; this distinct body is what lets an audit consumer tell "answers
delivered and the tool completed" from "answers delivered, then the turn died".
When the answers were never delivered at all the hook reports
ASK_USER_ANSWERED_NOT_DELIVERED_SUMMARY with tool_status="error" instead.
Hook contract: rewording it changes what those consumers see.
The tool.result hook payload for answers the turn discarded before resuming.
Reported with tool_status="error" when a sibling question in the same batch was
cancelled: textual_adapter aborts the turn before Command(resume=...), so
the resume payload — including this prompt's answers — is dropped, and the inline
widget is already unmounted, making the answers unrecoverable. The user answered,
but nothing downstream ever saw it.
Distinct from ASK_USER_ANSWERED_NO_RESULT_SUMMARY, and an error rather than a
success, precisely because ask_user results double as authorization records: a
"success" here would record an authorization that never took effect. Hook
contract: rewording it changes what audit consumers see.
One-line summary shown for an answered ask_user row before it is expanded.
Doubles as the tool.result hook payload's tool_output for an answered prompt
whose ToolMessage arrived, deliberately in place of the transcript, so
user-typed answers are not forwarded to hook scripts. When no ToolMessage
arrives the hook reports ASK_USER_ANSWERED_NO_RESULT_SUMMARY instead, while
the row still settles to this string. Rewording changes that hook contract as
well as the row; see textual_adapter and its tool.result tests.
The tool.result hook payload's tool_output for the cancelled path.
Rewording it changes that hook contract. Not rendered on any row: a live cancel
calls set_rejected (which records no output), and a transcript of (cancelled)
placeholders from a non-TUI client is summarized from the recorded status like
any other, so it reads as ASK_USER_ANSWERED_SUMMARY. The dismissal banner in
textual_adapter deliberately does not use this constant, and no longer even
shares its wording — the banner says "dismissed" where this says "cancelled".
That divergence is intentional: the banner is user-facing prose free to be
reworded, this is the hook contract. Do not "de-duplicate" them.
One-line summary shown for an ask_user prompt the middleware reported failed.
Also the tool.result hook payload's tool_output on that path — an answered
prompt whose ToolMessage came back with status="error" — deliberately in
place of the transcript, whose (error: ...) placeholders carry an arbitrary
detail string. Live widget failures are not this: they report their own error
text (see textual_adapter's invalid-payload and cancel branches). Rewording
changes that hook contract as well as the row.
The summaries an ask_user row may collapse to.
Narrows ToolCallMessage.defer_success so the transcript cannot be passed where
a summary belongs — the constraint that keeps user-typed answers out of
tool.result hook payloads.
Of the four *_SUMMARY constants below, only ASK_USER_ANSWERED_SUMMARY and
ASK_USER_FAILED_SUMMARY are members: the other two are hook bodies that no row
ever renders. Literal[...] cannot reference a plain string constant, so those
two values are restated here; both are annotated with this alias, so ty rejects
a reword that drifts. test_row_summary_alias_matches_its_constants pins it too.
Prefix for synthetic human messages (e.g. interrupt cancellation notices).
Such messages are written to the messages channel for the agent's benefit on
resume but are not user-authored, so they are filtered out of both the rendered
transcript and a thread's initial prompt. Shared here so the single producer
(textual_adapter) and its consumers (app, sessions) agree on one literal.
Sentinel tool_output used when formatting/coercing a tool result raises.
Lets both surfaces keep the terminal tool.result dispatch unconditional
without re-touching the offending content (whose __str__/__repr__ may itself
raise), so a hook consumer still sees the result rather than a dropped event.
Terminal status of a tool call, mirroring ToolMessage.status.
Build the tool.error hook payload (schema documented in hooks).
Build the tool.result hook payload (schema documented in hooks).
tool_output is capped to HOOK_TOOL_OUTPUT_LIMIT here so both surfaces
apply the identical cap regardless of where the raw output originates. When
the cap fires the value ends with TOOL_OUTPUT_TRUNCATION_MARKER (counted
within the cap, so the result never exceeds the limit) so a consumer can tell
a capped result from a short one. tool_args is intentionally not truncated
(see HOOK_TOOL_OUTPUT_LIMIT).
Build the tool.use hook payload (schema documented in hooks).
Classify buffered tool calls that never emitted a tool.use.
Both surfaces log the same end-of-stream diagnostic for tool calls still in
their buffer map when the stream ends: those whose args never parsed, and
those whose args parsed but whose tool_id stayed None (so tool.use was
gated out). Sharing the classification here keeps the two diagnostics from
drifting; each surface still emits its own log lines. parse_args is safe to
re-run (idempotent bar its one-shot warned latch).
Map a raw ToolMessage.status to the two-value hook domain, fail-closed.
"error" and "success" pass through. Any other present value — a future
provider status, an explicit None, or a typo — is unexpected and treated as
"error" (and logged), so an audit or notification hook is never told a
non-successful tool succeeded. Callers pass
getattr(message, "status", "success"), so a missing status arrives as
"success" and is not warned about.
Compute a stable key for buffering an in-progress streamed tool call.
Prefers the streaming index (stable across fragments of one call), then
the tool-call id, falling back to a positional placeholder so unrelated
id-less calls don't collide.
Build the LangGraph stream config dict.
Stamps the shared coding-agent-v1 trace-metadata contract via
build_coding_agent_metadata — identity block, plugin/runtime versions,
turn markers, and repo/git/cwd attribution — onto metadata. Metadata set
here propagates trace-wide to every run in the graph (root, llm, tool, and
subagent subgraphs), which is exactly what the contract's "always" and
"where-known" keys require, so the helper output is stamped once here.
Scope-restricted contract keys are deliberately not emitted. approval_policy
(root/interrupted only) and ls_subagent_id / ls_subagent_type (subagent
only) cannot live in this trace-wide metadata: LangGraph propagates each key
to all descendant runs (per-key config merge, langgraph#7926 /
deepagents#3634), so they would leak onto run types outside their contract
appliesTo set and fail validation. This runtime exposes no clean
per-run-type metadata seam to scope them, so they are omitted by design
rather than leaked. (Subagent runs still inherit the parent/root thread_id
and all required keys, satisfying the contract's grouping rule.)
Also injects the dcode version into metadata["lc_versions"] so LangSmith
traces can be correlated with specific releases. create_deep_agent supplies
the SDK version through the compiled graph config, and LangChain merges
nested metadata dictionaries so both versions survive at stream time.
Also records dcode_client_deepagents_version as a dcode-client diagnostic.
This describes the Deep Agents package installed alongside the TUI, which
can differ from a remote graph's Deep Agents runtime version. Editable
installs carry an +editable suffix, matching how the SDK stamps
lc_versions["deepagents"]; for sibling monorepo packages that suffix
identifies workspace HEAD relative to the pinned published SDK baseline.
Also records dcode_experimental=True when DEEPAGENTS_CODE_EXPERIMENTAL
is enabled, so experimental runs are filterable in trace metadata.
Also records dcode_auto_approve=True when auto-approve ("YOLO") mode is
active, so runs that ran tools without HITL approval are filterable in trace
metadata. This is a diagnostic key, not the contract-scoped approval_policy
key (see above), so it is safe to stamp trace-wide.
Also records dcode_term_program from TERM_PROGRAM when that is non-empty
after stripping, so traces are groupable by launch environment (e.g.
"iTerm.app", "vscode", "Apple_Terminal"). Blank values are treated as unset
to match every other reader of this variable — some shells export
TERM_PROGRAM="" rather than leaving it unset, and terminals that never set
it (Windows Terminal, ssh, the Linux console) omit the key entirely rather
than forming a junk grouping bucket. This is a diagnostic key, not part of
the contract.
Get the glyph set for the current charset mode.
Fire matching hook commands with payload serialized as JSON on stdin.
The event name is automatically injected into the payload under the
"event" key so callers don't need to duplicate it.
The blocking subprocess work is offloaded to a thread so the caller's
event loop is never stalled. Matching hooks run concurrently, each bounded
by HOOK_SUBPROCESS_TIMEOUT. Errors are logged and never propagated.
Schedule dispatch_hook as a background task with a strong reference.
Use this instead of bare create_task(dispatch_hook(...)) to prevent the
task from being garbage collected before completion.
Safe to call from sync code as long as an event loop is running.
Interleave hook decisions with human decisions in request order.
Extract @file mentions and return the text with resolved file paths.
Parses @file mentions from the input text and resolves them to absolute
file paths. Files that do not exist or cannot be resolved are excluded with
a warning printed to the console.
Email addresses (e.g., user@example.com) are automatically excluded by
detecting email-like characters before the @ symbol.
Backslash-escaped spaces in paths (e.g., @my\ folder/file.txt) are
unescaped before resolution. Tilde paths (e.g., @~/file.txt) are expanded
via Path.expanduser(). Only regular files are returned; directories are
excluded.
This function does not raise exceptions; invalid paths are handled internally with a console warning.
Create multimodal message content with text, images, and videos.
Convert ToolMessage content into a printable string.
Execute a task with output directed to Textual UI.
This is the Textual-compatible version of execute_task() that uses the TextualUIAdapter for all UI operations.
A question to ask the user.
Native tool-call data used by hook lifecycle owners.
Request payload sent via interrupt when asking the user questions.
Client-facing builder for the per-run graph context payload.
Callers populate this and pass it via context= to astream/ainvoke.
ConfigurableModelMiddleware and the interrupt_on when predicate read
it from request.runtime.context. In-process LangGraph coerces it into
CLIContextSchema (the registered context_schema); over the API it stays
a plain dict — which is why consumers handle both shapes.
In-progress state for a single streamed tool call.
args and args_parts are two representations of the arguments used one at
a time, depending on whether the provider delivers the value whole or in
JSON string fragments.
Collect file operation metrics during an interaction.
Result of UserPromptSubmit, including its prompt rewrites.
Track pasted images and videos in the current conversation.
Widget displaying an app message.
Widget displaying an assistant message with markdown support.
Uses MarkdownStream for smoother streaming instead of re-rendering
the full content on each update. Once a stream finishes, the message
is re-rendered from the complete source via Markdown.update() to
work around Textualize/textual#6518: MarkdownFence._update_from_block
refreshes the visible Label but leaves _highlighted_code pinned to
the first chunk, so any later recompose (click, focus change, theme
update) re-yields the stale value and wrapped fenced-code bodies vanish.
A full re-parse rebuilds every fence with correct internal state.
Streamed tokens are coalesced in _pending_append and flushed to the
MarkdownStream on a throttled timer (_STREAM_FLUSH_INTERVAL). Writing
every token immediately forced a markdown re-parse per chunk on the UI
event loop, which starved keyboard input while the model streamed.
Batching the writes keeps the event loop free so typing stays responsive.
Widget displaying a diff with syntax highlighting.
Compact grader result with complete, scrollable details on demand.
Widget displaying a summarization completion notification.
Widget displaying a tool call with collapsible output.
Tool outputs are shown as a 3-line preview by default. Press Ctrl+O to expand/collapse the full output. Shows an animated "Running..." indicator while the tool is executing.
A tool.result payload held back until the authoritative result arrives.
Used for an answered ask_user: the middleware owns the final status, and the
hook body must be the sanitized summary rather than the transcript of the
user's answers.
A validated rubric_evaluation_end event forwarded to the caller.
Bundling the two fields as named attributes (rather than two positional strings) makes the grading-run correlation self-documenting and removes the risk of transposing the run ID and the verdict at a call site.
Adapter for rendering agent output to Textual widgets.
This adapter provides an abstraction layer between the agent execution and the Textual UI, allowing streaming output to be rendered as widgets.
Discriminated union for the ask_user widget Future result.
Key for buffering an in-progress streamed tool call.
The streaming index (an int) when present, else the tool-call id (a
str), else a unique placeholder string (see tool_call_buffer_key). Exported
so both surfaces annotate their buffer maps identically rather than each
spelling the union — the exact drift this module exists to prevent.