From 584f6bea5b8987462f91e842031d002605707bb0 Mon Sep 17 00:00:00 2001 From: Josh Lehman Date: Tue, 10 Mar 2026 14:23:10 -0700 Subject: [PATCH] fix: migrate tlon installs to tloncorp package Switch new Tlon installs to @tloncorp/openclaw, add an on-load config migration for existing plugins.installs.tlon npm records, and document the rollout contract for the package rename. Preserve the tlon plugin/channel id and clear stale npm resolution metadata when rewriting old install specs so future plugin updates resolve cleanly. Regeneration-Prompt: | Migrate the Tlon OpenClaw plugin package from @openclaw/tlon to @tloncorp/openclaw without breaking existing installs. Keep the plugin id and channel id as tlon, switch onboarding and docs for new installs to the new npm package, and add an automatic config migration for existing plugins.installs.tlon npm records that still reference the old package. When rewriting stored install specs, clear old npm resolution metadata such as integrity and resolvedSpec so the next plugin update does not compare the new package against the old package artifact. Also capture the migration contract in repo docs so the companion tloncorp package can preserve version continuity and manifest invariants. --- docs/channels/tlon.md | 5 +- docs/refactor/tlon-package-migration.md | 73 +++++++++++++++++++++++++ docs/tools/plugin.md | 2 +- extensions/tlon/README.md | 2 + extensions/tlon/package.json | 2 +- src/config/legacy-migrate.test.ts | 56 +++++++++++++++++++ src/config/legacy.migrations.part-3.ts | 39 +++++++++++++ src/config/legacy.rules.ts | 13 +++++ 8 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 docs/refactor/tlon-package-migration.md diff --git a/docs/channels/tlon.md b/docs/channels/tlon.md index f3e70c7152a..c404ef9b14b 100644 --- a/docs/channels/tlon.md +++ b/docs/channels/tlon.md @@ -21,7 +21,7 @@ Tlon ships as a plugin and is not bundled with the core install. Install via CLI (npm registry): ```bash -openclaw plugins install @openclaw/tlon +openclaw plugins install @tloncorp/openclaw ``` Local checkout (when running from a git repo): @@ -32,6 +32,9 @@ openclaw plugins install ./extensions/tlon Details: [Plugins](/tools/plugin) +Existing installs from `@openclaw/tlon` are migrated to the new package name on config load so +future `openclaw plugins update` runs resolve from `@tloncorp/openclaw`. + ## Setup 1. Install the Tlon plugin. diff --git a/docs/refactor/tlon-package-migration.md b/docs/refactor/tlon-package-migration.md new file mode 100644 index 00000000000..2965ae5cac0 --- /dev/null +++ b/docs/refactor/tlon-package-migration.md @@ -0,0 +1,73 @@ +--- +title: "Tlon Package Migration" +summary: "Plan for migrating the Tlon plugin package from @openclaw/tlon to @tloncorp/openclaw" +read_when: + - Updating Tlon onboarding/install metadata + - Migrating existing Tlon plugin installs +--- + +# Tlon Package Migration + +This document captures the OpenClaw-side plan for migrating the Tlon plugin package from +`@openclaw/tlon` to `@tloncorp/openclaw` without breaking existing installs. + +## Goals + +- Keep the plugin id and channel id as `tlon`. +- Switch new onboarding and docs to install `@tloncorp/openclaw`. +- Auto-migrate existing `plugins.installs.tlon` records that still point at `@openclaw/tlon`. +- Preserve version continuity on the Tlon side so pinned or version-qualified specs can move cleanly. +- Keep local checkout installs (`./extensions/tlon`) working as before. + +## Required invariants + +The published `@tloncorp/openclaw` package must remain a drop-in replacement for the Tlon plugin. + +- `package.json.name` should be `@tloncorp/openclaw`. +- `package.json.version` should continue the existing `2026.x.y` line instead of resetting to `0.x`. +- `openclaw.channel.id` must stay `tlon`. +- `openclaw.install.npmSpec` must be `@tloncorp/openclaw`. +- `openclaw.extensions` must point at the real published entrypoint. +- `openclaw.plugin.json.id` must stay `tlon`. +- `openclaw.plugin.json.channels` must contain `tlon`. + +## OpenClaw migration behavior + +On config load, OpenClaw should detect old Tlon npm install specs and rewrite them in place. + +- Rewrite `plugins.installs.tlon.spec` from `@openclaw/tlon` to `@tloncorp/openclaw`. +- Rewrite version-qualified specs the same way, preserving the suffix after the package name. +- Apply this to any `plugins.installs.tlon` record that still references the old package name. +- Clear stale npm resolution metadata so the next `openclaw plugins update tlon` resolves against the + new package without comparing the old package integrity hash to the new package artifact. + +Fields to clear when the spec is rewritten: + +- `resolvedName` +- `resolvedVersion` +- `resolvedSpec` +- `integrity` +- `shasum` +- `resolvedAt` + +Fields to preserve: + +- `source` +- `installPath` +- `sourcePath` +- `version` +- `installedAt` + +## Rollout + +1. Publish `@tloncorp/openclaw` with the correct manifest and version continuity. +2. Update OpenClaw onboarding metadata and docs to point new installs at `@tloncorp/openclaw`. +3. Ship the config migration so existing installs start updating from the new package name. +4. Keep `@openclaw/tlon` available during the transition window. +5. Deprecate `@openclaw/tlon` after OpenClaw releases with the migration have had time to land. + +## External follow-up in tloncorp/openclaw-tlon + +- Publish the next `2026.x.y` version under `@tloncorp/openclaw`. +- Update the README to say the plugin is installed separately, not “included with OpenClaw”. +- Verify the published tarball contains the correct `openclaw.plugin.json`. diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index a257d8b7a45..1d2cdedb5ff 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -923,7 +923,7 @@ it’s present in your workspace/managed skills locations. Recommended packaging: - Main package: `openclaw` (this repo) -- Plugins: separate npm packages under `@openclaw/*` (example: `@openclaw/voice-call`) +- Plugins: separate npm packages, usually under `@openclaw/*` (examples: `@openclaw/voice-call`, `@tloncorp/openclaw`) Publishing contract: diff --git a/extensions/tlon/README.md b/extensions/tlon/README.md index bb28ef691eb..05ef14ac8da 100644 --- a/extensions/tlon/README.md +++ b/extensions/tlon/README.md @@ -2,4 +2,6 @@ Tlon/Urbit channel plugin for OpenClaw. Supports DMs, group mentions, and thread replies. +Install from npm: `openclaw plugins install @tloncorp/openclaw` + Docs: https://docs.openclaw.ai/channels/tlon diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 0cb79328d89..036fd7de6db 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -24,7 +24,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@openclaw/tlon", + "npmSpec": "@tloncorp/openclaw", "localPath": "extensions/tlon", "defaultChoice": "npm" }, diff --git a/src/config/legacy-migrate.test.ts b/src/config/legacy-migrate.test.ts index 4910c7f9488..5da792e5b7f 100644 --- a/src/config/legacy-migrate.test.ts +++ b/src/config/legacy-migrate.test.ts @@ -1,6 +1,62 @@ import { describe, expect, it } from "vitest"; import { migrateLegacyConfig } from "./legacy-migrate.js"; import { WHISPER_BASE_AUDIO_MODEL } from "./legacy-migrate.test-helpers.js"; +import { findLegacyConfigIssues } from "./legacy.js"; + +describe("legacy migrate tlon plugin install spec", () => { + it("flags legacy @openclaw/tlon install specs for auto-migration", () => { + const issues = findLegacyConfigIssues({ + plugins: { + installs: { + tlon: { + source: "npm", + spec: "@openclaw/tlon", + }, + }, + }, + }); + + expect(issues).toContainEqual({ + path: "plugins.installs.tlon.spec", + message: + "plugins.installs.tlon.spec moved from @openclaw/tlon to @tloncorp/openclaw (auto-migrated on load).", + }); + }); + + it("rewrites old tlon install specs and clears stale npm metadata", () => { + const res = migrateLegacyConfig({ + plugins: { + installs: { + tlon: { + source: "npm", + spec: "@openclaw/tlon@2026.2.21", + resolvedName: "@openclaw/tlon", + resolvedVersion: "2026.2.21", + resolvedSpec: "@openclaw/tlon@2026.2.21", + integrity: "sha512-old", + shasum: "old", + resolvedAt: "2026-03-01T00:00:00.000Z", + installPath: "/tmp/tlon", + version: "2026.2.21", + }, + }, + }, + }); + + expect(res.changes).toContain( + "Moved plugins.installs.tlon.spec → @tloncorp/openclaw@2026.2.21 and cleared stale npm resolution metadata.", + ); + expect(res.config?.plugins?.installs?.tlon?.spec).toBe("@tloncorp/openclaw@2026.2.21"); + expect(res.config?.plugins?.installs?.tlon?.version).toBe("2026.2.21"); + expect(res.config?.plugins?.installs?.tlon?.installPath).toBe("/tmp/tlon"); + expect(res.config?.plugins?.installs?.tlon?.resolvedName).toBeUndefined(); + expect(res.config?.plugins?.installs?.tlon?.resolvedVersion).toBeUndefined(); + expect(res.config?.plugins?.installs?.tlon?.resolvedSpec).toBeUndefined(); + expect(res.config?.plugins?.installs?.tlon?.integrity).toBeUndefined(); + expect(res.config?.plugins?.installs?.tlon?.shasum).toBeUndefined(); + expect(res.config?.plugins?.installs?.tlon?.resolvedAt).toBeUndefined(); + }); +}); describe("legacy migrate audio transcription", () => { it("moves routing.transcribeAudio into tools.media.audio.models", () => { diff --git a/src/config/legacy.migrations.part-3.ts b/src/config/legacy.migrations.part-3.ts index ccc07b4b99f..324c5fa4414 100644 --- a/src/config/legacy.migrations.part-3.ts +++ b/src/config/legacy.migrations.part-3.ts @@ -35,6 +35,16 @@ const AGENT_HEARTBEAT_KEYS = new Set([ const CHANNEL_HEARTBEAT_KEYS = new Set(["showOk", "showAlerts", "useIndicator"]); +function migrateLegacyTlonInstallSpec(spec: string): string | null { + if (spec === "@openclaw/tlon") { + return "@tloncorp/openclaw"; + } + if (spec.startsWith("@openclaw/tlon@")) { + return `@tloncorp/openclaw${spec.slice("@openclaw/tlon".length)}`; + } + return null; +} + function splitLegacyHeartbeat(legacyHeartbeat: Record): { agentHeartbeat: Record | null; channelHeartbeat: Record | null; @@ -97,6 +107,35 @@ function mergeLegacyIntoDefaults(params: { // tools.alsoAllow legacy migration intentionally omitted (field not shipped in prod). export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ + { + id: "plugins.installs.tlon.spec->tloncorp", + describe: "Move Tlon plugin install records to @tloncorp/openclaw", + apply: (raw, changes) => { + const plugins = getRecord(raw.plugins); + const installs = getRecord(plugins?.installs); + const tlon = getRecord(installs?.tlon); + const spec = typeof tlon?.spec === "string" ? tlon.spec : null; + if (!tlon || !spec) { + return; + } + + const nextSpec = migrateLegacyTlonInstallSpec(spec); + if (!nextSpec) { + return; + } + + tlon.spec = nextSpec; + delete tlon.resolvedName; + delete tlon.resolvedVersion; + delete tlon.resolvedSpec; + delete tlon.integrity; + delete tlon.shasum; + delete tlon.resolvedAt; + changes.push( + `Moved plugins.installs.tlon.spec → ${nextSpec} and cleared stale npm resolution metadata.`, + ); + }, + }, { // v2026.2.26 added a startup guard requiring gateway.controlUi.allowedOrigins (or the // host-header fallback flag) for any non-loopback bind. The onboarding wizard was updated diff --git a/src/config/legacy.rules.ts b/src/config/legacy.rules.ts index 420f6a4685d..c418e907740 100644 --- a/src/config/legacy.rules.ts +++ b/src/config/legacy.rules.ts @@ -46,6 +46,13 @@ function isLegacyGatewayBindHostAlias(value: unknown): boolean { ); } +function isLegacyTlonInstallSpec(value: unknown): boolean { + if (typeof value !== "string") { + return false; + } + return value === "@openclaw/tlon" || value.startsWith("@openclaw/tlon@"); +} + export const LEGACY_CONFIG_RULES: LegacyConfigRule[] = [ { path: ["whatsapp"], @@ -209,4 +216,10 @@ export const LEGACY_CONFIG_RULES: LegacyConfigRule[] = [ message: "top-level heartbeat is not a valid config path; use agents.defaults.heartbeat (cadence/target/model settings) or channels.defaults.heartbeat (showOk/showAlerts/useIndicator).", }, + { + path: ["plugins", "installs", "tlon", "spec"], + message: + "plugins.installs.tlon.spec moved from @openclaw/tlon to @tloncorp/openclaw (auto-migrated on load).", + match: (value) => isLegacyTlonInstallSpec(value), + }, ];