Follow-up to the review of the in-place Claude SDK update:
- Replace the shutdown of the current window's daemon with a shared SDK
generation. Once the SDK changes, every Claude daemon in the IDE restarts
before its next request, and a daemon serving a request is left alone, so
an update from the Dependencies page no longer aborts a reply with
"Daemon stopped".
- Run installs of the same SDK one after another, so the chat button and
the Dependencies page cannot run npm into the same directory at once.
- Report a run where every npm attempt timed out as a failure instead of
falling through to success.
- Add the previously installed version and the Claude CLI override to the
install result. The card says when the update left the newest supported
SDK in place, or when the custom CLI is what needs updating.
- Keep the update state in a shared store, so a card that remounts still
shows the running update and its result. The same error shown after the
update gets the button again.
- Show npm's latest output while updating, give up after 15 minutes so a
lost reply cannot block retries, and explain failures that come without
a reason.
Dialogs close as soon as the page hands an answer to Java. When Java
could not deliver it, the failure ack reached the page but nothing
handled it, so Codex kept waiting on a request the user could no longer
see and the turn looked stuck until it was stopped.
- Track each Codex answer the page sends; on a failure ack show a toast
and reopen the dialog once. Skip the reopen when Java marks the
failure as not retryable, the deadline has passed, it was already
retried, or the runtime closed the dialog in the meantime.
- Give control-command timeouts a readable error. A null message became
JsonNull, which Gson drops, so timeouts read as success on the page.
- Mark unknown or stale interactions as retryable: false.
- Log undelivered interaction responses in the IDE log.
- Route the nine Codex send sites through two shared helpers.
When the app-server advertises availableDecisions, that list already
carries the exec-policy amendment it will accept. The webview appended
proposedExecpolicyAmendment on top, so the dialog showed two identical
"allow with this command rule" buttons. Use the advertised list as-is
and only synthesize amendments when the server sends no list.
Rule buttons now show the command prefix or host they would allow,
truncated with the full text on hover.
common.retry is not defined in any locale, so the button rendered the raw
key. Switch to settings.cli.retry and assert the lookup-failure keys exist
in the shipped en/zh locales, since the i18n mock echoes unknown keys.
Codex streaming could omit commentary, reasoning, and edit cards between
batches until the conversation was reloaded. Historical exec wrappers also
collapsed supported parallel calls into generic exec cards and used command
labels that differed from live rendering.
Preserve snapshot ordering and complete message corrections:
- Compare complete raw message content when selecting the transported suffix;
keep structural signatures limited to snapshot scheduling.
- Apply a buffered full snapshot before its dependent tail update advances
the sequence barrier, retaining intermediate items during React batching
and stream completion.
Restore recorded tool batches without guessing result ownership:
- Associate Promise.all and Promise.allSettled results with their original
array slots, including independently printed index labels in forEach and
bounded for loops.
- Retain labeled arrays, primitives, plain text, and label-like values.
- Restore mixed command and write_stdin batches while keeping polling tied
to the original process card.
- Match identical commands through confirmed process identities or unique
explicit absolute working directories, including file URIs and Windows
paths, while preserving literal POSIX backslashes.
- Never assign unindexed parallel process receipts by arrival order; retain
the original wrapper whenever ownership cannot be established.
Keep local Promise computations out of the visible tool timeline:
- Preserve Promise.resolve expressions and indexed results as internal data
without executing historical source.
- Filter these entries through the shared rendering predicate so they neither
create cards nor interrupt command groups in live or historical messages.
- Separate rejection metadata from result fields, distinguish Promise.all
values from settled envelopes, and decode nested local receipts only when
the source proves that the complete settled receipt was printed.
- Keep flattened local Promise.all output in its original wrapper rather
than interpreting data fields named status or error as execution outcomes.
Align historical command rows with live presentation:
- Preserve recorded summaries, approval reasons, and parsed command actions.
- Accept both list_files and listFiles directory action names.
- Remove generated English command summaries and retain Bash tool identities
for legacy reads and searches so the shared renderer controls row labels.
Extend Java, React, and browser regression coverage for snapshot ordering,
process ownership, mixed batches, local result preservation, command labels,
and continuous tool grouping. Update the Codex runtime documentation and
browser fixtures to describe the resulting behavior.
Validation:
- 199 Java tests passed across CodexKnownExecReplayTest,
HistoryMessageInjectorTest, CodexHistoryPageIndexTest, and
StreamMessageCoalescerStreamEndHookTest.
- Gradle checkstyleMain passed.
- 67 targeted frontend tests passed across snapshot callbacks, MessageItem,
GenericToolBlock, tool constants, and command presentation.
- TypeScript test-project checking and git diff --check passed.
Cover the 19 commits merged by PR #1963:
- feat: persist the chat font-size level in ~/.codemoss/config.json so the
chosen scale survives IDE restarts, acknowledged by request ID
- perf: fetch the compressed entry and stylesheets in parallel and reuse
verified (SHA-256) startup assets across chat tabs
- fix: Token Tracker dashboard crashed with removeChild because the entry
was evaluated as a blob while lazy chunks resolved the canonical URL;
an import map keeps a single entry/React instance (#1951)
- fix: keep #L..-L.. visible on long file references in input file tags
- fix: scan plugin skills declared as individual directories (#1961)
- fix: keep the $ Codex skill list on partial skills/list errors
- fix: let Claude subagents keep their own permission mode
- fix: keep pasted image drafts in sessionStorage so tabs cannot share them
- fix: keep dropdown fly-outs anchored at any font zoom
- build: Aliyun Maven mirror first and foojay JDK toolchain resolution
webview/src/version/changelog.ts is regenerated by
webview/scripts/extract-changelog.mjs, not edited by hand.
Resolve semantic conflict with e0c356a5 (preserve entry module identity):
keep the verified startup-asset cache and add an import map that remaps the
canonical entry URL to the evaluated blob, so lazy chunks importing the
entry keep a single module identity while cached bytes are still reused.
Submenu geometry mixed zoomed getBoundingClientRect values with
layout-pixel scroll sizes, so above 100% font zoom the effort fly-out
drifted off its row and overflowed small viewports. Compute the whole
submenu layout in layout pixels and measure the natural content size
even when maxHeight clips the box.
The main dropdown now clamps its width to the viewport and matches
the CSS min-width floor, and open menus follow window resizes and
content changes through a ResizeObserver instead of measuring once
at open time.
localStorage drafts are shared by every same-origin chat tab, so one
webview's screenshots could be restored by another. Persist the draft
in sessionStorage instead: it still survives input remounts and page
reloads but never crosses webviews, and the legacy shared draft is
deliberately left behind because its owner is unknown.
When a save fails or exceeds the size cap, drop the previous draft so
removed images stay removed after a reload, and skip rewriting a
just-restored draft so full storage cannot wipe what was restored.
JCEF pages loaded via loadHTML cannot rely on localStorage surviving a
restart, so the chosen scale vanished every time the IDE reopened. The
level now lives in ~/.codemoss/config.json next to the font-family
preference and rides the existing resolved UI-font payload.
Explicit selections send set_font_size_level with a request ID and are
acknowledged through the resolved font config; a shared external store
keeps Settings, the global --font-scale and the OSR repaint nudge in
sync, browser storage stays an immediate cache only, an unreadable
config file is rejected instead of replaced, and a legacy browser choice
is migrated without pinning the default.
A partial `skills/list` failure (one unreadable SKILL.md, an invalid
frontmatter entry, a stale skill under another cwd) sets the catalog error
while still returning the valid skills. The `$` completion coordinator
treated any error as fatal and published an empty list, which erased every
Codex skill the user had (custom skills included) and marked the native
catalog authoritative, so the backend-scanned `$` list was ignored for the
rest of the session. The picker then reported "no results" with no way back.
Publish whatever the runtime did return on partial errors, and skip the
publish only when the read failed without any rows: a failed read is not an
authoritative empty inventory, so the backend-scanned fallback stays usable
until the runtime answers with real data. `nativeSkills.error` still reaches
the Skills settings warning.
Covered by two coordinator tests: partial error keeps native skills, and an
outright failure no longer swallows the backend-scanned `$` payload.
Full Auto could run sandboxed while the selector still read Full Auto. The
permission mode is app-level state that survives a reinstall, but the access
scope only lived in webview localStorage, so a wiped snapshot or a
pre-native-scope migration left the app-server on workspace-write and denied
writes outside the project — with no way to see why. The permissions panel made
it worse: it reported the legacy .idea record as both "desired" and "effective",
so it claimed full access while the runtime was sandboxed, and changing it there
was overwritten by the next message.
- Derive the access scope from the authoritative mode whenever no explicit
choice is recorded ('default' source), so a reinstall self-heals; an explicit
'user'/'native'/'migrated' scope is never overridden.
- Honour a persisted sandbox in the snapshot migration and stop treating the
never-written `codexFullAccessExplicit` flag as the only Full Auto evidence;
pair a persisted full-access preset with danger-full-access.
- Drive the permissions panel from the chat state (the value every turn sends)
and write panel changes back into it; show "effective" only from the
app-server's thread/settings/updated echo, never the requested value.
- Stop reporting fabricated sandboxDesired/sandboxEffective/sandboxConflict
from ProjectConfigHandler, and drop the matching webview branches.
- Export buildTurnSettings and pin the mode -> approval/sandbox contract with
tests, since the webview mirrors that mapping.
- Delete codex-sandbox-resolution.js and utils/permission-mapper.js: both were
unreferenced by production code, and the latter encoded a platform-dependent
Full Auto mapping that no longer exists.
Tests: webview 2440 passed (tsc clean), ProjectConfigHandler/CodexInteraction
handler tests, 32 persistent-codex-service tests.
The API rejects requests from a Claude Code older than a model's minimum
version ("Claude Code X does not support this model; version Y or newer is
required", error_code claude_code_version_too_old). The plugin runs the
Claude Code bundled in its own SDK, which updating the plugin or the
terminal CLI does not change, so the hint in the message does not help.
- Match the error in the diagnostic card and add an "Update SDK" button
that sends the existing update_dependency request and shows the result
in place.
- Restart the Claude daemon after a successful Claude SDK install or
update. The daemon caches the SDK module it imported, so the next
message would otherwise keep running the old SDK until the IDE restarts.
A failed fetch/decompress of the entry module previously surfaced only
as a blank chat tab with a console error. Append a plain-text fallback
message so users can tell a load failure from a slow tab.
Rewrite the bilingual release notes around the verified behavior in PR #1919 so users can identify new capabilities and fixes. Preserve contributor credits and regenerate the in-plugin changelog from the Markdown source.
Constraint: Markdown and in-plugin release notes must remain synchronized.
Confidence: high
Scope-risk: narrow
Tested: TypeScript typecheck; bilingual content consistency; changelog generation; git diff whitespace checks
Not-tested: Live IDE rendering of the release notes
Settle aborted Codex turns and preserve readable history when native requests time out or stale page reads are cancelled. Resolve CLI discovery against the pristine environment and launch Grok Windows shims through the shared spawn contract.
Keep early clipboard gestures until a paste scope registers, bound scope retries, re-arm failed permission timeout decisions, preserve native sandbox choices, and retain edit previews across streaming updates. Include regression coverage and synchronized bilingual v0.6.0 release notes.
Confidence: high
Scope-risk: moderate
Tested: 2430 webview tests; production and test TypeScript checks; webview production build
Tested: 156 Node tests passed with 1 skipped; 76 targeted Java tests; Checkstyle; staged whitespace checks
Tested: Changelog generator confirmed the generated file is current
Not-tested: Live IDE interaction and native Windows CLI execution
Codex no longer installs an SDK from the dependency section — detection
now happens on the CLI page, so the Codex card silently disappeared and
looked like a regression. Replace it with a permanent dashed notice that
explains where the entry moved, how chat actually runs (the official
app-server over the locally installed @openai/codex), and what to do when
the CLI is missing (npm install -g @openai/codex, then Re-check).
- DependencySection: render the migration notice; drop the Codex card
- 10 locales: replace codexSdkName/codexSdkDescription with
codexMigratedTitle/codexMigratedDesc
- test: assert the notice renders and no Codex SDK action remains
- Discover the CLI in the official standalone installer layouts on both
the Java detector and the Node resolver: CODEX_INSTALL_DIR, Windows
%LOCALAPPDATA%\\Programs\\OpenAI\\Codex\\bin, and
${CODEX_HOME:-~/.codex}/packages/standalone/current in both the
bin/ and flat layouts, without scanning unselected releases/
- Mark Codex installed only when "codex --version" really prints
"codex-cli <version>"; a half-installed npm launcher that still
executes no longer poses as a working CLI (docs contract updated)
- Point the chat diagnostics card and the CLI install dialog at the
official per-platform install/update instructions instead of the
old npm-first recipe, and sync the new wording across all ten UI
languages (zh also aligns 供应商管理 with the settings label)
Codex app-server approval events already carry proposed file diffs, but the permission dialog only listed paths and change kinds. Parse those native changes with the existing patch reader and render the per-file additions and deletions before a decision. Keep approval unavailable when the native request does not include diff content.
Tests: npx vitest run src/components/PermissionDialog.test.tsx; npx tsc -p tsconfig.test.json --noEmit; npm run build; node --test --test-name-pattern='file approval|file preview' ai-bridge/services/codex/codex-appserver-service.test.js
Fixes#1841
- Extract createCollapsePreference() factory; bash/edit preference modules
shrink to key constant + single preference object (one API convention)
- Extract usePreferredExpanded() hook; removes override logic duplicated
across BashToolGroupBlock and EditToolGroupBlock
- EditToolGroupBlock group summary now counts denied tools as failed,
matching EditFileItem's per-item error semantics
- Add regression test for denied edits in collapsed group summary
Preserve both Claude effort forwarding and Codex dispatch failure tests when resolving the message sender test conflict. Add browser coverage for aggregate, route and direct model names and xhigh/max forwarding.
A tab restored from a session the CLI's retention cleanup had removed kept
resuming its stale id: the paginated history read answered the missing
transcript with an empty success page, so the chat rendered empty, no
stale-session signal was raised, and every send failed with
"No conversation found with session ID".
- Report a missing transcript from the paginated read as missing, matching
the contract the full-history read already uses, and let the Java page
query raise SessionHistoryNotFoundException instead of falling back to a
second read that would fail the same way
- Retire the session id and cached transcript when either read reports the
transcript missing, settling the page error before the identity reset so
the frontend can still clear its spinner
- Announce session_resume_expired from the daemon when a send or a startup
resume fails on a missing transcript, so Java drops the id and the next
send starts a fresh session instead of repeating the failure
- Keep the CLI's real load error in terminalError and carry it through the
perpetual reader, the quiescence gate and the ownership check, so users
see the actual cause instead of "Runtime is closed"
- Treat an empty session id as the canonical no-session state in the
frontend and stop migrating a custom title under it
Observe viewport and content resizing with one shared animation frame so
streaming updates do not trigger duplicate scrolling. Give pending user
scrolls priority and let newer wheel directions supersede resume checks.
Cancel queued follow tasks when leaving chat and ignore automatic scroll
events before their flags clear. Use deferred, cancellable frame mocks and
resize targets to cover races and cleanup in regression tests.
Validation: npm run test passed with 233 files, 2309 tests, and TypeScript
checking. git diff --check also passed.
- Refresh LRU recency on native count recounts so a fresh entry is not
the first one evicted
- Free a paste offer's admission slot when its scope is retired between
the pre-send check and delivery
- Split the history loader's shared send-retry timer into list and count
timers, each with explicit teardown
- Remove unreachable guards: the waitForOperationThreadId settle probe,
captureRawSender script emptiness, HandlerContext null jsCallback,
ClipboardHandler null request ids, and the orchestrator's anchor
re-validation that the lastAlignedHistoryIndex contract already
guarantees
- Delete the unused codex-session-reader module and its test
- Count id-less codex turns by max sighting across overlapping native pages
and retire released session services from history counting
- Reject stale Claude history reads whose transcript was replaced mid-flight
and keep pagination cursors off obsolete reads
- Retry refused paste_image_scope publishes and materialize unreadable
clipboard images through the native snapshot path
- Trim image dedupe keys with gesture ids and add preparing-image feedback
Grok CLI asks with `_x.ai/ask_user_question` and expects a snake_case
outcome. The bridge returns accepted or cancelled, and a newer Grok
question replaces the one already open.
The Grok daemon now inherits the PermissionService routing id, so the
question file is written where the dialog watcher is looking.
The history list could not say how many messages each Codex thread
holds, because the thread listing only summarizes turns and the webview
had to guess from whatever fragment happened to be loaded. Add a
codex.countThreadMessages daemon method that walks the native history
pages, dedupes messages by uuid, and caches each tally per thread
revision, and give the webview a concurrency-capped queue that counts
the visible sessions and merges the results into paginated publishes
without losing edits or deletions made in between. Item reads that
return incomplete without a continuation cursor now fail loudly instead
of collapsing a partially hydrated turn into an empty transcript.