Log inGet started
·
editorpanel · drop-in viewer
asset⌬ editorpaneleditorPanelprimary: init.luau·part ofplugin dotbot.plugin·originates fromworld abf1a532-5…

panel.editorPanel

The DotBot panel: one dock tab, DotBot, in the right dock under the Agents menu beside the terminal agents' tabs.

byzero-proxy @ DESKTOP-DB3UJOJ·posted 21d ago
What it does

dotbot (editor panel)

The DotBot panel: one dock tab, DotBot, in the right dock under the Agents menu beside the terminal agents' tabs.

A conversation runs on an account, and the account is a setting of the conversation, shown on the composer's account chip and changed from it:

AccountPays
OrigoZero creditsthe agent included with OrigoZero
each connected Claude accountthat Claude plan
each connected ChatGPT accountthat ChatGPT plan

Several accounts of one provider connect side by side. Each is one of the plugin's agents: dotbot-claude runs on the first Claude account and dotbot-claude~2 on the second, so moving a conversation to another account is sending its next turn as that account's agent. A conversation keeps its history when it moves.

  • Toolbar: DotBot, then conversations, new chat, whether DotBot reacts to engine events, and overflow.
  • Conversations: every conversation, whichever account it ran on, named with that account. Opening one opens it on the account it ran on.
  • Transcript: user bubbles, the thinking row, tool cards, the answer, and a meta line per turn: model, thinking level, tokens, tool calls, duration, and which account paid. A switch of account says so in the conversation where the next turn will appear.
    • Thinking is folded, never hidden: a row that says Thinking… 4 s while the model thinks and Thought for 4.2 s once it has, with a chevron that opens the thoughts (streaming live while it thinks). A turn's thinking phases fold into one row when the turn ends, and a reloaded conversation shows the thinking it stored.
    • Tool cards name what a tool_exec actually ran, from the trace in its answer, for a live answer (cut at 2000 characters by the host) as well as a stored one. A picture whose file is gone says so.
    • Answers wrap: a markdown table is drawn as a table whose cells wrap, and a fenced code block wraps at the panel's edge instead of running past it.
    • Selection: text can be highlighted with the mouse and copied with Ctrl+C: user messages, thinking, tool details, error cards, code blocks, and (where the renderer supports selectable on a richText) every styled paragraph, list item and table cell of an answer.
  • Composer: the account chip, a model chip and a thinking chip offering only the levels the chosen model accepts, attach, send, and a line naming what is left of the account's usage.
    • While a turn runs (one the person sent, or one DotBot woke into on its own) the Working row shows and Send becomes Stop. Enter queues the message for when the turn ends; the interrupt button stops the turn and runs the message once it has ended. Queued messages sit above the composer, each one editable and droppable. Stop holds them: a stopped conversation starts nothing until the person sends a held message.
  • Account picker (the account chip): every account DotBot can run the conversation on, with its state and usage, then "Connect another Claude account", "Connect another ChatGPT account" and "Connected accounts…".
  • Connected accounts: one card per account with its plan, its own usage windows, and Use, Rename, Sign in again and Disconnect; Connect on an account that is not connected starts its sign-in in place.
  • Connect flows: ChatGPT's device code, and Claude's sign-in then paste, for the first account of a provider and for each further one.
  • Usage states: a notice under 25% of a window left, a warning under 10% offering a cheaper model only when that model is one this account can run, and a spent window naming the connected accounts that still have room, with a button that moves the conversation to the first of them. Nothing moves on its own.
  • Failure states: an expired sign-in, an expired code, a provider not issuing codes, an engine not linked to an OrigoZero account, and an account disconnected while a conversation was on it, which then asks for an account rather than sending anywhere.

Everything it shows comes from the plugin through source.module, and every word it puts on screen for a reading comes from view.module. The panel holds only what is on screen right now: which view, which popup is open, which account the open conversation runs on, how far a connect flow has run, and the unsent draft.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

The DotBot panel on edui: one dock tab, in the right dock under the Agents menu beside the terminal agents. It carries a toolbar, a transcript of user turns, thinking, tool calls, the answer and a per-turn meta line, a composer with the account, model and thinking chips, the connected accounts view, the connect flows, the usage notice / warning / blocked states, and the failure states. Which account a conversation runs on is a setting of the conversation, shown on the composer's account chip and changed from it: OrigoZero credits, or any Claude or ChatGPT account connected to DotBot. Each account is one of the plugin's agents (`claude` on the first Claude account, `claude~2` on the second), and moving a conversation to another account is sending its next turn as that account's agent. There is deliberately NO permanent line under the composer. It read "Runs on your ChatGPT plan - 4% of the weekly window left", and both halves were already on screen: every turn's meta line names who paid, and the usage banner names what is left. Two permanent copies of one fact is a fixture a person learns to read past. Everything the panel shows comes from `modules.dotbot.source`, and every word it puts on screen for a reading comes from `modules.dotbot.view`. The panel holds only what is on screen right now — which view, which popup is open, how far a connect flow has run, and the unsent draft.

onToggle( ) → void

shortCount(n: number) → string

A count a person reads at a glance: `9829`, `43.2k`, `1.4M`.

argtypedescription
nnumber

rowsFor(tabId: string) → void

argtypedescription
tabIdstring

freezeHistory(tabId: string, conversationId: string?, agentId: string?) → void

Take the history snapshot this tab's transcript sits on for the next turn. Called when a turn STARTS, which is the only moment the prefix is allowed to move.

argtypedescription
tabIdstring
conversationIdstring?
agentIdstring?

storedRows(tabId: string, conversationId: string, agentId: string) → void

The stored rows the transcript draws under the live ones: the snapshot while a turn is running on this conversation, and whatever the plugin holds otherwise.

argtypedescription
tabIdstring
conversationIdstring
agentIdstring

startNewChat(tabId: string, agentId: string) → void

Start a fresh conversation in `tabId`: the ONE place that says what "new chat" means, so the button and the menu item cannot mean different things. The live rows are the part that was being missed. They are the tail the transcript draws under whatever history it loaded, so clearing the conversation id without clearing them left the last turn's messages sitting in the "new" chat — the button looked broken, and the rows it stranded then merged against the next conversation's history, which is what put half a transcript on screen. @param agentId the agent whose conversation is being replaced

argtypedescription
tabIdstring
agentIdstring

newUid( ) → number

agentForAccount(account: string) → string

The panel id of the agent that runs on `account`, an account key (`anthropic-claude~2` -> `claude~2`), or nil when the plugin lists no agent of that provider.

argtypedescription
accountstring

agentNamed(agentId: string) → any

The agent `agentId` names, also when it is an account the plugin no longer lists (one disconnected while a conversation was on it): that is drawn as the provider's account, not connected, so the panel asks for one rather than moving the conversation somewhere nobody chose.

argtypedescription
agentIdstring

currentAgentId(tabId: string) → string

The agent `tabId`'s conversation runs on: the one the person chose (or the conversation's own turns ran on) in this panel, else the one the plugin recorded for the conversation, else the first connected agent, else the first the plugin lists.

argtypedescription
tabIdstring

switchAgent(tabId: string, agentId: string) → void

Put `tabId`'s conversation on `agentId`. The turn running now finishes where it is; the next one runs on the new account, and the conversation says so where the next turn will appear. A conversation keeps its history: the agent it moves to reads it as it stands.

argtypedescription
tabIdstring
agentIdstring

closeRow(row: any) → void

Stop a row streaming. A thinking row is timed from its first token to here, which is the time the model spent thinking and not the time the whole turn took.

argtypedescription
rowany

appendText(tabId: string, kind: string, text: string) → void

argtypedescription
tabIdstring
kindstring
textstring

closeStream(tabId: string) → void

argtypedescription
tabIdstring

lastRowLength(tabId: string) → number

How long the last transcript row's text is, for the panel's memo.

argtypedescription
tabIdstring

tabForTurn(payload: any) → string

Which tab a turn event belongs to. By the turn id first — that is the direct link between the `send` the tab started and everything that turn says. A tab that has a send in flight but has not yet read the id back matches next, because an event can arrive in the same drain that learns it. An event belonging to no tab is dropped: it is a turn this panel did not start.

argtypedescription
payloadany

foldTurnEvent(payload: any) → void

Fold one `turn` event into the tab's rows. Only the shapes the transcript draws are read. An event this does not know is dropped rather than rendered as its own JSON: a panel that prints `pipeline_hook_start` at a person is noise, and a turn that gains a new event type must not make the transcript unreadable.

argtypedescription
payloadany

awaitProvider(f: any, answer: any) → void

Put a flow onto the provider's own page: the code to type, or the page to approve on and paste back from, as the job answered it.

argtypedescription
fany
answerany

flowOf(provider: string) → any

How far the sign-in of `account` (an account key) has run.

argtypedescription
providerstring

popKey(tabId: string, which: string) → string

argtypedescription
tabIdstring
whichstring

togglePop(tabId: string, which: string) → void

argtypedescription
tabIdstring
whichstring

drainEvents( ) → void

Drain everything the plugin has said since the last draw. The panel is the only drainer, and one cursor serves every tab: the ring is the person's, not the tab's, and a second cursor would mean one tab's draw eating the events another tab had not read yet.

pump( ) → void

Run every job a flow is waiting on one step further. **Called from `build`, BEFORE the panel's own memo check** — never from `buildTab`. `build` answers `nil` when nothing it draws has changed, and a pump behind that check is a pump that only runs once something else has already moved. Nothing else does: a turn's whole life is a job this has to poll and a ring it has to drain, and neither changes the signature until this has run. The panel froze on "Sending…" while the turn it was waiting for had already finished and been paid for. Jobs first, then the ring: polling a send is what learns the turn id, and `tabForTurn` needs it to file the events that same drain is about to hand it.

startJob(provider: string, kind: string, ...: ?) → void

argtypedescription
providerstring
kindstring
...?

startAddAccount(tabId: string, provider: string) → void

Connect a further account of `provider` and put `tabId`'s conversation on it once its sign-in starts. The accounts already connected stay connected.

argtypedescription
tabIdstring
providerstring

pill(text: string, color: string, borderColor: string) → any

A pill naming a state in its own hue, the shape edui's type badge uses.

argtypedescription
textstring
colorstring
borderColorstring

banner(o: { [string]: any }) → any

argtypedescription
o{ [string]: any }

richMenu(o: { [string]: any }) → any

A popup whose rows carry a title over a subtitle, which `edui.menu` has no shape for. The frame matches edui's own menu so the two read as one language.

argtypedescription
o{ [string]: any }

sheet(children: { any }) → any

The card a connect flow, a failure or the accounts view puts in the body.

argtypedescription
children{ any }

chip(o: { [string]: any }) → any

argtypedescription
o{ [string]: any }

sendDraft(tabId: string, agent: any, how: string?) → void

Send whatever is in the composer. The ONE send path: the button and the Enter key both call this, so a message cannot behave differently depending on which one a person used. While a turn runs on the tab the message does not start a second one beside it: by default it waits in the tab's queue and starts the next turn when this one ends, and `how = "interrupt"` stops the running turn and runs the message as soon as that turn has ended.

argtypedescription
tabIdstring
agentany
howstring?

composerButton(id: string, icon: string, tooltip: string, onClick: (() → void

A small square icon button in the composer row.

argtypedescription
idstring
iconstring
tooltipstring
onClick((

queueRows(tabId: string, agent: any) → void

The messages waiting on a tab, above its composer: each one can be taken back into the composer to edit, dropped, or (once a Stop held it) sent.

argtypedescription
tabIdstring
agentany

onClick( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

onChange(v: ?) → void

argtypedescription
v?

onEnter(v: ?) → void

Enter sends. The field still reports every keystroke, so the Send button beside it keeps working; this is the same send, reached by the key people actually press.

argtypedescription
v?

sheetBody(tabId: string, agent: any, spec: any) → any

argtypedescription
tabIdstring
agentany
specany

onChange(v: ?) → void

argtypedescription
v?

act(kind: string) → void

argtypedescription
kindstring

providersListed( ) → void

The subscription providers the plugin lists, once each, in its order, with the name a person reads for each.

connectAccount(tabId: string, acc: any) → void

Put `tabId`'s conversation on the account `acc` and start its sign-in, in place: the conversation shows the sign-in's own sheet.

argtypedescription
tabIdstring
accany

accountsBody(tabId: string) → any

argtypedescription
tabIdstring

onChange(v: ?) → void

argtypedescription
v?

accountPickerRows(tabId: string, agent: any) → void

The account picker: every account DotBot can run a conversation on, the one it is on now ticked, then a further account of each provider, then the accounts view.

argtypedescription
tabIdstring
agentany

onAction( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

unavailable(av: any) → any

argtypedescription
avany

buildTab(tabId: string, agentId: string) → any

argtypedescription
tabIdstring
agentIdstring

onClick( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

onClick( ) → void

onDismiss( ) → void

onDismiss( ) → void

onClick( ) → void

onDismiss( ) → void

onClick( ) → void

onDismiss( ) → void

onClick( ) → void

onDismiss( ) → void

onClick( ) → void

onClick( ) → void

onDismiss( ) → void

signature(tabId: string, agentId: string) → string

Everything a tab draws, in one string. Same string, same picture.

argtypedescription
tabIdstring
agentIdstring

menuHint( ) → void

The short, right-aligned note the Agents menu shows beside the entry: the state of the account the open conversation runs on.

build( ) → void

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

16+items
▣
module · born here
❒asset
# view What the DotBot panel says, apart from how it is drawn. Every function here is pure: it takes readings from `modules.dotbot.source` and returns the words and the state the panel renders. | Function | Answers | |----------|---------| | `emptyState(availability)` | The title and detail shown when no DotBot surface is present. | | `hintLine(agent, usage)` | The line under the composer naming what pays and what is left, or nil when there is nothing honest to say. | | `usageNotice(agent, usage, models)` | The banner above the composer: a notice under 25% left, a warning under 10% offering a cheaper model only when that model is one the agent can run, and a blocked state naming the reset. | | `problemNotice(agent)` | The banner a failed sign-in puts above the composer. | | `composerState(availability, agent, usage)` | Whether the field takes typing, what it says while empty, and whether it carries the model and thinking chips. | | `connectSheet(agent, flow)` | The sheet a disconnected, expired or unlinked agent shows in place of a conversation. | | `connectedNotice(agent, account)` | The line a freshly-connected agent shows above its first turn. | | `metaLine(meta)` / `metaWho(meta)` | The per-turn meta line: model, thinking level, tokens, tool calls, duration — and who paid. | | `pickerSubtitle(agent)` / `pickerAction(agent)` | What an agent's row in the picker says, and the word on its button when it needs one. | | `rowKey(row)` / `mergeRows(history, live)` | A transcript row's identity (a tool row is its call, a thinking row or usage line is itself) and the merge of stored history with the live rows, which shows the overlap once. A history list is keyed once, when it first reaches `mergeRows`, so a merge on every redraw costs the live rows. | | `groupCarrierRows(rows, toolsRunIn)` | Runs of `tool_exec` calls collapsed into one row naming the tools they ran. | | `thinkingLabel(row, now)` / `finishThinking(row, now)` / `foldThinking(rows, from)` / `thinkingRow(...)` / `toggleOpen(state, key)` | Thinking as a folded row: its header, its clock, the fold of one turn's phases into one row, and the open/closed state. | | `splitMarkdown(md)` / `tableNode(...)` / `tableWeights(...)` / `codeNode(...)` / `markSelectable(node)` | An answer's tables and fenced code lifted out of the markdown and drawn so they wrap, and every piece of an answer marked selectable. | | `imageNode(P, key, path, exists)` | A tool's picture, or the line saying it is no longer available. | | `linkText(id, label, onClick)` | Text that looks like a link and acts like one, its callback keyed by `id`. | The thresholds the usage ladder turns on are `noticeThreshold` (25) and `warnThreshold` (10).
▲ 0↑ born
▣
module · born here
❒asset
# transcript The DotBot panel's transcript: a conversation's rows as the scroll area one tab draws. ```luau local Transcript = require(".transcript") local transcript = Transcript.new({ source = Source, -- traceOf / toolsRunIn, to read a tool answer icons = { down = ..., right = ..., alert = ..., bot = ... }, expanded = expandedRows, -- which rows are open, by row key onToggle = function() expandEpoch += 1 end, }) local node = transcript.render(tabId, messages, { conversation = convId }) ``` | Member | What it does | |--------|--------------| | `PAGE` | 40: the rows a transcript draws before the "Show earlier messages" row, and how many more each press of it draws. | | `new(deps)` | A transcript over `deps.source`, `deps.icons`, `deps.expanded` and `deps.onToggle`; `deps.pageSize` overrides `PAGE`. | | `render(tabId, messages, opts?)` | The virtual scroll area holding the newest rows of `messages`, with a "Show earlier messages" row above them when there are more. `opts.conversation` names the conversation (a tab on a new one starts again at one page); `opts.now` is the clock a thinking row's seconds are read against. | | `showEarlier(tabId)` | Draws one page more of the earlier rows on the next render. | | `stats(tabId)` | What the last render did: `{ rows, drawn, built, kept, hidden }`. | ## What a redraw costs Each row is drawn from what it shows: its text, its tool call and answer, whether it is open, a thinking row's seconds. A row that shows the same as it did last time keeps the subtree it already has, and its callbacks are carried into the new build (`edui.app` `collectHandlers` / `adoptHandlers`), so a delta streaming into the last answer builds that answer and nothing above it. A row holding a callback with no key is built fresh every time, because an unkeyed callback is numbered by the order a build reaches it. Only the newest rows are drawn, so what a redraw costs is set by what is on screen rather than by how long the conversation is. The earlier rows are one press away and nothing is dropped. Tested by `src/lua/lib/tests/suites/dotbot_panel.testSuite`.
▲ 0↑ born
▣
module · born here
❒asset
# surface <!-- zero:scaffolded-readme --> <!-- Replace the line below with what this module provides and who consumes it, then delete both comment lines. --> TODO: describe this module.
▲ 0↑ born
·
plugin · born here
❒asset
# dotbot What this plugin does and who it is for, in a paragraph. ## Use ```luau local dotbot = require("dotbot") local answer = await(dotbot.doSomething("x")) ``` ## Methods - `doSomething(input)`: what it does, and what it answers.
▲ 0↑ born

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.