Conversation
check_typeof_vals classified evaluator results by the set of their values
rather than their kinds. Every object is a distinct value, so as soon as a
prompt collected more than one response, object scores (e.g.
{length, has_word}) were reported as "Unsupported types". The key check
behind it also passed plain objects to areSetsEqual, which expects Sets.
Classify by kind, as the Flask version already does, and compare each
object's keys as sets. A lone null score, which typeof calls an object,
now reports the usual unsupported-type error instead of crashing.
Affects JavaScript evaluators and Python evaluators run in the browser
(Pyodide); Python run through Flask was already correct.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three state updates in GlobalSettingsModal called the zustand store setter from inside a React state updater, which runs during render, so React warned "Cannot update a component while rendering GlobalSettingsModal" for any store subscriber. handleChangeSetting also saved to the backend or browser storage from inside its updater, which React may run twice in development. The store's global settings now follow the dialog's settings through one effect, and every change goes through applySettings, which keeps a ref of the latest settings so saves happen outside any updater and quick successive changes don't overwrite each other. Loading still takes only the keys the settings type defines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nvas A first version of ChainBuddy, ChainForge's in-app assistant. From the chat panel in the bottom-right corner, it reads the canvas and proposes changes: new nodes and connections are drawn dashed, nodes it would change or remove are outlined, and a card in the chat lists each change (edits as before and after). Nothing changes until the user accepts, and it never runs anything. It supports the Prompt, TextFields and JavaScript Evaluator nodes, and uses the model chosen under AI Support, on OpenRouter or Ollama. Everything lives in src/chainbuddy/, kept apart from the rest of the app: - knowledge/: a human-readable store of what ChainBuddy knows and the rules it works under (README, model instructions, one guide per node). - flowApi/: its actions (get_flow, describe_node, list_models, propose_changes) and the checks on every proposal, e.g. model IDs must come from list_models, and new nodes' inputs must be connected. - adapters/: the only code that touches the store; translates node data to and from ChainBuddy's settings, and shows or applies change sets. - model/, runtime/: a small streaming, tool-calling client for OpenAI-style APIs (OpenRouter, Ollama) on the openai SDK already in use, and the loop. - ui/: the panel and the proposal card. The app changes by mounting the panel in App.tsx, plus a webpack rule that bundles the knowledge files as text. Tests round-trip every supported node in the example flows through the adapters, and check the knowledge files agree with the code. A live harness (liveAgent.test.ts, skipped unless CHAINBUDDY_LIVE is set) runs the tools against a real model. Known gaps: no undo after accepting; an edited node that had run can show as up to date; the chat doesn't reset when another flow is opened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… flow After opening a New Flow in the middle of a conversation, ChainBuddy worked from its memory of the previous flow: it skipped get_flow, so it didn't see the blank starter nodes and added new ones beside them, or attached an evaluator to the empty Prompt Node. - propose_changes refuses unless get_flow was called since the user's last message, so edits made by hand between messages are seen too. - The panel starts a fresh conversation when none of the nodes it last saw are left on the canvas, and says so in the chat. - A change set can't connect to or from a node that stays blank (a Prompt Node with no text or models, a TextFields Node with no values). - The instructions say to fill in a new flow's blank nodes rather than add new ones, and to compare models by listing them in one Prompt Node. - A read-only setting repeated back unchanged is ignored rather than reported as a problem, which was causing needless retries. - The live harness gains a scenario for the blank flow New Flow creates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Proposed nodes are real nodes, drawn dashed, until the user accepts or rejects them. If the flow was autosaved while a proposal waited, reloading brought those nodes back with no card left to accept or reject them, so changes the user never accepted stayed on the canvas. The canvas adapter now removes proposed nodes and connections that no live proposal owns, and clears leftover outlines, when the panel loads and whenever a flow is opened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#457 added @mantine/hooks at ^6.0.22, but the locked @mantine/core, dates, dropzone and prism (all 6.0.21) each require @mantine/hooks at exactly 6.0.21, so `npm ci` failed with a peer-dependency error. Match the rest of the Mantine packages instead of upgrading them all. The lockfile also lacked peer packages npm installs automatically (@testing-library/dom, @popperjs/core, range-analyzer, a newer picocolors), so npm reported it out of sync with package.json; syncing it adds those and updates npm's dev flags on 15 entries. Nothing is removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three bugs in accepting proposals, found in code review: - Accept only marked a proposal done after applying it, which takes a few ticks, so a double-click (or a Reject meanwhile) ran on it again. A second run could add deferred connections twice, sending every input value twice. accept() now marks it "Applying…" first, and a second Accept or a Reject is ignored. - A node deleted since the proposal made accept() throw partway, leaving the change set half-applied while the card said "Couldn't apply". It now checks every node the proposal needs first; if one is gone it changes nothing and says so. An unexpected failure midway now says some changes may already be applied. - The panel only recorded the nodes it had seen after each reply, so accepting a proposal that replaced every node made the next message look like another flow, and the conversation was reset. Nodes an accepted proposal leaves now count as seen. Adds the first tests of the store-backed canvas, against a small zustand store standing in for ChainForge's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
After the rebase onto main, saving stopped working for the rest of the session whenever a loaded flow held leftover proposed nodes. main's FlowLoadGuard (#460) refuses saves until a loaded flow's nodes render exactly as loaded, but ChainBuddy removed the leftovers as soon as the nodes arrived, so the load never counted as finished: every save and autosave logged "Not saving: a flow is still loading." and wrote nothing. The cleanup now waits until the canvas has been still for a second, by which time the load has finished. A test drives the real FlowLoadGuard to cover the interaction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Models used to other template languages write {{country}}. ChainForge
reads that as a variable named "{country", so ChainBuddy connected to an
input by that name, and the model would have been sent "What is the
capital of France}?". Proposals with {{ or }} in prompts or values are
now refused, with a note on ChainForge's single-brace syntax.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What ChainBuddy knew about each node type was spread across six files, in parallel if-chains: its settings and checks (nodeSpecs.ts), what makes it blank and an Evaluator-only message (validate.ts), card labels and formatting (describe.ts), handles and data translation (nodeData.ts), the guide list (knowledge/index.ts), and the node list typed into instructions.md. Each node type is now one NodeKind (nodes/prompt.ts, textfields.ts, evaluator.ts): its name, guide, settings (label, check, read-only, required, how list items show), output, inputs, what makes it blank, handles, and the translation to and from node data. Kinds are pure; the canvas passes in ChainForge's template parser and model lookup. The checks, the proposal card, the canvas adapter, the tools and the model's list of node types all read NODE_KINDS, and nodeSpecs.ts is gone. Adding a node type is now: write its guide, write its NodeKind, list it in nodes/index.ts. A test registers a stand-in Items Node kind and checks it can be proposed, checked, connected to the existing Prompt Node, shown on the card and translated, with nothing else changed. The evaluator had three names; it's now "JavaScript Evaluator" everywhere, and a test checks each guide's name matches its kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… flows The chat panel held all the conversation logic: the messages, running the model, decision notes, aborting, and a heuristic for noticing another flow (seenNodes, handledAccepts and a check on every send) that had caused two bugs. It now lives in useChainBuddySession; the panel (501 lines, now 277) only draws what the hook returns. Another flow is noticed by an event instead: the canvas adapter's watchForFlowSwitch fires when a node update leaves none of the last node ids, skipping empty updates (loading a flow empties the canvas first). The new-conversation note now appears as soon as the flow opens, and a waiting proposal is withdrawn. A run cut short by a flow switch or Start over no longer writes its messages into the new conversation; a plain Stop still keeps them. The first version of the detector recursed until the stack overflowed: the switch handler rejects the waiting proposal, which changes the store and re-entered the detector before it had recorded the new flow's nodes. It now records them first; a test reproduces the overflow on the old order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…anvas Proposed nodes are fitted into the part of the canvas the chat panel doesn't cover, once React Flow has measured them, instead of after a fixed delay. Proposed additions, changes and removals get a stronger outline and a label saying what they are and where to decide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
After New Flow, ChainBuddy fills in the blank TextFields and Prompt nodes, but an edit only outlined the node, so the canvas looked unchanged. An edit to an unfinished node (one its kind says is missing something) is now shown on the node itself, labelled "Proposed contents". The node keeps its old data under chainbuddyOriginal, so rejecting it, or reloading a flow saved while the proposal waited, puts it back. get_flow still reads the node as it was. Edits to finished nodes are still only outlined. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…canvas Fitting a whole proposal left of the chat panel could zoom out until the nodes were unreadable. The view now zooms out no further than 0.65; a proposal that doesn't fit starts from its left and runs under the panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Text that arrived after a tool call was sometimes joined onto the
model's previous message ("characters:I've proposed"): the state
updater read whether text was still streaming only when React ran it,
after the flag had changed. It's now read when the text arrives.
Proposal ids were counted per canvas, so a canvas created again (hot
reload, for one) reused ids the panel had already shown, and the new
proposal got no card. Ids are now unique.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each node type listed the node types its output could connect to, so a new node that others feed into meant editing their kinds. Now a kind's output names what it gives (values, responses or scored_responses), and `accepts` lists what its inputs take. A new node type fits in without changing the others. The model is told what each type gives and accepts up front. Drops the unused "view-only" support level. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README described running nodes, reading results, undo, three more actions and four tests as if they existed; they're now under Planned. Rules list what's enforced, and where. The guides no longer advise on runs ChainBuddy can't make, drop header fields nothing read and the "Connects to" tables (now said in Inputs), mark required settings, and say to keep the models a blank Prompt Node already has. The knowledge test now also checks required, list and code settings, and what each guide says its inputs accept. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…a.ts away nodeData.ts mixed three jobs. Node kinds now use ChainForge's template parser directly, so `inputs(settings)` takes no parser, CanvasPort loses inputsFor, and the stub canvas's simplified parser goes. Model IDs, building a model's LLMSpec, and the list of models move to adapters/models.ts. supportOf and inputsOf join the registry, and the handle and data translation the canvas alone uses moves into canvas.ts. The live test's environment gets a `window`, which the parser needs. Also makes redrawing a node fail safe. It used to take the node off the canvas before working out its new edges, so an error there (here, from half-edited code during hot reload) lost the node, and the error was swallowed. Everything that can fail now happens first, and errors are logged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work in progress: a first version of ChainBuddy 2.0, plus two fixes found along the way.
ChainBuddy 2.0
A chat panel in the bottom-right corner of the canvas. You ask for a flow or a change; ChainBuddy reads the canvas and proposes changes, drawn on the canvas for you to accept or reject:
It supports the Prompt, TextFields and JavaScript Evaluator nodes for now, and uses the model chosen under AI Support, on OpenRouter or Ollama.
Everything lives in
src/chainbuddy/, kept apart from the rest of the app. The app itself only changes by mounting the panel inApp.tsx, plus a webpack rule incraco.config.jsthat bundles the knowledge files as text.knowledge/flowApi/adapters/model/,runtime/openaiSDK already in use, and the loop that runs toolsui/Commits
@mantine/hooksto 6.0.21 sonpm ciworks again. Add the frontend packages the code imports but package.json left out #457 added@mantine/hooksat^6.0.22, but the locked@mantine/core,dates,dropzoneandprism(all 6.0.21) require exactly 6.0.21, sonpm cifailed onmain. The lockfile also gains the peer packages npm installs automatically, which it lacked. A plainnpm ci, the tests and a production build all pass.main, removing leftover proposed nodes as a flow loaded keptmain's FlowLoadGuard (Don't autosave an empty flow over the saved one while a flow is loading #460) from ever seeing the load finish, so every save was refused for the rest of the session. The cleanup now waits for the canvas to settle; a test drives the real guard.{{double-brace}}variables. ChainForge reads{{country}}as a variable named{country, so the model would be sent a stray brace.NodeKindper node type, in a registry. Everything ChainBuddy knows about a node type (settings, checks, how to read and write its data) is in one object insrc/chainbuddy/nodes/; adding a type means adding one kind toNODE_KINDS. A test registers a stand-in Items Node kind to prove it.ChainBuddyPanelonly draws;useChainBuddySessionruns the model and tracks proposals. Opening another flow now starts a new conversation and says so.values,responses,scored_responses) andacceptslists what its inputs take, replacing per-kind lists of target node types. A new node type no longer means editing the others.adapters/models.ts, and foldnodeData.tsaway. Kinds use ChainForge's template parser directly (no moreCanvasPort.inputsForor simplified test parser). Also makes redrawing a node fail safe: an error while redrawing used to lose the node silently.Testing
src/chainbuddy/__test__/liveAgent.test.ts, runs the tools against a real model. It's skipped unlessCHAINBUDDY_LIVEis set, e.g.CHAINBUDDY_LIVE=openrouter:anthropic/claude-haiku-4.5.Not done yet
🤖 Generated with Claude Code