Repository navigation
Conversation
…e scene store divide_zone over the hosted MCP crashed with "Cannot read properties of undefined (reading 'getState')" (Hawkesbury run 2): the store's history step wrapped a host's runtime too, and the hosted server's store has no history. Main now applies a host's runtime without that step; this case keeps it so. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- add_wall, add_level, create_stair, place_items, create_room, furnish_room, search_assets and the fourteen room and floor tools (divide_zone, merge_zones, set_floor_foundation, create_mezzanine…) each have one contract and one core operation, registered on the MCP and defined by the chat from the same object. create_wall, create_level, place_item, create_stair_between_levels and cut_opening leave the MCP: add_wall, add_level, place_items, create_stair, add_door and add_window replace them. create_roof keeps its own definitions for now. - An operation that reads construction the host derives (re-derived rooms, auto ceilings, floor plates) returns afterReconcile; both hosts run it through applyAgentOutcome with their own reconciler, in one undo step. - A mutating shared tool says what it achieved: what the scene holds after the call, against a copy taken before it, so a host that writes its node map in place no longer reads every creation as unchanged. - add_object requires a reason, what the object stands in for, checked before its script runs; verify_scene lists authored objects with their reason. A plain box with a wall's size, or a floor plate, is refused. - The MCP takes the host's item library for search_assets, place_items and furnish_room; a standalone server keeps the built-in list. A search with no match points to add_object. - No listed input schema holds a tuple, so Claude Code keeps every tool (it had dropped nine room and floor tools), and shared tools declare loose outputs, so a field core adds no longer fails a client that listed them. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d opening) A cased opening or an arched passage, as the editor's door panel offers: add_door takes openingKind 'opening' and builds a door node with no leaf, framed or arched by its outline. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in its way A build that deletes plan-only floors, then copies a floor up, pushed every floor above with each copy, gap included, so a model spent calls on reorder_level. A copy now fills the free floor; without a gap nothing changes, in the editor's Duplicate too. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run 3 (2026-10-05) wrote its front door's three glass strips off as "not possible": both tools take code, a script for what the fields cannot express, and their descriptions stopped at outline, type and style. They now say that glass strips, a pattern of lites or carved panels are written as a script in code, never left as not possible. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit 923f64005ae8abc2a05977ed34ead326eec2d0c3)
Working parity, from the MCP ↔ chat runner's first report: - add_door and add_window are one core operation, addWallOpening, and one result the contract declares: ok, the id, wallId, localX, t, wallLength, clamped, coordinateSystem, sillHeight, message and achieved. The MCP's `position` copy of t is gone; the input keeps it. A rebuilt opening answers the same, rebuiltOpeningResult. - find_by_type refuses a level that is not there (level_not_found), as get_zones does; it answered "nothing found". - add_level on a scene with no building starts it as the editor does: a site (unless one stands), a building, its ground level. An agent that cleared the scene could not begin again with the tools. - create_project saves the session's scene as the project's first draft: from any other session it was scene_not_found until a save. - Every write says where it went, top-level: project, and `unsaved` with how to keep it on a scratch scene no project holds (writeTarget). A result's own `note` stays the operation's. Shared-table cases for find_by_type and the fresh start; the MCP's tests move from `position` to t and achieved. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run 3 took five attempts to paint a pier grey through patches:
`material: {color}` reported applied and stored {} (the schemas drop
unknown keys), `materialPreset: null` was refused (no way to clear), the
preset hid the material set under it, and an unknown preset rendered grey
with no word.
honestNodePatch, on apply_patch's updates: a path the node would drop or
change is refused naming it (unknown_field: material.color, where a colour
goes in material.properties.color), a material the library lacks is refused
naming the nearest of the target's kind (unknown_material), a material a
set preset hides is refused naming the preset (shadowed_field), and null
clears a field (field_required when the field is required). Free-form
records (metadata, slots) pass as sent.
Slice: S1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
S1's rename of place_item into place_items placed on a level's floor only. As main's place_item did, an item may now name its host (targetNodeId), its position in level coordinates as main took it: - a wall: along it from its start, y the height of the item's bottom (required), on the side of the wall the point is on (the front faces its left; the back is turned round), centred in the wall or, for a wall-side fixture, on its face; kept inside the span and the height, as the editor's wall placement keeps it; wallId, wallT and side set; - a ceiling: under it, its top flush; - an item standing on a floor: in its frame. An object built with add_object takes it on its real surface under the point, or hangs a ceiling item from its underside above it (a sloped vault), and the result names the part (restingOn); a catalog item, on its top; - a room, a slab or a level: its floor. Each item is refused on its own: height_required, item_too_tall, unsupported_host (a door), host_not_found, host_not_on_level (an item on an item). An id the library lacks stays refused (asset_not_found) rather than placed as main's 0.5 m placeholder. The surface logic is the chat's place_on_surface's, moved into core. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tions S1's create_stair dropped what main's create_stair_between_levels set: - railingMode (none, left, right, both; default both); - materialPreset, on the flight and its segment, refused when the library lacks it, naming the flooring it has; a stair takes no paint, so the colour it points to is a flat library one (library:preset-*). The lab's requireMaterialRef checks it, as apply_patch and update_node do: upstream's unknownMaterialPresetRefusal rides on its node-slots module, which the lab does not have yet; the two meet when the lab merges upstream; - name (default Staircase N); - the opening controls, under main's names: createDestinationSlabOpening, createSourceCeilingOpening, destinationSlabId, sourceCeilingId, openingWidth, openingLength, openingOffset, openingCenter and openingRotation (in degrees, as the flight's rotation). With none of them, the stair cuts its own opening as the editor's stair tool does (openingOffset is its margin, 0.08 m by default; both cuts off cuts none). With any, the opening is cut as given, as main cuts it: a floor opening owned by the stair, with the pose the live opening systems move it by, and the stair's own opening off so nothing is cut twice. Main centred that opening at z + length / 2 whatever the flight's turn; here it follows the climb, so a turned flight's opening stays over it. slab_not_found and ceiling_not_found refuse ids that name no slab or ceiling. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…read alike A Zod tuple renders as prefixItems in the chat's JSON schema and as a flattened array on the MCP, so agent-surface parity found the two input schemas apart. openingCenter is a length-2 number array, as every other contract writes an (x, z) point. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pascal-3d workflows said place_items places on a level's floor only; it now takes a host per item (targetNodeId), as main's place_item did. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… default requireMaterialRef names a flat library colour (library:preset-*) unless its caller asks for paint, as paint does: honest patches and create_stair then point to no tool a slice may lack, and create_stair needs no flag. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… it empty 2026-10-03, a Claude Code run on the hosted MCP: create_project in a session that had another project loaded returned that project's levels, and the next save would have written it into the new project. The session is now bound to the new project first, then, when it was bound to another one, its scene is emptied to the default site, building and level, with no undo back to the old one. Work not bound to any project is kept: it is what the new project is for. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… was not loaded from 2026-10-03, a Claude Code run on the hosted MCP: the server reloaded, the session started over on a blank scene, and save_scene(projectId) wrote it over the project's draft (8 levels, 10 imported plans, the calibration and the alignment, no history). save_scene now refuses scene_not_loaded when the session's scene was not loaded from (or created for) the target project or scene and that one holds more than the default site, building and level; replace: true writes over it on purpose. Live sync already writes only to the scene a session loaded. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Run 4's first save_scene, right after create_project bound the session to its project, was refused "projectId is required for Supabase store. Call create_project first." With no id and no projectId, save_scene now saves the current scene to the scene this session is bound to (create_project, load_scene or an earlier save); with nothing bound, it saves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A floor copy is hundreds of ids the model never uses; written into the prompt cache once, they are read back on every call after. The result keeps the counts per type in `copied`, lists the first 40 ids and says how many it left out (`newNodeIdsOmitted`), which the MCP declares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since owned floor openings, a stair's opening is a floor-opening node on the floor above, owned by the stair. The check already looks for it; these cases pin it: an owned opening on the floor above counts, one drawn on the stair's own ceiling does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every write gives an `achieved` (what the scene holds after the call), but the MCP's add_object returned none; the chat's add_object did. The headless parity gate could not see it, its chat lane unreachable on that row; parity with an editor tab showed it. The MCP now counts the change against a copy of the scene before the call, as its shared tools and the chat do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What this does
One thing to know before reading the diff: the branch is stacked on #1007, so the diff carries that PR's commits as well. The author asks reviewers to read only the commits after
Start at |
Run 4 (L66): the agent made the porch an outdoor room, so the house wall behind it had a room on both faces and knew no outside. The front door faced the hall, view_scene showed its outside from inside, and the porch face took the inside finish. A room is outdoor when it has no ceiling and can be left without crossing a wall, through a separator no room stands behind or into another such room, found as a fixed point. A wall face on an outdoor room is exterior, so door facing, view_scene, facades and finishes follow on both paths. A room that only lost its ceiling, a kitchen open to a terrace, and a courtyard walled on every side stay inside; a carport (no ceiling, an open side) is outside. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ywhere L59 replaced "blocks door clearance" with the item, its size and the door it is in the way of; the MCP test and the clearance log still read the old text, and the old reason entry was left unused. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r too large for its room L59, run 4: furnish_room skipped the catalog's 2.34 m bathtub as blocking the bath's door; the agent then set it there itself with place_items, which checked only that its centre stood in a room, and verify_scene ended on two blocked doors. A floor item is now refused on its own when the room it stands in holds it in no turn (too_large_for_room, with both sizes, pointing to a smaller one or add_object at the room's size), or when it stands in the space a door needs, as verify_scene's blocked-door check sees it (blocks_door, naming the door and a spot in the room that clears every door, when there is one). Overlaps are not refused: a chair under its table and a bed on its rug overlap by design. Items on a host keep their own checks. furnish_room's skips name the item's size, the door in the way, or that the room is too small and what instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tant (L68) The store's new-scene level wrote a literal 2.5. It now writes DEFAULT_LEVEL_HEIGHT, as the hosted MCP's default scene and a new project's empty scene do, so the three cannot drift apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test asked for one roofing material by its id's prefix, which is one library's naming, not every library's. It now asserts roofing first as the library tags it (surfaces include roof), which every library with roofing passes; which roofing that is stays the data's business. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s what they protect
apply_patch held the honest-update check inline. A patch guard now
registers from its own module, run by order; apply_patch runs what is
registered and passes an McpError through as it is. The honest-update check
is the first, from honest-patch-guard.ts, and answers as it did:
{ code, patchIndex, id, message }.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A door's swing and a sectional door's track run behind its facing; add_door always faced the wall's front, so a garage drawn with its front inside ran its overhead track on the street, and a front door behind a porch faced the hall. add_door now faces a door out when the wall knows its outside (resolveWallExteriorSide): rotation π and side 'back' when the outside is the back. Walls that do not know their outside, and inside walls, keep the front as before. One rule for agents and people: the editor's door tool places a door on an outside wall facing out whichever face is hovered (placedDoorFace); R still flips it. The shared opening cases pin both wall directions and the inside wall. Doors made in the same call as their walls are not covered: their walls learn their sides only afterwards. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The outdoor-sides cases came with checks of a facade fill and a view's framing from the porch, tools this branch does not have. The porch case now checks what it owns: the wall is exterior on the porch face, and add_door places a door that faces the porch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir winders) into the shared tools foundation Where main and this branch did the same thing, main's version stands: - measure_stair and fit_stair (pascalorg#1000) are shared tools next to create_stair: one stairs contract module and both operations registered. - verify_scene keeps main's informational issues (hasIssues ignores severity 'info') with this branch's authored objects and checks. - create_stair_between_levels stays replaced by create_stair; main's sizing of a new flight from the design targets (planStairCreation) is carried into create_stair in the next commit. - The tool lists (annotations, README, the skill) gain main's two tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main sizes a new flight from the stair's design targets and resolves its rise against what it stands on and arrives at (planStairCreation, pascalorg#1000). create_stair now builds its flight the same way: the run and the risers come from the design targets unless given, and the stair keeps uniform risers and its flight's step count. main's two sizing cases now run on create_stair. The ring of a stair's owned opening may start at another corner once main re-plans it after a move; the case compares the ring's extent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4315e5b to
7904148
Compare
…into the foundation Both sides kept: the foundation's furnish_room export and asset catalog, main's optional hosted service tools, registered before the resources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… labels it, a Separator Divide, merge, delete and an outdoor room's outline now say "a separator (the editor's Separator: a room boundary with no wall)", so an agent describes what the person finds in the Scene panel instead of an "open divider", the door and window panels' word for the bars between panes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment-only: each comment keeps the failure that motivated the code and drops which session found it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7904148 to
01038f0
Compare
An object named after something Pascal builds is built with a hint naming the tool for it. Four of those hints named tools this server does not register (create_reference_elements, create_stairs_and_lifts, correct_plan_reading, apply_facade), so an agent following one called a tool that fails. They now name add_wall, create_room with its floor tools, add_door / add_window, create_stair and create_roof; lifts and balconies, which have no tool here, get no hint. A test checks every tool a hint names against tools/list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… falls in With opening controls, create_stair reported the storey's first slab and first ceiling as the surfaces it cut, and took any slab upstairs as proof a hole was cut there. On a floor of several rooms that named the wrong room's slab, and an opening under no slab was still reported cut. It now picks the slab above and the ceiling below that hold the opening's centre; a slab or ceiling id given still wins. main's create_stair_between_levels picked the first surface the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… above is cut
slabHoleCut counted every opening the flight cut, so a stair cutting only the
ceiling below reported a hole in the slab above, and an agent took the upper
floor for open when it was not. It is now true only for an opening drawn on
the floor above, as the chat's create_stair meant it on main ("auto floor
opening").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A capture asked with ephemeral: true comes back to its caller on snapshot:captured (matched by requestId) and the host stores nothing: no project snapshot, no thumbnail. The hosted chat's view_scene uses it to let the agent look at what it built from an angle it picks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r tab for the picture view_scene has one contract in core and its view in core (sceneViewPlan): the target framed from a side, a street height or an eye placed by hand, or a photo's camera (camera: straighten_facade_photo's camera.pose) rendered at the photo's aspect. The chat renders it in its own editor; the MCP has no renderer and asks its host (SceneViewHost), which refuses no_editor_open when no editor answers. registerTools takes its hosts as one object. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… build A roof's dirty mark clears when its merged shell is queued, frames before the shell is built (one roof a frame; the 290's seven overlapping segments took about 40 s), so the scene-ready count read the roof as done and a view_scene picture could show its segments apart, fighting where they overlap. The count now includes the roofs waiting for their shell (getPendingRoofMergeCount). Checked live: view_scene right after create_roof. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A roof queued for its shell but never mounted stays queued; counting it held every capture until the stall timeout. The pending count takes the roofs that have a shell to build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…both surfaces view_scene's note (a picture to compare with the reference, not a measure: sizes and counts come from the tools) moves to core as sceneViewNote, so the MCP and the chat say the same. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…il scale L51: run 3 compared whole facades only and settled for a plain door against the photo's door with three glass strips. view_scene's target now takes a door, a window or an item on a floor and frames its own box: an opening along its wall at its height, as deep as the wall; an item by its asset's dimensions and turn. An opening is seen from its outside face by default (its wall's exterior side, else the side it faces), whichever way the wall was drawn: a close-up to lay beside the photo's crop of the same element. Both surfaces take the pose from core. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
L51's other half: run 3 cropped the photo 9 times in the shell. view_scene
takes photo { source, region }, the region in the photo's pixels as an
agent measures it; core checks it (photo_region_invalid) and hands it to
the host, which crops. The MCP returns the view and the crop, each after its
label, with photoRegion in the result; a host that cannot crop refuses
photo_crop_unavailable.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e on both The hosted crop capped its size at 1280 px and the chat's did not: a whole 4000-px photo given as the region went to the model at full size. Core owns the cap (PHOTO_CROP_LONGEST, photoCropSize) for both hosts. view_scene's photo.source said "the URL of a file the user attached", which the hosted MCP refuses: it now says a data URL, and in the chat an attached file's URL too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
L51 live (2026-10-05): the front door's crop came back 85 × 155 px, its four glass strips about 8 px each, under what a vision model resolves. A crop shorter than 512 px on its longer side is enlarged to it (no new detail, but the strips stand apart); a long one is still held to 1280 px. Both hosts size their crop with photoCropSize. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…or a slab S10 live (20:33): view_scene could not look at the steps it built (nothing_to_view, "no walls to look at"). A column frames round its position at its height, a fence along its run, a slab over its outline (at least 30 cm tall, so a lawn still frames), a stair round its foot as far as it could reach; as doors, windows and floor items do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t it holds furnish_from_plan live (22:30): a level with furniture placed from the plan and no walls yet answered nothing_to_view, so the agent could not look at what it placed. A level, a building or the scene with no walls now frames everything on it (items, slabs, fences, columns, stairs) by the boxes view_scene already gives each; nothing_to_view is for nothing built. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A client shows the server's instructions before the agent's first call. The host can now add its own lines there, such as what the agent does when a tool needs the editor tab in front; without them the server sends none, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…our values The camera input takes a photo's position, target, field of view and aspect, whoever measured them; its description no longer sends the agent to a photo tool this surface may not offer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tab Over the hosted MCP the user's editor tab renders the view: with none open the call is refused editor_tab_required, and a tab in the background must be brought to the front (editor_tab_hidden), as the host answers them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es or dates Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
01038f0 to
4e70b48
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4e70b48. Configure here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Folded into #1014, the sync bundle that brings this PR's content (with its review fixes and docs) together with the rest of the tested work, tested there by the owner. This PR stays as the review map for its part. |

Reviewer guide: the agent's vision
Merge order: after #1007 (the shared tools foundation), which this branch is stacked on. Until #1007 merges, this PR's diff also shows #1007's commits: review only the commits after
8f8bb72ce. Then pascalorg/private-editor#847, which points its submodule here.What changes for a user (an agent on the MCP or in the hosted chat):
view_scene: the agent looks at what it built from a viewpoint it picks (a compass side, a height, a street-level eye, an orthographic face, a photo's own camera) and gets the picture back, to compare with a reference before it fixes anything.How to test:
bun install && bun run cion this branch.view_sceneis refusedview_unavailable. The hosted MCP renders it in the user's editor tab (the private PR).What to read:
packages/core/src/agent-operations/scene-view.ts: where the eye goes (the pose), the size, the photo crop request, and the note. Pure, shared by both surfaces.packages/core/src/agent-tools/view-scene.ts: the contract.packages/mcp/src/tools/view-scene.ts: the MCP tool, which asks its host (SceneViewHost) for the picture and the crop.packages/editor/src/components/editor/snapshot-capture.ts: the ephemeral capture.What to skim:
packages/viewer/src/systems/roof/roof-system.tsxandviewer/index.tsx: a capture waits for roofs still building, and only for roofs on screen;packages/mcp/src/server.ts:sceneViewsandinstructionsoptions; the registration and annotation lists.What to ignore:
*.test.ts,plugin-evals/tool-annotation-justifications.json,scripts/openai-tool-annotation-policy.ts.Not in this pack: the photo tools (locating and straightening a facade photo), cropping an uploaded photo (it arrives with Plans in), and an inventory of what a reference shows (Checks).
What does this PR do?
view_sceneon both agent surfaces, from one contract and one planner in core:target_not_found,nothing_to_view,photo_region_invalid) refuses before any render is asked.photo { source, region }returns the photo's crop of the element beside the view, sized by one rule on both hosts (photoCropSize: a small crop enlarged to 512 px, a large one capped).view_sceneand asks itsSceneViewHostfor the picture; without one it refusesview_unavailable, without a crop host a photo is refusedphoto_crop_unavailable.ephemeral: trueon the thumbnail request; the frame comes back onsnapshot:captured, stored nowhere). The viewer holds a capture for roofs on screen whose merged shell is still to build.createPascalMcpServer({ instructions }): the host's lines for what a client reads at connect.No schema change; old scenes load as before.
Draft: it stays a draft until the slice is verified (the live checks and your QA).
How to test
bun install && bun run ci.bun test packages/core/src/agent-operations/scene-view.test.ts packages/mcp/src/tools/view-scene.test.ts packages/mcp/src/server.test.ts.Screenshots / screen recording
Not added yet.
Checklist
bun devbun checkto verify)mainbranch🤖 Generated with Claude Code
Note
Medium Risk
Broad changes to agent mutation paths (rooms, stairs, placement, add_object refusals) and scene apply/reconcile behavior; mostly covered by expanded contract tests but affects all three surfaces.
Overview
Unifies MCP and chat agent building tools in core with one implementation per tool and large declarative fixture tables so core, MCP, and chat runners stay aligned (
surfaces, optionalcheckcallbacks, catalog in context).New or expanded operations:
add_level(including fresh site/building on empty scenes),add_wall,create_room(openings by edge, outdoor terraces, reconcile for slab/ceiling ids),create_stair(owned floor openings, custom opening geometry),furnish_room,place_items(hosted placement, door/room guards),search_assets, room structure tools (divide_zone, foundations, mezzanine, etc.), plus exports forapplyAgentOutcomeandmergeSceneChanges.add_objecthardening: new objects require a reason (checked before compile where fixtures say so); plain wall-sized or floor-plate boxes are refused with pointers toadd_wall/ slabs; optional hints when names overlap Pascal types; edits skip shape gates and keep metadata.Honesty and safety:
achievedChanges/ deleteachievedon MCP+chat;honestNodePatchandrequireMaterialReffor patches and materials;applySceneChangesnow updates parent/child links;find_by_typerefuses missing levels;verify_scenecovers stair floor-openings and authored-object listing;scene-measurecheckpoint deltas;writeTargetfor unsaved projects.Reviewed by Cursor Bugbot for commit 4e70b48. Bugbot is set up for automated code reviews on this repo. Configure here.