Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/auto-derive-slug.md
Original file line number Diff line number Diff line change
@@ -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
31 changes: 29 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -39,6 +42,13 @@ export async function resolveConfig(overrides?: Partial<StarOfficeConfig>): 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;
}
Expand All @@ -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);
Expand Down
9 changes: 4 additions & 5 deletions test/integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down