source
The DotBot panel's data source — the one seam between the editor panel and the DotBot agent surface.
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.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2The DotBot panel's data source — the ONE seam between the editor panel and the DotBot agent surface. EVERY READING HERE COMES FROM THE PLUGIN. There is no fixture, no scenario switch and no constant standing in for a person's plan, balance or usage. An engine whose build carries no `dotbot.*` trusted exports answers `available().loaded = false` and every list empty, and the panel says so — which is the honest thing for a build with no agent behind the panel to say, and is what this file used to lie about. A panel that draws "Max plan · 68%" at somebody who has not signed in is worse than a panel that draws nothing. What this module DOES do is translate. The plugin and the panel were written against each other's specs rather than against each other, and they disagree in ways that cost nothing to fix here and everything to discover late: plugin panel
levelNamed(id: any) → Level
A level's record: this table's spelling when it has one, else the id itself with its first letter raised. Nil only for what is not a level name.
| arg | type | description |
|---|---|---|
| id | any |
percent(fraction: any) → number
Round a `0..1` fraction to whole percent.
| arg | type | description |
|---|---|---|
| fraction | any |
contextLabel(window: any) → string
A context window as a person reads it: `1000000` -> `"1M context"`.
| arg | type | description |
|---|---|---|
| window | any |
clockTime(unixSeconds: any) → string
A unix second as a clock time in the engine's own timezone, or nil. For a moment inside today — when a conversation was last touched, when a message landed. A window's RESET is not one of those; see [`resetPhrase`].
| arg | type | description |
|---|---|---|
| unixSeconds | any |
resetPhrase(unixSeconds: any) → string
When a window resets, said the way a person would say it, or nil. A clock time alone is only honest for a window that resets within the day. This one said `%H:%M` and every caller wrote the word "today" in front of it — on a WEEKLY window, which is the one most people are on, so a limit five days out read as one that came back this afternoon. The day is part of the answer, so it is part of the value rather than a word the caller adds. Today and tomorrow are named as such because that is what a person checking whether to keep working needs; further out is the weekday, and past a week the date, because "Tuesday" stops being useful once there are two of them.
| arg | type | description |
|---|---|---|
| unixSeconds | any |
midnight(d: ?) → number
| arg | type | description |
|---|---|---|
| d | ? |
countdown(unixSeconds: any) → string
How long until `unixSeconds`, as `mm:ss`, or nil when it is not a time. A sign-in code expires, and a person deciding whether to start typing it needs to know how long they have. The figure is the provider's own expiry, counted against this machine's clock — never a duration written down here.
| arg | type | description |
|---|---|---|
| unixSeconds | any |
accountWindows(limits: any) → void
Every rate-limit window a provider has actually reported, tightest first. `UsageLimits` carries three optional windows and a provider populates the ones it has. A window with no readable fraction is left out: the accounts view draws a bar per entry, and a bar with no figure behind it is a bar reading zero.
| arg | type | description |
|---|---|---|
| limits | any |
planLabel(plan: any) → string
A plan as the provider names it (`max`, `plus`) with its first letter up.
| arg | type | description |
|---|---|---|
| plan | any |
new(deps: { [string]: any }) → void
Build a source over `deps`. `deps.call(name, ...)` is one `trusted.call("dotbot." .. name, ...)`: it answers `(true, value)` with what the export returned, or `(false, nil)` when this build has no such export. Nothing else is read from anywhere.
| arg | type | description |
|---|---|---|
| deps | { [string]: any } |
call(name: string, ...: ?) → void
| arg | type | description |
|---|---|---|
| name | string | |
| ... | ? |
readTable(name: string, ...: ?) → any
A read-only export's answer as a table, or nil when this build has no such export or the export refused. `nil` is "nothing is known", never "the answer is empty" — every caller below distinguishes the two.
| arg | type | description |
|---|---|---|
| name | string | |
| ... | ? |
jobStart(family: string, ...: ?) → any
| arg | type | description |
|---|---|---|
| family | string | |
| ... | ? |
jobPoll(family: string, id: string) → any
| arg | type | description |
|---|---|---|
| family | string | |
| id | string |
available( ) → void
Whether the panel has a DotBot surface to draw at all.
accounts( ) → void
Every provider account this build can sign in to, exactly as the plugin states them. Never carries a token — the trusted layer scrubs one that somehow appeared, and this passes the scrubbed record straight through.
accountOf(account: string) → any
One account by its key.
| arg | type | description |
|---|---|---|
| account | string |
accountsFooter( ) → string
The line under the accounts list, naming where a sign-in is kept. A statement about this build's storage, which is true of every build: credentials live in the plugin's own private directory on this machine.
usage(provider: string) → Usage
The last usage reading for a provider, or nil when there is none. `nil` is the state before the first turn of a session: no provider reports a window until a call has come back carrying the headers. The panel says "usage shows after the first turn" for it, which is true; inventing a percentage here is what made the panel lie.
| arg | type | description |
|---|---|---|
| provider | string |
noteCredits(balance: any) → void
Note the balance a finished turn reported.
| arg | type | description |
|---|---|---|
| balance | any |
includedUsage( ) → Usage
The included agent's reading: credits, when one has been reported.
summaryFor(agent: any, account: any, usage: Usage?) → void
How an agent's state reads in the picker's subtitle and above the composer. Every figure in it came from the plugin; the sentence is the only thing built here.
| arg | type | description |
|---|---|---|
| agent | any | |
| account | any | |
| usage | Usage? |
fromPluginAgent(a: any) → Agent
Translate one agent record from the plugin's vocabulary into the panel's, and give it the state line it is drawn with.
| arg | type | description |
|---|---|---|
| a | any |
agents( ) → void
The DotBot agents this build offers, in the order the picker lists them.
usageOf(agent: any) → Usage
The reading that belongs to an agent, whichever purse it spends from. The two subscription agents read a provider's rate-limit window; the included agent reads a credit balance, which arrives on a finished turn rather than from any endpoint. A caller that asked by provider key alone could not tell the included agent's absent reading from a provider it had never heard of.
| arg | type | description |
|---|---|---|
| agent | any |
agent(id: string) → Agent
One agent by id, or nil.
| arg | type | description |
|---|---|---|
| id | string |
panelAgentId(pluginId: string) → string
The other direction: the panel's id for an agent the plugin named. The plugin writes `dotbot-chatgpt` in a conversation address; the panel and its tabs know that agent as `chatgpt`. Anything reading an address the plugin wrote has to come through here.
| arg | type | description |
|---|---|---|
| pluginId | string |
pluginAgentId(agentId: string) → string
The agent id the plugin knows this one by.
| arg | type | description |
|---|---|---|
| agentId | string |
terminalAgents( ) → void
The terminal agents the picker lists under the DotBot ones. Empty until something can actually look: which CLI agents are installed on this computer is a fact about the machine, and the panel listing three of them with invented states was the clearest fabrication in this file. The picker draws no section for an empty list.
models(agentId: string) → void
The models the agent can run on, each naming the levels it takes. Straight from `dotbot.models`, which answers from ZeroMind's own offer for the included agent and from the connected plan for the other two. An agent with no connection has no model list, and the panel's picker is empty rather than full of models nobody can run.
| arg | type | description |
|---|---|---|
| agentId | string |
offerOf(answer: any) → void
What the agent offers, in the shape the engine's choice is judged against: each model with the efforts it takes and the one it states for itself, and the model the plugin would run on unasked.
| arg | type | description |
|---|---|---|
| answer | any |
engineChoice(agentId: string) → any
The model and the effort the ENGINE runs this agent on, judged against what the plugin offers: `{ model, effort, modelFrom, effortFrom, notOffered?, effortNotTaken? }`, nil when this engine holds no choice.
| arg | type | description |
|---|---|---|
| agentId | string |
refusalOf(agentId: string) → string
Why the last model or effort picked on `agentId` was refused, or nil.
| arg | type | description |
|---|---|---|
| agentId | string |
currentChoice(agentId: string) → void
What a turn on this agent runs on: the model and the effort the engine chose (`engineChoice`), which every turn puts on the session before it runs. On an engine that holds no choice, it is what the plugin states: the person's choice, else what a turn naming nothing runs on. An agent that offers no model reads empty, because a chip that invented one would be telling a person the wrong thing about their own turn.
| arg | type | description |
|---|---|---|
| agentId | string |
level(id: string) → Level
One thinking level's record by id.
| arg | type | description |
|---|---|---|
| id | string |
model(agentId: string, modelId: string) → Model
One model's record by id, or nil.
| arg | type | description |
|---|---|---|
| agentId | string | |
| modelId | string |
levelsFor(agentId: string, modelId: string) → void
Every thinking level `modelId` accepts, in order.
| arg | type | description |
|---|---|---|
| agentId | string | |
| modelId | string |
address(conversationId: string, agentId: string) → string
Address a conversation the way the plugin's own grammar does.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string |
settleNow(family: string, ...: ?) → void
Run a short job and say whether it was accepted. `setModel` and `setReasoning` are unpriced, so they ask nobody — but they are still jobs, because the trusted handler that reaches the plugin may not yield, so the plugin does the work OFF the call that started it. **That means the first poll is `pending`, not `done`.** This polled once and treated anything short of `done` as a refusal, so against a real engine every choice failed: measured on a live editor, the first poll said `pending` and the next, 100ms later, said `done`. Nothing surfaced either — the chip fell back to what it was — so the model and the thinking level could not be changed at all, with no error shown. The test fake settled on the first poll, which is why this was green.
| arg | type | description |
|---|---|---|
| family | string | |
| ... | ? |
applyChoice(conversationId: string, agentId: string, chosen: any) → void
Put the engine's choice on the agent's session: the model, then the effort, each only where the session runs something else. Answers whether the session now runs it, and the plugin's reason when not.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string | |
| chosen | any |
setModel(conversationId: string, agentId: string, modelId: string) → boolean
Choose the model this agent runs on. The ENGINE keeps the choice and refuses a model the agent does not offer, naming the ones it does; the session is then put on it.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string | |
| modelId | string |
setReasoning(conversationId: string, agentId: string, level: string) → boolean
Choose the thinking level. The ENGINE keeps the choice and refuses a level the model does not take, naming the ones it does.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string | |
| level | string |
setReactive(agentId: string, reactive: boolean) → boolean
Switch an agent's reactivity on or off. The PLUGIN decides. Nothing is remembered here: the answer is read back off the agent list, so a switch that did not take does not leave the panel showing one that did.
| arg | type | description |
|---|---|---|
| agentId | string | |
| reactive | boolean |
newChat(agentId: string) → string
The model a conversation runs on: the one this panel chose, else the one the plugin says a turn naming nothing would use. Open a NEW conversation on `agentId`, and answer its id. **The panel cannot do this by clearing its own state.** An address that names no conversation does not mean "a new one" — it means "this agent's current one", and the plugin continues whatever that is. So clearing `convo[tab]` and sending put the next message straight back into the old chat, which is exactly what a person saw: press New chat, type, and the previous conversation reappears with the message appended to it. (`""` is TRUTHY in Luau, so `convo[tab] or currentConversation()` did not even fall through — it passed the empty string on as the address. And the fallback answers the most recent conversation, which is the one being left. Neither route could ever have opened a new one.) `/new` is the plugin's own verb for this, so the conversation is opened where conversations live rather than guessed at from here.
| arg | type | description |
|---|---|---|
| agentId | string |
modelOf(conversationId: string, agentId: string) → string
The model a turn on this agent will run on: `currentChoice`'s, the one the next turn puts the session on before it runs. Keyed by the agent alone: a session is one agent, so every conversation on it runs the one choice the engine holds for that agent.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string |
levelOf(conversationId: string, agentId: string) → string
The thinking level a turn on this agent will run at, on the same rule.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string |
conversations( ) → void
refreshConversations( ) → boolean
Ask the plugin for the conversation list again on its next read. The list is held until a change marks it stale, and a conversation another engine on the same account opened raises no event here, so the panel asks for it when the person opens the conversations menu.
agentOfConversation(conversationId: string) → string
The conversation the panel opens on: the most recent one, or none. `""` means "start a new one", and the panel addresses a send to the agent alone — which is how the plugin is told to open a conversation. The agent `conversationId`'s latest turn ran on, as the panel names agents, or nil when the plugin has no record of one.
| arg | type | description |
|---|---|---|
| conversationId | string |
alternativesTo(agentId: string) → void
The connected agents a turn could move to from `agentId`: every other account that is connected, has no problem in front of it and has room left in the reading it last reported. The same provider's accounts come first, in the plugin's order.
| arg | type | description |
|---|---|---|
| agentId | string |
currentConversation( ) → string
splitDelivery(text: string) → void
A message split into what the engine delivered and what the person actually typed. Either half can be empty: a woken turn is all delivery and no person, and an ordinary message is all person and no delivery.
| arg | type | description |
|---|---|---|
| text | string |
stripPreamble(text: string) → string
Drop a fenced preamble from a message's text. Matched literally — the sentinels contain `-` and `/`, which a Luau pattern would read as anything but themselves. An unterminated fence takes the rest of the message: a preamble whose closing sentinel was lost is still a preamble, and showing half of it is worse than showing none.
| arg | type | description |
|---|---|---|
| text | string |
encodeArgs(input: any) → string
| arg | type | description |
|---|---|---|
| input | any |
encodeArgs(input: any) → string
| arg | type | description |
|---|---|---|
| input | any |
contentText(content: any) → string
| arg | type | description |
|---|---|---|
| content | any |
resultText(content: any) → string
The readable text of a tool result, for a caller holding one already. Exported because a live turn and a reloaded conversation carry the same answer in two shapes: an event's `content`, and a stored `tool_result` block's. One reader for both, so a transcript does not say different things depending on how it was loaded.
| arg | type | description |
|---|---|---|
| content | any |
truncatedTrace(text: string) → void
The records of a trace read out of an answer that was CUT SHORT. The host cuts a tool's answer to 2000 characters before it sends it as an event and ends it with a note (`...[truncated, N chars total]`), so the text a live row holds is not valid JSON once the script returned anything sizeable, and decoding it fails. The trace is not lost with it: the answer's keys come in a fixed order (`logs`, `ok`, `trace`, `value`), so the trace sits ahead of the large part and is usually whole. This finds the array and reads the records that are complete, stopping at the first one the cut went through.
| arg | type | description |
|---|---|---|
| text | string |
traceOf(resultText: any) → void
The `{ tool, ok, durationMs }` records a `tool_exec` answer carries, in the order the calls ran. One reader for a stored answer, which is whole, and a live one, which may have been cut short (see `truncatedTrace`). Both levels are read, because the answer arrives wrapped: `tool_exec` returns `{ ok, value, error, trace, logs }` and the tool surface puts that under `result`.
| arg | type | description |
|---|---|---|
| resultText | any |
toolsRunIn(resultText: any) → void
The tools a `tool_exec` call actually ran, in order, from the trace its own answer carries. **The carrier is not the work.** `tool_exec` runs a script, and the script is what calls the world's tools; a transcript that named the carrier showed six identical `tool_exec` rows and never once said what happened. The names come from the answer's `trace`, which records `{ tool, ok, durationMs }` per call, so they are the labels the engine used rather than anything parsed out of the script. **A live answer is read the same way as a stored one.** The event a running turn delivers is cut at 2000 characters, so for any script that returned something sizeable the text was not JSON and this answered nothing: the row went on saying `tool_exec` until the conversation was reloaded from the stored, whole answer. `traceOf` reads both. Empty for a call that ran no tools at all (a script that only computed), and for an answer this cannot read. Both mean the same to a caller: there is nothing better to call the row than what the model called it.
| arg | type | description |
|---|---|---|
| resultText | any |
conversationTitle(preview: any, id: string) → string
What a conversation is recognised by in a list, from the plugin's preview of its first message. Three things the raw preview gets wrong, all of them visible at once in a real list: * **The fences.** A first message carries the orientation preamble and a woken turn carries an engine delivery, so the preview began `<!--zm:orientation--> ## Where you are and how this works`. That is the machinery announcing itself where a person looks for their own first sentence. * **Empty is not nil.** `""` is TRUTHY in Luau, so `preview or id` never fell through and a conversation with no preview drew a row with nothing in it. Several of those in a row read as a broken list. * **It is not one line.** A preview runs to whatever the person typed, newlines and all, and a menu row given a paragraph grows to hold it, which is what made the blank rows tall.
| arg | type | description |
|---|---|---|
| preview | any | |
| id | string |
history(conversationId: string, agentId: string?) → void
One conversation's messages, oldest first. The included agent keeps its transcript on ZeroMind rather than in this plugin's database, so `history` errors for it and this answers empty: its turns reach the panel as events while they run.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| agentId | string? |
sendable(agentId: string) → boolean
Whether the next send on this agent goes through without asking. The trusted layer answers from the consent grant this world session holds; a build with no surface answers false, which is also what the composer needs to hear.
| arg | type | description |
|---|---|---|
| agentId | string |
events(cursor: number?) → void
Drain every event after `cursor`. A cursor older than the ring's tail comes back `dropped`, and the panel refetches rather than miss a turn. The ring is the trusted layer's; this only passes the drain through.
| arg | type | description |
|---|---|---|
| cursor | number? |
start(provider: string, code: string?) → any
Begin connecting `provider`. `code` carries what a person pasted, for the providers whose flow ends that way.
| arg | type | description |
|---|---|---|
| provider | string | |
| code | string? |
connectStep(value: any) → any
Normalise what a finished `connect` answered into the step the panel draws. **Which sign-in a provider runs is the PLUGIN'S answer, not a list in the panel.** The trusted layer asks the provider's own spec and reports `flow` — `device_code` for the one that issues a short code a person types on the provider's page, `paste_code` for the one that sends them to a page and shows them a code to paste back. A panel that decided this by looking at the provider key drew the wrong sheet the moment the key it matched on (`chatgpt`) stopped being the key the plugin uses (`openai-chatgpt`) — which is exactly what happened.
| arg | type | description |
|---|---|---|
| value | any |
poll(id: string) → any
Ask a connect job where it is. Both families are polled, because `connect.start` starts one of two depending on whether a code was pasted, and the panel holds one job id either way. A poll of the family this id does not belong to answers "no such job", which is not the answer — so the other is tried.
| arg | type | description |
|---|---|---|
| id | string |
start(provider: string) → any
| arg | type | description |
|---|---|---|
| provider | string |
poll(id: string) → any
| arg | type | description |
|---|---|---|
| id | string |
start(provider: string) → any
Begin opening the provider's sign-in page. The destination comes from the plugin's own account state; no caller names a URL.
| arg | type | description |
|---|---|---|
| provider | string |
poll(id: string) → any
| arg | type | description |
|---|---|---|
| id | string |
start(provider: string) → any
| arg | type | description |
|---|---|---|
| provider | string |
poll(id: string) → any
| arg | type | description |
|---|---|---|
| id | string |
start(provider: string) → any
| arg | type | description |
|---|---|---|
| provider | string |
poll(id: string) → any
| arg | type | description |
|---|---|---|
| id | string |
start(account: string, label: string) → any
| arg | type | description |
|---|---|---|
| account | string | |
| label | string |
poll(id: string) → any
| arg | type | description |
|---|---|---|
| id | string |
start(conversationId: string, text: string, attachments: { any }?, agentId: string?) → any
Begin a turn. The first one in a world session is what the trusted layer puts the confirmation in front of.
| arg | type | description |
|---|---|---|
| conversationId | string | |
| text | string | |
| attachments | { any }? | |
| agentId | string? |
poll(id: string) → any
| arg | type | description |
|---|---|---|
| id | string |
stop(turnId: string) → boolean
Stop a turn. Ungated and synchronous — stopping is always allowed.
| arg | type | description |
|---|---|---|
| turnId | string |
queueOf(tabId: string) → void
| arg | type | description |
|---|---|---|
| tabId | string |
turnOn(tabId: string) → any
The turn on `tabId`: `{ running, turnId?, jobId?, conversation?, agentId?, startedAt, woken?, stopping?, awaiting?, reason? }`, or nil when the tab has run none. A turn whose id the plugin has not answered yet is running too.
| arg | type | description |
|---|---|---|
| tabId | string |
turnRunning(tabId: string) → boolean
Whether a turn runs on `tabId` right now.
| arg | type | description |
|---|---|---|
| tabId | string |
queued(tabId: string) → void
The messages waiting on `tabId`, oldest first: `{ id, text, agentId, conversation, held, interrupt?, problem? }`. A held message waits for the person to send it; the others start the next turn by themselves.
| arg | type | description |
|---|---|---|
| tabId | string |
stopTurn(tabId: string, keepQueue: boolean?) → boolean
Stop the turn running on `tabId`. A turn whose id has not arrived yet is stopped the moment it does. A plain stop holds every queued message, so nothing starts until the person sends; `keepQueue` is the interrupt's stop, which leaves the queue to run.
| arg | type | description |
|---|---|---|
| tabId | string | |
| keepQueue | boolean? |
unqueue(tabId: string, id: number) → string
Take a queued message off `tabId`, answering its text (for the composer to edit), or nil when there is no such message.
| arg | type | description |
|---|---|---|
| tabId | string | |
| id | number |
release(tabId: string, id: number) → any
Send a held message: now when the tab is idle, else as the next turn.
| arg | type | description |
|---|---|---|
| tabId | string | |
| id | number |
forgetTurn(tabId: string) → void
Forget `tabId`'s turn and its queue: a new chat starts empty.
| arg | type | description |
|---|---|---|
| tabId | string |
startNext(tabId: string) → any
Start the next queued message on `tabId` if the tab is idle and the message is not held. Answers the message started, for the panel to put in the transcript, or nil.
| arg | type | description |
|---|---|---|
| tabId | string |
pollTurns( ) → void
Advance every tab's send job one step. Answers, per tab whose job moved, what the panel should say: `{ [tabId] = { status, message? } }`. **The job's answer says which turn is the send's.** While the job is pending, the record takes the turn id of the first event the tab is handed, because a turn's first event can arrive before its job is polled. A turn the plugin woke into can arrive the same way. So a send the plugin accepted under another id runs from here until its own end, and a send the plugin refused leaves the record describing the turn it took, which is a woken turn and runs until that turn's end.
observeTurn(tabId: string, payload: any) → any
Fold one turn event into `tabId`'s record. Answers the queued message this event started, when the turn it ended let one start, for the panel to put in its transcript; nil otherwise. An event of a turn this tab is not tracking is a turn that is running: the plugin woke into it on its own. It replaces a finished record, so it has its Working row and its Stop like any other.
| arg | type | description |
|---|---|---|
| tabId | string | |
| payload | any |
trustedCall(name: string, ...: ?) → void
The included agent works through the engine's OrigoZero session, so an engine with none is linked the way the engine links: its own device-code flow (`auth.login`), whose state carries the code the service issued and the page it is entered on. The panel shows that code and asks the engine to open that page (`auth.openLinkPage`); it names no page and opens none itself, and it never shows a code the service did not issue (#14973).
| arg | type | description |
|---|---|---|
| name | string | |
| ... | ? |
linkState( ) → any
The link as it stands: `{ phase, userCode?, verificationUrl?, error? }`.
linkStart( ) → any
Start the engine's link, or re-attach to the one already running.
openLinkPage( ) → void
Ask the engine to open the page the pending link's code is entered on. `(true)` when it did, `(false, why)` when it would not or could not.
forPlugin(me: any) → void
| arg | type | description |
|---|---|---|
| me | any |
plugin( ) → void
await(promise: ?) → void
| arg | type | description |
|---|---|---|
| promise | ? |
spawn(fn: ?) → void
| arg | type | description |
|---|---|---|
| fn | ? |
consentRequest( ) → void
The engine draws the dialog a method's manifest entry asks for, on the way through `plugins.call`. Nothing here decides it, which is the point: a plugin that chose not to ask could not skip it, and neither can this.
consentGranted( ) → void
openUrl(url: string) → void
| arg | type | description |
|---|---|---|
| url | string |
newJobId( ) → void
nowMs( ) → void
warn(message: string) → void
| arg | type | description |
|---|---|---|
| message | string |
awaitingPerson( ) → void
Whether the engine is holding a dialog for this plugin right now. The panel cannot see that dialog: the engine draws it above everything, and the call is parked inside the host. Without asking, "waiting for you to answer" and "waiting for the plugin" are the same spinner, which is how a dialog nobody noticed reads as a hang.
serviceCall(name: string, ...: ?) → void
Reach one of the service's verbs by the name the source spells it with. `openSignIn.start` names `service.openSignInStart`: the dotted form is the surface's own vocabulary and the camel form is Luau's, and this is the one place they meet. Every family's `poll` is the exception, and it is not a special case so much as the shape of the thing: a job id is a job id, so ONE `service.poll` reads any of them. The forwarder this replaced spelled that out per family and answered the same function every time.
| arg | type | description |
|---|---|---|
| name | string | |
| ... | ? |
Level = { id: string, name: string, sub: string? }Model = { id: string, name: string, context: string, levels: { string } }Agent = {Usage = {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.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ 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".
Scoped to this part · feeds back into the world's score.