Skip to content

Restructure into monorepo with cross-language client SDKs#1

Merged
Tyler-RNG merged 10 commits into
mainfrom
client-sdk-scaffold
Apr 24, 2026
Merged

Restructure into monorepo with cross-language client SDKs#1
Tyler-RNG merged 10 commits into
mainfrom
client-sdk-scaffold

Conversation

@Tyler-RNG
Copy link
Copy Markdown
Owner

Summary

  • Restructures this repo from a single plugin package into a pnpm workspace with four publishable artifacts sharing one version + one wire-schema source of truth.
  • Adds TypeScript, Kotlin (JVM + Android), and Swift client SDKs that implement the CharacterManifest wire protocol (animation graph, coroutine/async/actor sprite player, FrameSource adapter seam, streaming <<<state>>> / <<<state-N>>> marker parser, asset cache).
  • Wires full CI (TS + Kotlin + Swift) and a single-tag release workflow publishing all four artifacts to GitHub Packages on v* tags.

New layout

sprite-core/
├── schema/                         ← TypeBox wire schema (source of truth)
├── fixtures/                       ← language-agnostic conformance JSON
├── scripts/check-versions.mjs      ← version-sync gate
└── packages/
    ├── plugin/                     ← @tyler-rng/sprite-core (unchanged publish name)
    ├── client-js/                  ← @tyler-rng/sprite-core-client
    ├── client-kotlin/              ← :core + :android Gradle modules
    └── client-swift/               ← SwiftPM package

The plugin's install flow is unchanged — @tyler-rng/sprite-core still publishes from packages/plugin/ to GitHub Packages, same name, same npmSpec.

Drift fixed in-flight

  • TS marker parser upgraded to carry <<<state-N>>> count (was behind Kotlin).
  • Kotlin CharacterManifest gained the emotions field (was missing from the wire mirror).
  • AgentAvatarSource ported 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.
  • TS runner in packages/client-js/src/fixture-runner.test.ts — 23 fixture cases + 19 unit tests, all passing (42 total).
  • Kotlin + Swift fixture runners to follow in a sibling PR once their CI lanes are validated.

Release flow

One v<version> tag triggers parallel publishes after verify-versions passes:

  • @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-swift runs swift test as a gate)

See docs/RELEASING.md for the full cadence, consumer install recipes, rollback, and secrets checklist (no extra repo secrets needed beyond the auto GITHUB_TOKEN).

Known pre-existing issue

Plugin's pnpm typecheck fails against openclaw@2026.4.15-beta.1api.registerSystemPromptContribution and 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 cleanly
  • pnpm --filter ./packages/client-js test — 42/42 pass (19 unit + 23 fixture)
  • pnpm --filter ./packages/client-js typecheck — clean
  • node scripts/check-versions.mjs — all five version sources agree at 1.0.0
  • CI first run on push: validate Kotlin Gradle build + Swift macOS test against the real runners
  • Manual smoke: cut a v1.0.0-beta.1 tag in a sandbox repo to confirm the publish workflow wires correctly before going 1.0.0

🤖 Generated with Claude Code

Tylerw9954 and others added 10 commits April 23, 2026 08:55
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>
@Tyler-RNG Tyler-RNG merged commit 9fdefe4 into main Apr 24, 2026
1 of 4 checks passed
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.

2 participants