Skip to content

chore: sync main with upstream google/A2UI main - #44

Open
louis-bompart wants to merge 63 commits into
mainfrom
claude/sync-upstream-main
Open

louis-bompart wants to merge 63 commits into
mainfrom
claude/sync-upstream-main

Conversation

@louis-bompart

Copy link
Copy Markdown
Collaborator

Brings the fork's main up to date with upstream google/A2UI main (d98472e, 63 commits ahead of our main at 409ded2). The branch is upstream main as is; the fork's main has no commits of its own.

Merge with "Create a merge commit", not squash or rebase, so upstream's commits stay in our history. PRs we later open upstream from this fork can then base on it cleanly.

This prepares the regrouping of the Zod stack (#22 to #43) on current upstream.

🤖 Generated with Claude Code

josemontespg and others added 30 commits October 26, 2026 10:00
)

GitHub moved `ubuntu-latest` to Ubuntu 26.04. `swift-actions/setup-swift`
refuses it ("Version 26.04 of Ubuntu is not supported") and Playwright has no
Chromium build for it, so every pull request fails `format-check` and
`ci-renderers`. Pin the two jobs that need them to `ubuntu-24.04` until the
upstream actions catch up.
…-project#3186)

The entry point targets A2UI v1.0 and bundles catalogs/mcp/v1/catalog.json.
configureMcpCatalog replaces createMcpCatalogFunctions as the single host entry
point (MCP client resolver, message processor, default message version,
onMcpAppResource and sandbox location); CallMcpToolImplementation ships
pre-constructed and reads that configuration when it runs. callMcpTool extracts
ui:// MCP App resources into McpAppResourcePayload and accepts a function call
as its arguments.

The community MCP sample clients move to this API in the next change, together
with their switch to the monorepo builds of @a2ui/*.
…d the monorepo builds (a2ui-project#3349)

The two community MCP sample clients compiled @a2ui/catalog-mcp from source
against the published @a2ui/web_core, which cannot follow the catalog now that
it targets the monorepo's newer core. They alias @a2ui/catalog-mcp, @a2ui/lit
and @a2ui/web_core to the monorepo dist/ (tsconfig paths and Vite aliases),
drop the npm copies, adopt the current core API (Catalog takes a protocol
version, getClientCapabilities takes options, defaultCatalog), and register
CallMcpToolImplementation with configureMcpCatalog. The community CI workflow
builds those packages at the repository root first (TODO a2ui-project#1698).
…cp (a2ui-project#3351)

McpAppBridge implements the host side of the MCP Apps protocol on
@modelcontextprotocol/ext-apps/app-bridge over the shared double-iframe
sandbox runtime from typescript/catalogs/shared/sandbox/: ui/open-link,
ui/message, ui/request-display-mode, ui/update-model-context, tool input and
result notifications, tool execution through a host callback, and host context.
Payload validation checks function call arguments against allowedFunctions.
The McpApp component that uses the bridge follows in the next change; nothing
is exported from the entry point yet.
…og to @a2ui/catalog-mcp (a2ui-project#2799)

Add mcpCatalog, the v1 MCP catalog with the McpApp universal component
(A2uiMcpApp) and callMcpTool. The component hosts an MCP App in the shared
double-iframe sandbox and drives it through McpAppBridge; without a configured
client, it dispatches allowed tools/call requests as A2UI actions. The entry
point exports mcpCatalog and A2uiMcpApp, and the testing harness
(FrameTestHarness) renders a component of the catalog through the real sandbox
proxy for the package's browser tests.
…andbox (a2ui-project#3352)

A fixture MCP App (tests/fixtures/apps/test_app.ts, bundled by
build:test-fixtures) and Karma scenarios that run McpApp, McpAppBridge and
callMcpTool together through the real sandbox proxy: initialization and host
context, bound data in both directions, tool calls with and without a
configured client, model context and messages to the host.
…roject#3591)

## Summary

Removes the TypeScript and Dart evaluation bridges added in a2ui-project#3237 and collapses the Python evaluation helper into `eval/a2ui_eval/runner.py`. Cross-language prompt generation and parser parity are covered by the deterministic `conformance/agent/` suites, so LLM evaluations only need to run against the Python Agent SDK.

## Changes

- Deleted `eval/a2ui_eval/bridges/` (`base.py`, `python_bridge.py`, `ts_bridge.py`, `ts_runner.mjs`, `dart_bridge.py`, `dart_runner.dart`, and `__init__.py`).
- Added `eval/a2ui_eval/runner.py` with direct Python prompt generation, subprocess-isolated payload validation, and `DEFAULT_PARSE_TIMEOUT_SEC = 15.0`.
- Updated `eval/a2ui_eval/scorers.py` and `eval/a2ui_eval/strategies/` (`format.py`, `subagent_tool.py`, and `__init__.py`) to call `a2ui_eval.runner` directly without an `sdk` parameter.
- Removed `--sdk` and `--sdks` from `eval/main.py`, `eval/tasks.py`, and `eval/bin/run_ci_evals.py`, and removed the `SDK` column from `eval/bin/report_evals.py`.
- Replaced `eval/tests/{conftest.py,test_bridges.py,test_dart_bridge.py}` with Python-only `eval/tests/test_runner.py` and updated `eval/tests/{test_scorers.py,test_strategies.py,test_main.py,test_run_ci_evals.py}`.
- Updated `eval/README.md`, `eval/DESIGN.md`, `eval/CONTRIBUTING_USE_CASES.md`, and `.agents/skills/a2ui-add-eval-datapoint/SKILL.md`.
…and gated scoring (a2ui-project#3393)

## Summary

Upgrades the A2UI evaluation suite (`eval/`) to test the full inference contract with a gated 3-stage scoring pipeline, validation self-retries, local and frontier model profiles, and dataset trace-import and case-expansion tools.

## Changes

- Extended `eval/datasets/dataset_schema.json` and `eval/a2ui_eval/dataset.py` to parse `catalogs`, `examples`, `allowed_messages`, `initial_state`, `stream_chunk_size`, `tags`, and Stage 2 `assertions` (with an enum for the 4 A2UI message types).
- Updated `eval/a2ui_eval/runner.py` to normalize protocol versions via `validate_version` and apply catalog pruning transformers. It also loads few-shot example payloads, seeds `RendererState`, passes `allowed_messages`, and tests incremental streaming parsing.
- Exported `PayloadFacts` and `collect_payload_facts` from `eval/a2ui_eval/scorers.py` and shared them with `eval/bin/import_trace.py`. Both Stage 2 scoring and trace importing walk nested `updateDataModel` and `createSurface.dataModel` trees identically.
- Added `eval/a2ui_eval/strategies/skill_mode.py` (`SkillGenerator` evaluation strategy) and extended `eval/a2ui_eval/strategies/subagent_tool.py` with `--subagent-format {direct_json,express}`, `--max-validation-retries`, and v2 sample prompt keys (`protocol_role`, `generation_rules`, and `system_prompt`).
- Restricted `DEFAULT_STRATEGIES` to production strategies (`direct`, `subagent_tool`, and `express`) while keeping `elemental`, `atom`, and `skill_mode` opt-in via `--strategies`.
- Added `--profile {cloud,local,offline}`, `--no-judge`, `--max-connections`, `--working-limit`, `--model-base-url`, `--max-validation-retries`, `--subagent-format`, and `--tags`/`--tier` to `eval/main.py`, `eval/tasks.py`, and `eval/bin/run_ci_evals.py`.
- Added `frontier` optional dependencies (`openai`, `anthropic`, `mistralai`) to `eval/pyproject.toml`.
- Tagged a 19-sample `smoke` tier and Stage 2 assertions across the 35-sample `eval/datasets/core_v0_9_1.yaml` and `eval/datasets/core_v1_0.yaml` suites, including three new inference-coverage cases (`multiCatalogWeatherChart`, `prunedContactCard`, and `statefulPriceUpdate`).
- Updated `eval/README.md`, `eval/DESIGN.md`, `eval/CONTRIBUTING_USE_CASES.md`, and `.agents/skills/a2ui-add-eval-datapoint/SKILL.md`.
…2ui-project#3010)

catalogs/mcp/v1/examples/mcp-app-3d-geometry.json renders an McpApp whose
3D scene is driven by the host data model and by tool calls. validate.py
learns to validate the examples of a catalog that extends a surface using
the basic catalog (merge_catalogs + the mcp config), the docs workflow
publishes the examples with the spec, and the catalog package gains a
browser test that runs the example through the real sandbox.
…i-project#3356)

The Lit explorer links @a2ui/catalog-mcp, serves the sandbox proxy from
public/a2ui-sandbox, and hosts the MCP App examples with a mock MCP server
(src/mcp/): tool calls are answered locally, ui/request-display-mode resizes
the app, and the generated example tests cover mcp-app-3d-geometry.
…2ui-project#3357)

Same integration as the Lit explorer: @a2ui/catalog-mcp in the demo
catalog, the sandbox proxy served from public/a2ui-sandbox, a mock MCP
server and resizer in src/mcp/, and the generated example test for
mcp-app-3d-geometry.
…rer (a2ui-project#3358)

Same integration as the Lit and React explorers, plus the closure-compiler
externs for the web_core v0.9 surface the McpApp component touches, the
angular.json asset entry for the sandbox proxy, and the generated example
spec for mcp-app-3d-geometry.
…plyChange (a2ui-project#3593)

`paths` is a plain object, so a frame sending a data-model-change for
`toString`, `constructor` or `__proto__` resolved to the inherited function
instead of `undefined` and skipped the `unbound` result. `applyChange` now
only accepts own keys of `data.paths`.
a2ui-project#3372)

* fix(swift): format numbers, currencies and plurals in en_US by default

formatNumber and formatCurrency used Locale.current and pluralize
defaulted to it, so their output depended on the device locale and the
functions.yaml conformance cases failed on non-US machines (a2ui-project#3144).

Add BasicFunctions.defaultLocale (en_US), matching web_core and Dart,
and make the three functions take a `locale:` that defaults to it. Hosts
that want another locale build the catalog with
BasicCatalog.makeCatalog(version:locale:) or
BasicCatalogImplementation.makeCatalog(version:locale:), backed by the
new V09BasicFunctions/V10BasicFunctions.makeFunctions(locale:).
makeCatalog(version:) still returns the shared instances.

Fixes a2ui-project#3144

* test(swift): drop the a2ui-project#3144 known issue from the expressions validate cases

formatCurrency now formats in en_US by default, so
test_expression_parser_nested_function_call no longer depends on the host
locale and runs as a plain case.

* refactor(swiftui): fold makeCatalog(version:) into makeCatalog(version:locale:)

The two SwiftUI factories built the same catalog. Give locale a default
of BasicFunctions.defaultLocale instead of keeping a second copy.

---------

Co-authored-by: Pete Biencourt <piebie@google.com>
…i-project#3374)

Protocol v1.0 gives ValidationResult a severity of error, warning or
info (default error). ResolvedCheck.isValid ignored it, so a check
failing with warning or info severity made Node.isValid false: a Button
was disabled and its action reported VALIDATION_FAILED instead of
dispatching.

Match web_core and Dart:
- ResolvedCheck gains `passed` (the raw outcome) and `severity` (error
  when missing); `isValid` is false only for an error-severity failure.
- Node.validationErrors lists only error-severity messages.
- New Node.validationResults returns every failed check with its code,
  message and effective severity, for renderers to show as hints.

NodeResolver is unchanged; its action guard already uses
ResolvedCheck.isValid.

Refs a2ui-project#3081
… principles (a2ui-project#3639)

### Summary

Adds a TypeScript/Web coding principle that scripts and tools are invoked through Yarn (`yarn <script>`, `yarn workspace <name> run <script>`, `yarn dlx`) and never through `npm run` or `npx`. The repository is a Yarn Berry workspace monorepo, so `npm`/`npx` bypass workspace resolution and are inconsistent with the rest of the repo. This came up in review on a2ui-project#3226, where a Playwright `webServer` command used `npm run dev -- --port 5173` instead of `yarn dev --port 5173`.

### Changes

- `.agents/skills/coding-principles/subskills/typescript/yarn-workspace-tooling.md`: new subskill with the rule, a reviewer checklist (direct argument passing, `yarn install --immutable` in CI, `touch yarn.lock && yarn install` before `yarn npm publish` inside `dist/`, scope, the existing `npx playwright install` exception in `web_ci.yml`), and good/bad examples for a Playwright `webServer` command and a workflow step.
- `.agents/skills/coding-principles/subskills/typescript/INDEX.md`: registers the subskill in the routing table with its activation criteria; the `ts-morph` reference entry moves from 6 to 7.

### Verification

- `./scripts/fix_format.sh --check` passes.
- Relative links in `INDEX.md` resolve to the new file.
…tter build (a2ui-project#3614)

* feat(a2ui_core): deliver surface errors and warnings through a group queue

SurfaceModel.dispatchError and dispatchWarning now put the report in a
FIFO queue and return once it is queued. The queue hands reports to
listeners in a microtask, never inside the call that found them, so a
listener may change framework state directly.

SurfaceGroupModel owns one queue for its surfaces and exposes onError and
onWarning, which receive every surface's reports after that surface's own
listeners. addSurface moves a surface's undelivered reports into the
group's queue, detachSurface holds them until the surface is added again,
and deleteSurface and SurfaceModel.dispose drop them.

SurfaceModel.onReportsDelivered fires after a delivery that included the
surface's reports. A listener that throws no longer stops later reports.
Its error goes to the current zone's uncaught-error handler, or is printed
in the root zone. An error that no listener receives is printed, and an
unheard warning is logged at FINE.

Tests that read reports right after building a resolver, changing data or
evaluating an expression now wait for the delivery. Disposing a resolver
inside a listener no longer cancels the reports already queued.

⚠️ Agent-generated

* feat(a2ui_core): add processor error listeners and deliver per message

MessageProcessor takes onError and onWarning beside onAction, and
subscribes them to the group's events of the same name, so an app can set
one listener for every surface before the first message.

processMessages and processMessagesAsync open a delivery scope around the
synchronous application of each message. Errors and warnings that mounted
resolvers report while a message is applied reach their listeners when
that message ends, before the next message and before an exception from a
later message propagates. A report found inside processMessages is
therefore seen right after it returns, as before the queue, also when the
same payload deletes the surface afterwards. processMessages called from a
listener delivers after that listener returns. Problems in a message
still throw.

The resolver test that read a report right after processMessages no
longer waits for a microtask.

⚠️ Agent-generated

* fix(a2ui_core): report resolver diagnostics straight to the surface

NodeResolver no longer keeps its own queues of errors and warnings, its
fallback microtask or its discard on dispose and on a failed update. It
dispatches each diagnostic to the surface as it finds it, so the
surface's queue delivers it after the resolver call returns and disposing
the resolver drops nothing. The per-resolver deduplication of unknown
types and cycles is unchanged. The note about a deeper unresolved
ChildList is logged where it is found.

The constructor, updates and dispose of a resolver, and every catalog
function call in DataContext, run under a resolution guard. While it is
open, a processMessages that ends a message defers its delivery to a
microtask, so no listener runs inside resolver work or a catalog function.

Tests that pinned the dropped behaviour now expect an unchanged-null
expression error to arrive after disposal, a failed update to deliver
what it found once, and the unresolved-reference warning to be logged
before the root is published.

⚠️ Agent-generated

* fix(a2ui_flutter): let error listeners call setState and request frames

A2uiSurface builds its resolver as before, and the diagnostics it finds
now reach the surface's and the processor's listeners after the build,
so a listener may call setState directly. While it is mounted, the
widget requests a frame from SurfaceModel.onReportsDelivered, so a host
that defers its setState to a frame callback gets that frame.

error_listener_set_state_test.dart, carried from the superseded fix
branch, covers a processor listener and a surface listener. New tests
cover a deferring host whose surface mounts after the app settles, a
jump through a list that builds and drops surfaces, and a mounted
surface updated and deleted in one payload.

⚠️ Agent-generated

* docs(a2ui_core, a2ui_flutter): record the delivery change and move the README listener

The a2ui_core CHANGELOG lists the breaking changes to error and warning
delivery and the new processor, group and surface events. The
a2ui_flutter CHANGELOG records that listeners may call setState and that
the widget requests a frame after delivery, and that it needs the next
a2ui_core release.

The README Quick Start subscribes one error listener on the processor's
group model next to the action listener, instead of one per surface in
onSurfaceCreated. It passes decoded JSON to processMessages and a version
to getRendererDataModel. The advice to defer a setState caused by an
error listener is replaced by: listeners call setState directly, or
schedule a frame if they defer.

⚠️ Agent-generated

* test(a2ui_core): pin the guard around resolver disposal and the standalone drop

A node's destruction listener that calls processMessages while
NodeResolver.dispose runs no longer delivers inside dispose. Without the
resolution guard around dispose that report arrived before dispose
returned, and no test noticed.

Disposing a standalone surface drops its queued report. The detached
variant could not show this, because a held report is never delivered
either way. The new test observes the drop through the platform log,
which would print an undelivered error.

⚠️ Agent-generated

* test(a2ui_core): give tests that expect unheard errors a listener

Six existing tests build a resolver over a surface whose unknown type or
cycle they expect, with no error listener, so each run printed seven
"A2UI error with no listener" lines. They now subscribe a listener that
ignores the reports. The conformance harness does the same where it
builds a resolver only to read the tree.

⚠️ Agent-generated

* chore(a2ui_core): mark the hidden delivery helpers internal

attachProblemChannel, runInMessageScope and listenersOf are hidden from
the package barrel. Marking them @internal also makes an import of them
through lib/src from another package an analyzer warning.

⚠️ Agent-generated

* docs(a2ui_core, a2ui_flutter): name build-time processMessages and failed updates

The processMessages doc and the README say that processMessages called
from a widget's build runs its listeners inside that build, and the doc
says that a listener which deletes a surface or processes more messages
changes what the later messages of the same payload see. The a2ui_core
CHANGELOG records that a resolver update that throws now delivers what
it found and keeps it deduplicated, and that the nested ChildList
warning is logged when found. The processMessages doc and the CHANGELOG
also say that a catalog function returning a signal runs its body when
the result is read, so a processMessages call from that body during a
data write delivers its reports inside the write.

The NodeResolver class doc names data writes among the calls whose
diagnostics are delivered after they return. ComponentContext's doc no
longer says its default reporter dispatches immediately, and the
A2uiSurface doc no longer ties delivery to the surface's own listeners.

⚠️ Agent-generated

* refactor(a2ui_core,a2ui_flutter): keep only the delivery fix in this change

Remove the public API the fix does not need and the behaviour changes
nobody has to act on, so the change delivers errors after the call that
found them and keeps them when a resolver is disposed, and nothing more.

- Drop `MessageProcessor({onError, onWarning})`,
  `SurfaceGroupModel.onError` and `onWarning`, and
  `SurfaceModel.onReportsDelivered` with its frame request in
  `A2uiSurface`. Apps subscribe per surface as before.
- A throwing listener is logged at SEVERE by `EventNotifier.emit` and the
  other listeners still run, as on main. Unheard errors and warnings are
  neither printed nor logged.
- `detachSurface` holds no reports.
- The `a2ui_flutter` README Quick Start is main's again. Only the bullet
  about `setState` changes.
- Both CHANGELOGs carry one entry each for this change.

⚠️ Agent-generated

* refactor(a2ui_core): deliver each error and warning in a microtask, without a channel

`dispatchError` and `dispatchWarning` schedule one microtask per report.
The `ProblemChannel`, its message scope around `processMessages`, the
resolution guard and the two hidden entry points go. A report found while
`processMessages` applies a message now reaches listeners a microtask after
it returns. `NodeResolver` still reports straight to its surface, so
disposing a resolver drops nothing.

⚠️ Agent-generated

* docs(a2ui_core): state the delivery change as what a listener and a test see

⚠️ Agent-generated

* docs(a2ui_core): describe delivery as a microtask after the call that found the problem

The `processMessages` and `onError` docs still described per-message
delivery. Each report now arrives a microtask after the `dispatchError`
call that reported it, so a later message may already have fixed what it
describes, and reports not yet delivered when the surface is disposed are
dropped. The CHANGELOG recommends `Future<void>.delayed(Duration.zero)`,
which waits for every pending microtask, over `Future<void>.value()`,
which waits for one round.

⚠️ Agent-generated

* test(a2ui_core, a2ui_flutter): pin one-microtask delivery and share the setState host

The standalone-surface test awaits one microtask, since two would also
pass if delivery slipped by one. The scrolled-list test checks that items
from before and after the jump were built, instead of a count that depends
on the viewport and cache extent. `ErrorCountingHost`, which calls
`setState` directly, moves to `test/support/` and replaces the host in
`a2ui_surface_test.dart` that still deferred through a post-frame callback.

⚠️ Agent-generated

* style(a2ui_core): restore the one-line catalog check in DataContext

The braces were left over from unwrapping the resolution guard.

⚠️ Agent-generated

* fix(a2ui_core): deliver reports dispatched before a surface is disposed

`SurfaceModel.dispose()` closes the error and warning listeners a
microtask later, so a report dispatched before it still reaches them. A
report dispatched after it is dropped. An error found by a message that a
`deleteSurface` in the same payload follows now arrives, as it does when
the two messages come in separate payloads.

⚠️ Agent-generated
…rs from one A2uiRequestProcessor (a2ui-project#3299)

* feat(python/adk): wire SendA2uiToClientToolset and converters to A2uiRequestProcessor and RendererState

- Allow SendA2uiToClientToolset to accept structured example turns (such as processor.examples) or None in addition to markdown strings, and avoid duplicating schema or example instructions already present in llm_request.config.system_instruction.
- Add optional a2ui_state (RendererState or A2uiStateProvider) to SendA2uiToClientToolset so tool-based Canvas agents validate and apply payloads against a session RendererState.
- Add optional state parameter to parse_content_to_parts in a2ui.a2a.
- Allow A2uiPartConverter to accept processor and state, and infer version from the active catalogs' protocol_version when omitted.
- Update A2uiEventConverter to read system:a2ui_processor, system:a2ui_state, and system:a2ui_stream from session state, reuse A2uiPartConverter across events, and stream partial ADK events via ResponseStream.

Signed-off-by: Nan Yu <nanyu@google.com>

* feat(python/adk)!: read SendA2uiToClientToolset calls in an inference format

- Bind the tool to an A2uiRequestProcessor built from the catalogs, the
  example turns and a new inference_format_factory argument, defaulting to
  ExpressFormatFactory, so the tool is no longer tied to Direct JSON.
- Compile the tool's argument (renamed a2ui_json -> a2ui_payload) with the
  format's parser instead of parse_and_fix, then validate it: against the
  RendererState when one is given, else with validate_payload. The tool
  still returns A2UI JSON messages for the part converter.
- Build the prompt from the format's prompt generator, which writes example
  turns in the format's syntax, and skip each section (rules, catalogs,
  examples) the system instruction already holds.
- get_part_converter reads text-based A2UI with the toolset's format.
- The rizzcharts sample passes DirectJsonFormatFactory to keep Direct JSON;
  the shared ADK conformance case uses a valid v0.8 payload and a
  format-neutral error message.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/adk): track streams by object; normalize examples like catalogs

- A2uiEventConverter keeps the streams it is feeding in a WeakSet instead
  of a set of id()s, so a later stream cannot reuse an abandoned one's id
  and an abandoned stream is not kept alive.
- SendA2uiToClientToolset reads a collection of examples once in its
  constructor, so a generator or set is not exhausted, accepts any iterable
  of turns from a provider, and rejects bytes, a single message, a single
  turn or a mapping with a TypeError.

Signed-off-by: Nan Yu <nanyu@google.com>

* refactor(python/adk): give _SendA2uiJsonToClientTool one processor

The tool took the catalogs, the examples and the format factory, and also
cached an A2uiRequestProcessor built from them, so the same inputs lived in
two places. It now takes only the processor (or a provider of one per
context) and the optional renderer state.

SendA2uiToClientToolset keeps its public arguments, which every caller in
this repo and in google3 uses, and binds them to the processor: once, when
the toolset is created, for fixed catalogs and examples, or per context when
either is a provider. A markdown string of examples, which most existing
agents pass, goes to the processor's prompt through a thin format wrapper,
so the processor is the only source of the prompt.

Signed-off-by: Nan Yu <nanyu@google.com>

* refactor(python/adk): read only the processor in A2uiEventConverter

The converter read the processor, the catalogs, a renderer state and a
response stream from four session-state keys, and cached a part converter
keyed on their ids. The processor already covers the other three:

- Catalogs: `processor.active_catalogs`. The `system:a2ui_catalogs` key
  stays as the fallback for agents that store only catalogs, as the
  samples and every google3 caller do today.
- State: the RendererState bound to the processor with
  `A2uiRequestProcessor(state=...)` or `create_processor(..., state=...)`.
  `state_key` is removed.
- Stream: the converter opens one stream per invocation with
  `processor.open_stream()` when the format can stream, feeds it each
  partial event and closes it on the final event, instead of expecting a
  ResponseStream object in session state. `stream_key` is removed.

The part-converter cache is gone: with a processor the parser is the
processor's own, so building an A2uiPartConverter per event is cheap.

Signed-off-by: Nan Yu <nanyu@google.com>

* refactor(python/adk)!: drive the toolset and converters from one processor

The processor already holds the catalogs, the example turns, the inference
format and the bound RendererState, so nothing else needs to be passed or
read from session state.

- SendA2uiToClientToolset(a2ui_enabled, a2ui_processor) takes the processor
  or an A2uiProcessorProvider. A2uiCatalogsProvider, A2uiExamplesProvider,
  A2uiStateProvider, inference_format_factory, a2ui_state and markdown
  examples are removed, with the helpers that bound them to a processor.
  The tool checks payloads against the processor's bound state.
- A2uiPartConverter(processor, *, bypass_tool_check, fallback_text) drops
  catalogs, parser, version and state.
- A2uiEventConverter drops catalogs_key and the system:a2ui_catalogs key.
- create_a2ui_part (a2ui-a2a) labels a part with the message's own version
  when none is passed. The v0.9.1 basic catalog reports protocol v0.9, so
  the converters could not tell its parts' MIME type from the catalogs.
- Samples store the processor under system:a2ui_processor. Rizzcharts
  builds its processor from its example files as turns; its v0.9 examples
  named the v0.8 catalog id, which they now fix. The orchestrator subagents
  build a Direct JSON processor for their converters.

Signed-off-by: Nan Yu <nanyu@google.com>

* refactor(python/adk): name A2uiEventConverter's part converters for what they are

- Return early when there is no processor, instead of a three-way branch.
- Rename base_converter to a2ui_part_converter (the A2uiPartConverter) and
  effective_converter to convert_part (the callable ADK calls per part).
- The streaming wrapper builds the fallback part from the event
  converter's own fallback_text instead of reading A2uiPartConverter's
  private attribute.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/adk): read only text from the system instruction; import ADK's event converter at the top

- _instruction_text reads a str instruction, or the text of a Content, a
  Part or a list of them, and skips parts without a string text, so an
  unusual instruction can no longer make the join raise.
- event_converter.py imports google.adk.a2a.converters.event_converter at
  module level, as a module so that patching convert_event_to_a2a_events
  still takes effect. The module already needs the a2a extra and imports
  ADK's part_converter from the same package.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/adk): drop a streamed response's rest after a failed chunk; read examples as UTF-8

- A2uiEventConverter marks an invocation whose chunk failed instead of
  leaving the closed stream in place. Its later partial chunks are dropped
  without a warning per chunk, and its final event sends the fallback text
  and removes the entry. Before, every later chunk logged that the stream
  was closed and the final event sent nothing. Removing the stream instead
  would open a new one mid-block and send the rest of the block as text.
- The rizzcharts sample reads its example files with pathlib alone and as
  UTF-8.

Raised in review on a2ui-project#3632.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/adk): leave thought parts to ADK and update rizzcharts prompt

- A2uiPartConverter and A2uiEventConverter skip A2UI parsing/streaming for model thought parts (part.thought=True) so ADK converts them with thought metadata intact.
- Update rizzcharts sample prompt instructions to reference the renamed a2ui_payload tool argument and the example turn templates.
- Clean up type annotations and collections.abc imports in python/a2ui_adk.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/adk): close open stream on non-text final events and sync docs

* fix(python/adk): address review on processor state, stream cleanup and tags

- Document that ADK's session services copy session state on every load,
  so a canvas agent keeps its RendererState, or a processor bound to it,
  in its own store keyed by session ID and gives the toolset a provider
  that reads it. The agent development guide no longer suggests storing a
  state-bound processor in session state.
- A2uiEventConverter closes a stream left open after a final event with no
  parts, which ADK converts without calling the part converter, and logs
  any trailing parts it has to drop.
- A2uiEventConverter warns when the processor key holds something other
  than an A2uiRequestProcessor.
- The tool reads a block whose closing tag is missing: it unwraps whenever
  the payload has format content and rereads the blocks as complete.
- Reword two CHANGELOG entries so no code span wraps across lines.
- The streaming tests call the part converter during conversion, as ADK
  does.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/adk): keep processors out of session state and isolate streams

- Add A2uiProcessorStore, which keeps each session's request processor in
  memory: persistent ADK session services save state as JSON and can't hold
  a processor, and every service copies state on load.
- A2uiEventConverter takes the processor or a provider in the required
  keyword argument a2ui_processor, replacing processor_key. A canvas
  agent's text-based A2UI now updates the live state.
- Key streams by invocation and branch or author, so ParallelAgent
  sub-agents don't share one, and drop streams idle for
  stream_idle_timeout seconds.
- Warn when the toolset or converter gets one processor for every request
  with a bound RendererState.
- Log tool and parse errors by type, with the text only at debug level.
- Samples and docs use the store instead of session state.

Signed-off-by: Nan Yu <nanyu@google.com>

* chore(python/adk): fix license headers

Signed-off-by: Nan Yu <nanyu@google.com>

---------

Signed-off-by: Nan Yu <nanyu@google.com>
…project#3546)

* fix(swift): refuse agentOnly functions in bindings and actions

The v1.0 spec says a function whose allowedCallers is agentOnly cannot
be bound to component properties or run by renderer UI actions; only an
agent-initiated callRendererFunction may run it. DataContext evaluated
any resolved function, so a binding, check or function-call action
could run an agentOnly function locally.

DataContext now reports FunctionError.callerDisallowed (surfaced as
EXPRESSION_ERROR) and returns null before resolving the call's args or
running it, as Dart's CatalogInvokerExtension.invoke does. RPCHandler
calls the function directly, so agent-initiated calls are unaffected.

Fixes a2ui-project#3545

* fix(swift): only refuse agentOnly renderer calls under v1.0

allowedCallers is a v1.0 feature and callRendererFunction requires a v1.0
catalog, so v0.9 bindings, checks and actions are left unrestricted.

* chore(swift): use the 2024 license header year in AgentOnlyFunctionTests

---------

Co-authored-by: Pete Biencourt <piebie@google.com>
…ect a number out of double range (a2ui-project#3627)

* fix(a2ui_agent): reject a JSON number out of the range of a double

The direct JSON reader read a literal such as 1e400 as Infinity, which
jsonEncode cannot write, so a streamed response threw a
JsonUnsupportedObjectError instead of an A2uiError. The reader now fails
such a number, so compile throws A2uiParseError and a streamed message
holding one is not emitted.

(cherry picked from commit e8c0f81)

⚠️ Agent-generated

* fix(a2ui_core): put the regex parse message in details, not the exception

`validateRegex` raised an `A2uiExpressionError` whose `details` held the
`FormatException` object, so encoding the error as JSON threw. The core
error now carries the parse message.

⚠️ Agent-generated
* fix(swift): scale Text headings with Dynamic Type

A2UIText rendered the h1-h5 variants and Markdown headings with fixed
point sizes, while body and caption text used semantic text styles.
With a larger system text size, body text grew past the headings: at
the largest accessibility size body was 53pt and h1 stayed at 32pt.

Headings now use semantic text styles (largeTitle, title, title2,
title3, headline), so they scale with Dynamic Type and keep their
order at every size. A2UIText.headingFont(level:) holds the mapping
and is shared by the variants and Markdown headings; Markdown levels
deeper than 5 use a semibold subheadline.

Fixes a2ui-project#3254

* fix(swift): correct copyright year in A2UITextTests header

---------

Co-authored-by: Pete Biencourt <piebie@google.com>
…P App sandbox (a2ui-project#3646)

### Summary

This PR removes `allow-same-origin` and `allow-popups` from the inner iframe sandbox in the community Lit MCP App sample (`samples/community/client/lit/mcp-apps-in-a2ui-sample`) and adds `DEFAULT_INNER_SANDBOX` and `resolveInnerSandbox()` in `samples/community/client/shared/mcp_apps_inner_iframe/sandbox.ts`.

Previously, `McpApp.initializeSandbox()` passed `sandbox: 'allow-scripts allow-forms allow-popups allow-modals allow-same-origin'` via `sendSandboxResourceReady()`. Combining `allow-scripts` with `allow-same-origin` on an inline `srcdoc` iframe allowed untrusted HTML to inherit the outer proxy iframe's origin and access `window.parent`. With this change, `McpApp.initializeSandbox()` passes `'allow-scripts allow-forms allow-modals'`, and `resolveInnerSandbox()` defaults to `DEFAULT_INNER_SANDBOX` when `sandbox` is omitted while logging a `console.warn` if `allow-same-origin` is passed for an inline `srcdoc` iframe.

Resolves a2ui-project#3431

### Changes

* Update `samples/community/client/lit/mcp-apps-in-a2ui-sample/ui/custom-components/mcp-apps-component.ts` to pass `sandbox: 'allow-scripts allow-forms allow-modals'` in `sendSandboxResourceReady()`.
* Add `DEFAULT_INNER_SANDBOX` and `resolveInnerSandbox(sandbox, isSrcdoc)` in `samples/community/client/shared/mcp_apps_inner_iframe/sandbox.ts` so the inner iframe resets to `'allow-scripts allow-forms allow-modals'` when `sandbox` is omitted, preserves explicit host-provided flags, and logs a warning if `allow-same-origin` is used on an inline `srcdoc` iframe.
* Add unit tests for `resolveInnerSandbox()` in `samples/community/client/shared/mcp_apps_inner_iframe/sandbox.spec.ts`.

### Verification

* Ran `npx vitest run client/shared/mcp_apps_inner_iframe/sandbox.spec.ts` in `samples/community` (18 passed).
* Built and linted `@a2ui/mcp-apps-in-a2ui-sample`, and verified in Chromium that the inner `about:srcdoc` iframe runs with `sandbox="allow-scripts allow-forms allow-modals"` and `window.origin === "null"` while completing the MCP App handshake and tool call flow.
* fix(a2ui_core): resolve accessibility.hidden as a dynamic value

The framework adapter blueprint (section 3) says a component's
accessibility `label`, `description` and `hidden` are dynamic and
"arrive resolved like any other binding". v1.0 types `hidden` as a
DynamicBoolean in common_types.json AccessibilityAttributes. The binder
gives every component whose schema does not declare `accessibility` an
`accessibility` behavior with `label` and `description` as dynamic
values, but left `hidden` static, so a `hidden` bound to a data path
reached `node.props` as the raw `{"@path": ...}` map rather than its
value.

`hidden` now resolves like `label` and `description`: it reaches
`node.props` as a binding to its value, and a bound `hidden` follows
the data. `live` is a static enum and stays as written.

The v1.0 basic catalog components do not declare `accessibility`, so
this applies to them. The v0.9 basic catalog components declare it
through ComponentCommon, whose v0.9 schema has no `hidden`, so they are
unaffected. A v0.9 component whose schema leaves out `accessibility`
gets the injected `hidden` along with `label` and `description`. v0.9
defines no `hidden`, so for a v0.9 payload that sets none this only adds
a `hidden` that resolves to null.

⚠️ Agent-generated

* fix(a2ui_core): check validation results in function arguments as their validity

`Catalog.invoke` has checked a function's resolved arguments against its
argument schema since a2ui-project#2995, and the RPC layer added in a2ui-project#3086 shares
that check through `argumentErrors`. The v1.0 validation rules
(`required`, `regex`, `length`, `numeric`, `email`) return a
ValidationResult, so `and`, `or` and `not` over them received an object
json_schema_builder cannot read. It threw "Unknown JSON type", the
binder reported EXPRESSION_ERROR, and a Button gated by such a check
stayed disabled with valid data. The v1.0 basic catalog examples
09_login-form and 32_advanced-form-validator do this. A validation
result bound from the data model, such as `{"valid": false}`, failed the
DynamicBoolean schema check with EXPRESSION_ERROR.

Rule 1 of the v1.0 protocol's "DynamicBoolean and CheckRule Coercion"
says a DynamicBoolean that evaluates to a validation result stands for
its `valid` boolean. `argumentErrors` now does this on a catalog for
v1.0 or later: the arguments match when they do with each
ValidationResult, and each object with a boolean `valid`, replaced by
that boolean, at any depth. Any object with a boolean `valid` counts,
because the protocol lets a validation result carry more properties.

When the arguments fail with every replacement made, they are all
checked again with none made. So an argument whose schema is an object,
and which holds an object with a `valid` key, still matches, unless
another value in the same call needs the replacement. When both checks
fail, the errors reported are those of the check with none made. Only
the check uses the replacement: the function receives each argument as
it was resolved. The v1.0 `and`, `or` and `not` read their operands
through `ValidationResult.validityOf`, so they work either way, and the
v1.0 `formatNumber` and `formatCurrency` now read their DynamicBoolean
`grouping` the same way, since a validation result can now reach it.
Passing plain booleans to functions, as rule 1 also describes, would
need the coercion directed by each argument's schema.

On a v0.9 catalog nothing is replaced: the v0.9 `and`, `or` and `not`
read a `{"valid": ...}` map as an object, so a check that let it stand
for a boolean would pass an operand the function then reads as truthy.
On every catalog a ValidationResult is checked as its JSON object
rather than throwing, so an unsupported one is an argument error.

⚠️ Agent-generated

* refactor(a2ui_flutter)!: move the v0.9 basic catalog to v0_9/basic_catalog.dart

The framework adapter blueprint (section 5) has an adapter export its
v1.0 basic catalog from the primary catalog entry point and its v0.9
catalog from a `v0_9` subpath. This frees the primary entry point for
the v1.0 catalog: `package:a2ui_flutter/basic_catalog.dart` moves to
`package:a2ui_flutter/v0_9/basic_catalog.dart`, and its source to
`lib/src/catalog/basic/basic_catalog_v0_9.dart`, so the v1.0 source can
take the plain name as the v1.0 library does. Nothing else changes: the
library exports the same `basicCatalog()`, `BasicComponents`,
`A2uiMarkdown` and `A2uiMarkdownBuilder`.

Section 1 of the blueprint describes such legacy entry points as aliases
that re-export the root runtime. Here the runtime has always been a
library of its own, `package:a2ui_flutter/a2ui_flutter.dart`, which is
version-agnostic, and the catalog another. So the v0.9 path holds only
the catalog, as `basic_catalog.dart` does, and a v0.9 app changes one
import. The tests that exercise the v0.9 catalog import it from the new
path.

To port this onto a tree where `basic_catalog.dart` is already the v1.0
catalog, apply the moves by hand. Git would move that v1.0 library to
the v0.9 path without a conflict, and would move the v1.0 source away
from `lib/src/catalog/basic/basic_catalog.dart`, which conflicts where
`basic_catalog_v0_9.dart` already exists.

BREAKING CHANGE: `package:a2ui_flutter/basic_catalog.dart` no longer
exists until the v1.0 catalog takes its place. Import the v0.9 catalog
from `package:a2ui_flutter/v0_9/basic_catalog.dart`.

⚠️ Agent-generated

* feat(a2ui_flutter)!: add the v1.0 basic catalog at basic_catalog.dart

`package:a2ui_flutter/basic_catalog.dart` now implements the v1.0 basic
catalog: `basicCatalog()` carries the id, protocol version, functions,
instructions and definitions of a2ui_core's `BasicCatalog.v1_0()`, and
`BasicComponents` pairs each shared widget builder with its v1.0
component schema. This follows the framework adapter blueprint
(section 5): one set of builders, and one catalog per protocol version
with that version's schemas and core functions. This commit adds the
v1.0 catalog and leaves the shared builders unchanged, so at this commit
a v1.0 surface accepts TextField `placeholder`, Slider `steps` and Video
`posterUrl` but ignores them, and its TextField still checks
`validationRegexp`. Each has a commit of its own.

A v1.0 `BasicComponents` exists, rather than reusing the v0.9 one with
v1.0 schemas swapped in, because an app that builds a catalog of its
own from basic components needs them checked against the v1.0 schemas.
The v0.9 TextField schema ends in `unevaluatedProperties: false` and has
no `placeholder`, so a v0.9 component would reject the v1.0 props.

The v1.0 components are not wrapped in the v0.9 `BasicTheme`. v1.0
removed `theme` and `primaryColor`, and a2ui_core rejects a v1.0
`createSurface` that carries a theme, so the components use the app's
Material theme as they find it. The module blueprint's "Theme ignored"
test, which expects such a message to render, therefore does not apply,
and the codebase blueprint records that.

The test harness passes raw JSON to `processMessages`, which routes each
message by its own `version`, so one harness serves both versions, and
takes extra catalogs for mixed surfaces. The examples test renders all
43 v1.0 basic catalog examples, as well as the 44 v0.9 ones. Two of the
v1.0 examples, 09_login-form and 32_advanced-form-validator, need the
a2ui_core fix for validation results in function arguments.
`test/v1_0_test.dart` covers a `createSurface` carrying its components
and data, `@index`, `openUrl` running from a tap and refused from a
binding, a failing check disabling a Button, a boolean `false` check,
and a bound Icon `svgPath`. A custom catalog test renders a component
from its own catalog, named by `catalogId`, beside the basic one.

The README now starts from v1.0:
- The host passes the agent's JSON to `processMessages` and gives the
  processor an `outboundListener`, which receives the
  `rendererFunctionResponse` to each `callRendererFunction` and each
  `callAgentFunction`.
- The host sends `a2uiRendererCapabilities` and `a2uiRendererDataModel`,
  the v1.0 names, and disposes the processor.
- Custom components go in a catalog of their own, with relative
  `common_types.json` references, and an agent without a2ui_core gets
  the catalog's document from `toJson()`.
- Actions run as a user activation, so builders call them from gesture
  handlers.
A Protocol v0.9 section covers the v0.9 import, custom v0.9 catalogs,
and, for a host that serves both versions, the per-version envelopes,
data model key and capabilities key, and the version that
`getRendererDataModel` then needs. The codebase blueprint describes
both entry points, both schema sources and the theme deviation.

BREAKING CHANGE: `package:a2ui_flutter/basic_catalog.dart` returns as
the v1.0 catalog under the same names. v0.9 code that still imports it
compiles, but registers the v1.0 catalog, which its v0.9 surfaces do
not name. Import the v0.9 catalog from
`package:a2ui_flutter/v0_9/basic_catalog.dart`.

⚠️ Agent-generated

* fix(a2ui_flutter): check TextField.validationRegexp only in the v0.9 catalog

v1.0 removed `TextField.validationRegexp` in favour of `checks` with the
`regex` function (evolution guide, section 2.2). The v1.0 TextField
schema has no `unevaluatedProperties: false`, so a2ui_core still accepts
the property on a v1.0 surface. Only the builder can ignore it, and the
shared builder enforced it on both versions: a v1.0 TextField carrying
`validationRegexp` showed "Invalid format" for text the agent never
asked to check.

Both TextFields now build through a new private `_build`, which takes
the pattern as a parameter. `buildTextField`, used by the v1.0 catalog,
passes none and checks no pattern, while the TextField's `checks` still
apply. `buildTextFieldV0_9`, used by the v0.9 catalog, passes
`validationRegexp` as before.

⚠️ Agent-generated

* feat(a2ui_flutter): show TextField.placeholder

v1.0 adds a `placeholder` to TextField. The builder passes it to the
field as `InputDecoration.hintText`, shown while the field is empty.

When a field has a label, Material shows hint text only while the label
floats above the field, and a resting label sits where the hint would
be. TextField requires `label`, so with the label left to rest the
placeholder would stay invisible until the field takes focus. While a
placeholder is set, the label therefore always floats. The test checks
the placeholder's opacity, which a check that it is merely built would
miss.

The shared builder reads `placeholder` on both versions. The v0.9
TextField schema ends in `unevaluatedProperties: false` and has no
`placeholder`, so a2ui_core rejects it there.

⚠️ Agent-generated

* feat(a2ui_flutter): move Slider in its steps

v1.0 adds `steps` to Slider, "the number of discrete divisions in the
slider range", an integer of at least 1. With `steps`, the builder sets
the Flutter Slider's `divisions` to it and writes the value on that
grid as `min + range * k / steps`. Computing each value from the range,
rather than adding `k` steps of `range / steps`, keeps more grid values
exact: 10 steps over 0 to 1 give 0.3 rather than 0.30000000000000004.
A value with no fraction is written as an int, as before.

Any number of steps snaps. A large count costs nothing to draw, because
Flutter's Slider leaves out tick marks closer than three tick widths
(`slider.dart`, `adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth`).
`steps` is ignored when the range is empty, which has nothing to divide,
or too large for a double, which renders the slider disabled.

Without `steps` nothing changes: a range that is a whole number from 2
to 1000 moves in steps of 1 and writes `min + (moved - min).round()`,
and any other range moves continuously. A v0.9 test pins that path for
a fractional `min`, which a shared step formula would turn into float
noise. The shared builder reads `steps` on both versions, but the v0.9
Slider schema ends in `unevaluatedProperties: false` and has no
`steps`, so a2ui_core rejects it there.

⚠️ Agent-generated

* feat(a2ui_flutter): show Video.posterUrl until playback starts

v1.0 adds `posterUrl` to Video, an image "to display before the video
plays". The media player shows an `http` or `https` poster in the video
frame until playback starts. The poster covers the frame, whose ratio
is a 16:9 placeholder until the video reports its own, so a poster of
another ratio leaves no bars around it.

Once the current media has been played, the frame shows the video, even
when it is paused at 0:00 or paused before the first position update. A
new URL starts over. Before the first play, the poster gives way to the
video while the position is past 0:00, as after scrubbing, and returns
if the position comes back to 0:00. When the video fails, the poster
gives way to the failure frame, so the error shows in the frame as well
as in the controls. A poster that fails to load counts as none: the
frame stays empty until the video is ready, then shows the video's
first frame, as it does without a poster.

The shared builder reads `posterUrl` on both versions. The v0.9 Video
schema ends in `unevaluatedProperties: false` and has no `posterUrl`, so
a2ui_core rejects it there.

⚠️ Agent-generated

* feat(a2ui_flutter): show Text without common Markdown markers when no A2uiMarkdown renders it

The v0.9, v0.9.1 and v1.0 basic catalog implementation guides ask a
renderer that cannot render Text as Markdown to strip common Markdown
markers. v1.0 Text has no heading variants, so models write headings as
`#` lines, and 17 of the 43 v1.0 basic catalog examples do. Before, an
app without an `A2uiMarkdown` showed the `#`, `**` and backticks as
written.

Every Text that no `A2uiMarkdown` renders now shows its text without the
opening heading marker and one quote marker on each line, link and
image syntax (keeping the link text or the image's alt text), bold,
italic, strikethrough and code delimiters, and backslash escapes. That
is a body Text without an `A2uiMarkdown` above it, a caption Text, and a
v0.9 heading Text, the last two of which an `A2uiMarkdown` never
renders. A body Text that is a single heading line also takes the style
of that heading level, so v1.0 headings still look like headings. Level
6 takes the `h5` style, the smallest heading variant v0.9 has.

Choices, since the text comes from the agent:
- Every pattern is linear in the text. None backtracks over more than
  one line, and single `*` or `_` emphasis cannot contain its own
  marker. A test renders lines of about 50,000 characters of unclosed
  `[`, links, emphasis and heading spaces in under five seconds.
  Patterns of the usual shape take tens of seconds on them.
- The text of code spans and backslash escapes is kept as written, so
  `` `**kwargs` `` and `\*` stay literal, while markers around a code
  span, as in `` **`config.yaml`** ``, are removed.
- A one-line heading in a body Text also loses its closing `#`
  sequence, but only when whitespace precedes it, so `# Learn C#` keeps
  its `#`. Other Text keeps a closing sequence.
- List, table and fence structure is not parsed: list markers and table
  pipes stay, and markers inside them, fences included, are stripped
  like any others. A heading line inside multi-line text loses its `#`
  but keeps the body style, since styling one line of a Text needs the
  rich text an `A2uiMarkdown` provides.
- The stripping is best effort. An escaped backtick pair can still read
  as a code span, and a one-line body Text that is a `#` line indented
  four spaces still reads as a heading.

The README describes this next to `A2uiMarkdown`.

⚠️ Agent-generated

* feat(a2ui_flutter): show warning and info check results as hints

In v1.0 a check's condition can return a validation result with a
`severity`. a2ui_core normalizes every check into `isValid`,
`validationErrors` and `validationResults` on `node.props`: only a
failure of `error` severity, which a check without a severity has,
makes `isValid` false and fills `validationErrors`, while
`validationResults` holds every failure, the `warning` and `info` ones
too. The framework adapter blueprint (section 5) says the inputs SHOULD
render failing `warning` and `info` results "as non-blocking hints
styled by severity".

`ComponentProps.validationResults` reads that list, and the docs of
`isValid`, `validationErrors` and the Button builder now say they cover
`error` severity only.
Each basic input other than Button shows the message of the first
failing `warning` or `info` check as a hint, only while it has no error:
TextField and DateTimeInput as their decoration's helper text, and
CheckBox, ChoicePicker and Slider as small text under the control. A
hint does not block, so a Button stays enabled.

Two departures from the blueprint, which the codebase blueprint
records:
- Warnings and info notes share one style rather than one each,
  because Material's color scheme has no warning role.
- A Button shows no hint. Its only place for a message is the Tooltip
  it gets while a check fails with an error, and a Tooltip that came and
  went with a hint would remount the enabled button and drop its focus.
The TypeScript web elements show no hints at all.

No basic validation function produces a hint: the v0.9 ones return
booleans, and the v1.0 ones return results without a severity, which
count as errors. A check bound to a validation result object in the
data model produces one on either version, since a2ui_core does not
gate check normalization by version.

⚠️ Agent-generated

* fix(a2ui_flutter): read a validation result as a boolean by its validity

Rule 1 of the v1.0 protocol's "DynamicBoolean and CheckRule Coercion"
says that whenever a DynamicBoolean, such as a boolean component
property, evaluates to a validation result, the renderer uses its
`valid` boolean. a2ui_core resolves the property to the result itself,
and `ComponentProps.asBoolean` read anything but a `bool` as null. In
the v1.0 basic catalog, a CheckBox `value` bound to a validation rule,
such as `{"@call": "required", ...}`, therefore read as unchecked.

`asBoolean`, and so `props.boolean`, now reads a ValidationResult, or a
map with a `valid` key, by its validity through
`ValidationResult.validityOf`, the rule a2ui_core's v1.0 `and`, `or`
and `not` use. Any other value that is not a `bool` still reads as
null.

The rule applies on both versions. `asBoolean` is static and has no
catalog version to gate on, and v0.9 gives an object in a boolean
property no meaning, so reading one by its `valid` changes only payloads
whose v0.9 meaning is undefined. A v0.9 boolean property holding a
`{"valid": ...}` object read as null before. This differs from the v0.9
`and`, `or` and `not`, whose argument check rejects such an object.

The test relies on the `hide ValidationResult` import that the hints
commit added to `component_props_test.dart`, so a port that takes this
commit without that one needs the import as well.

⚠️ Agent-generated

* feat(a2ui_flutter): map accessibility.live and hidden in NodeView

v1.0 adds `live` ("off", "polite" or "assertive") and `hidden` (a
DynamicBoolean) to a component's `accessibility`. The framework adapter
blueprint (section 3) asks for the attributes to be mapped in one place
per adapter, such as the node dispatcher, so custom components get them
too. NodeView already put `label` and `description` on a Semantics node
around each resolved component, and now maps the other two there:
- `polite` and `assertive` make that node a live region. Flutter's
  `Semantics.liveRegion` is a single flag with no assertive level, so
  both announce politely. `off`, or no `live`, sets no flag at all,
  which leaves the semantics of every other component as it was.
- A `hidden` that reads as true excludes the component and its whole
  subtree from semantics, as `aria-hidden` does. A bound `hidden`
  follows the data through the a2ui_core binding fix for
  `accessibility.hidden`, and one set by a validation rule reads by its
  validity through `ComponentProps.asBoolean`.
The semantics widgets stay in the tree whatever the attributes are, so
changing one keeps the subtree's state.

The mapping applies on both versions. v0.9 defines neither attribute,
but a v0.9 payload that writes one as a literal gets it. A bound
`hidden` on a v0.9 basic component is not resolved, since the v0.9
ComponentCommon schema has no `hidden`.

Known limits:
- A live region announces changes to its own label. On a TextField the
  region is the control's own node, so changes to its label are
  announced, but not its typed value. On a Slider the region is a parent
  node that takes in the label and value text above the control, and on
  a CheckBox, ChoicePicker or Slider it takes in the check message below
  the control, so changes to these are announced. A Button's child text
  and a CheckBox's label sit in the control's own node below the region,
  so their changes are not announced. The README says this.
- An explicit `accessibility.label` does not replace the label a
  control takes from its own text, which the v1.0 protocol asks for. On
  a Button, CheckBox or Slider, `label`, `description` and `live` land
  on a parent node of the focusable control, and on a TextField or Text
  the explicit label is joined to the widget's own label. This predates
  v1.0 support, and replacing a label needs the controls to place it
  themselves. The README and the codebase blueprint record it.
- An open Modal's content is drawn in the Overlay, outside the Modal's
  subtree, so a `hidden` Modal hides its trigger but not its open
  content.

With this commit the adapter covers the v1.0 sections of the framework
adapter blueprint, apart from the deviations the codebase blueprint
records, so the codebase blueprint now pins the blueprint's current
commit, b1dd650: the v1.0 alignment of b161f9b plus later edits that
only change paths.

⚠️ Agent-generated

* refactor(a2ui_core): check v1.0 arguments with validation results as booleans in one pass

`argumentErrors` tried the schema with each validation result swapped for
its validity and, when that failed, again with none swapped, for a function
whose argument schema takes an object that holds `valid`. No v1.0 function
takes one. On v1.0 the single swapped pass now stands, and its errors are
the errors.

⚠️ Agent-generated

* refactor(a2ui_flutter): strip Markdown markers from a Text with the marker list alone

The fallback for a Text with no `A2uiMarkdown` protected code spans and
backslash escapes behind sentinels and trimmed a heading's closing `#` run.
Code delimiters and escape backslashes are now two more entries in the
marker list, so a marker inside a code span is stripped too, and a closing
`#` run stays in the heading text.

⚠️ Agent-generated

* style(a2ui_flutter): drop an obvious local type in the Text marker loop

⚠️ Agent-generated

* fix(a2ui_core): keep an object argument with a boolean valid as an object

`argumentErrors` on a v1.0 catalog swapped every `ValidationResult`, and
every object with a boolean `valid`, for that boolean before the schema
check, so a function taking an `object` argument that held `valid`, or a
`ValidationResult` itself, failed the check with EXPRESSION_ERROR, "Value
`true` is not of type `object`".

The check now leaves arguments as their JSON and instead widens the
function's resolved argument schema, once per catalog and function, so
that on v1.0 or later each boolean position also accepts an object with a
boolean `valid`. A validation result still passes where a DynamicBoolean
coerces it, and passes as itself where an object is expected.

⚠️ Agent-generated

* fix(a2ui_flutter): leave escapes and code spans literal, drop closing #s

A Text that no A2uiMarkdown renders removed backslash escapes and code
span backticks before the emphasis markers, so the text they protect was
then stripped as markers: `\*not italic\*` showed as "not italic", and
`__init__` in backticks as "init". Escaped characters and code span text
are now hidden from the marker patterns as private-use characters and
restored afterwards, so they show as written.

A heading's closing sequence of #s, as in `## Heading ##`, showed after
its text. A pattern that stays linear in the text now drops it, in a
one-line Text and in the heading lines of a longer one.

⚠️ Agent-generated

* refactor(a2ui_flutter): clamp a Slider move to its range in one step

The snapped value was bounded by `max` alone, in each of the two stepping
branches. A single `clamp(min, max)` on the stepped value says the same
thing once, and holds the lower bound too, although a move cannot cross
it: Flutter's Slider hands `onChanged` a value from `min` to `max`, and
each branch adds a non-negative amount to `min`.

⚠️ Agent-generated
…ws (a2ui-project#3231)

* perf(swift): incremental node resolution and leaf-level SwiftUI redraws

* refactor(swift): isolate NodeBox property mutations to MainActor

* perf(swift): unify incremental node resolution across component and template updates

Signed-off-by: Pete Biencourt <piebie@google.com>

* fix(swift): address PR review feedback on incremental NodeResolver

Signed-off-by: Pete Biencourt <piebie@google.com>

---------

Signed-off-by: Pete Biencourt <piebie@google.com>
…2ui-project#3559)

* fix(python/agent): move Direct JSON protocol schemas into base rules

Direct JSON's generate_catalog_instructions() returned the protocol
schemas (agent-to-renderer and common types) along with the catalog
schema. SkillGenerator calls it once per catalog, so a monolithic Direct
JSON skill repeated the protocol schemas for every catalog.

generate_base_rules() now returns the workflow rules plus the protocol
schemas, and generate_catalog_instructions() returns only catalog
schemas. generate() and prompt_snippet are unchanged.

Fixes a2ui-project#3526

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/agent): read schema prompt catalogs from any iterable

Convert catalogs to a list once before the empty check, so a generator
is neither treated as non-empty when it yields nothing nor exhausted
before linking.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/agent): address review feedback on Direct JSON base rules

- Make the schema block helpers private (_protocol_schema_prompt,
  _catalog_schema_prompt).
- Document in generate_base_rules that common types are pruned against
  all of the generator's catalogs, and the A2uiCatalogError it can raise.
- Note in the CHANGELOG that a Direct JSON catalog skill needs a2ui-core.
- Test the allowed_messages override on generate_base_rules.

Signed-off-by: Nan Yu <nanyu@google.com>

* fix(python/agent): follow changelog conventions; make schema block helpers module-public

- Use the `**BREAKING CHANGE**:` prefix, link a2ui-project#3526, end with the PR link,
  and tighten the entry.
- Rename `_protocol_schema_prompt` / `_catalog_schema_prompt` to
  `protocol_schema_prompt` / `catalog_schema_prompt`: they are imported by
  the sibling prompt_generator module, and direct_json/__init__.py keeps
  them out of the package's public API.

Signed-off-by: Nan Yu <nanyu@google.com>

---------

Signed-off-by: Nan Yu <nanyu@google.com>
josemontespg and others added 27 commits October 27, 2026 23:30
…ct#3669)

* docs: fix accessibility attributes in component reference

* docs: use 'or later' for accessibility key versions

* docs: add v0.9 accessibility tab and fix example component id
…he schema (a2ui-project#3653)

## Summary

Coerce function arguments declared as `DynamicBoolean` that resolve to a `ValidationResult` object to their boolean `valid` property in TypeScript, Python, and Dart core SDKs, driven by the function's argument schema rather than hardcoded function names.

## Changes

- TypeScript (`@a2ui/web_core`):
  - Added `src/catalog/validation_result_coercion.ts` implementing `validityOf`, `isDynamicBooleanSchema`, and `coerceValidationResults`.
  - Updated `createCatalogInvoker` in `src/catalog/types.ts` to coerce arguments before schema parsing for v1.0+ catalogs.
  - Added unit tests in `src/catalog/validation_result_coercion.test.ts`.
- Python (`a2ui-core`):
  - Added `a2ui/core/resolution/validation_result_coercion.py` with `validity_of`, `is_dynamic_boolean_annotation`, `is_dynamic_boolean_schema`, and `coerce_validation_results`.
  - Updated `DataContext._execute_function` in `data_context.py` to coerce arguments after initial validation and prior to execution.
  - Added unit tests in `tests/test_resolution.py`.
- Dart (`a2ui_core`):
  - Added `lib/src/resolution/validation_result_coercion.dart` providing `isDynamicBooleanSchema`, `coerceValidationResults`, and `coerceValidationResultArgs`.
  - Updated `CatalogInvokerExtension.invoke` in `lib/src/resolution/data_context.dart` to apply coercion before schema validation.
  - Added unit tests in `test/validation_result_coercion_test.dart`.
- Conformance:
  - Added four conformance cases in `conformance/core/functions.yaml` exercising `formatNumber` with passing and failing validations, as well as `and` and `not` with nested validator calls.
- Documentation:
  - Added CHANGELOG entries across `@a2ui/web_core`, `a2ui-core`, and `a2ui_core`.
…pport list path traversal (a2ui-project#3677)

## Summary

`ExpressCompiler` previously folded all data path assignments (`$/... = ...`) into a single `updateDataModel` message at `path: "/"`, even on surface updates. It also coerced every intermediate path segment into a dictionary object. As a result, partial data updates overwrote the entire surface data model, and list element paths such as `$/items/0/state = "Ready"` failed.

## Changes

- Update `specification/proposals/express/a2ui_express.md`, `blueprints/modules/a2ui_agent.blueprint.md`, `blueprints/codebases/python/a2ui_agent/codebase.blueprint.md`, and `blueprints/codebases/typescript/agents/a2ui_agent/codebase.blueprint.md` to document surface-creation folding and surface-update per-path `updateDataModel` emission.
- Update `conformance/agent/express/compiler.yaml` and `conformance/agent/express/decompiler.yaml` for per-path `updateDataModel` emission, root `$/ = {...}` replacement, array element updates, in-scope array overrides, and list auto-vivification.
- Update `ExpressCompiler` in `python/a2ui_agent`, `typescript/agents/a2ui_agent`, and `dart/a2ui_agent` to emit one `updateDataModel` message per assigned path in non-creating scopes and preserve reassigned statement order.
- Support root map replacement on `$/ = {...}`, list indexing, and canonical numeric-segment list auto-vivification (`0` through `10000`) when folding `createSurface` data models across all three SDKs.
- Update `ExpressDecompiler` in `python/a2ui_agent` to flatten non-empty map values in `updateDataModel` at non-root paths into sorted leaf assignments.
- Update `ExpressDecompiler` in `dart/a2ui_agent` to sort map keys when flattening data models and allow numeric segments in `_isDataPath`. This lets `00_incremental.json` round-trip in v0.9 and v1.0.
- Update unit tests, round-trip harnesses, and `CHANGELOG.md` entries across Python, TypeScript, and Dart.
a2ui-project#3686)

* fix(swift): drop unused JSONSchemaBuilder dependency from BasicCatalog (a2ui-project#3623)

* docs(swift): reference A2UICommonSchema.allSchemas in AGENTS.md
…olver (a2ui-project#3705)

## Summary

Migrate `@a2ui/lit` surface rendering and `@a2ui/web_core` universal Web Components to resolve and bind components through `NodeResolver` and `ComponentNode`.

## Changes

* Update `A2uiController` in `typescript/core/web_core/src/universal/a2ui-controller.ts` to derive controller properties directly from `host.node.props` when `host.node` is provided. It keeps the `GenericBinder` path when only `host.component` and `host.context` are set.
* Update `A2uiLitElement` in `typescript/core/web_core/src/universal/a2ui-lit-element.ts` to recreate its `A2uiController` if `this.node` changes to a new `ComponentNode` instance after initialization. Widen `renderNode` to accept `ComponentNode | A2uiChildRef`.
* Update `A2uiSurface` in `typescript/renderers/lit/src/surface/a2ui-surface.ts` to create and dispose a per-surface `NodeResolver`, expose `rootNode`, and render the root `ComponentNode` via `renderA2uiNode(this._rootNode)`.
* Add unit tests in `a2ui-controller.test.ts`, `a2ui-lit-element.test.ts`, and `a2ui-surface.test.ts` covering direct `ComponentNode` binding, controller recreation on node replacement, child `ComponentNode` rendering, and surface disposal.
…ui-project#3706)

## Summary

Migrate `@a2ui/angular` component hosts and property binding to the `ComponentNode` tree that `NodeResolver` produces. Angular then renders the same resolved tree as the Lit and React renderers instead of resolving each property through the `DataContext`.

## Changes

* Add `ComponentBinder.bindNode(node)` and `ComponentBinder.unbind(bound)`; deprecate `ComponentBinder.bind(context)`.
* Add `node?: ComponentNode` to `Child` and export `ComponentKey` as the type of `ComponentHostComponent.componentKey`.
* Add `SurfaceNodeResolver`, provided by `SurfaceComponent`, so every host under a surface shares one `NodeResolver` for the lifetime of the surface view. Hosts rendered outside a surface create and dispose their own.
* `ComponentHostComponent` mounts from the resolved node: it reports `unknown-type` and `cyclic` nodes as errors, warns and waits while a node is pending, and re-subscribes to `componentModel.onUpdated` to re-bind when a component's properties change.
* `provideA2Ui` registers an environment initializer that calls `initializeAngularReactivity` before any `MessageProcessor` or `NodeResolver` exists.
* `AngularWcHost` accepts `node` and binds through `bindNode` when one is set. Clearing `node` leaves the current context and bindings in place; `set context` performs the rebind.
* `ButtonComponent` invokes the action closure that node props deliver, and still resolves a raw action spec for callers on the deprecated path.
* Export `CheckValidationResult` (`severity: 'error' | 'warning' | 'info'`) and add `CheckProps.validationResults`, which the node path fills from the resolver's check outputs.
* Explorer Closure externs: add `ZodFirstPartyTypeKindExterns` and `ZodExterns.prototype.{typeName, getter, unknownKeys, left, right, a2uiChildRef}`.
* Specs use real `AngularCatalog` and `SurfaceModel` fixtures, inject `ComponentBinder` before creating resolvers, flush effects with `TestBed.tick()`, and cover: binding from a node without `DataContext` subscriptions, write-back through `WritableBinding`, check outputs and action closures from node props, a child reference that carries its node, a child whose definition arrives after the parent mounted, a shared `SurfaceNodeResolver`, and `AngularWcHost` keeping its context when `node` is cleared.
…ts, and hero section (a2ui-project#3713)

* update header layout, responsive breakpoints, and landing page hero
* enlarge header logo and clean duplicate header info from navigation drawer
* disable raw markdown view action button
…a2ui-project#3306)

# Description

`Catalog.to_json()` raises `TypeError: 'NoneType' object is not iterable` for a catalog that holds a function defined without an argument schema, and so does every path that serializes a catalog through it. Since a2ui-project#3230 that includes the `MessageProcessor` constructor whenever it is given two or more catalogs. The issue lists the paths. Fixes a2ui-project#3305.

## Cause

`FunctionApi` and `FunctionImplementation` take `schema` as optional (default `None`). `validation_schema` recorded such a function as `None`. `to_json` derives the entry of a function defined in code from `validation_schema`, so it passed the `None` to `_function_document`, which calls `dict(schema)`.

## Change

When it assembles `validation_schema`, the catalog records a function without an argument schema as `{}`, as if it had been given `schema={}`. `to_json` then writes it as a call whose `args` may be any object:

```json
"ping": {
  "type": "object",
  "properties": {"@call": {"const": "ping"}, "args": {"type": "object"}},
  "required": ["@call", "args"],
  "returnType": "any"
}
```

This describes how the function already behaves: strict validation and renderer function calls accept any args for it, before and after this change. `FunctionApi.schema` and `resolved_function_schema` still return `None`, so nothing that reads them changes. The `FunctionApi` docstring now says that `None` means the function takes any args.

web_core and Dart core require the schema by type. Rejecting a missing schema in Python core would break existing callers, among them eight functions in `python/a2ui_core/tests`.

## Tests

- `test_catalog_json.py`: in v0.9 and v1.0, a function without a schema gives the same `validation_schema` and `to_json` as one with `schema={}`, and `to_json` writes its `args` as `{"type": "object"}`. A v1.0 processor inlines such a catalog in its renderer capabilities, and the inlined document validates against `catalog_definition.json`.
- `test_peer_catalogs.py`: a `MessageProcessor` builds with such a catalog and the basic catalog, and keeps both as they are.

All four fail without the change.

On this branch:

- `uv run pytest` at the root passes 2857 (46 skipped, 5 xfailed).
- `uv run pyink --check python/a2ui_core` is clean, and `uv run --all-packages mypy .` reports no issues.
- The agent SDK's `schema_to_prompt` and `validate_payload`, which raised for such a catalog, run.
….0 API (a2ui-project#3658)

## Summary

Restores the pre-1.0 call sites in `@a2ui/web_core`, `@a2ui/angular`, and `@a2ui/lit` that the 1.0 API changes broke, without changing any 1.0 signature. Moving a large body of internal v0.9 renderers and tests onto the 1.0 packages needed these adjustments. Each one is a case where 0.9.x code compiled and ran but 1.0 rejects it.

## Changes

`@a2ui/web_core`

- `new Catalog(id, components?, functions?, themeSchemaOrOptions?, instructions?)` without a `protocolVersion` constructs a `'0.9'` catalog (deprecated overload).
- `new SurfaceModel(id, catalog, theme, sendDataModel?, dataModel?)` with a plain object in the third slot is read in the 0.9.x order: the object is the theme and the two arguments after it shift. A `Map` is still `availableCatalogs`; an array still throws.
- The deprecated `SurfaceModel.catalog` getter is typed `Catalog<T, F>` again.
- `new ComponentModel(id, type, properties)` constructs without a `catalog`.
- `MessageProcessor.getClientCapabilities()` takes no arguments (defaulting to `{versions: ['v0.9']}`) and accepts the pre-1.0 `{version}` option. `getRendererCapabilities` accepts `{version}` too but still requires at least one version.
- A `MessageProcessor` with only pre-1.0 catalogs and no `validationConfig` accepts an unversioned `createSurface`, `updateComponents`, `updateDataModel`, or `deleteSurface` envelope as v0.9.
- On a pre-1.0 surface without a `validationConfig`, `MessageProcessor` no longer checks the function calls nested in component properties. Unknown functions fail at evaluation time, as in 0.9.x. `PayloadValidator.validateComponent` takes a `{skipNestedFunctions}` option for this.
- `DataContext.set(path, null)` on a pre-1.0 surface stores `null`. `DataModel.set` takes a `preserveNull` argument.
- (v0_9) `add`, `subtract`, `multiply`, `divide`, `equals`, `not_equals`, `greater_than`, `less_than`, `contains`, `starts_with`, and `ends_with` are back in `BASIC_FUNCTIONS` and `createBasicCatalogFunctions()` (deprecated; also exported as `LEGACY_V09_FUNCTIONS` and `LEGACY_V09_FUNCTION_APIS`). A `null` string argument to the string functions is a missing argument, not the text `"null"`. `basicCatalog` registers only the specification's functions, exported as `V09_SPEC_FUNCTIONS` and `createV09SpecFunctions()`, so `basicCatalog.toJson()` still matches the published document.

`@a2ui/angular`

- `new AngularCatalog(id, components?, functions?, themeSchema?, instructions?)` without a `protocolVersion` constructs a `'0.9'` catalog (deprecated overload).
- `@a2ui/angular/v0_9` re-exports `DefaultMarkdownRenderer`.

`@a2ui/lit`

- `<a2ui-surface>` subscribes to `componentsModel.onCreated` and `onDeleted` with optional chaining, so a partial surface double without event sources no longer throws.
…ation (a2ui-project#3578)

The runtime now awaits pending Promises nested anywhere in the resolved
arguments of a function call before it validates them and runs the
function. Calls whose arguments are all settled stay synchronous. At the
top level of an expression a rejected call is reported as an
EXPRESSION_ERROR and resolves to undefined; nested, the rejection fails
the enclosing call. A reactive subscription to an asynchronous call holds
undefined until the result settles and ignores completions that arrive
after its arguments changed, and RpcHandler awaits pending arguments
before validating a callRendererFunction.
…xpress (a2ui-project#3634)

* fix(ts-agent): compile Event userMessage and nested action slots in Express

`Event("reply", userMessage="...")` silently dropped `userMessage`, and a
function call used as the action of a list item (for example
`Cards([{label: "Docs", action: openUrl("...")}])`) compiled to a bare
`{"@call": ...}` that the processor rejects.

- `Event` now accepts `userMessage=` and `context=` keyword arguments.
  `userMessage` compiles to the v1.0 `event.userMessage` field and throws
  `ExpressValidationError` before v1.0; any other keyword on `Event` is
  rejected rather than dropped.
- `_compileValue` carries the catalog schema at the current position, so an
  `Action`-typed sub-schema inside array items or object properties wraps
  function calls in `functionCall` like a top-level action slot does.
- `ExpressDecompiler` writes `userMessage=` back so events round-trip.
- Document the `Event` signature and nested action slots in the Express
  proposal.

Fixes points 1 and 2 of a2ui-project#3598.

* fix(ts-agent): check the userMessage binding key as an own property

* fix(ts-agent): compile the values of an Event as data, not actions

The event's name, userMessage and context entries are DynamicValues, so
a function call among them is a plain call. The action flag of the slot
the event sits in no longer reaches them, which also matches the Dart
compiler.

* fix(ts-agent): accept a function call as an Event userMessage

Action.event.userMessage is a DynamicString: a string, a data binding or
a function call. The compiler accepted only the first two, so an event
the decompiler wrote with a call did not compile back. The error for any
other value now names what the model wrote (a map literal, a list, the
number 42) instead of printing the AST.

* fix(ts-agent): keep a property named items apart from array items

resolveSubschema took the key 'items' to mean the array items, so an
object property named items was never looked up among the properties.
It is now two methods, resolveItemSchema and resolvePropertySchema, and
the item slot is resolved once per array rather than once per element.

Before v1.0 the decompiler writes an event carrying a userMessage as a
map literal, since that version's compiler rejects userMessage= and the
literal compiles back as written.
…xpress (a2ui-project#3635)

* fix(py-agent): compile Event userMessage and nested action slots in Express

`Event("reply", userMessage="...")` silently dropped `userMessage`, and a
function call used as the action of a list item (for example
`Links([{label: "Docs", action: openUrl("...")}])`) compiled to a bare
`{"@call": ...}` that the processor rejects.

- `Event` now accepts `userMessage=` and `context=` keyword arguments.
  `userMessage` compiles to the v1.0 `event.userMessage` field and raises
  `ExpressValidationError` before v1.0; any other keyword on `Event` is
  rejected rather than dropped.
- `_compile_value` carries the catalog schema at the current position, so an
  `Action`-typed sub-schema inside array items or object properties wraps
  function calls in `functionCall` like a top-level action slot does.
  `CatalogSchemaHelper` gains `resolve_subschema` and the shared
  `is_action_slot` helper to walk the schema.
- `ExpressDecompiler` writes `userMessage=` back so events round-trip.

Fixes points 1 and 2 of a2ui-project#3598 for the Python SDK.

* fix(py-agent): compile the values of an Event as data, not actions

The event's name, userMessage and context entries are DynamicValues, so
a function call among them is a plain call. The action flag of the slot
the event sits in no longer reaches them, which also matches the Dart
compiler.

* fix(py-agent): accept a function call as an Event userMessage

Action.event.userMessage is a DynamicString: a string, a data binding or
a function call. The compiler accepted only the first two, so an event
the decompiler wrote with a call did not compile back. The error for any
other value now names what the model wrote instead of printing the node.

Whether a property is an action slot now comes from its catalog schema
alone, as in TypeScript; the fallback on the property names `action` and
`submitAction` is gone, and the inline test catalogs that relied on it
declare the common `Action` type.

* fix(py-agent): keep a property named items apart from array items

resolve_subschema took the key "items" to mean the array items, so an
object property named items was never looked up among the properties.
It is now two methods, resolve_item_schema and resolve_property_schema,
and the item slot is resolved once per list rather than once per element.

Before v1.0 the decompiler writes an event carrying a userMessage as a
map literal, since that version's compiler rejects userMessage= and the
literal compiles back as written.
… Express (a2ui-project#3636)

* fix(dart-agent): compile Event userMessage and nested action slots in Express

`Event("reply", userMessage="...")` silently dropped `userMessage`, and a
function call used as the action of a list item (for example
`Links([{label: "Docs", action: openUrl("...")}])`) compiled to a bare
`{"@call": ...}` that the processor rejects.

- `Event` now accepts `userMessage=` and `context=` keyword arguments.
  `userMessage` compiles to the v1.0 `event.userMessage` field and throws
  `A2uiValidationError` before v1.0; any other keyword on `Event` is
  rejected rather than dropped.
- `_Context` carries the catalog schema at the current position, so an
  `Action`-typed sub-schema inside array items or object properties wraps
  function calls in `functionCall` like a top-level action slot does.
  `CatalogSchemaHelper.resolveSubschema` descends into `items` and
  properties through `$ref`s and `allOf`/`oneOf`/`anyOf`.
- `ExpressDecompiler` writes `userMessage=` back so events round-trip, and
  writes a `functionCall` nested in a list item as a plain call.
- The Express conformance harness runs the cases that carry `userMessage`
  as written in v1.0 only, since the v0.9 Action has no such field.

Fixes points 1 and 2 of a2ui-project#3598 for the Dart SDK.

* fix(dart-agent): name only the keyword parameters in the Event error

The name is positional, so listing it among the parameters an unknown
keyword could have been contradicted the error itself.

* fix(dart-agent): accept a function call as an Event userMessage

Action.event.userMessage is a DynamicString: a string, a data binding or
a function call. The compiler accepted only the first two, and the
decompiler wrote such an event as a map literal where TypeScript and
Python write Event(..., userMessage=fn(...)). Both now take a call. The
error for any other value names what the model wrote.

Record the removal of Event(name=...) as a breaking change.

* test(dart-agent): run the function-call userMessage cases as written in v1.0 only

* fix(dart-agent): keep a property named items apart from array items

resolveSubschema took the key 'items' to mean the array items, so an
object property named items was never looked up among the properties.
It is now resolveItemSchema and resolvePropertySchema, the item slot is
resolved once per array, and _Context derives isAction from its slot so
the two cannot be set apart.

The decompiler reaches nested schemas through the same helpers, so an
item schema written as a $ref writes its action slots as calls, as the
compiler reads them. Before v1.0 it writes an event carrying a
userMessage as a map literal, since that version's compiler rejects
userMessage= and the literal compiles back as written.
…slots (a2ui-project#3637)

* test(conformance): cover Express Event userMessage and nested action slots

Add compile and decompile cases for the two Express gaps reported in a2ui-project#3598:

- `Event(name, context?, userMessage=?, context=?)`: `userMessage` compiles to
  the v1.0 `event.userMessage` field (string or `$path`), `context=` is
  accepted by keyword, an unknown keyword or a duplicate context is a
  validation error, and `userMessage` before v1.0 is a validation error.
- A function call in an action slot nested inside a list item compiles to a
  `functionCall` action, also when the item is reached through a variable.
- The decompiler writes `userMessage=` back and nested action slots round-trip.

New catalogs `list_item_action_catalog_v1_0.json` and
`list_item_action_catalog_v09.json` declare a `Links` component whose
`items[].action` is the common `Action` type.

* test(conformance): a function call in an Event context stays a plain call

* test(conformance): a function call is a valid Event userMessage

Action.event.userMessage is a DynamicString, so a function call compiles
and round-trips, and a map literal is a validation error.
…eprints (a2ui-project#3707)

## Summary

Extend the Spec-Driven Development (SDD) blueprint schema to track per-feature commit SHAs (`implemented_features: {feature_name: "<40-hex-sha>"}`). Update the blueprint scripts and pin all 12 codebase blueprints across `a2ui_core`, `a2ui_agent`, and `a2ui_framework_adapter`.

## Changes

* Update `blueprints/validate_blueprints.py` to parse `null`/`~` YAML scalars and check that `implemented_features` is a mapping `{feature_name: <40-hex-sha>}` (or `{}`), checking commit reachability via `git cat-file -e`.
* Update `blueprints/skills/a2ui-blueprint-compliance/scripts/check_compliance.py` and `test_check_compliance.py` to detect per-feature drift when a feature blueprint changes independently of its parent module blueprint.
* Configure `fetch-depth: 0` in `.github/workflows/validate_blueprints.yml` so `git cat-file -e` can inspect historical blueprint commits in CI.
* Update `blueprints/README.md`, `docs/proposals/spec_driven_development.md`, and the 3 SDD skill guides under `blueprints/skills/` to document the `implemented_features` commit mapping format.
* Create `blueprints/codebases/dart/a2ui_agent/codebase.blueprint.md` and pin `module_commit` and `implemented_features` across all 12 codebase blueprints (`dart/a2ui_core`, `typescript/core/web_core`, `python/a2ui_core`, `swift/core`, `python/a2ui_agent`, `typescript/agents/a2ui_agent`, `dart/a2ui_agent`, `dart/a2ui_flutter`, `typescript/renderers/react`, `typescript/renderers/lit`, `typescript/renderers/angular`, and `swift/swiftui`).
…2ui-project#3731)

* chore(python)!: move the file resolver into the file upload sample

FileResolver (a2ui.extensions.file_resolve in a2ui-agent-sdk) and
GenAIFileResolver (a2ui-adk) resolve file pointers, which the A2UI protocol
doesn't define; only the community file_upload_summarizer sample uses
them. They're Python-only, they're the only reason a2ui-agent-sdk depends
on httpx, and as a general file security utility they imply a guarantee
the SDK shouldn't make.

Move both into samples/community/agent/adk/file_upload_summarizer/
file_resolver.py with their tests, and point the sample at the local
module. Remove a2ui.extensions and the httpx dependency from
a2ui-agent-sdk, GenAIFileResolver from a2ui-adk, and the three
test_file_resolver_* conformance cases with their handler.

Signed-off-by: Nan Yu <nanyu@google.com>

* docs(python): link the file resolver removal entries to a2ui-project#3731

Signed-off-by: Nan Yu <nanyu@google.com>

---------

Signed-off-by: Nan Yu <nanyu@google.com>
…2ui-project#3726)

## Summary

Unifies the A2UI v1.0 wire version on bare `'1.0'` (without a `'v'` prefix) across message envelopes, capability payloads, and catalog definitions, while preserving semver tolerance in catalog version parsing (`Catalog.fromJson` / `Catalog.from_json`).

Resolves a2ui-project#3097

## Changes

- Updated `"version"` in `specification/v1_0/json/{agent_to_renderer,renderer_to_agent,renderer_data_model}.json` from `{"const": "v1.0"}` to `{"const": "1.0"}` and updated the top-level version key in `specification/v1_0/json/{renderer_capabilities,agent_capabilities}.json` from `"v1.0"` to `"1.0"`.
- Updated v1.0 examples and prose in `specification/v1_0/docs/`, `specification/v1_0/test/`, `catalogs/basic/v1/`, `catalogs/mcp/v1/`, and `conformance/` to use `"1.0"` in message envelopes and capability maps.
- Set the wire value of `ProtocolVersion.V1_0` (Python), `ProtocolVersion` (TypeScript), `A2uiProtocolVersion.v1_0` (Dart), and `A2UIProtocolVersion.v10` (Swift) to `'1.0'`.
- Preserved semver tolerance in catalog version parsing (`Catalog.fromJson`, `Catalog.from_json`, `A2uiProtocolVersion.tryParseSemVer`, `A2UIProtocolVersion(lenientRawValue:)`, and `VersionAdapterFactory.getAdapter`).
- Typed `Catalog.protocol_version` and `Catalog.protocolVersion` with the protocol-version enum or union type in each SDK.
- Regenerated v1.0 Pydantic models, TypeScript Zod schemas, Dart schema constants, and the Python v1.0 basic catalog builder.

## Impact & Risks

- Breaking change for v1.0 callers that pass or compare `'v1.0'` on v1.0 wire message envelopes or capability payloads. All v1.0 message envelopes, capability keys (`getRendererCapabilities()`), and serialized catalog definitions use `'1.0'`.
@wiz-coveo-enterprise-connector

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations 3 Medium
SAST Finding SAST Findings -
Software Management Finding Software Management Findings -
Total 3 Medium

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio.

(e: unknown) =>
e instanceof A2uiCatalogError &&
e.message.includes("has no component 'Nope'") &&
e.message.includes('https://example.com/chat'),
@louis-bompart
louis-bompart added this pull request to stack #55 October 29, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.