appplayer_core 0.1.22 copy "appplayer_core: ^0.1.22" to clipboard
appplayer_core: ^0.1.22 copied to clipboard

AppPlayer Core — shared Flutter library for MCP server connection, bundle handling, and UI runtime orchestration.

0.1.22 - 2026-08-07 — a drag can be dispatched, a bundled asset can be read, and a reattach reports what it achieved #

ui.drag on the debug host. Tap, type, scroll and screenshot were there; drag was not, so a widget whose behaviour is a drag — a tree that reorders, a kanban card that moves — could not be verified on a running app at all. The gesture is dispatched as a press, a held interval, stepped moves and a release: one jump is not a drag to a recogniser, it is one enormous delta with no gesture in between.

bundleRootPath reaches the media path. A bundled sound arrives as a file: reference after the core rewrites bundle://, and nothing downstream could read those bytes — playback worked (the player opens the path itself) while anything needing the bytes, such as a waveform, got nothing.

resubscribed after reconnect logged count: <number of URIs attempted>. A reattach where every single subscribe was refused printed the same line as one where all of them landed, with the failures on their own earlier lines — so the summary read as success. It was read as success: someone debugging a board that had gone silent took it for a healthy reattach and looked elsewhere.

  • The line now carries resubscribed and failed separately.
  • reattach returns a ReattachResult instead of nothing, so the two numbers are assertable rather than only printable. isTotalFailure is the case that matters — the connection is up and every stream on it is dead.
  • appplayer_core warns once per reattach that re-subscribed nothing, at a level a log reader notices, rather than leaving it to be inferred.

Reported by a consumer board (ESP32 air-quality node) whose owner misread the line exactly as described.

0.1.21 - 2026-08-06 — an open app keeps trying to reconnect #

ConnectionHealthMonitor gave a server up for good once its attempt count ran out. The count only clears when a health check observes connected, and after the monitor stops retrying that can never happen — so a foreground app with the screen on sat there not dialling, and only leaving and re-entering it (or a background round trip, or a wake) brought the connection back. Found on real hardware: switch a hotspot off and back on a minute later and the board never returns.

The tier that hurt first was the one tuned to recover fastest. checkInterval was doing two jobs — the keepalive/detection sweep AND the retry pace — so Pro dropping it to 2s to catch BLE hard-drops also burned its 5 attempts in about fifteen seconds, against ~105s for the default tier.

  • The two cadences are now separate. checkInterval paces detection only; retries pace themselves with an exponential backoff (reconnectDelay doubling up to the new maxReconnectDelay, default 30s).
  • maxReconnectAttempts defaults to 0 = unlimited: while monitoring runs the monitor does not give up. What keeps a dead server from being dialled every couple of seconds is the backoff ceiling, not an attempt count. Hosts that want the old behaviour can still pass a positive cap.
  • One reconnect in flight per server: a fast sweep no longer stacks a dial on top of the one already waiting out its backoff.
  • startMonitoring() clears grown backoff (a foreground return is new information), stopMonitoring() stands down a reconnect already waiting, and a wake-driven sweepStale() resets the backoff for what it sweeps.
  • An app open on a server does not back off at all. The backoff exists to stop dialling a server nobody is looking at; a full-screen app is the explicit statement that this connection is supposed to exist, and the user is watching it fail. Engaged servers retry at the first interval (Pro: every ~1s) for as long as the app is open, and an explicit host cap does not strand them either. Hosts declare this through the new isEngaged seam; appplayer_core wires it to "a runtime is open for AppHandle.server(id)". A dashboard tile is deliberately NOT engaged — treating every tiled server that way would dial the whole home screen every second.
  • That fixed interval is fixed against the clock, not against the sweep. An engaged retry re-arms from inside its own chain; leaving the next attempt to the health sweep made the real spacing reconnectDelay + up to one checkInterval, so Pro's 1s was landing as 1-3s. The loop exits on exactly three conditions — the app closed, the connection entry is gone, or monitoring stopped — so nothing dials a server nobody is watching, or a serverId that no longer exists.
  • The "max attempts reached" warning is logged on the transition instead of on every tick — at a 2s cadence it was filling the log.

Behaviour change, intended: every tier now retries indefinitely while foreground. Nothing to change at call sites — maxReconnectDelay is additive and the default flip is the fix.

Floors: mcp_client ^2.1.0 → ^2.2.0 (internal dependency at its latest published version). Everything else was already there.

SRS NFR-REL-002/003 + new NFR-REL-005/006, FR-HEALTH-003/004/006 + new FR-HEALTH-007 · DDD/TEST connection-health-monitor · regressions TC-HEALTH-010~019 and IT-001b (11 mutations killed, including the wiring).

New: hintReachable([serverId]) on the core service — the door for "this may be reachable now" signals (network returned, device sighted on the discovery axis, user pressed retry). A connection waiting out its interval dials at once instead of at the end of a number picked without knowing anything; omitting the id serves every failed connection, which is the only path remote/cloud servers have (nothing ever "sights" them). Signals accelerate; they do not replace the timer, because no single signal source covers every transport.

bindOnlineChanges(Stream<bool>) turns a host's network-availability source into that signal, firing on the offline→online edge only — platforms repeat "connected" for every interface change, and the first observation says where we are rather than that anything changed (treating it as a regain dials on every launch). It lives here rather than in a host recipe because it needs no radio and no plugin, every tier already depends on this package, and it is the only reachability signal a remote / cloud server has.

Paired with it, stalledServers — the servers an open app is waiting on (failed connection ∩ app open). A host that wants to hear a device come back rather than dial for it should observe exactly these and stop when the set empties; observing everything ever registered is the always-on scan the discovery axis was deliberately scoped away from.

The retry pace applies between dials, never on top of one: the chain awaits its attempt and holds the server's slot across it, so a 1s pace on a dial that takes five seconds is one dial every six, not five overlapping ones. Bounding the dial itself stays with the host connector (Pro bounds board dials at 20s); core connect() has no deadline of its own.

0.1.20 - 2026-08-05 — floors move to the spec 1.4.1 cut #

No source change in this package. The floors move because a caret bound on a 0.x minor cannot reach the next one, so without this bump a consumer of appplayer_core keeps resolving the previous runtime and never sees the cut:

  • flutter_mcp_ui_core — the registry narrows in four places (retired legacy enum spellings on linear.distribution and qrCode.errorCorrection, the otpInput.autoSubmit property, and a required value on option objects), which is what makes that release a minor rather than a patch.
  • flutter_mcp_ui_runtime — vector assets (SVG) draw in every AssetRef slot including icon, union-typed slots read every branch they declare (Dimension objects, action lists, bindings in enum and EdgeInsets slots), ink overlays paint above what the document paints, and ten declared properties gained implementations. Brings flutter_svg transitively.

Consumers that pin appplayer_core need only this bump; the surface they compile against is unchanged.

0.1.19 - 2026-08-03 — mcp_ui 1.4 cut #

Floors flutter_mcp_ui_core ^0.4.3 → ^0.5.0 and flutter_mcp_ui_runtime ^0.5.3 → ^0.6.0.

Both are minor because the schema narrows: AssetRef slots reject a bare string carrying no scheme, the icon slots take the new IconRef, and thirteen string properties that documented their values in prose now declare enum. A caret bound on ^0.5.x cannot reach 0.6.0, so this floor is what lets any AppPlayer tier see the new runtime at all.

What arrives with it: one asset resolution path for every AssetRef slot (image / avatar / icon / box.decoration converge, and bundle:// and client:// are resolvable for the first time), navigation.openUrl, and 23 new widgets. No API in this package changed — the surface is identical and the bump is the dependency cut.

0.1.18 - 2026-08-02 - Install into host-provided storage; streams survive a background resume #

Added #

  • AppPlayerCoreService.installBundleFromBytes — install from .mcpb bytes already in hand. A host that fetched the archive itself has bytes and never a path; installBundleFromFile is the same call with a read in front of it.
  • AppPlayerCoreService bundleInstallStore: — install into and read installed bundles from host-provided storage instead of a directory. When given, bundleInstallRoot is not used as one.
  • BundleInstallerAdapter.onStore and BundleLoaderAdapter(installStore:) — the same seam one layer down.

Changed #

  • BundleApplicationAdapter requires the bundle to carry readable files, not a filesystem directory. A bundle installed into host storage now adapts; previously it was refused outright with BundleAdaptException(unsupportedEntryPoint) before anything was read.
  • JS tool entry scripts are read through the bundle's own file surface rather than File(<directory>/<entry>), so bundles that carry JS tools work on hosts with no filesystem.

Desktop and mobile behaviour is unchanged — omit bundleInstallStore and the filesystem path is taken exactly as before.

Fixed #

  • An app left open across a background round trip stopped streaming. A subscription and a notification handler both live on the CONNECTION, and mobile tears the connection down in the background and rebuilds it with a NEW client on return. Tool calls kept working (those resolve the live client per call), so the screen looked healthy while only the stream was dead — and pressing Subscribe again did nothing, because the runtime binding had never been lost and there was nothing left for the runtime to do. Re-attached on the new client:
    • ConnectionManager.onClientAttached — hook fired whenever a server's client is replaced (first connect included).
    • ResourceSubscriber.reattach — re-issues the wire resources/subscribe for every URI recorded under an ownerKey, plus the initial read so the first value after a resume is current rather than the frozen one. Bindings are NOT re-registered; they never went away.
    • AppPlayerCoreService wires the two, covering the full-screen app AND the dashboard's per-device summary runtime (a composed tile watching the same device is otherwise the one surface still frozen).

0.1.17 - 2026-08-02 - Boot on the web #

Fixed #

  • initialize threw Unsupported operation: Platform._operatingSystem on the web and took the whole host down before the first frame — a blank page. One line built the LifecycleCoordinator with platformSuspends: Platform.isAndroid || Platform.isIOS, and Platform is dart:io. It is now !kIsWeb && (...).

    false is the truthful value on the web rather than a way around the throw: a tab has no process to suspend, and no native background port is injected for continuity to pause against. The value was also not injectable — the coordinator takes the flag but initialize hardcoded it, so a host could not work around this from outside. The neighbouring ports (backgroundPort, permissionPort, notificationPort) all have seams and web hosts had already passed them by injecting no-ops; this was the next line.

    Reported against a release web build with source maps, so the frame was the line and not a guess.

Verified #

Against the real app, both directions. appplayer_cloud in Chrome resolving this package from a local path booted with no exception; the same app with the published 0.1.16 rendered its error screen naming app_player_core_service.dart 621:34. Same app, same browser, one line different.

Not in this release — the browser regression #

A @TestOn('browser') boot regression is written (test/integration/web_boot_test.dart) and is skipped, because it hangs: headless Chrome loads and initialize never completes there, while the same initialize completes in the real app. The difference is what a host injects, so the harness — not the fix — is what is unfinished. It ships skipped with that reason attached rather than deleted, because the gap it names is real: every other test that calls initialize runs on the VM, which is why a dart:io call sat on the boot path unnoticed.

0.1.16 - 2026-07-29 - JavaScript tool runtime per platform #

Changed #

  • The per-bundle JavaScript runtime now resolves per platform. JsToolIsolate became a conditional export: the existing embedded-engine implementation on platforms with dart:io, and a Web Worker implementation elsewhere. The native implementation is the same code, relocated to js_tool_isolate_io.dart — no behavior change off the web.
  • The JS-side host bridge contract moved to src/js/js_bridge_protocol.dart and is shared by both branches: host.<atom>.<verb>() returns a Promise resolved through __hostResolve / __hostReject exactly as before. Only the transport differs — an isolate port natively, postMessage on the web — so a bundle's JavaScript behaves identically on both.

Added #

  • Web branch of the JS tool runtime. Bundle JavaScript runs in a Web Worker, never on the page: a Worker has its own global scope and no DOM, so the bundle cannot reach the document, application state, storage or cookies. Main-thread evaluation is not offered as an alternative path. Everything crossing the boundary is a JSON string, so a bundle cannot hand the host a live JavaScript object; a dispatcher error becomes a rejected Promise on the JS side rather than a runtime failure.
  • web dependency, used only by that branch.

Fixed #

  • The shared bootstrap parenthesises the transport call. A bare function (p) {...} in statement position parses as a function declaration and is rejected for having no name, so the host bridge failed to install. This was introduced by the refactor and reached the native branch too, where no test could see it — the embedded engine cannot start inside flutter test, so nothing had ever executed that bootstrap. The browser suite now evaluates the native variant of the string in a real engine, which closes that gap.

Notes #

  • Hosting requirement for the web branch: the Worker is created from a blob, so the page policy must allow worker-src blob:, and the Worker needs 'unsafe-eval' — executing caller-supplied JavaScript is the feature. This fails far from its cause, so it is also recorded in the source.
  • This lifts dart:ffi out of the web dependency graph, which is what stopped flutter build web for any application depending on this package.

0.1.15 - 2026-07-28 - Multi-origin composition (MCP UI DSL v1.4 Composition Profile) #

Added — entry context on the open paths (platform spec 19 §4.3, MCP UI DSL §8.9) #

  • openAppFromServer(..., entry:, identity:) and openAppFromBundle(..., entry:, identity:) — carry how an app was reached and who is looking at it. An entry naming a route opens the app on that page instead of its own initialRoute, which is what lets a scanned code, a deep link, or an app-to-app open land where it asked. Both parameters are optional and absent for a launcher open, so every existing call is unchanged.

  • Re-exports the entry value types (EntryContext, EntryIssuer, EntryNotice, IdentityContext, IdentityState, IdentitySubjectKind, EntrySession, EntryStateKeys) so a host consuming only appplayer_core can build them.

  • Deferred entry (DeferredEntryResolver, DeferredEntrySource, FirstLaunchStore) — an entry that sent someone to an app store resumes on first launch (§3.5). The policy is pure and the platform pieces are injected, so the rules hold on a platform that can carry a code and on one that cannot.

    • The absence of a source is meaningful. A host with no mechanism supplies none, and that turns the obligation into an offer of manual recovery rather than silence. A source that answers "this install did not begin at an entry" produces nothing instead — prompting there would be a question about something that never happened.
    • A mechanism that threw is treated as a mechanism we do not have.
    • The launch is marked seen either way: a recovery offered twice is a nag, and a code recovered twice would reopen the same entry on a launch nobody connected to it.
  • EntryOpener — turns a resolved target into an open session. Tiers differ in chrome, not in what a target means: "a server target is an endpoint you register and open" is the same sentence everywhere, so it stopped being rewritten per tier. A server learned from an entry is registered under an id derived from its endpoint, so scanning the same medium twice reuses one row instead of accumulating one per scan. localServer needs a discoverer the tier wires (discovery is a host capability); without one the entry fails visibly rather than dialling something else. A listing deliberately has no path to a screen here — acquisition is the marketplace's act, and a path from a listing id to a render would blur install and run.

  • EntryLink.parse — reads the opaque code out of a claimed https link. Host matching is exact: a suffix match would accept evil-entry.example.test for entry.example.test, and a build resolving codes from a host it does not claim is resolving someone else's registry. Everything after the path prefix is the code, so an issuer may partition its code space however it likes and this side stays ignorant of the shape. A link this build does not claim is rejected with a reason, not swallowed — the host falls through to whatever it normally does with a URL.

  • Entry resolution pipeline (EntryResolverPort, EntryPipeline, EntryTarget and friends) — the host side of platform spec 19. Every acquisition path (an intercepted link, a scanner, a deferred entry recovered after an install) produces the same code and takes this one path, so the rules hold whichever door the code came through. The resolver itself is a port: the platform never assumes where the medium registry lives.

    • canIdentify — a build with no sign-in refuses a required entry instead of rendering it as a guest. Answering a demand for identity by ignoring it produces a screen that looks like it worked, which is the failure mode this whole layer exists to prevent. Distinct from an unsupported target: the destination is fine, the viewer cannot be established.
    • Enforced here because each of these failures is invisible from the outside: a stale answer is never replayed (custody may have changed since it was minted), a guest entry never resolves to an account-gated target, and an entry this host cannot open is reported rather than substituted — a silently swapped target looks exactly like a working one.
    • Wire parsing defaults to the safe reading: an unknown status is not ok, and an unparsed identityPolicy demands identity rather than assuming guest. Guessing open would render a guest surface for an entry we failed to understand; guessing required merely asks someone to sign in.
    • EntryTarget.toEntryContext() is the only thing that crosses into the document — route, params, issuer, grant scope. The grant token and the medium's owner/holder never do.
  • launchRoute: alongside entry: on both open paths. An in-app open (DSL §4.3.1 navigation.openApp) names a page without being an arrival, so it sets the route and leaves the document's entry.* tree absent (§8.9.1).

  • AppSession.launchRouteMissing — true when the entry named a page this app no longer declares. Spec 19 §9.6 puts the disclosure on the host, and a host cannot render a log line; without a surface to read, "fell back" and "worked" are the same outcome from outside.

A route the app no longer declares is not honoured silently: the runtime falls back to the app's own initial route and the miss is logged (entry.route.missing) for the host to disclose. A stale binding that quietly renders the home page is indistinguishable from a working one, and bindings outlive app versions.

Re-opening a handle whose runtime is still alive adopts the newer entry. Without that the same medium scanned twice would render the first scan's context.

Added #

  • openSavedDeviceAsOrigin(id) — opens a saved device as a composition origin through the same ConnectionManager the launcher uses, and adoptConnectionAsOrigin({id, client}) under it. Origins used to be opened on a private stack while the launcher used its own, both keyed by the same device id, so neither could see the other's link: opening a device from a composed screen and then from its own app dialled twice, and the board — single-peer — refused the second with Transport disconnected. One connection per device now, shared by both.

Fixed #

  • Opening a device's own screen silenced every composed tile watching the same device — permanently, and with nothing to show for it: the subscription stayed live, the socket stayed up, and closing the screen did not bring it back. Client.onNotification keeps one handler per method, so once one connection per device is shared the notification router took the slot from the kernel connection the tiles listen on. Both now register through SharedClientNotifications, which takes the slot once and fans out. Subscriptions are reference-counted the same way (SharedResourceSubscriptions), so one consumer releasing no longer stops the other's stream.
  • A composed tile re-read the device's UI document on every mount. The connection was never dropped — the socket stayed up across open and close — but entering the screen again cost ui://app + ui://page/main over the wire, so the tile spun and looked like it was reconnecting while the standalone screen came back instantly. Resolved definitions are now cached per origin and dropped when the origin is (re)opened, which is the only moment the document can have changed: a device that rebooted with new UI necessarily got a new connection first. Measured after the fix: re-entry 1.1s against 1.2s for the first open, both tiles fully rendered. (Fixed in the composition_host recipe and re-vendored.)
  • Composition treated "the host lists this connection id" as "the origin is open". A connection whose link had gone was still listed, so a composed tile decided it had nothing to open and never asked again while every call it made landed on a dead link — the device worked when opened on its own and never appeared in a multi-device screen. Openness is now decided by liveness. (Fixed in the composition_host recipe and re-vendored.)

Added #

  • registerDefinitionResolver(resolve) — the host resolver behind a view / route DefinitionSource that names an origin. Registering it is how this host claims the Composition Profile; without it view fails closed and renders its fallback rather than resolving a foreign $ref against the app's own server (spec §18.7.3), which would put one device's UI under another's identity.
  • useKernelDefinitionResolver({readOwn}) — the canonical wiring, reading through the kernel's outbound mcp.* surface. Platform spec 06-tool-registry.md already declares "the app/bundle drives mcp.* directly and fetches a resource (e.g. a dashboard UI)" as the default path, and those tools are already on this host's in-process dispatcher — so composition needs no new transport, no new connection registry, and no manifest field, only a reader. Parses the shape a board actually serves (contents[0].text carrying escaped JSON) and accepts an already-decoded map.
  • The registered resolver is applied to every app/bundle runtime at creation, alongside the existing stream sources.
  • openOrigin hook — a document names an origin; the host opens it on first use. Registering a device does not hold a connection open, and holding one would be wrong: several boards serve a single peer at a time, so a permanent connection each has the last one to connect reset the others.
  • Origin-scoped acting and watching — the resolver wiring now also installs a tool caller and a resource watcher for a named origin. Rendering and acting are separate halves and only the first existed: a composed screen drew each device's UI while every control in it reached a session with no client for that device, and a live reading rendered its label and never a value. Tool calls go out as mcp.call_tool on the named connection; a watch reads the current value once (a subscription reports only changes) and then follows notifications/resources/updated.

Security #

  • Fails closed by design: an empty origin with no readOwn, an unrecognised origin key, and an empty connection id all throw rather than falling back to the app's own server (spec §7.10.1 rule 6).

Changed #

  • flutter_mcp_ui_runtime floor raised ^0.5.2 → ^0.5.3 (the view widget + registerDefinitionResolver seam).
  • brain_kernel floor raised ^0.1.8 → ^0.2.0 (resource subscription on a kernel connection).

0.1.14 - 2026-07-22 - Durable reconnect (token re-grant seam) #

Additive (0.x → patch). No public API removed.

  • Durable reconnect — ServerReGrant seam. A marketplace server's credential is a short-lived per-user connectionToken baked into ServerConfig.transportConfig. When a connect attempt fails and the server carries a bearer token, ConnectionManager now calls an optional host-supplied re-grant hook, refreshes the token, and retries the connect once (the retry runs without re-grant so a persistently bad server can't loop). This closes the gap where opening a saved server app with an expired token 401'd until a manual reinstall — openAppFromServer, reconnect() and ConnectionHealthMonitor all funnel through connect(), so all three are covered.
    • New: typedef ServerReGrant = Future<ServerConfig?> Function(ServerConfig stale) (barrel-exported), ConnectionManager.tokenReGrant (mutable, optional), AppPlayerCoreService.serverReGrant setter (host wires it after the marketplace session exists).
    • Fully backward-compatible: when no hook is wired (or the server carries no bearer token) connect/reconnect behave byte-for-byte as before — static-token, hand-typed and discovered (tcp/ble/serial) servers are untouched.
  • Doc hygiene: removed dangling doc/spec references (docs/, specs/, spec §N) from source comments so nothing points outside the published package. No code change.

0.1.13 - 2026-07-18 - Flutter-plugin promotion · metadata-only install · single-route apps · debug MCP · connection continuity #

Additive across the tracks landed since 0.1.12. ^0.1.12 consumers pick these up on floor-bump; no public API removed.

Added #

  • Flutter-plugin promotion (Platform Integration Foundation, FR-PLATFORM)appplayer_core declares a flutter: plugin: with native Android/iOS adapters (background execution, OS permission, notification); desktop/web degrade to the Dart ports' NoOp. onLifecyclePhase(AppLifecyclePhase) drives the foundation.
  • Metadata-only installfetchServerMetadata(serverId) / fetchBundleMetadata(BundleRef) read a card's name/icon WITHOUT rendering, so install ≠ run (the launcher tile is populated, the UI loads on first open).
  • Single-route application wrappingAppLoader.wrapAsApplication(...) promotes a bare served page into a single-route application (app name = page title), so a server app renders with the standard chrome (AppBar/Close) and no separate metadata serving.
  • Debug MCP host — opt-in (enableDebugMcp, settings-gated, non-web) MCP server on 127.0.0.1:<port>/mcp exposing ui.screenshot / ui.tree / ui.tap / ui.type for test automation; debugCaptureWrap(child) gives the capture/tap primitives a stable render boundary.
  • Connection continuity — transport-drop handling (_handleTransportDrop with DisconnectReason) + keepAliveSweep(...) for reconnect/resume.

Dependencies #

  • Floors raised to current latest at cut time: mcp_client ^2.1.0, mcp_bundle ^0.4.8, flutter_mcp_ui_core ^0.4.1, flutter_mcp_ui_runtime ^0.5.1, brain_kernel ^0.1.8.

0.1.12 - 2026-07-14 - Theme state rebuilt on every app entry #

Fixed #

  • AppSession.buildWidget / buildDashboardWidget rebuild the theme state on every entry: the runtime's ThemeManager is a process-wide singleton, so the previous app's palette/mode leaks into the next one and any runtime widget's dispose clears the brightness pin. Entry now applies the app's own declared theme — or a SYSTEM BASELINE with real light AND dark token sets — whenever ownership changes hands, then re-pins the current host brightness. The bare defaultLight() default has no dark tokens, which is why undeclared apps rendered "weird dark" on first entry until another app left a full palette behind in the singleton.
  • The rebaseline skip-gate verifies the singleton's FINGERPRINT (not just an ownership tag): runtime teardown resets the ThemeManager behind the session's back (MCPUIRuntime.destroyreset()), so a stale tag made same-app re-entry skip over the bare default — re-open of the same app rendered weird while a detour through another app healed it.

0.1.11 - 2026-07-13 - Marketplace connect credential + bundle cache invalidation #

Fixed #

  • streamableHttp transport carries accessToken as Authorization: Bearer (+ headers passthrough, explicit header wins); it was dropped entirely, so token-gated servers rejected the handshake (401 → "Transport disconnected").
  • install/uninstall invalidate the bundle's session runtime + metadata caches (FR-INSTALL-009) — a reinstall/update no longer keeps rendering the old definition until app restart.

Changed #

  • Floor mcp_client ^2.0.1 (spec-optional description parse fix).

0.1.10 - 2026-07-12 - Extension-transport seam conformance (additive) #

Changed #

  • AppPlayerCoreService.connectExtensionTransport now delegates to the brain_kernel core connectExtension(clientHost, …) helper off the abstract KernelClientHost, dropping the redundant concrete McpClientKernelHost field/ref and the inline probe-and-cast (the is-no-promotion footgun is now sealed inside the kernel helper). Behaviour unchanged.
  • Floors brain_kernel ^0.1.2 → ^0.1.7 (the ExtensionTransportConnect capability interface + connectExtension helper). No API change to appplayer_core's own surface.

0.1.9 - 2026-06-22 - Capability tools registration seam (additive) #

Added #

  • AppPlayerCoreService.registerCapabilityTools(tools) — additive public seam to register host-supplied in-process capability tools (e.g. a desktop io.* process/device tool-pack) after boot. The core depends on no capability package, so platform-specific adapters (dart:io process execution, etc.) stay in the host layer; the tools share the same in-process dispatcher as the standard bk.* / mcp.* surface. Safe to call more than once.

0.1.8 - 2026-06-10 - Extension transport connection seam (additive) #

Added #

  • AppPlayerCoreService.connectExtensionTransport({id, transport}) — new public method that lets a host app connect to an external MCP server over a host-supplied extension transport (serial / usb / ble / tcp / ws) without adding the transport's FFI / platform dependencies to appplayer_core. The transport is built by the calling app (e.g. using classes exported from mcp_bridge) and injected here; the core routes it through McpClientKernelHost.connectWith (brain_kernel 0.1.2). Returns a KernelClientConnection whose callTool / readResource / listTools reach the remote server. Throws StateError when the kernel is not booted.

Changed (dependency floor) #

  • brain_kernel ^0.1.1^0.1.2connectExtensionTransport delegates to McpClientKernelHost.connectWith and re-exports clientTools, both new in brain_kernel 0.1.2. The floor guarantees these symbols are present.

Backward compatibility #

  • Fully additive. No existing AppPlayerCoreService method or constructor changed. Apps that do not use extension transports see no behavior change.

Tests #

  • 302/302 (301 + 1 skipped) PASS. analyze 0 issues.

0.1.7 - 2026-06-01 - brain_kernel KernelApp + bundle session bridge integration + MCP serving #

  • BrainBridge removed (phase D · 2026-05-24) — 451 lines + 6 facade wrappers + 2 tests dropped. AppPlayerCoreService now calls KernelApp.boot(...) directly, registers standardTools(app), and delegates setActiveBundle / scopeIdFor. Zero external-shell cascade.
  • bundle session bridge wiring (2026-05-25) — BundleSessionBridge lifecycle wired at 5 points of AppPlayerCoreService (boot / activate / onClose / closeApp / dispose). _sessions is a per-bundleId map. McpAtom + AgentAtom gain optional bridge / session arguments and wrap dispatch in bridge.runScoped(session, ...).
  • MCP serving (MCP Serving 1.0) — _activateBundleSections exposes the active bundle at the well-known bundle://manifest.json resource (shared by the local-bundle and served-bundle paths). openAppFromServer reconstructs a served bundle: it detects the document, parses it with McpBundleLoader.fromJson, and runs the same kernel activation a local bundle uses (knowledge / settings / behavior come live); tool execution stays remote and the UI loads via ui://app. New servedResources / readServedResource accessors. ApplicationLoader.load gains an optional resources parameter so the server is listed only once.
  • import unification — the bridge ships inside the kernel (brain_kernel/lib/src/system/bridge/), so hosts import only package:brain_kernel/brain_kernel.dart.
  • analyze cleanup — removed 5 unnecessary_import hints across test/src/dashboard/dashboard_bundle_test.dart + test/src/session/app_session_impl_test.dart (info → 0).

Changed (dependency floor) #

  • brain_kernel ^0.1.0^0.1.1 — the served-bundle path relies on brain_kernel 0.1.1 (bundle behavior activation + the MCP serving surface). This transitively raises mcp_bundle to 0.4.1; appplayer_core references no 0.4.1-only symbol directly, so its own mcp_bundle floor stays ^0.4.0.

Tests #

  • 301 + 1 skipped PASS · analyze 0 issues.

0.1.6 - 2026-05-04 - flutter_mcp_ui 0.4.0 / 0.5.0 + mcp_bundle 0.3.1 alignment #

  • Bumps flutter_mcp_ui_core to ^0.4.0 and flutter_mcp_ui_runtime to ^0.5.0 for the spec 1.3.3 alignment cycle.
  • Bumps mcp_bundle to ^0.3.1 (cascade: EthosRecord JSON round-trip, BundleFolder.agents reserved, AgentsSection / PhilosophySection models, EthosStorePort extensions).

0.1.5 - 2026-05-03 - flutter_mcp_ui 0.3.2 / 0.4.4 alignment #

Changed #

  • Bump flutter_mcp_ui_core to ^0.3.2 and flutter_mcp_ui_runtime to ^0.4.4 for the M3 token shorthand consumption layer (text.variant, box.padding, card.shape/elevation, button.elevation, icon.size/sizeToken), FormFactorScope token tracking, opt-in per-form-factor property override, and box flat-form constraint properties.

0.1.4 - 2026-05-02 - Field-report logging stack — corrects 0.1.3 wiring #

Supersedes the misaligned 0.1.3 release. The logging primitives shipped in 0.1.3 conflated AppPlayer Core's own diagnostic logger with the MCP notifications/message log channel; this release re-architects them so a single in-app LogBuffer collects both sources, distinguished by LogEntry.source, and the MCP logging spec (notifications/message + logging/setLevel) is wired to its own routing path.

Changed (breaking — 0.1.3 → 0.1.4) #

  • LogEntry now requires a source: LogSource (enum core / mcp). level field is now McpLogLevel (RFC 5424 8 levels — verbatim) instead of the 4-level LogLevel. Construct via LogEntry.fromCore(LogLevel) (4→8 mapping) or LogEntry.fromMcp({serverId, params}).
  • LogBuffer.atLeast parameter changed from LogLevel to McpLogLevel. Added withSource(LogSource) filter.

Added #

  • BufferLoggerLogger adapter that pushes records into a LogBuffer as source=core entries. Pair with a console adapter inside CompositeLogger so a single Core diagnostic call lands in DevTools (development) AND the in-app LogBuffer (field report).
  • MCP logging spec wiring (MOD-RUNTIME-005a, NFR-OBS-006~007):
    • NotificationRouter routes notifications/message into a host-provided McpLogMessageHandler callback (serverId, params).
    • AppPlayerCoreService.initialize(... onMcpLogMessage: ...) parameter.
    • AppPlayerCoreService.setMcpLoggingLevel(serverId, McpLogLevel) — sends logging/setLevel so the server filters its own emission (server-side filter, spec-canonical).
    • McpLogMessageHandler typedef and McpLogLevel (re-export from mcp_client) in the public barrel.

Rationale #

Two log layers, one destination:

  • Development uses OS standard log pipelines (host ConsoleLoggerdart:developer.log → DevTools / Console.app / logcat). Core diagnostics also flow there via CompositeLogger.
  • Field reports require an in-app surface that production users can export when filing an issue. BufferLogger (Core diagnostics) and onMcpLogMessage (server logs) both feed the same LogBuffer, distinguished by LogEntry.source.

0.1.3 - 2026-05-02 - Logging primitives (LogEntry / LogBuffer / ScopedLogger) #

Added #

  • LogEntry — structured record (timestamp, level, message, context, error, stackTrace).
  • LogBufferChangeNotifier ring buffer (default 1000 entries) with scope/level filters. Tier shells (Pro / X / Custom) read this buffer to render in-app log viewers.
  • ScopedLoggerLogger decorator that injects a fixed scope map (e.g. {serverId, handle}) into every log call's context, so downstream filters can isolate logs per connection/app.
  • CompositeLogger — fan-out to multiple inner loggers (typical use: console adapter + LogBuffer adapter side-by-side).

Core internal modules (ConnectionManager / ToolDispatcher / AppSession / NotificationRouter / ResourceSubscriber) are unchanged — composition roots inject a ScopedLogger and the existing _logger.debug(...) calls automatically carry the scope.

Note: This release misaligned the LogBuffer wiring with the MCP logging spec — see 0.1.4 for the corrected design (LogEntry.source, LogEntry.fromMcp, NotificationRouter notifications/message handler, setMcpLoggingLevel API).


0.1.2 - 2026-05-01 - Tool dispatcher align with runtime 0.4.3 #

Changed #

  • ToolDispatcher.call now returns Future<dynamic> (the decoded JSON response) instead of Future<void>. Host self-fold removed; the runtime applies auto-merge against its own state.
  • runtime parameter dropped from ToolDispatcher.call — no longer needed.
  • AppSessionImpl._onToolCall returns the dispatcher's response so the runtime can fold it.
  • Runtime dependency raised to flutter_mcp_ui_runtime: ^0.4.3 (carries auto-merge + event variable + errorBoundary/errorRecovery event.{error, stack} fixes).

0.1.1 - 2026-04-30 - mcp_client 2.0 dependency #

Changed #

  • Upgraded mcp_client constraint to ^2.0.0. Public API of appplayer_core is unchanged — mcp_client is consumed internally and not re-exported.

0.1.0 - 2026-04-28 - Initial Release #

Added #

  • AppPlayerCoreService orchestrator owning connection lifecycle, sessions, bundle install pipeline, and tool dispatch.
  • Session abstractions — AppSession, DashboardSession, AppHandle.
  • Connection observability — ConnectionInfo, ConnectionResult, ConnectionState, ConnectionHealthMonitor with HealthMonitorConfig.
  • Bundle handles and host ports — BundleRef, BundleEntryPoint, BundleFetcher, InstalledAppBundle.
  • Dashboard bundle composition — DashboardBundleRef, BundleSource, SlotDefinition, SlotBindingRule.
  • Apps registry — AppsRegistry + RegistryMetadataSink automatic metadata refresh.
  • Tenant model — TenantContext, TenantSource for multi-tenant variants.
  • Host ports — ServerStorage, CredentialVault, AppMetadataSink.
  • Observability ports — Logger, MetricsPort.
  • Re-exports from flutter_mcp_ui_runtimeFormFactor, FormFactorScope, ViewMode/ViewModeResolver, AppSpacing / AppIconSizes / AppTypography / AppDensity (and their scale companions), TrustLevel, TrustLevelManager.
  • Re-export of MCPUIDSLVersion from flutter_mcp_ui_core.
  • Active-state extension via app_activity.dart.
0
likes
0
points
954
downloads

Publisher

verified publishermakemind.dev

Weekly Downloads

AppPlayer Core — shared Flutter library for MCP server connection, bundle handling, and UI runtime orchestration.

Homepage
Repository (GitHub)
View/report issues

Topics

#mcp #appplayer #bundle #ui #flutter

License

unknown (license)

Dependencies

brain_kernel, flutter, flutter_js, flutter_mcp_ui_core, flutter_mcp_ui_runtime, mcp_bundle, mcp_client, meta, uuid, web

More

Packages that depend on appplayer_core

Packages that implement appplayer_core