release: 6.0.0 — Claude becomes one with your IDE - #76
Merged
Merged
Conversation
The twelve onX handlers and their helpers move out of the orchestrator into ConversationEvents, ToolEvents, TaskEvents, SignalEvents and ControlEvents; onEvent is now the seven-arm dispatch the map always described it as. agentStatusOf becomes AgentStatus.parse next to the enum it names. The collaborators the handlers read become internal instead of private; nothing else about them changes.
…e signals leave the orchestrator SessionLifecycle owns the process, the launch gates, the credential question and what a terminated process means; BinaryCatalog owns what initialize told us and the request that fills it; SessionSignals holds the rate limits, session state, auth status and the last cost and context usage. ClaudeSession keeps its verbs as one-line delegations and is down to wiring, state holders and the event dispatch. The three contract tests that scan the lifecycle follow it to its file.
requestInitialize hand-rolled its control request, so its callback ran on the process reader thread: it wrote the five non-volatile catalogue fields there and, when the binary's model list lacked the pinned default, called changeModel, whose fireState walks the Swing hierarchy and the tab list from that thread. Every other question already rides SessionQueries.ask, which hops to the EDT before the callback; initialize now does too, as one more entry in the Asks catalogue.
stop() left the three PollSchedule timers running: with an unfinished subagent on disk the revival poll kept re-reading every agent transcript every five seconds for the rest of the tab's life, and the quota poll kept asking a process that was gone. The next launch re-arms what it needs the moment a prompt goes out, so stopping them costs nothing.
Disposing a session terminated its process but never remembered it was disposed, and never cancelled a sign-in in flight. A user who pressed Sign in and closed the tab while the browser leg was open got, on success, restartSession() -> start() -> a fresh claude process for a session no manager listed any more: alive until the IDE exited, burning quota and holding the project directory. shutdown() now records the disposal and cancels the login, start() refuses on a disposed session, and the sign-in's restart is skipped once the chat is gone.
…hdogs failAll cleared the pending map and invoked the callbacks, but the thirty-second watchdog each request had scheduled was only reachable from inside the pending entry, so every in-flight request left a task on the scheduled executor to fire against nothing. A restart loop accumulated them. The watchdog travels with the entry now, and one settle path cancels it whichever way the request ends.
EditSnapshotStore kept the full pre-image of every edited file for as long as the session lived and nothing ever cleared it, while the transcript that could still show a Restore for those rows is capped at two thousand entries. The store is an LRU of five hundred snapshots now, which is more than the cards a transcript can still reach; a Restore on an older card takes the existing 'could not revert' path. Disposing the session empties it outright.
Safety guardrails were at risk of reading work on the guard as offensive tooling and refusing or interrupting it, which stalls maintenance of the control itself. The directive now says explicitly what the guard defends against and that working on it is authorised defensive engineering.
It is itself a guardrail — the last one between a compromised or careless session and the machine — so treating work on it as suspect weakens the layer that stops bypasses.
SessionPersistence takes restore, the open-tab record and the title (rename and the generated one). The forwarders are gone and every caller reaches the owner: cards for pending permissions and edit snapshots, tokens for the counters, diffs.refreshAfterRewind, rollback, lifecycle for isStarting/refreshBootState/dismissLoginCard, turnControl.interrupt, agentScanner.scan, and ClaudeSettings — through the now-public project — for provider, checkpointing and the guard state. The two constants with a single user move there (EXPIRED_TOKEN_NOTICE to ConversationEvents, the control timeout to SessionControlClient). The detekt baseline is emptied: neither LargeClass nor TooManyFunctions holds on ClaudeSession any more, which was the agreed signal that the split is done.
SessionProcess owns the ClaudeProcess handle, the launch generation, spawning for one generation and what an exit code means; SessionLifecycle keeps the policy — when to start, the gates, the boot state, stop and shutdown. The generation is still bumped at the same points (start, the first line of stop and of shutdown), so a late exit callback is ignored exactly as before. refuseRemoteProject no longer clears a starting flag that is never set when the gates run.
LoginCoordinator keeps the public surface, the route decision and the sign-in state. LoginAttempt (LoginFlows.kt) is the single PTY flow; the card and the dialog are two LoginUi implementations of it, so the two near-identical flows collapse into one. SignInCompletion is the verify-and-vault step. SessionNotifier gains the two notifications with an action that were built inline. Deltas from folding the dialog flow into the shared one: its code now goes through submitCode, so the 45 s verify watchdog covers it too; the dialog path stores the setup token like the card path always did; and a card sign-in that fails verification no longer raises a balloon on top of the card's own error.
…n the EDT Pressing Sign in ran resolveEnv() — which sources the user's env script and waits up to 15 s for it — and PtyProcessBuilder.start() on the EDT; verifying a finished sign-in resolved the env on the EDT again. The attempt now starts on the pool and reports back through edt; a cancel that lands while the spawn is in flight terminates the fresh PTY instead of leaving it orphaned, and a second press while one is starting is ignored. EdtAuthContractTest pins both hops.
…reader The whole read loop sat inside one runCatching, so an exception from a listener callback (onToken writing to a locked PasswordSafe) ended the reader: the code prompt that followed was never parsed, and the flow then reported the exit code as a clean success. Each callback is now isolated and logged at warn, the reader keeps going, and scanning stops once the URL, the prompt and the token have all been seen instead of re-parsing the accumulated output on every chunk. The PTY spawn is injectable so the reader has a test.
cancel() destroyed the PTY and the reader then reported the killed process's non-zero exit as a failed login: the card flipped from idle to an error after the user had cancelled, and the dialog route showed a 'Login failed' balloon right after 'Login canceled'. A cancelled flow now reports nothing.
SessionListing owns 'Open previous session': the newest files with title, first prompt, branch and timestamp. It reads each line through SessionTranscriptReader.parseRecord — the one JSONL line parser, now exposed — instead of carrying its own Json instance, and still stops at the first line that completes the three fields. EntryDTO and SessionRef move to their own file.
ProtocolParser keeps the top-level and system tables and the two status notices. MessageParsers takes assistant, user and stream_event; ControlParsers takes the control frames and the rate-limit event, with control_request going through a subtype table like everything else, so a new control subtype is a row and not another branch in the one function that grows per feature. unwrapToolError had no caller outside the parser and is private.
Msg is its own sealed hierarchy (ui/jcef/Msg.kt) and JcefBridge only parses. SessionControl gains three sealed sub-families — Vuln, Navigation, Onboarding — so the router's else-chain onto three boolean handlers is gone: every when is exhaustive and a new message that nobody handles is a compile error, not a warn at runtime. ChatBridgeRouter is the seven-arm dispatch; each family has a Bridge* handler in ui/. The whitelist and suspension arithmetic moves to GuardWhitelists and SecuritySuspensions as pure operations on State; the live half of a settings-menu toggle is the SettingsMenuApply table; the panel registry and its broadcasts are LivePanels; VulnPromptedActions lives in vuln/ next to what it quotes; pooled payloads go through JcefHost.execBuilt, which skips a disposed browser.
A settings toggle now learns whether the key is known by applying it to the live state once, then persists through update {}, so the block handed to update is pure on State.
The card's file_path comes from the tool input, and View diff read it synchronously on the EDT with no containment check and no size cap: a card naming a file under the home directory opened it in a diff, and a large one froze the UI while it was read. DiffPresenter.readCurrent decides — inside the project root, under 1 MB, absent files diff against nothing — and the read happens on the pool; openDiff is then handed the text it already has.
… the page are their own files PageAssembly builds the document and its CSP (appNames and CSS_PARTS live there now, which is where the frontend test loader and the guard-view contract read them). PageRoute is the ladder's pure part. SchemePageServer is the custom-scheme registration and its resource handler. PageDelivery is the ladder itself: the watchdogs, promotion, the loopback server and which rung has been proven. JcefHost keeps the browser, the JS query, exec and its queue, the deferred blocks and the load and navigation handlers. util/Edt.kt gains edtNow — run inline on the EDT, hop otherwise — which replaces the host's private copy.
The replacement chat for a closed last tab is built into a component that is not on screen yet. JBCefOsrComponent.addNotify is what creates the browser, and the 2.5 s ready watchdog was armed in deliver(), before that — so on a cold CefApp, or right after disposing the previous browser, the deadline fired before the first navigation could start and promote() walked the ladder. Its last two rungs, INLINE and NOTICE, loaded through loadHTML, which navigates to file:///jbcefbrowser/… and is refused by the host's own navigation guard: ready stayed false, the queue never drained, no script ran — no composer, no tabs, 'loading' forever — until Open previous session built a browser on a warm CefApp that met the deadline. The watchdog is now armed on the main frame's first onLoadStart, when a browser exists to load; the two dead rungs and the RemoteDevNotice page they served are gone (PageRoute is SCHEME → LOOPBACK → nothing); exec runs nothing in a disposed host and the unreachable JcefHost.dispose() is folded into the Disposer registration; and ChatTabsPanel.close opens the replacement in a finally, so a teardown that throws still leaves a chat on screen.
… switch on GitAction.behaviour is sealed — InitRepository, Prompt(text), Ide(actionId), Host(run) — and perform() is one exhaustive when over it. The two when(action.id) blocks that duplicated the catalogue as arms (and answered a new row with 'No prompt is wired') are gone: a row now says what it does where it is declared, and a row without a behaviour does not compile. Kind and ideActionId are derived so the catalogue's readers and its test keep their vocabulary. GitInit owns the one process the plugin spawns and the VCS mapping that follows; IdeActionInvoker owns the platform-action invocation. IdeActionApiContractTest follows both.
refresh() was reentrant only by a flag: a second caller arriving while a snapshot was being collected had its callback dropped, and the re-run answered the first caller twice. With two chats open, the one that asked second never repainted its Git card after a branch change. Callers now queue; each is answered by a collection that started after it asked, exactly once.
Four files in main and three tests still carried prose: the mechanism each one held (what UntrustedState disarms and why, why the trust gate leaves the binary paths out, why session-scoped suspensions are keyed per project, what the two reachability scans can and cannot see) now sits in the package maps next to the file it describes. One line in ClaudeProcess was the orphaned half of a sentence the previous sweep cut.
SettingsMenuRows builds the JSON the page draws; JcefSettingsMenu keeps the key vocabulary and applies a toggle to State. Both were one 300-line object with two unrelated reasons to change.
…cefHost PageLoadHandler holds the main-frame failure verdict and reports three outcomes: started, arrived, missed. installNavigationGuards is the pair of CEF handlers that refuse any navigation off the plugin's own page. JcefHost keeps the browser, the queue and the deferred blocks. PageStateRecoveryContractTest follows the handler.
… session The two forSession factories take the lambda plumbing out of ClaudeSession, which is left with declarations and its verbs. The injected-lambda constructors stay, because the tests drive both through them.
GuardFixture holds the base policy and the read/bash/edit builders; GuardProbe is the base class the guard tests extend for v/rule/why against their own policy. The same five helpers were copied into 27 files. No assertion changes: every test keeps the policy it had, spelled as basePolicy().copy(...) where it differed from the base.
Thirteen names promised an ASK, a card, a third-party distinction or a per-caller behaviour that the suite deleted releases ago while the assertion underneath said DENY. The verdicts are untouched; the names now say what is asserted, so the next reader does not trust the sentence over the code. SecurityRuleFamiliesTest also joins the shared fixture, the one file the previous commit missed.
…ALLOW' Eight fuzz tests asserted only that a vector did not pass, so a vector caught by the wrong rule — whitelistable under a different label, reported under a different reason — read as green. Each now asserts DENY and the rule (or, where the bases mix families on purpose, the small set of rules they belong to). The unreadable-script fuzz pins PRIVILEGE_ESCALATION for its sudo variant, because the command families are judged before the script is opened; that order is the guard's, and the test says so.
…lank A dash stood for false, so an enabled column full of false read as "not applicable" and truncated=false as nothing at all; Lain, 2026-09-12, looking at service_actions. True stays a tick.
… the 250-line ceiling tools.css had reached 255 lines and the frontend size gate was red.
…ns says which context judged Overriding the selection keys on top of the tree's context replaced what the platform's selection rules had derived from the real selection, which is what a plugin's actions read. The tree is matched by its selected item, by identity or by name, and used untouched; the synthetic context stays for a view that is not showing. The answer now carries context=tree|synthetic so a disabled column can be read.
… Requests view Github.PullRequest.Show is enabled only by the list's own context, which derives the request id from the selected row; the view model behind it is Internal at 262, so the row is selected in the list itself and the action fired with the list's context, as a click would. The browser is the fallback when the view does not list the number; shown says which. Lain, 2026-09-12: "abrela en el ide".
…ata, and what a release leaves behind Two vcs domains. pull_request_ops creates, comments on, watches and merges a pull request through the IDE's GitHub account: GHGQLRequests create and mergeabilityData, the REST comment and merge-commit requests, all public in the GitHub plugin at 262. pr_checks polls until the checks settle; pr_merge refuses unless the merge state is clean and no check failed, and says that merging can publish. release reads tags, workflow runs and a Release through the executor's generic GET with the same account, and the plugin's Marketplace versions through the public API with no token. Lain's condition for publishing 6.0.0, 2026-09-12: the whole chain from the IDE, never gh.
The previous README described a chat with an optional MCP switch that was off by default; 6.0.0 is a different product. The manual is now organised by what the user asks for, in their own words, and opens with the part nobody could infer from a tool list: Claude opens files, takes you to a line, shows a commit in the Log, a diff in the diff viewer, a pull request in its view, and drives the IDE's windows, settings and actions for you without moving your focus. Counts (178 tools, 55 domains) come from IdeRule; setting labels match the settings page; the tool-by-tool reference stays in docs/SKILL_INVENTORY.md.
Both blocks still called 6.0.0 a polish release, which is what the release job would have put in the GitHub Release body and the Marketplace's What's New. They now name the plugin's own MCP servers, God Mode, the mirror, the cards, pull requests and releases through the IDE's account, the guard inside the servers and the three guard behaviours of this release, and are dated. The Problems view and the opt-in log stay out because they did not ship.
Owner
Author
|
This Release is being driven by Claude Code Native Plugin. |
… the guard sees an own call flat The guard reads a tool call's path and content keys at the top level, which is where the native tools carry them; an own call carried them one level down, under args, so a write judged through the meta-tool did not look like a write to it. OwnTools.guardInput lifts the arguments beside the tool name before either gate (the server's GuardGate and the session's broker) evaluates the call, so both shapes get the same verdict. Independently, the tools that create or move files now refuse a destination outside the project root, as delete_file and the VCS write tools already did: write_file, create_file, replace_text, insert_text, move_file, file_from_template and worktrees add. GuardGateTest pins the nested write with a probe that reports the target missing.
CodeQL on the release pull request: the per-session socket directory was created owner-only, but its parent under the system temp directory took the default permissions, so other local users could list the session ids. The parent is now created owner-only and, when an older build left it open, tightened on the next start.
…instead of opening the browser The GitHub plugin replaces the list with the request's details inside the same content, so the second open of the same number found no list row and fell back to the browser. When no row is found, the tool now looks for the component of the Pull Requests window whose context enables Refresh Pull Request and fires it there (shown=refreshed); the browser is only for an IDE without the view.
A Unix socket takes the process umask at bind, which the plugin does not control; the owner-only directory around it was the only thing keeping other local users from connecting. The endpoint now sets the socket to rw------- right after bind, so the file defends itself whatever the umask, and ServerEndpointTest pins it.
Owner
Author
|
Re the CodeQL alert Local information disclosure in a temporary directory (
Three owner-only layers now sit around every socket (parent, session directory, socket file), plus the per-request token. |
…still without the focus ToolWindow.show marks the window visible but leaves another window selected on the same side, so 'show me the pull request' answered shown while the user kept looking at Bookmarks. Every reveal of a tool window now activates it with autoFocusContents=false: the window takes its place in front like a selected tab, the keyboard focus stays where it was, and the focus contract keeps refusing activate(..., true).
…n it is already open View Pull Request opens the request as an editor tab named by its number; asked again while that tab sat behind others, the tool refreshed the details panel instead of putting the tab in front, which is what 'show me the pull request' means. The tool now selects the open tab first (openFile without focus, like a source file), opens it from the list otherwise, and answers none rather than refreshing anything.
…without its defaults The models were only ever decoded, so the encoders the serialization plugin generates never ran and the package sat under the branch floor. Each model is now encoded and decoded back, full and empty, under the protocol's Json and under one that omits defaults; the usage window and rate-limit helpers get their branch cases too.
OsvScanner reached OsvHttp through a plain call, so nothing could stand in for the network and its 45 lines were never run. The call is now an injected function, OsvHttp.post by default, and the test drives the batch, the hydration, the silences, a malformed answer, cancellation at both points and the cut above the hydration cap.
…udit reads it first EnvScriptLoader.load ran a shell nobody tested, and SourceScriptAudit had no test at all. A temporary script is now sourced on POSIX and its export comes back; a blank, missing or exiting script yields nothing; the audit passes a harmless script, names a destructive one and ignores what it cannot read.
…ugh a real control client SessionQueries and RemoteControl were only reached through the live session. They now run against SessionControlClient with a fake scheduler: each ask writes its subtype and decodes its reply on the EDT, a stopped session answers nothing, the usage reply feeds the quota warnings, and Remote Control records state, error and the notice for every outcome. The protocol round-trip test takes the formatter's shape.
…d the gate probes the stand-in binary The login coordinator built its attempt and the attempt built its PTY, so no test could stand between them. Both take a factory now, the real ones by default. A scripted process plays the binary's own output: the url and the code prompt reach the ui, the pasted code reaches the pty, a verified login restarts the chat, a failed one carries the binary's wording, cancelling forgets the attempt, and another provider is told to use its key. The gate's probe, renewal short-cuts and the dialog login ui are driven against fake-claude and the test dialog manager.
…d tool window A Pull Requests tool window with a list of rows is registered in the headless fixture: the request's row is selected and yields the list's context, an unknown number yields none, the window opens with and without focus, and an open timeline tab is selected while a missing one is reported.
…un against the headless IDE A target's parsing and the contexts it yields (a file with its editor, PSI and caret element, a directory, the project), the reveal of a file and of a registered tool window and its content, an action dispatched and a toggle flipped through IdeActions, and every verb of the card places against the windows the fixture has and lacks.
…eveal test stops depending on test order NoticeNarrator had no test of its own: status notices, notifications loud and quiet, denied permissions with their best reason, recalled memories, persisted and failed files, plugin installs by status, both refusal shapes, informational notices by level and the logged rest. The reveal test no longer asserts on windows another fixture may have registered in the shared light project.
…nd the coverage gate measures what can run MarketplaceGateway takes its fetch as a parameter, the HTTP one by default, so the update rows, their defaults, a dead endpoint and a reply that is not a list are all tested; GitHubAvailability runs headless with and without the plugin. Two corrections to what kover measures: the uiTest source set was read as production code and showed up as a package at zero, and GitHubGateway joins the gateways that need a live plugin, an account and the network, on the same grounds as the database and OSV ones. No floor moves.
This was referenced Sep 12, 2026
Closed
Merged
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.
Summary
Release 6.0.0. The plugin runs four MCP servers of its own inside the IDE (
code,run,vcs,ops, 178 tools in 55 domains, on by default as Claude God Mode), mirrors every action in the IDE without taking the focus, draws the IDE calls as cards with links into the IDE, drives pull requests and releases through the IDE's GitHub account, and passes every IDE call through the guard inside the servers. Underneath: the code is restructured one responsibility per file, the page is TypeScript, the plugin log has a Log view, and no deprecated or internal platform API remains. Full text in CHANGELOG.md and RELEASE_NOTES.md under 6.0.0.Related issue
n/a
Type of change
Risk and rollback
Risk: the IDE integration is on by default; a session that could not reach a server says so and falls back to the native tools. Settings gain
ideMcpfields with a catalogue marker; an older state is adopted into God Mode on first load. The permission surface is unchanged: every own call is judged by the same guard.Rollback: a user stays on 6.0.0 until they update; turning God Mode off (the flame, or Settings) disables the servers and the rules without reinstalling. Settings written by 6.0.0 are read by older builds as unknown keys and ignored.
Checklist
developbranch.verifyPluginis Compatible across 253 → 263.* on IU and PY, experimental API only.How was this tested?
Notes for reviewers
This pull request was opened, will be commented on, watched and merged through the plugin's own
pull_request_opstools from inside the IDE.