Log inGet started
·
plugin · drop-in viewer
asset⌬ pluginpluginprimary: surface.module·originates fromworld abf1a532-5…

dotbot

What this plugin does and who it is for, in a paragraph.

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

dotbot

What this plugin does and who it is for, in a paragraph.

Use

local dotbot = require("dotbot")
local answer = await(dotbot.doSomething("x"))

Methods

  • doSomething(input): what it does, and what it answers.

Interface

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

This asset doesn't declare a schema or structured payload.
⌬ Metadata
description import{"at":3667.888404852,"importer":"aef4a205-7820-4ed5-ba55-8183353199c4","iteration":24,"source":{"guid":"ecffa096-bb76-42e4-bf4b-ffa9707fa705","path":"/zero/source/dotbot.plugin/plugin.wasm"}}source_checksum58b3bb52c2ca71abf5c6728027b6df34a50bfbf6fd391cbe3b5ffe6cb6b57b44

Sub-parts

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

28items
·
editorpanel · born here
❒asset
# 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: | Account | Pays | |---------|------| | OrigoZero credits | the agent included with OrigoZero | | each connected Claude account | that Claude plan | | each connected ChatGPT account | that 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.
▲ 0↑ born
▣
module · born here
❒asset
# dotbot_service The DotBot bridge, minus its privileges. `src/lua/trusted/dotbot/` is the layer between the DotBot panel (user-VM Luau, untrusted) and the DotBot plugin (trusted scope, holds a person's ChatGPT / Claude sign-in). Everything in that layer that is a **rule** rather than a **capability** lives in this module: the job table, the consent choreography, the event ring, the mirror the read-only exports answer from, and the scrub every value passes through on its way out. ```lua local Service = require("@builtin::modules.dotbot_service") local service = Service.new({ plugin = function() return rawget(_G, "dotbot") end, await = task.await, spawn = task.spawn, consentRequest = function(o) return consent.request(o) end, consentGranted = function(o) return consent.granted(o) end, openUrl = function(url) return system.openUrl(url) end, newJobId = function() return __crypto.uuid_v4() end, nowMs = function() return os.clock() * 1000 end, }) ``` ## Why the split It is what makes the layer testable. The module is a **factory**: it holds no state of its own and no privileged global, so a test can construct an instance over fakes and drive every rule in it. Being a builtin module, a world script can construct one too — and wins nothing, because its instance talks to its own fakes. The one instance that talks to the real consent gate, the real plugin namespace and the real `system.openUrl` is built in the trusted VM, out of globals only the trusted VM holds, and is reachable only as `trusted.call("dotbot.<method>", …)`. ## The shape, and where it comes from | Constraint | Consequence | |---|---| | A `trusted.call` handler must not yield (`trusted/exports.luau`) | Nothing that awaits is answered in the frame. Awaiting verbs are start/poll **jobs**; reads are answered from a **mirror** a spawned task fills, which stays current until a change marks it stale, so an open panel asks the plugin nothing while nothing changes. | | There is no push from the trusted VM into a user VM | The panel **drains a cursor** (`events(cursor)`) rather than registering a callback — so no trusted code ever calls into a user VM, and a world script cannot install itself as the panel's event handler. | `stop` is the one verb answered in the frame: stopping is always allowed and always cheap, and a stop that needed a confirmation would be a stop that lands after the turn it was meant to end. Its outcome arrives as a turn event. ## Signing in A person's plan is reached by one of two flows, and **the plugin says which**. `connectStart` reads it from the plugin's own account state (`M.signInFlow`) and, when a logged-out account names none, asks the plugin — never the caller, whose choice of flow would be a choice of which endpoint the person is sent to. | Flow | `connect.start` answers | Then | Finished by | |---|---|---|---| | `deviceCode` (ChatGPT) | `{ flow = "deviceCode", state = { userCode, verificationUrl, … } }` | the person types the code on the provider's page | `checkSignIn` until the account connects | | `pasteCode` (Claude) | `{ flow = "pasteCode", state = { authorizeUrl } }` | the person signs in and the browser ends on an address carrying the code, which they paste | `completeSignIn(provider, pasted)` | `cancelSignIn` abandons either. ### Several accounts of one provider Every flow takes an **account key**: the provider's key (`anthropic-claude`) for its first account, the provider and a slot (`anthropic-claude~2`) for each further one. `addAccountStart(provider)` connects a further account beside the ones already connected: behind the same `connect` confirmation, it asks the plugin for the key the new account signs in under (`newAccount`) and starts that account's sign-in, answering what `connect` answers plus the `account` it started. `renameAccountStart(account, label)` keeps a person's name for an account, as they wrote it (one line, at most 64 characters), and asks nobody. A dialog that names an account names which one, by the email its provider stated, once a provider has more than one: the name a person gives an account is set by an ungated call, so the dialog's words stay the plugin's own. Only `connect` asks for a confirmation. The three that continue it do not, for `stop`'s reason: they carry out a `connect` the person has already confirmed and is at that moment completing on the provider's own page, they spend nothing, and `completeSignIn` cannot connect anything on its own — the plugin holds a single-use verifier from the gated `connect` and answers "no pending login" to anything else. A second dialog inside one flow is how a person learns to stop reading the first. ## Spending `sendStart` and `commandStart` both ask for the `spend` grant, keyed by action, plugin and the agent's display name, remembered for the world session. A slash command is gated because a slash command can run a turn — `/compact` summarises a conversation with the model — so it spends the plan exactly as a message does. ## What it will not do - Return a token, a credential id or a header. `M.scrub` drops every credential-named key at every depth, on every value leaving the layer — results and event payloads alike. It is the second line: DotBot's account and event types carry no token by construction. The names it drops are exact, not a substring match on `token`, so the usage counters a person is shown (`tokens`) and a turn's `turnId` survive. - Take a URL. `openSignInStart(provider)` reads the verification URL out of the plugin's own account state for the provider named, checks it is an http(s) page, shows a person its **host** in the dialog, and only then opens it. Account state naming none errors the job. - Answer, dismiss or count a consent dialog. There is nothing here that settles one; the gate is the engine's, and only a person at the keyboard answers it. - Put a caller's own words on a dialog. `detail` is resolved through the plugin's own agent and account lists and the flow **errors when it cannot be named** rather than falling back to the id it was handed. The two arguments a caller does supply beyond a message — a pasted sign-in code and a command line — are shape-checked (`M.pastedCode`, `M.commandLine`) and reach no dialog: the pasted code's flow draws none, and a command's dialog names the agent. - Read a plugin's refusal as an answer. The plugin host **raises** one where the call is awaited, so `call` reports it as a failure and a job never settles `done` on a sign-in that did not happen. ## Constants | Name | Meaning | |---|---| | `RING_CAPACITY` | 512 — events held. A cursor older than the tail is answered `dropped = true`. | | `JOB_LIMIT` | 8 — jobs live at once, across every caller. A ninth `start` is `{ ok = false, reason = "busy" }`. | | `JOB_TTL_MS` | 600000 — a job nobody polls is dropped after ten minutes. | | `RETRY_MS` | 2000 — how long a read the plugin could not answer waits before it is asked again. A read that has an answer stays current until a change marks it stale: an `account`, `usage` or turn-ending `turn` event, a turn in a conversation the list does not hold, a job this service ran, or `refresh(name)`. A sign-in, a sign-out and an `account` event mark the accounts, the agents, the models and the usage. A new conversation list marks the history of each conversation whose message count or last update it shows moved. | | `MIRRORED` | The reads the mirror holds: `agents`, `accounts`, `models`, `usage`, `conversations`, `history`. `refresh(name)` takes one of them. | | `TURN_ENDS` | The turn event types that end a turn (`end`, `done`, `error`), after which the conversation list, the history, usage, accounts and agents are asked again. | | `PLUGIN_NAME` | `"dotbot"` — the name the consent gate resolves the dialog's display name from. | | `DEVICE_CODE` / `PASTE_CODE` | `"deviceCode"` / `"pasteCode"` — the two sign-in flows, as `connect.start` names them back. | Tested by `src/lua/lib/tests/suites/dotbot_exports.testSuite`.
▲ 0↑ born
▣
module · born here
❒asset
# source The DotBot panel's data source — the one seam between the editor panel and the DotBot agent surface. **This implementation is a fixture.** Every reading it hands back is a constant written into its `init.luau`. It talks to no plugin, no provider and no network, spends nothing, and holds no credential. It exists so the panel can be built and looked at while the real surface is built elsewhere. The contract is shaped after the `dotbot.*` trusted exports, so swapping the fixture for the real surface is a change to this one file: | Half | Members | |------|---------| | Read-only, answered in the frame | `available`, `agents`, `agent`, `terminalAgents`, `accounts`, `accountsFooter`, `models`, `model`, `levelsFor`, `level`, `usage`, `conversations`, `currentConversation`, `agentOfConversation`, `alternativesTo`, `history`, `sendable`, `events` | | Asking a read again | `refreshConversations`: the conversation list is asked again on its next read. `conversations` and `history` translate each plugin answer once and hand back the same list until the plugin answers again. | | Start / poll, because a trusted handler may not yield | `connect.start` / `.poll`, `openSignIn.start` / `.poll`, `disconnect.start` / `.poll`, `addAccount.start` / `.poll`, `renameAccount.start` / `.poll`, `send.start` / `.poll` | | Read-write but unpriced | `setModel`, `setReasoning`, `modelOf`, `levelOf`, `stop` | | The turn each tab runs, and what waits for it | `submit`, `turnOn`, `turnRunning`, `queued`, `stopTurn`, `unqueue`, `release`, `forgetTurn`, `pollTurns`, `observeTurn` | | The half only a fixture has | `fixture.enable`, `fixture.scenario`, `fixture.approve`, `fixture.setConnected`, `fixture.dropEventsBefore`, `fixture.reset` | A `start` answers `{ ok = true, id }` or `{ ok = false, reason = "busy" }`. A `poll` answers `pending`, `awaitingPerson`, `done`, `refused` or `error`. A device-code flow carries its code, its page and its expiry on the `awaitingPerson` answer, because that is what a person has to be shown while the job waits. Two `done` answers carry a flag that says the fixture did not do the thing: `openSignIn` reports `opened = false` with the address it would have opened, and `send` reports `delivered = false` with the reason. The panel shows both rather than leaving a button that looks like it worked. `fixture.enable(false)` takes the data away, and the panel says "DotBot is not available in this build" instead of drawing a conversation nobody had. ## One turn per tab A tab runs one turn at a time, whoever started it: a send from the composer, or a turn the plugin woke into on its own. `turnOn(tabId)` is that turn's record, and the Working row, the Stop button and the composer all read it. `submit` starts a turn on an idle tab; while one runs it queues the message (`"queue"`, the default) or stops the running turn and runs the message once that turn has ended (`"interrupt"`). A turn's `end` starts the next queued message. `stopTurn` holds the queue, so a stopped tab stays stopped until the person sends again (`release`). `unqueue` takes a queued message back to edit or drop. ## Scenarios `fixture.scenario(name)` puts the readings into one state at a time: `normal`, `fresh`, `notice`, `warning`, `blocked`, `signInExpired`, `codeExpired`, `codesUnavailable`, `notLinked`.
▲ 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
▣
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
# 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
backing path · dotbot.plugin

Problems

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

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬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.