Repository navigation
Fix the app tools, the system mode, the chat screens and the build and browser tools (four review passes) - #48
Merged
Conversation
- system mode: requests go one at a time until the logon was accepted, so parallel calls send a wrong password once; the password command's answer is shared; system_status/app_list are bounded (30 s); a redirect names its target; a malformed % in the URL no longer stops the server; a URL that does not parse no longer echoes its credentials - appclient: least-recently-used eviction, an evicted session is not brought back by a late act; without `row` a screen action wins over a row action of the same name; a breaker refusal is passed through; a non-JSON 2xx body is shown sanitised - adaptive card: tables cut to the snapshot's rows, unshown rows are no edits, a card over the answer budget is left out - MCP Apps: untouched choices and check boxes send nothing; message box choices with parentheses - snapshot: out-of-range character references, linear stripTags Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq
oblomov-dev
pushed a commit
to abap2UI5-addons/abap-agent-runtime
that referenced
this pull request
Oct 7, 2026
A "delete selected" button after a table with a DELETE per row was reachable by its id only: without `row` the first action of the name won, and that was the row action, which then asked for a row. Same rule as mcp-server's findAction (abap2UI5/mcp-server#48). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq
- an act queued behind one in flight is resolved against the screen its caller saw and refused, sending nothing, when its action or fields name something else on the screen that answered (NEXT then DELETE_ALL) - a backend start during build_backend is refused: it booted the output from before the build, which the build then reported as built - snapshot answers are fitted to ANSWER_BUDGET (long values cut and made read-only, rows dropped from the end, a note beside); the adaptive card counts together with the snapshot - an argument a tool does not declare is refused; max_rows refuses booleans, arrays and blanks; errors echo at most 80 characters Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq
…sessions - untouched inputs are compared with what was rendered, not with the snapshot: CRLF in a textarea, a newline in a text input, a multi-select in another order or with a key without an option were written back on every press; a textarea keeps a leading newline - what the user typed and the focus survive a render of the same screen (while an act runs, after a failed one) - Refresh follows a session that moved on and reads its current screen; an act refused for it says Refresh shows it - a button whose arguments only a browser computes is disabled - the size report measures the content (it never shrank) and starts after ui/notifications/initialized; Refresh keeps max_rows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq
oblomov-dev
pushed a commit
to abap2UI5-addons/abap-agent-runtime
that referenced
this pull request
Oct 7, 2026
… values Parity with abap2UI5/mcp-server#48: - an argument a tool does not take is refused (app_act { value, event } fired the event without the edits) - snapshot answers are fitted to 60,000 characters (fit_snapshot: long values cut and read-only, rows dropped from the end, a note beside) - errors echo at most 80 characters of an argument (viewxml=>echo) - an act's max_rows is stored with the snapshot it built; app_describe answered the stored one for any max_rows asked - @CLOSE_POPUP / @CLOSE_POPOVER with a row or args is refused - the copilot's apply_pending refuses a value typed into a dialog instead of writing the stale main-model value over the user's Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq
…res and answers - interact_app waits for the roundtrip each action started before the next (the frontend dropped an event fired during one; both were "performed") - backend stop kills a start in flight and the start says it was stopped - run_app/interact_app cut a page taller than 4096 px; screenshot_view leaves out pictures over 8000 px or past its image budget; one PNG file per call - the lint reads the sandbox's whole JSON (one app's many findings broke every later lint) and lists the first 100 findings; read_app pages by lines (from_line); a full run_unit_tests is counted per object; migrate_report's files are named, not inlined, past the budget - the npm build refuses a dev object named like a framework class - errors echo at most 80 characters (classNameOf, oneOf, boundedInt, parseActions, parseSizes, scaffold_app, migrate_report) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq
oblomov-dev
added a commit
to abap2UI5-addons/abap-agent-runtime
that referenced
this pull request
Oct 7, 2026
…ass (#4) * LLM client, session, generative UI and copilot: fixes from a review pass Language model (z2ui5_cl_agent_llm*, anthropic provider): - effort and the server-side fallback are sent only to models that take them: Claude Haiku 4.5 and Sonnet 4.5 answered every call with a 400. - The system prompt goes as a cached block (cache_control ephemeral) - the generative UI's vocabulary is the same on every call, twice per view. - Only end_turn and stop_sequence are an answer, as the interface says: a pause_turn or a provider's guardrail reason no longer passes as one. - The audit keeps the answer first and every prompt cut before escaping, so the 2000 characters never lose the answer and stay JSON; cache token counts are audited. Session, settings, MCP: - app_act masks the audited values against the screen they were typed into: masked after the act, a value sent with an event that opened a popup or another app went into the log as it was (an IBAN, a password). A key the screen does not know is also checked as the path /<key>. - An APP deny rule applies to every event (get_policy), not only at app_start and app_attach - an app reached by navigation, or denied after the session started, was operable. - app_attach rolls back what the restore ran when the app is refused. - An MCP session in use keeps its row (changed_at refreshed hourly) instead of expiring a day after initialize. Generative UI and copilot: - The copilot validates, checks and fires the action by its id: two buttons with one event and different arguments showed "Low" and ran "High". - A literal other than a text takes no line break and no brace: ^ and $ match per line, and "10rem" + newline + "{/PATH}" passed as a size. - icon, src and href are icon URIs whatever the profile types them (sap.m.SegmentedButtonItem's icon took any URL). - Event arguments go in the app's order, left out only from the end - the app reads them by position. - Sample rows (genui_samples) carry only the listed fields, sensitive ones masked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq * Copilot: a sensitive field read by a composite cell binding is masked A cell bound to several fields or an expression ("{IBAN} ({BANK})") is a column COL<n> in the snapshot, and check_sensitive( ) on that name never matched - the IBAN went to the model in clear. The column is now judged by the fields its binding reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq * Concurrent acts, unknown MCP sessions, sensitive texts, NUL in JSON - app_act claims the session before it fires an event (MCP mode): two calls on one session both ran main( ) and fired the event - a document posted twice, two forks of the session. The claim moves changed_at under the old value and is committed at once (abap2UI5 rolls back around main( )); the second call is refused before anything runs. Not in the copilot, which runs inside an abap2UI5 roundtrip. - A request on an unknown or ended Mcp-Session-Id is a 404 (Streamable HTTP), so the client starts a new session instead of losing its name in the audit log; an initialize carries no session to check. - The snapshot keeps which model paths each text of /texts was read from, beside the JSON (the contract is unchanged); the copilot masks a text that shows a sensitive field ("IBAN: DE89..."). - json_string( ) escapes NUL as \u0000, as JSON.stringify does - built on its own, so a platform that cannot build the character leaves the rest of the escaping as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq * A claim that holds while the act runs; admin and audit app fixes Session (follow-up to the claim of the last commit): - claim( ) puts changed_at ahead (the claim time plus 600 s) instead of to the present: a call that loaded the row after the claim saw the claimed value as current, claimed it again and fired the event a second time while the first one's main( ) still ran. A row ahead of now is taken; save( ) writes the present back and releases it, a refused or failed act gives the old value back (release( )). - Every act that saves is claimed in MCP mode, values only and a closed layer included: saved over a session another act had moved on from, it brought the old one back. Admin app: - Save writes only the language model settings the form changed since it was loaded: written from a stale form, a privacy switch another administrator turned off was back on. - A new URL, destination or provider without a key typed anew removes the stored key: the write-only key was sent to whatever host an administrator named, and Test call showed the answer. - The last ADMIN rule cannot be deleted - without one nobody may change the settings again. Audit app: - The outcome filter runs in the database, and a search looks through the newest 5000 entries rather than 500; the title says when the window was full or the list cut, instead of a "0 call(s)" that was not true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq * Follow-ups from a review of this PR's own changes - Copilot: a COL<n> cell is judged by every path its binding reads - an expression's references and absolute paths too - and by the row path (/T_PARTNER/*/IBAN) as well as the table path; the snapshot's binding_paths( ) is public for it. - Admin app: a form opened on a draft from before mt_llm_shown removes no key on Save; the last-admin guard counts the stored ADMIN rows, not the form's stale list. - MCP: the changed_at refresh is committed at once - an app start's roundtrip rolled it back. - README: the request body (cached system block, effort and fallbacks per model), the stop-reason allow list, the audit's 400-character cut, the copilot's masking of composite cells and texts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq * app_act by event name: a screen action before a row action without row A "delete selected" button after a table with a DELETE per row was reachable by its id only: without `row` the first action of the name won, and that was the row action, which then asked for a row. Same rule as mcp-server's findAction (abap2UI5/mcp-server#48). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq * MCP: unknown arguments, answer size, echoes, max_rows, copilot dialog values Parity with abap2UI5/mcp-server#48: - an argument a tool does not take is refused (app_act { value, event } fired the event without the edits) - snapshot answers are fitted to 60,000 characters (fit_snapshot: long values cut and read-only, rows dropped from the end, a note beside) - errors echo at most 80 characters of an argument (viewxml=>echo) - an act's max_rows is stored with the snapshot it built; app_describe answered the stored one for any max_rows asked - @CLOSE_POPUP / @CLOSE_POPOVER with a row or args is refused - the copilot's apply_pending refuses a value typed into a dialog instead of writing the stale main-model value over the user's Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq --------- Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 7, 2026
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.
Behaviour fixes from four review passes, covering:
Every fix has a regression test, and each one was reproduced before it was fixed.
System mode (
lib/system.mjs)system_statusandapp_listgive up after 30 s.%inA2UI5_MCP_SYSTEM_URLno longer stops the server from starting.Location.App client (
lib/appclient.mjs, vendored by the playground, protocol and the VS Code extension)app_act a1sent at once fired NEXT and then DELETE_ALL.row, the screen action of that name wins over a row action.App tools (
server.mjs,lib/args.mjs,lib/budget.mjs)ANSWER_BUDGET. Long values are cut and become read-only, then rows are dropped from the end, with a note saying what was cut.max_rowsrefuses booleans, arrays and blank strings.Build and browser tools (
lib/runtime.mjs,lib/npm-backend.mjs,lib/spawn.mjs)interact_apptiming: each action waits for the roundtrip it started. Before, the frontend dropped an event fired during a roundtrip, and both clicks were reported as performed.build_backendruns is refused. Before, it booted the old output, which the build then reported as built.backend stopnow stops a start in flight, and the start reports that it was stopped.run_appandinteract_appcut a page taller than 4096 px.screenshot_viewleaves out pictures over 8000 px or past its image budget. Each call gets its own PNG file.read_apppages by lines (from_line).run_unit_testsis counted per object.migrate_report's files are named, not inlined, once they pass the budget.Adaptive Card and MCP Apps (
lib/adaptive-card.mjs,lib/mcp-app-view.mjs)max_rows.Snapshot (
lib/viewxml.mjs,lib/snapshot.mjs)�no longer throws the snapshot.stripTagsis linear.Not in this PR
read_appstaleness is judged against it);deploy_apperroring after it wrote the class when the lint config is missing;remove_appleaving interfaces and tables behind;add_agent_setupwith a dangling.claudelink.appclient/snapshot/viewxml, and this repo re-vendors the protocol renderer (Adaptive Cards: app text is never Markdown; linear htmlToText protocol#4, merged). Both come as follow-up PRs.Checks
A2UI5_MCP_SKIP_NETWORK_TESTS=1 npm test(with Chromium): 439 passed, 16 skipped, 0 failed.🤖 Generated with Claude Code
https://claude.ai/code/session_013nvd2W2zUT5ZyemG91n2oq