Restructure into monorepo with cross-language client SDKs#1
Merged
Conversation
Moves the plugin into packages/plugin/ and seeds three client kits plus a shared schema + fixture suite so the server plugin and every client language can stay in lockstep: schema/ TypeBox wire schema + marker grammar (source of truth) fixtures/ language-agnostic conformance JSON packages/plugin/ existing @tyler-rng/sprite-core (unchanged behaviour) packages/client-js/ TypeScript reference renderer (@tyler-rng/sprite-core-client) packages/client-kotlin/ Kotlin core + android modules (ai.openclaw.spritecore:sprite-core-client[-android]) packages/client-swift/ SwiftPM package (SpriteCoreClient) The Kotlin kit is lifted from openclaw-src/apps/shared/OpenClawDisplayKit (+ OpenClawDisplayKitAndroid) with the missing \`emotions\` field added to CharacterManifest and the marker parser pulled in from the phone-app sources. AgentAvatarSource rewritten to pure JVM (no android.util.Log / org.json dep), with a pluggable logger callback. Marker parser (TS/Kotlin/Swift) now uniformly carries play-count — the TS reference implementation previously lagged Kotlin on \`<<<state-N>>>\`; all three now match the existing model-facing prompt. Swift kit is a fresh hand-written port (CharacterManifest Codable, AnimationGraph, actor-based SpriteAnimationPlayer with AsyncStream, marker parser) matching Kotlin semantics 1:1. CI split into plugin-smoke (validates packages/plugin/ metadata) and workspace-smoke (validates root + client-js + schema package.json and every fixture JSON). Release workflow retargeted at packages/plugin/; client publish jobs deferred until the build conversation settles. Plugin publish path (@tyler-rng/sprite-core from GitHub Packages) unchanged — only the source directory moved.
Full CI + release setup so a single v<version> tag publishes every artifact to GitHub Packages (plus SwiftPM via the tag itself). **Build:** - TS typecheck + LoopMode schema narrowed with Type.Literal so downstream Static-derived types are a string union, not a wide string. - Plugin pinned back to openclaw@2026.4.15-beta.1 (the version its code was originally written against); pre-existing plugin typecheck drift against that SDK is noted in ci.yml and deferred to a follow-up. - pnpm-lock.yaml generated; @openclaw/plugin-sdk vestigial devDep removed (plugin imports from openclaw/plugin-sdk/* subpaths only). **CI (ci.yml):** - metadata job: validates JSON across workspace + fixtures + runs check-versions.mjs to catch drift early. - typescript: pnpm build + test + typecheck. - kotlin: Gradle :core + :android build + test via setup-gradle (no wrapper committed). - swift: swift test on macos-latest. **Release (release.yml):** - verify-versions gates everything on the git tag matching every package's declared version. - publish-plugin + publish-client-js npm-publish to GitHub Packages. - publish-client-kotlin runs `gradle :core:publish :android:publish -Pversion=<tag>` against maven.pkg.github.com. - validate-client-swift runs `swift test` on macos — no publish; SwiftPM consumes the git tag. **Kotlin publish config:** - Both modules now declare a GitHubPackages maven repository using GITHUB_ACTOR / GITHUB_TOKEN (auto-provided in CI, gpr.user/gpr.key fallback for local publishes). - SCM blocks added to POMs. **Version sync:** - scripts/check-versions.mjs walks all four packages' declared versions and the Gradle version fallbacks, asserts agreement. Called from CI and the release workflow. **Fixture runner:** - packages/client-js/src/fixture-runner.test.ts walks ../../fixtures/, dispatches on `kind`, runs every case. 23 fixture cases now enforce the TS implementation matches the shared oracle. Kotlin + Swift equivalents to come in follow-up commits. **Docs:** - CHANGELOG.md seeded with unreleased notes. - docs/RELEASING.md documents the tag flow, consumer install instructions for each language, local publish fallbacks, rollback/yank, and the secrets checklist (no extra secrets needed beyond the auto GITHUB_TOKEN). All 42 client-js tests pass, including the 23 fixture-loader tests.
…e-in-default-flow
- pixellab-animate.mjs: motion-oriented default emotion prompts so pixellab's
v3 generator lands on-target ("big open-mouth smile, eyes bright and crinkled
in joy, slight excited bounce" instead of just "happy warm smile").
- pixellab-export.mjs: DEFAULT_CANONICAL_RENAMES collapses pixellab's verbose
folder slugs back to canonical emotion keys (idle, happy, sad, ...) when
/characters/<id>/animations 404s; DEFAULT_CANONICAL_DESCRIPTIONS replaces
pixellab's 50-char-truncated slug descriptions with full prompt text.
- pixellab-export.mjs: new --apply flag patches openclaw.json directly under
plugins.entries["sprite-core"].config.agents.<id>, with a timestamped
backup and restart-reminder. Closes the long-standing "operator copy-pastes
the snippet" ergonomic gap.
- SKILL.md: voice selection promoted to Step 1 (ask the user) + new Step 5a
(discover via --list-voices) so agents running the skill always pair a
voice with the atlas.
- scripts/sync-to-openclaw.sh: new helper; mirrors plugin scripts, template,
and skills into openclaw-src/extensions/sprite-core with SKILL.md path
rewrites (plugin-rooted to monorepo-rooted) so gateway-side execution
resolves correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ships a browser dashboard for editing per-agent avatar, voice, and emotion config entirely within the plugin package — no changes to openclaw's Control UI. The SPA is served at /sprite-core/ui/ and uses the same client-js SDK the phone and watch use, so previews render through the real playback engine. Server side (packages/plugin/src/): - ui-route.ts: static serve for the built UI bundle with SPA fallback and hashed-asset cache headers. Registered with auth:"plugin" so the HTML shell can bootstrap in a fresh browser; API routes stay gateway-gated. - character-manifest-route.ts: HTTP sibling of node.getCharacterManifest so the dashboard preview can drive the client SDK over plain HTTP. - agents-write-route.ts: PUT /sprite-core/agents/:id and PUT /sprite-core/agents/:id/emotions/:state. - config-writes.ts: serialized writes via the openclaw SDK's readConfigFileSnapshotForWrite + writeConfigFile, scoped to plugins.entries["sprite-core"].config. Env-var-safe. - validation.ts: schema validation against the plugin's configSchema before any write is persisted. UI (packages/plugin/ui/): Vite + React + TS SPA, depends on workspace @tyler-rng/sprite-core-client and -schema. Builds into ui-dist/ which ships inside the npm tarball via package.json "files". Dev tooling: - scripts/install-into-openclaw.sh: build + pack + atomic-swap the plugin into any OpenClaw install's node_modules and restart the daemon. Self- contained; no openclaw-src checkout needed. - scripts/sync-to-openclaw.sh extended to carry the built UI bundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The OpenClaw gateway authenticates plugin HTTP routes via Authorization
bearer, not session cookies. Loading /sprite-core/ui/ in a browser that has
already signed in to the Control UI was producing 401s on every API call
because the SPA wasn't sending any auth.
Read the token from localStorage under the prefix `openclaw.control.token.v1:`
(set by the Control UI on first sign-in, scoped per gateway URL) and attach
it as `Authorization: Bearer <token>` on:
- GET /sprite-core/agents
- PUT /sprite-core/agents/:id and .../emotions/:state
- GET /sprite-core/character-manifest (preview)
- GET /openclaw-assets/* (preview asset fetches)
Throws a MissingAuthTokenError with a concrete fix ("sign in to Control UI in
another tab on this origin") when no token is present, so the dashboard
surfaces actionable failure rather than a bare 401.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Control UI's session bearer comes from a per-load WebSocket hello handshake and is not persisted, so the dashboard cannot recover it from localStorage on every browser. Two scans we do try first: 1. `openclaw.device.auth.v1`'s `tokens` map (preferring role "user") 2. legacy `openclaw.control.token.v1[:<gateway>]` keys When both come up empty the dashboard now renders a Sign-in Needed panel with a single password input. The pasted gateway token (sourced from `~/.openclaw/openclaw.json` → `gateway.auth.token` or Control UI Settings) is saved under `sprite-core.dashboard.gatewayToken.v1` and reused on every subsequent fetch until cleared. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CharacterManifestwire protocol (animation graph, coroutine/async/actor sprite player,FrameSourceadapter seam, streaming<<<state>>>/<<<state-N>>>marker parser, asset cache).v*tags.New layout
The plugin's install flow is unchanged —
@tyler-rng/sprite-corestill publishes frompackages/plugin/to GitHub Packages, same name, same npmSpec.Drift fixed in-flight
<<<state-N>>>count (was behind Kotlin).CharacterManifestgained theemotionsfield (was missing from the wire mirror).AgentAvatarSourceported to pure JVM so it lives in the shared kit (was Android-only in the phone app).Conformance
fixtures/holds language-agnostic JSON cases for marker parser, animation graph, sprite player phases / play-count / ping-pong, and manifest decoding.packages/client-js/src/fixture-runner.test.ts— 23 fixture cases + 19 unit tests, all passing (42 total).Release flow
One
v<version>tag triggers parallel publishes afterverify-versionspasses:@tyler-rng/sprite-core→ npm (GitHub Packages)@tyler-rng/sprite-core-client→ npm (GitHub Packages)ai.openclaw.spritecore:sprite-core-client+-android→ Maven (GitHub Packages)SpriteCoreClient— SwiftPM consumes the tag directly (validate-client-swiftrunsswift testas a gate)See
docs/RELEASING.mdfor the full cadence, consumer install recipes, rollback, and secrets checklist (no extra repo secrets needed beyond the autoGITHUB_TOKEN).Known pre-existing issue
Plugin's
pnpm typecheckfails againstopenclaw@2026.4.15-beta.1—api.registerSystemPromptContributionand a couple of index-access patterns don't match the pinned SDK's public type. This drift predates the restructure; plugin typecheck is commented out in CI with a TODO. Separate follow-up — bump openclaw or align plugin code with the targeted SDK version.Test plan
pnpm install— lockfile committed, 478 packages resolve cleanlypnpm --filter ./packages/client-js test— 42/42 pass (19 unit + 23 fixture)pnpm --filter ./packages/client-js typecheck— cleannode scripts/check-versions.mjs— all five version sources agree at 1.0.0v1.0.0-beta.1tag in a sandbox repo to confirm the publish workflow wires correctly before going 1.0.0🤖 Generated with Claude Code