From 7d1b86a24706c2992e62f0bd0fbd79127143a0f8 Mon Sep 17 00:00:00 2001 From: Yilin Jing Date: Wed, 25 Mar 2026 11:20:10 +0800 Subject: [PATCH 1/2] fix: align env contract with platform and support isPublic/password/maxAgents Platform injects WORLD_ID, WORLD_NAME, WORLD_PASSWORD, MAX_AGENTS, WORLD_PUBLIC, PUBLIC_ADDR, PUBLIC_PORT, GATEWAY_URL, PEER_PORT, DATA_DIR. These now take precedence over legacy STAR_OFFICE_* aliases. - config.ts: platform envs first, add WORLD_PASSWORD/MAX_AGENTS/WORLD_PUBLIC - index.ts: honor resolved isPublic instead of hardcoding true - types.ts: add isPublic to StarOfficeConfig - test/config.test.mjs: coverage for env precedence and legacy fallback Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- src/config.ts | 38 +++++++++++++++--------- src/index.ts | 2 +- src/types.ts | 1 + test/config.test.mjs | 69 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 test/config.test.mjs diff --git a/src/config.ts b/src/config.ts index 873db13..b290090 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,19 +13,20 @@ import { discoverPublicAddr } from "./discover.js"; export function loadConfig(overrides?: Partial): StarOfficeConfig { return { - worldId: overrides?.worldId ?? process.env["STAR_OFFICE_WORLD_ID"] ?? process.env["WORLD_ID"] ?? "star-office", - officeName: overrides?.officeName ?? process.env["STAR_OFFICE_NAME"] ?? process.env["WORLD_NAME"] ?? "Star Office", - port: overrides?.port ?? int(process.env["STAR_OFFICE_PORT"] ?? process.env["PEER_PORT"], 19000), - publicPort: overrides?.publicPort ?? intOrUndef(process.env["STAR_OFFICE_PUBLIC_PORT"] ?? process.env["PUBLIC_PORT"]), - publicAddr: overrides?.publicAddr ?? process.env["STAR_OFFICE_PUBLIC_ADDR"] ?? process.env["PUBLIC_ADDR"] ?? null, + worldId: overrides?.worldId ?? process.env["WORLD_ID"] ?? process.env["STAR_OFFICE_WORLD_ID"] ?? "star-office", + officeName: overrides?.officeName ?? process.env["WORLD_NAME"] ?? process.env["STAR_OFFICE_NAME"] ?? "Star Office", + port: overrides?.port ?? int(process.env["PEER_PORT"] ?? process.env["STAR_OFFICE_PORT"], 19000), + publicPort: overrides?.publicPort ?? intOrUndef(process.env["PUBLIC_PORT"] ?? process.env["STAR_OFFICE_PUBLIC_PORT"]), + publicAddr: overrides?.publicAddr ?? process.env["PUBLIC_ADDR"] ?? process.env["STAR_OFFICE_PUBLIC_ADDR"] ?? null, gatewayUrls: overrides?.gatewayUrls - ?? parseList(process.env["STAR_OFFICE_GATEWAY_URLS"] ?? process.env["GATEWAY_URL"]) + ?? parseList(process.env["GATEWAY_URL"] ?? process.env["STAR_OFFICE_GATEWAY_URLS"]) ?? ["https://gateway.agentworlds.ai"], - password: overrides?.password ?? process.env["STAR_OFFICE_PASSWORD"] ?? "", + password: overrides?.password ?? process.env["WORLD_PASSWORD"] ?? process.env["STAR_OFFICE_PASSWORD"] ?? "", adminPassword: overrides?.adminPassword ?? process.env["STAR_OFFICE_ADMIN_PASS"] ?? "1234", - maxAgents: overrides?.maxAgents ?? int(process.env["STAR_OFFICE_MAX_AGENTS"], 20), + maxAgents: overrides?.maxAgents ?? int(process.env["MAX_AGENTS"] ?? process.env["STAR_OFFICE_MAX_AGENTS"], 20), + isPublic: overrides?.isPublic ?? bool(process.env["WORLD_PUBLIC"], true), broadcastIntervalMs: overrides?.broadcastIntervalMs ?? int(process.env["STAR_OFFICE_BROADCAST_MS"], 3000), - dataDir: overrides?.dataDir ?? process.env["STAR_OFFICE_DATA_DIR"] ?? process.env["DATA_DIR"] ?? "./data", + dataDir: overrides?.dataDir ?? process.env["DATA_DIR"] ?? process.env["STAR_OFFICE_DATA_DIR"] ?? "./data", frontendDir: overrides?.frontendDir ?? process.env["STAR_OFFICE_FRONTEND_DIR"] ?? "./frontend", memoryDir: overrides?.memoryDir ?? process.env["STAR_OFFICE_MEMORY_DIR"] ?? "./data/memos", geminiApiKey: overrides?.geminiApiKey ?? process.env["GEMINI_API_KEY"], @@ -43,8 +44,8 @@ export async function resolveConfig(overrides?: Partial): Prom } const explicitWorldId = overrides?.worldId - ?? process.env["STAR_OFFICE_WORLD_ID"] - ?? process.env["WORLD_ID"]; + ?? process.env["WORLD_ID"] + ?? process.env["STAR_OFFICE_WORLD_ID"]; if (!explicitWorldId) { config.worldId = deriveSlug(config.dataDir ?? "./data", config.officeName ?? "Star Office"); } @@ -60,6 +61,7 @@ function logConfigSummary(config: StarOfficeConfig): void { console.log(` port: ${config.port}`); console.log(` publicAddr: ${config.publicAddr ?? "(none)"}`); console.log(` publicPort: ${config.publicPort ?? "(same as port)"}`); + console.log(` isPublic: ${config.isPublic ?? true}`); console.log(` gatewayUrls: ${config.gatewayUrls?.join(", ") ?? "(none)"}`); console.log(` dataDir: ${config.dataDir}`); @@ -67,7 +69,7 @@ function logConfigSummary(config: StarOfficeConfig): void { console.warn("[office] WARNING: No PUBLIC_ADDR — world will register on gateway without reachable endpoints"); } if (!process.env["WORLD_ID"] && !process.env["STAR_OFFICE_WORLD_ID"]) { - console.log(` (slug auto-derived from identity)`); + console.log(" (slug auto-derived from identity)"); } } @@ -83,7 +85,9 @@ function deriveSlug(dataDir: string, name: string): string { return `${prefix}-${hash}`; } } - } catch { /* identity not yet created — fall through */ } + } catch { + // identity not yet created — fall through + } const rand = Math.random().toString(36).slice(2, 8); return `${prefix}-${rand}`; } @@ -100,6 +104,14 @@ function intOrUndef(val: string | undefined): number | undefined { return Number.isFinite(n) ? n : undefined; } +function bool(val: string | undefined, fallback: boolean): boolean { + if (!val) return fallback; + const normalized = val.trim().toLowerCase(); + if (["1", "true", "yes", "on", "public"].includes(normalized)) return true; + if (["0", "false", "no", "off", "private"].includes(normalized)) return false; + return fallback; +} + function parseList(val: string | undefined): string[] | undefined { if (!val) return undefined; return val.split(",").map((s) => s.trim()).filter(Boolean); diff --git a/src/index.ts b/src/index.ts index af7e0d2..6758bcd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,7 +79,7 @@ export async function createStarOfficeWorld( publicAddr: config.publicAddr ?? null, gatewayUrls: config.gatewayUrls, maxAgents: config.maxAgents ?? 20, - isPublic: true, + isPublic: config.isPublic ?? true, password: config.password ?? "", broadcastIntervalMs: config.broadcastIntervalMs ?? 3000, dataDir: config.dataDir ?? "./data", diff --git a/src/types.ts b/src/types.ts index 050bbd6..5ea40e7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -129,6 +129,7 @@ export interface StarOfficeConfig { password?: string; adminPassword?: string; maxAgents?: number; + isPublic?: boolean; broadcastIntervalMs?: number; dataDir?: string; frontendDir?: string; diff --git a/test/config.test.mjs b/test/config.test.mjs new file mode 100644 index 0000000..0c2fabc --- /dev/null +++ b/test/config.test.mjs @@ -0,0 +1,69 @@ +import { afterEach, test } from "node:test" +import assert from "node:assert/strict" + +const ORIGINAL_ENV = { ...process.env } + +function resetEnv() { + for (const key of Object.keys(process.env)) { + if (!(key in ORIGINAL_ENV)) delete process.env[key] + } + for (const [key, value] of Object.entries(ORIGINAL_ENV)) { + process.env[key] = value + } +} + +afterEach(() => { + resetEnv() +}) + +test("loadConfig prefers platform env vars over legacy STAR_OFFICE_* aliases", async () => { + process.env.WORLD_ID = "platform-world" + process.env.STAR_OFFICE_WORLD_ID = "legacy-world" + process.env.WORLD_NAME = "Platform Name" + process.env.STAR_OFFICE_NAME = "Legacy Name" + process.env.PEER_PORT = "19001" + process.env.STAR_OFFICE_PORT = "19000" + process.env.PUBLIC_PORT = "9550" + process.env.STAR_OFFICE_PUBLIC_PORT = "9549" + process.env.PUBLIC_ADDR = "3.17.5.202" + process.env.STAR_OFFICE_PUBLIC_ADDR = "127.0.0.1" + process.env.GATEWAY_URL = "https://gateway.example.com" + process.env.STAR_OFFICE_GATEWAY_URLS = "https://legacy-gateway.example.com" + process.env.WORLD_PASSWORD = "platform-password" + process.env.STAR_OFFICE_PASSWORD = "legacy-password" + process.env.MAX_AGENTS = "42" + process.env.STAR_OFFICE_MAX_AGENTS = "7" + process.env.WORLD_PUBLIC = "false" + process.env.DATA_DIR = "/platform-data" + process.env.STAR_OFFICE_DATA_DIR = "/legacy-data" + + const { loadConfig } = await import("../dist/config.js") + const config = loadConfig() + + assert.equal(config.worldId, "platform-world") + assert.equal(config.officeName, "Platform Name") + assert.equal(config.port, 19001) + assert.equal(config.publicPort, 9550) + assert.equal(config.publicAddr, "3.17.5.202") + assert.deepEqual(config.gatewayUrls, ["https://gateway.example.com"]) + assert.equal(config.password, "platform-password") + assert.equal(config.maxAgents, 42) + assert.equal(config.isPublic, false) + assert.equal(config.dataDir, "/platform-data") +}) + +test("loadConfig still supports legacy STAR_OFFICE_* env vars when platform vars are absent", async () => { + process.env.STAR_OFFICE_WORLD_ID = "legacy-world" + process.env.STAR_OFFICE_NAME = "Legacy Name" + process.env.STAR_OFFICE_PASSWORD = "legacy-password" + process.env.STAR_OFFICE_MAX_AGENTS = "7" + + const { loadConfig } = await import("../dist/config.js") + const config = loadConfig() + + assert.equal(config.worldId, "legacy-world") + assert.equal(config.officeName, "Legacy Name") + assert.equal(config.password, "legacy-password") + assert.equal(config.maxAgents, 7) + assert.equal(config.isPublic, true) +}) From 5562723cc317bcab8add8c5ae6a257d062ef533c Mon Sep 17 00:00:00 2001 From: Yilin Jing Date: Wed, 25 Mar 2026 11:24:04 +0800 Subject: [PATCH 2/2] chore: add changeset for platform env contract fix Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .changeset/platform-env-contract.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/platform-env-contract.md diff --git a/.changeset/platform-env-contract.md b/.changeset/platform-env-contract.md new file mode 100644 index 0000000..70b923a --- /dev/null +++ b/.changeset/platform-env-contract.md @@ -0,0 +1,5 @@ +--- +"@resciencelab/star-office-world": patch +--- + +Align env contract with platform: WORLD_ID, WORLD_NAME, WORLD_PASSWORD, MAX_AGENTS, WORLD_PUBLIC now take precedence over legacy STAR_OFFICE_* aliases. Added isPublic config support so deployed worlds honor platform visibility settings.