From 4b844d8527f3ebf862f5a0ab4cbce61bbbb804b0 Mon Sep 17 00:00:00 2001 From: Yilin Jing Date: Wed, 25 Mar 2026 00:57:30 +0800 Subject: [PATCH 1/2] fix: auto-derive unique slug from identity when WORLD_ID is not set When no WORLD_ID env var is provided, the slug was always 'star-office', causing all deployed instances to appear identical on the gateway. Now derives a unique slug from the world-identity.json keypair (format: 'star-office-<6char-hash>'), ensuring each instance has a distinct URL suffix. Explicit WORLD_ID still takes priority when set. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- src/config.ts | 31 +++++++++++++++++++++++++++++-- test/integration.test.mjs | 9 ++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index 655c0a4..873db13 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,6 +5,9 @@ * Call resolveConfig() for full config with auto-discovered public IP. */ +import fs from "node:fs"; +import path from "node:path"; +import { createHash } from "node:crypto"; import type { StarOfficeConfig } from "./types.js"; import { discoverPublicAddr } from "./discover.js"; @@ -39,6 +42,13 @@ export async function resolveConfig(overrides?: Partial): Prom config.publicAddr = await discoverPublicAddr(); } + const explicitWorldId = overrides?.worldId + ?? process.env["STAR_OFFICE_WORLD_ID"] + ?? process.env["WORLD_ID"]; + if (!explicitWorldId) { + config.worldId = deriveSlug(config.dataDir ?? "./data", config.officeName ?? "Star Office"); + } + logConfigSummary(config); return config; } @@ -56,11 +66,28 @@ function logConfigSummary(config: StarOfficeConfig): void { if (!config.publicAddr) { console.warn("[office] WARNING: No PUBLIC_ADDR — world will register on gateway without reachable endpoints"); } - if (config.worldId === "star-office") { - console.warn("[office] WARNING: Using default worldId 'star-office' — set WORLD_ID for unique identification"); + if (!process.env["WORLD_ID"] && !process.env["STAR_OFFICE_WORLD_ID"]) { + console.log(` (slug auto-derived from identity)`); } } +function deriveSlug(dataDir: string, name: string): string { + const prefix = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "world"; + const idFile = path.join(dataDir, "world-identity.json"); + try { + if (fs.existsSync(idFile)) { + const saved = JSON.parse(fs.readFileSync(idFile, "utf8")) as { publicKey?: string; seed?: string }; + const key = saved.publicKey ?? saved.seed ?? ""; + if (key) { + const hash = createHash("sha256").update(key).digest("hex").slice(0, 6); + return `${prefix}-${hash}`; + } + } + } catch { /* identity not yet created — fall through */ } + const rand = Math.random().toString(36).slice(2, 8); + return `${prefix}-${rand}`; +} + function int(val: string | undefined, fallback: number): number { if (!val) return fallback; const n = parseInt(val, 10); diff --git a/test/integration.test.mjs b/test/integration.test.mjs index 37c1e8b..aae0111 100644 --- a/test/integration.test.mjs +++ b/test/integration.test.mjs @@ -91,9 +91,8 @@ describe("Star Office World — Agent Integration", () => { const resp = await fetch(`http://127.0.0.1:${PORT}/peer/ping`); const data = await resp.json(); assert.equal(data.ok, true); - // Since SDK v1.4.0, worldId is the crypto protocol identity (aw:sha256:...); - // the human-readable slug is in data.slug. - assert.equal(data.slug, "star-office"); + // Slug is auto-derived from identity when WORLD_ID is not set + assert.ok(data.slug.startsWith("star-office"), `slug should start with 'star-office', got: ${data.slug}`); assert.equal(data.worldName, "Star Office"); assert.equal(data.agents, 0); assert.equal(data.maxAgents, 10); @@ -108,8 +107,8 @@ describe("Star Office World — Agent Integration", () => { }); assert.equal(result.status, 200); assert.equal(result.data.ok, true); - // Since SDK v1.4.0, worldId is the crypto protocol identity; slug is human-readable. - assert.equal(result.data.slug, "star-office"); + // Slug is auto-derived from identity when WORLD_ID is not set + assert.ok(result.data.slug.startsWith("star-office"), `slug should start with 'star-office', got: ${result.data.slug}`); assert.ok(result.data.manifest); assert.equal(result.data.manifest.name, "Star Office"); assert.ok(result.data.manifest.actions); From 206fae9fc957130c531b6a20a5a12ad73fe69d9f Mon Sep 17 00:00:00 2001 From: Yilin Jing Date: Wed, 25 Mar 2026 00:57:43 +0800 Subject: [PATCH 2/2] chore: add changeset for auto-derive slug fix Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .changeset/auto-derive-slug.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/auto-derive-slug.md diff --git a/.changeset/auto-derive-slug.md b/.changeset/auto-derive-slug.md new file mode 100644 index 0000000..f959814 --- /dev/null +++ b/.changeset/auto-derive-slug.md @@ -0,0 +1,5 @@ +--- +"@resciencelab/star-office-world": patch +--- + +Auto-derive unique slug from identity keypair when WORLD_ID is not set, preventing duplicate 'star-office' slugs on the gateway