module DotBotTranscript
The DotBot panel's transcript: a conversation's rows as the widget
tree one tab draws, rebuilt row by row. A row whose content has not
changed since the last draw is the same subtree it was, its callbacks
carried over, so a delta streaming into the last answer rebuilds that
answer and nothing above it. The newest rows are drawn and the rest wait
behind a "Show earlier messages" row, so what a redraw costs is set by
how much is on screen rather than by how long the conversation is.
imagePaths(text: string) → void
Every image path a tool result mentions.
`capture` writes a file and answers with its path — so a transcript that
only printed the path made the reader open the Files tab to see the shot
the agent just took. Matched on the extension rather than on the tool's
name, because several tools produce images and the reader wants all of
them.
| arg | type | description |
|---|
| text | string | |
readableArgs(args: any) → void
One argument table as lines a person reads, rather than the JSON it was
stored as.
The value is parsed — it arrives as JSON and has to be — but a raw object
printed back at the reader is the transcript showing its filing system
instead of what happened. A long value is cut, because the point of the row
is what the call WAS, and an argument carrying a whole file is answered by
opening the file.
| arg | type | description |
|---|
| args | any | |
scriptArg(args: any) → string
The program a carrier call was given, or nil for an ordinary tool call.
One argument named `script` and nothing else: that is what `tool_exec`
takes. A call with other arguments is an ordinary call and reads as one.
| arg | type | description |
|---|
| args | any | |
looksLikeMarkdown(text: string) → boolean
Whether a tool's answer is a written page rather than data.
Markers only, checked at the start of a line so a stray `#` inside a value
does not turn a data dump into a document: a heading, a bullet, a fence, or
bold anywhere. JSON is excluded outright — an object or array is data even
when a string inside it happens to contain a `*`.
| arg | type | description |
|---|
| text | string | |
millis(v: any) → string
A number of milliseconds as a person reads one.
traceLine(record: any) → string
One record of a tool answer's trace as the line a person reads.
| arg | type | description |
|---|
| record | any | |
resultShape(text: string) → void
A tool answer taken apart into the things a reader wants separately.
`nil` when the text is not an envelope this recognises, which is the honest
answer: the raw text is then shown rather than a shape invented for it.
| arg | type | description |
|---|
| text | string | |
imageAvailable(path: string) → boolean
| arg | type | description |
|---|
| path | string | |
renderMarkdown(P: any, md: string) → void
Markdown as widgets: an answer, or a page a tool answered.
The shared renderer draws the prose. A table and a fenced code block are
drawn here (`View.splitMarkdown` lifts them out), because the renderer
draws neither so that it can wrap or be selected: a table came out as raw
`| a | b |` lines running past the panel's right edge. The renderer's
verbatim lines (an indented block) are made to wrap and be selectable for
the same reason.
| arg | type | description |
|---|
| P | any | |
| md | string | |
inline(text: string) → void
| arg | type | description |
|---|
| text | string | |
toolDetailNodes(source: any, row: any, key: string) → void
What an opened tool row shows: what went in, what came back, and any
picture the call produced.
`nil` when neither the arguments nor the result reached the panel — that is
what decides whether the row is openable at all, so a chevron never appears
over nothing.
| arg | type | description |
|---|
| source | any | |
| row | any | |
| key | string | |
heading(text: string) → void
| arg | type | description |
|---|
| text | string | |
mono(text: string) → void
| arg | type | description |
|---|
| text | string | |
data(value: any) → void
A decoded value, rendered. One row per line: the field's name dim
beside its value, indented by how deep it sits, and no punctuation.
| arg | type | description |
|---|
| value | any | |
plainError(text: any) → void
A failure as a person reads it: one sentence, the service's own reason
under it when it gave one, and the raw text kept for "Details".
The raw text is an operator's: `hook 'llm_call' failed: API error: Claude
API returned 400 Bad Request from https://…: {"type":"error",…}`. Drawn as
it was, in red, it was a wall a person had to decode to learn that Claude
refused one request. The pipeline's own wrapping is taken off, a provider's
HTTP refusal is said as one, and its JSON is read for the message inside it.
| arg | type | description |
|---|
| text | any | |
tidyTurnRows(messages: { any }) → void
One account of a failure per turn.
A failed turn reached the panel three times: as the `error` event, as the
pipeline's chat sentence for surfaces that render no errors, and again as
the `end` event's message. All three were drawn, so one refusal read as an
error, an apology and the same error again. Within a turn (the rows between
two of the person's messages) an error is drawn once, and an answer row that
only restates it, or apologises generically beside it, is not drawn.
| arg | type | description |
|---|
| messages | { any } | |
chatBlocks(blocks: { any }) → void
The renderer's blocks, set for a chat: inline code readable, paragraphs a
little closer, and nothing hanging below the last block (the row's own gap
is the space between messages).
| arg | type | description |
|---|
| blocks | { any } | |
gapAfter(kind: string?, nextKind: string?) → number
The space under a row, read from what comes next: a turn's own pieces sit
close together, and a new exchange starts with room above it.
| arg | type | description |
|---|
| kind | string? | |
| nextKind | string? | |
rowNodes(source: any, icons: any, expanded: { [string]: boolean }, onToggle: () → void
The nodes one transcript row draws: one, or two when an open row adds its
detail beneath it. A row of a kind the transcript does not draw adds none.
| arg | type | description |
|---|
| source | any | |
| icons | any | |
| expanded | { [string]: boolean } | |
| onToggle | ( | |
imageState(result: any) → string
Whether each picture a tool answer names is on disk, as one string. Read
for an open row only: a closed row draws no picture.
| arg | type | description |
|---|
| result | any | |
sameStamp(a: { any }, b: { any }) → boolean
| arg | type | description |
|---|
| a | { any } | |
| b | { any } | |
keyedOnly(bucket: { [string]: any }) → boolean
Whether every callback a row registered is keyed. An unkeyed callback is
numbered by the order the build reached it, so a kept subtree carrying one
would answer to whichever closure the next build numbered the same; a row
holding one is built fresh every time instead of kept.
| arg | type | description |
|---|
| bucket | { [string]: any } | |
new(deps: { [string]: any }) →
Build a transcript.
a tool answer (`traceOf`, `toolsRunIn`); `icons` holds the `down`,
`right`, `alert` and `bot` paths; `expanded` is the open/closed table,
keyed by row key, that the panel keeps; `onToggle()` runs after a row is
opened or closed; `pageSize` overrides `M.PAGE`.
| arg | type | description |
|---|
| deps | { [string]: any } | `{ source, icons, expanded, onToggle, pageSize? }`: `source` reads |
windowOf(tabId: string, conversation: string) → void
| arg | type | description |
|---|
| tabId | string | |
| conversation | string | |
showEarlier(tabId: string) → void
Draw one page more of the earlier rows on `tabId`'s next render.
| arg | type | description |
|---|
| tabId | string | |
stats(tabId: string) → any
What `tabId`'s last render did: `{ rows, drawn, built, kept, hidden }`.
`rows` is every row the conversation has, `drawn` the rows on screen,
`built` the rows whose nodes were made by that render, `kept` the rows
that carried the nodes they already had, and `hidden` the rows behind
"Show earlier messages".
| arg | type | description |
|---|
| tabId | string | |
render(tabId: string, messages: { any }, opts: { [string]: any }?) → any
`messages` as the scroll area `tabId` draws.
to (a new one starts again at one page), and the clock a thinking
row's seconds are read against (`os.clock()` when absent).
| arg | type | description |
|---|
| tabId | string | |
| messages | { any } | |
| opts | { [string]: any }? | `{ conversation?, now? }`: the conversation the rows belong |
rowFor(key: string, stamp: { any }, make: () → void
One row's nodes: the ones it already has when `stamp` says it shows
what it showed last time, else new ones from `make`.
| arg | type | description |
|---|
| key | string | |
| stamp | { any } | |
| make | ( | |