fix: intercept /sessions and /resume slash commands in WebUI (#6224) - #2
Closed
webtecnica wants to merge 1 commit into
Closed
fix: intercept /sessions and /resume slash commands in WebUI (#6224)#2webtecnica wants to merge 1 commit into
webtecnica wants to merge 1 commit into
Conversation
…a#6224) Add a native-intercept branch in the block of send() alongside the /pet special-case. When the user types /sessions or /resume, expand the sidebar and refresh the session list instead of sending the raw slash text to the agent. Root cause: the agent command registry exposes sessions/resume as non-CLI-only commands, so the autocomplete popup shows them, but the WebUI send-time dispatch had no branch to catch them, causing the literal text to be sent as a prompt.
webtecnica
pushed a commit
that referenced
this pull request
Jul 18, 2026
…esquena#6243) * fix(reasoning): gate reasoning_effort ladder to GLM-5.2+ on native zai Z.AI's API (docs.z.ai) defines reasoning_effort as GLM-5.2+ exclusive, but hemes-webui advertised the full 6-level ladder for all 7 GLM models because _candidate_supports_reasoning has an unconditional glm token match and _filter_reasoning_efforts_for_provider had no ZAI branch. Six of seven catalog models (glm-5.1, glm-5, glm-5-turbo, glm-4.7, glm-4.5, glm-4.5-flash) showed a selector whose values the endpoint silently ignores, and GLM-4.7 (forced thinking that cannot be disabled per Z.AI docs) showed a 'none' option with no effect. Add a ZAI branch to _filter_reasoning_efforts_for_provider mirroring the existing OpenAI/Gemini/Anthropic ceiling pattern: strip the whole ladder for pre-5.2 GLM models and for the forced-thinking GLM-4.7 family; preserve the full ladder for GLM-5.2+ (whose accepted values match VALID_REASONING_EFFORTS exactly). The gate is scoped to the native zai provider only (aliases glm/z-ai/z.ai/zhipu all resolve to zai); aggregator providers are untouched because they route through their own routers, not Z.AI's native endpoint. The glm family-detection heuristic in _candidate_supports_reasoning is unchanged — GLM models DO support the thinking on/off toggle at the family level; this fix is specifically about the reasoning_effort intensity ladder. State layer: agent.reasoning_effort config + UI dropdown options derived from resolve_model_reasoning_efforts. Invariant: UI options and coercion now agree and match Z.AI's per-model docs (max offered only for GLM-5.2+, none never offered for forced-thinking models). Out of scope: the thinking:{type:...} request-field translation lives in the external agent/gateway layer. * fix(reasoning): close ZAI coercion gap + harden test assertions Address Greptile review on nesquena#6219 (round 1): 1. Vacuous test assertion (test_glm_5_2_preserves_none_sentinel): the 'or' fallback made the assertion always-true, so a regression stripping 'none' for GLM-5.2 would go undetected. Rewrote to inject 'none' via the raw source (mocking _resolve_model_reasoning_efforts_impl) and assert it survives — the test now genuinely exercises the preservation branch. 2. Coercion gap for non-max stored levels on pre-5.2 GLM: the existing 'if ceiling and raw not in ceiling' guard treats an empty ceiling as 'no rule' (preserving the configured effort verbatim per nesquena#3505), so a stored 'high'/'medium'/'low' for glm-5.1/glm-4.5/glm-4.7 on native zai was forwarded to Z.AI unchanged and silently ignored — contradicting the PR's stated UI/coercion agreement invariant. Root cause: the ZAI gate returns [] to mean 'known-empty' (no reasoning_effort at all), but the coercion path treated all [] as 'ambiguous/unknown, preserve verbatim'. Fixed by extracting the ZAI decision into _zai_glm_reasoning_efforts_supported (True/False/None sentinel) shared by both the filter and coercion, then special-casing the known-False result in coerce_reasoning_effort_for_model to return '' (send no field). The nesquena#3505 preserve-verbatim behavior for genuinely-unknown models on non-zai providers is unchanged. Added 10 regression tests (all fail before the coercion fix, pass after): - All 6 levels (max..minimal) coerce to '' for each pre-5.2 GLM + glm-4.7 - All 4 aliases (glm/z-ai/z.ai/zhipu) resolve through the same coercion gate - GLM-5.2 preserves all 6 levels verbatim - Regression guard: unknown model on custom: provider STILL preserves verbatim State layer: agent.reasoning_effort config + the value forwarded to Z.AI. Invariant now fully holds: UI offers no options for pre-5.2 GLM AND coercion sends no reasoning_effort field for any stored level on those models. * fix(reasoning): preserve ZAI GLM 4.5-5.1 thinking toggle when effort ladder empty Address nesquena-hermes round-2 review on nesquena#6219: returning [] for the effort ladder on sub-5.2 GLM models hid the entire reasoning chip in the composer (static/ui.js:4932 treats empty supported_efforts as 'no reasoning control at all'), silently regressing the working thinking on/off toggle for GLM-4.5/4.6/ 5.0/5.1 users. Per Z.AI's own docs, those models accept the thinking {type:enabled|disabled} toggle even though they do not accept the reasoning_effort intensity ladder. Fix decouples thinking-toggle capability from the effort ladder: Backend (api/config.py): - Refactor _zai_glm_classification returns one of 'effort' (GLM-5.2+), 'thinking' (GLM-4.5 up to but not including 5.2), 'forced' (GLM-4.7 family), or None (non-zai / non-GLM). Single source of truth shared by all three consumers. - _zai_glm_reasoning_efforts_supported now wraps classification for the coercion contract (unchanged behavior). - _zai_glm_thinking_toggle_supported returns True for 'effort' or 'thinking', False for 'forced', None otherwise. - get_reasoning_status gains a supports_thinking_toggle field = bool(supported) OR (zai_thinking is True). Non-zai providers default to bool(supported_efforts) so their chip-visibility behavior is unchanged. Frontend (static/ui.js): - New _currentReasoningToggleSupported state var (default undefined = treat as true so legacy responses without the field do not newly hide the chip). - _applyReasoningChip shows the chip when hasEffortLadder OR toggleSupported. Empty efforts + toggle=True keeps the chip visible with just the None/On control (the existing _applyReasoningOptions already shows 'none' when the ladder is empty). Empty efforts + toggle=False (GLM-4.7 forced) hides it. - Profile-transition and fetch-failure resets now pass supports_thinking_toggle:false alongside the empty efforts so the chip hides during the unknown-state window, matching the prior reset contract. Tests (30 new): - _zai_glm_classification parametrized across all three tiers + aliases + defer - get_reasoning_status supports_thinking_toggle per tier (GLM-5.2 both, GLM-4.6 toggle-only, GLM-4.7 neither, non-zai defaults to effort capability) - Frontend _applyReasoningChip behavior via node driver: empty efforts + toggle=True stays visible, toggle=False hides, effort ladder alone is sufficient, absent field keeps prior behavior State layer: agent.reasoning_effort config + supports_thinking_toggle field in /api/reasoning + composer chip visibility. Invariant: the chip is hidden ONLY when the model supports neither the effort ladder nor the thinking toggle (GLM-4.7 forced, or genuinely non-reasoning models); GLM-4.5-5.1 retain the working On/None control they had before the round-1 effort gate. * fix(reasoning): make ZAI thinking toggle two-way + force GLM-4.7 stored none Address nesquena-hermes round-3 review on nesquena#6219 — two SILENT gaps in the thinking-toggle path, plus a click-handler sibling I found while auditing. Gap #1 — ONE-WAY toggle for GLM-4.5/4.6/5.0/5.1 (api/config.py:4247, static/ui.js:4902, static/index.html:757): The round-2 fix kept the chip visible for thinking-tier models but the only rendered dropdown option was 'None' (the HTML had no Default option, and set_reasoning_effort rejected empty effort with 400). So a GLM-4.6 user could turn thinking OFF but never back ON — worse than the original bug. Fix: - set_reasoning_effort now accepts empty effort as 'clear the override' (removes agent.reasoning_effort so the provider default takes effect). Invalid values still raise ValueError. - static/index.html gains a <div data-effort=''>Default</div> option. - _applyReasoningOptions always shows both Default ('') and None alongside the effort ladder, so a thinking-tier model (empty ladder + toggle=true) renders an operable Default+None two-state control. - Click handler (ui.js:5106) checks option presence (if(opt)) not truthiness (if(effort)) — the old check silently ignored data-effort='' clicks, which would have left the Default button dead even after the HTML/backend changes. Gap #2 — GLM-4.7 not forced when 'none' stored (api/config.py:3984, :3857): When GLM-4.7 had agent.reasoning_effort=none configured, coercion preserved 'none' via the early return at line 3985, so streaming built disabled reasoning for a model that forces thinking on regardless. Separately, when the raw capability source listed 'none', resolve_model_reasoning_efforts reattached it to GLM-4.7's supported_efforts (['none']), leaking an 'off' option to the UI for a forced-thinking model. Fix: - coerce_reasoning_effort_for_model checks _zai_glm_classification == 'forced' BEFORE the generic 'none' early-return, coercing stored 'none' to '' (default = thinking on) for forced models. - resolve_model_reasoning_efforts returns [] early for forced-tier models, skipping the 'none' reattachment entirely. Tests (17 new): - Gap #2: coerce('none', glm-4.7) -> '', regression guard that non-forced GLM still accepts 'none'; resolve does not reattach 'none' for forced but DOES for thinking-tier; end-to-end get_reasoning_status for forced+stored-none. - Gap #1 backend: set('') clears the key (no raise), set('garbage') still raises, all 7 valid levels still save. - Gap #1 frontend: three new node-driver tests asserting the dropdown exposes both Default and None for thinking-tier (two-state), Default+None+ladder for effort-tier, and the off->on->off round trip keeps both visible throughout. - Updated test_reasoning_show_hide.test_set_reasoning_effort_rejects_invalid to reflect the new contract (empty accepted, garbage rejected). Regression gate: gap#2 coerce tests fail without the forced-tier check (3 failures); gap#1 backend test fails with 'ValueError: effort is required' without the empty-acceptance change. 219 passed, 1 pre-existing skip across the full reasoning + chip + config-cache surface. State layer: agent.reasoning_effort config + supports_thinking_toggle field + composer dropdown options. Invariant: the thinking toggle is now genuinely two-way for GLM-4.5-5.1 (Default=on, None=off, both always visible) and GLM-4.7 forced-thinking never offers 'none' in any path (coerce, resolve, status, or UI). * CHANGELOG: GLM per-version reasoning controls (nesquena#6219) --------- Co-authored-by: Ruby Hartono <58564005+rh-id@users.noreply.github.com> Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Owner
Author
|
This PR is superseded — the fix was already merged into |
Owner
Author
|
Closing — already addressed in master via a release commit. |
webtecnica
added a commit
that referenced
this pull request
Aug 13, 2026
…SE relay (nesquena#6961) Read/surface half of the maintainer's split for PR nesquena#6961 (child approval routing, nesquena#6943). The resolve half (#1/#2/#3) stays in a follow-up gated on the agent contract (agent#82009). #4 (CORE): scope the child->parent cache by canonical state-db/profile path and only cache positive lookups, so a miss under one profile can no longer poison another profile's identical child id, and a late state.db write is picked up on the next lookup. Adds invalidate_child_parent_cache(). #5 (SILENT): use one aggregate projection (own queue + delegated-child queues, deduped by stable approval id / gateway mirror token) unconditionally on all three surface paths — sidebar attention summary, /api/approval/pending, and the SSE initial snapshot — so a parent-with-1 + child-with-1 now reports count 2 instead of 1. #6 (SILENT): publish the aggregate parent head/count to the parent's SSE subscribers whenever an owned child queue changes (submit_pending, submit_gateway_pending_mirror, retire_gateway_pending_mirror, resolve_gateway_pending_local, resolve_child_approval_locked), so a pure-SSE parent consumer sees child enqueue/resolve without waiting for the 1.5s poll.
webtecnica
added a commit
that referenced
this pull request
Aug 25, 2026
…SE relay (nesquena#6961) Read/surface half of the maintainer's split for PR nesquena#6961 (child approval routing, nesquena#6943). The resolve half (#1/#2/#3) stays in a follow-up gated on the agent contract (agent#82009). #4 (CORE): scope the child->parent cache by canonical state-db/profile path and only cache positive lookups, so a miss under one profile can no longer poison another profile's identical child id, and a late state.db write is picked up on the next lookup. Adds invalidate_child_parent_cache(). #5 (SILENT): use one aggregate projection (own queue + delegated-child queues, deduped by stable approval id / gateway mirror token) unconditionally on all three surface paths — sidebar attention summary, /api/approval/pending, and the SSE initial snapshot — so a parent-with-1 + child-with-1 now reports count 2 instead of 1. #6 (SILENT): publish the aggregate parent head/count to the parent's SSE subscribers whenever an owned child queue changes (submit_pending, submit_gateway_pending_mirror, retire_gateway_pending_mirror, resolve_gateway_pending_local, resolve_child_approval_locked), so a pure-SSE parent consumer sees child enqueue/resolve without waiting for the 1.5s poll.
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.
Problem
When typing
/sessionsor/resumein the WebUI composer, the autocomplete popup shows them as valid commands (they're exposed by/api/commands), but they get sent as plain text to the agent instead of executing. This is because the agent command registry exposes both as non-cli_onlycommands, but the WebUI send-time dispatch instatic/messages.jshad no branch to catch them.Fix
Add a native-intercept branch in the
_parsedCmd && !_cmdblock ofsend()(alongside the/petspecial-case at ~line 1451). When the user types/sessionsor/resume:renderSessionList()This follows the same guard pattern used by other branches in this block, with
typeofchecks before calling any function.Root cause analysis (per maintainer)
api/commands.py:96-108list_commands()emits every registry entry not markedgateway_onlyor in_NEVER_EXPOSEsessionsandresumeare registered withoutcli_only, so they pass the filter_AGENT_COMMANDS_RUN_ON_WEBUI(line 1136) containsreload-mcp,reload-skills,codex-runtime,credits— but NOTsessionsorresumeAlternative considered
Marking them
cli_only=Trueon the agent side would make the existingcli_onlybranch fire, but that's worse UX given the WebUI genuinely supports session browsing/resuming.Closes nesquena#6224