Repository navigation
Added
-
A plugin's set of nested classes (contract
ofClasses, 2026-10-16): Studio reads a holder ofstatic final class X extends <element>classes, each with its singleton constant, itspublic finalfields and its one-return
setup method (project/managed/ManagedClasses). A field reference —Screens.MARKET.beerBuy, orMARKET.close
inside the holder — reads as the plugin's value and is written back asScreens.MARKET.beerBuy. Fields are added,
renamed, repointed and removed by binding like any set's constants; a class is added with its constant and an
@Overridesetup, renamed with its constant (the class follows when it was named from it,FAVORED_SHOP→
FavoredShop), and removed only while nothing outside it uses the constant, the class or a field. A bareclose
inside the class is not read as a reference yet: writeMARKET.close. -
Project ▸ Migrate… carries the bot over a plugin change by its author's
@Migraterules. Each row is an old
element the bot still uses, with its uses (double-click one to go to it) and its rule's state. Write rule
writes the element's signature and a default body intoMigrations, marked to be written, and opens it in the
editor. Preview lists each use before and after. Apply rewrites every use a written rule covers, as one
version after a safety one. Remove finished rules removes each rule whose element the bot no longer uses,
and the file when that empties it. A retired type's values still go through Copy values… (the migration
window). Migrate compares the bot with the classpath the project loaded before its last change: an upgrade, a
removal, or a dev build rebuilt under the same version. It also opens from a banner above the canvas when that
change broke something, from a Review tab row, and from the upgrade's question, whose Migrate first moves the
version and leaves the code for Migrate. -
migration write(MCP): the assistant sets a seeded rule's body — one value, or one statement for avoid
rule, with the imports it names — checked to compile, and the seed's@Refactormark goes.edit_code's values
could not sayself.markers(List.of(marker)), so a rule could be seeded and never written over MCP. -
The assistant's
migrationtool is Migrate (MCP).listgives Migrate's rows, each element with its first
uses and its rule, the rules naming nothing, the finished rules and the retired types' values.seedwrites an
element's rule, which the assistant then writes withedit_code.applypreviews each use before and after,
and withconfirmwrites them.remove_finishedremoves the finished rules.copy_valuescopies a retired
type's values by the bot's own@Migratemethod for the pair, andswitch_usesandremove_migratedstay as
they were.readandset, with their mapping lines, are gone: the rule is the mapping.plugins set_version
takesrulesFirst, the upgrade's Migrate first: only the pom moves, and the answer counts what broke. -
One migration engine behind
@Migraterules (migrate/). A bot's rule names the
old method, constructor, field or whole type by its own signature, and Studio finds that element in the jars the
bot used to compile against. It then rewrites every use in the project through the rule's body, keeping each
use's own spelling. A chain such asnamed("home").marker("a").marker("b")comes out whole. Rules with neither
or both ofvalueandname, rules that match nothing, and two rules for one element are refused with a reason.
With no rule, nothing is rewritten. A rewrite that would not compile is left as written and its function is
marked@Refactor.migrate.Brokenlists what the new jars break and whether a rule covers each element. -
The old API survives a plugin change. Each load records the classpath it loaded, and one that differs keeps
the last as the classpath before (~/.cache/botmaker/api). A-SNAPSHOTjar is recorded as a copy of the bytes
loaded, so a dev reload can still read what the bot used to call. -
One finder of uses (
nav/Uses): calls,new,this(…)/super(…), method references, field reads and
writes,caselabels, type names and imports, matched by binding. Signature changes (MethodReferences) and
migrations both use it. -
The assistant's costly answers show up.
tool-times.tsvin Studio's cache now also records how much text and
how many images each tool call answered, sosort -k5 -nfinds the replies that cost the most. A file written
before these columns is moved aside totool-times.old.tsv. At connect, the MCP server names the bot's plugins
that have a guide and points toplugin_guide. It no longer pastes the guides into its instructions, which
clients cut short. -
A plugin's assistant tool may take nested values and one of several actions. Studio serves a nested record
as an object, a list of records as an array of them, and an action tool as one flat object:action, whose
description says what each action needs and does, and the actions' fields, each naming the actions that take
it when not all do. NooneOf, which Gemini reads poorly. -
Studio's own MCP tools are declared as a plugin's are: each an
AssistantToolover a parameter record, in
assist/FileTools,StudioTools,RunToolsandProjectTools;McpToolsonly serves them. The hand-written
schemas and thetext()/number()/flag()readers, which turned a missing value into""without a word, are
gone: a missing or ill-typed argument is refused,REFUSED: <tool>: 'value' is required.add_file'skind
andset_setting'skeyare enums, sent as a JSONenum;add_method/edit_signature'sparamsare typed.
One wire change: each ofapply_edits' edits names its kind asaction(wasop).
edit_variable'snameandtypemay be left out, as its description always said. An argument a tool does
not take is refused, where a tool with none ignored it; an answer loses its trailing blank lines. -
Studio serves 37 MCP tools where it served 58, and a third fewer characters (23,847, was 31,650 —
McpBudgetTest's ceiling is now 26,000). Families of verbs became one tool of several actions,action
picking one:edit_code(insert, set, delete, move, add_argument, edit_variable) replacesinsert_block,set_slot,
delete_block,move_block,add_argument,edit_variableandapply_edits; one edit is a list of one;review(list, mark, unmark, undo) replaceslist_review,mark_reviewed,remove_mark,undo_change;versions(list, checkpoint, revert),plugins(list, search, add, set_version, remove),migration(list,
read, set, switch_uses, remove_migrated) andparams(list, set_default, reset) replace their 16 tools.
A field another action takes is refused, naming the action's own. While a plugin moves,
plugins list,
plugins searchandversions liststill answer. The descriptions are shorter; how a slot's value is spelt is
said once, inedit_code's. Studio's answers name the new tools (versions revert,plugins list). -
A plugin's value writes are undone with ↶. Each
PluginValueswrite is one step of the editor's undo, named,
and a plugin'sHistory.batchmakes several one step under its own label; ↶/↷ put every file it wrote back. Before,
only a Project History safety version could take one back. A batch takes one safety version, not one per write.
The history moved out ofCodeEditorServiceintoservices/EditorHistory;plugin/HostHistoryis the contract's
Historyover it. -
The assistant's
undoandredotake the editor's last step back or do it again, as ↶/↷ do, and say which
step and what is next each way.undo_changestays: it puts a review item's function back from the versions. -
A plugin lists the bot's boolean methods and adds one (
PluginValues.booleanMethods,
createBooleanMethod):public static boolean name() { return false; }in the class it names, or a new one. -
A tool's answer in several text parts reaches the client as one text, the parts a blank line apart.
-
quit_studio closes Studio as its window's close button does, half a second after it answers, so a client
can restart it with the bridge's start_studio. -
/mcp-helloproves to a client that it talks to this Studio before the client sends the token: it signs
the client's nonce with the token (HMAC-SHA256) and says nothing of the token itself. Every other path still
asks for the token.
Fixed
- A fast scroll lagged and showed members being drawn (2026-10-18). Three causes, each fixed in
EditorCanvas:- A scroll's draw ran after the frame that painted the new position, so nearly every frame of a fast scroll
showed blanks. It now runs before the pulse lays the scene out. - Every forced layout called
applyCsson the scroll pane. JavaFX restyles every node under a node
applyCssis called on, clean or not. So each draw restyled every member drawn so far, and every render
restyled the hidden kept roots too. Now only the new member (MemberPlaceholder.draw) or the new root is
styled. A switch back went from 11 to 2 ms on the benchmark. - Once a render settles (300 ms), the members still blank are drawn about 6 ms per pulse, nearest the
viewport first, the member at the top kept in place. A fast scroll after that costs about 8 ms a frame
instead of 70.
- A scroll's draw ran after the frame that painted the new position, so nearly every frame of a fast scroll
- The file tabs were sometimes hidden (2026-10-18). Since the canvas moved below the tab strip, a canvas
or viewer asking for more height than the centre column had squeezed the strip to nothing, because a tab
pane's minimum height is 0. The strip now keeps its height (CenterTabs), and the area below gives way,
clipped. - Each render walked the whole bot again for its parameters and its sets' members (2026-10-18 ...