diff --git a/kiloclaw/controller/src/config-writer.test.ts b/kiloclaw/controller/src/config-writer.test.ts index ab1d43fc8f..a91ca60004 100644 --- a/kiloclaw/controller/src/config-writer.test.ts +++ b/kiloclaw/controller/src/config-writer.test.ts @@ -243,6 +243,76 @@ describe('generateBaseConfig', () => { expect(config.models.providers.kilocode.models).toEqual([{ id: 'kept/model', name: 'Kept' }]); }); + it('sets X-KiloCode-OrganizationId header when KILOCODE_ORGANIZATION_ID is set', () => { + const { deps } = fakeDeps(); + const env = { ...minimalEnv(), KILOCODE_ORGANIZATION_ID: 'org_abc123' }; + const config = generateBaseConfig(env, '/tmp/openclaw.json', deps); + + expect(config.models.providers.kilocode.headers['X-KiloCode-OrganizationId']).toBe( + 'org_abc123' + ); + expect(config.models.providers.kilocode.models).toEqual([]); + }); + + it('does not set org header when KILOCODE_ORGANIZATION_ID is not set', () => { + const { deps } = fakeDeps(); + const config = generateBaseConfig(minimalEnv(), '/tmp/openclaw.json', deps); + + // No kilocode provider entry created when neither baseUrl nor orgId is set + expect(config.models).toBeUndefined(); + }); + + it('preserves existing kilocode config when adding org header', () => { + const existing = JSON.stringify({ + models: { + providers: { + kilocode: { + baseUrl: 'https://tunnel.example.com/', + headers: { 'X-Custom': 'value' }, + models: [{ id: 'kept/model', name: 'Kept' }], + }, + }, + }, + }); + const { deps } = fakeDeps(existing); + const env = { ...minimalEnv(), KILOCODE_ORGANIZATION_ID: 'org_xyz789' }; + const config = generateBaseConfig(env, '/tmp/openclaw.json', deps); + + expect(config.models.providers.kilocode.headers['X-KiloCode-OrganizationId']).toBe( + 'org_xyz789' + ); + expect(config.models.providers.kilocode.headers['X-Custom']).toBe('value'); + expect(config.models.providers.kilocode.baseUrl).toBe('https://tunnel.example.com/'); + expect(config.models.providers.kilocode.models).toEqual([{ id: 'kept/model', name: 'Kept' }]); + }); + + it('removes stale org header when KILOCODE_ORGANIZATION_ID is no longer set', () => { + const existing = JSON.stringify({ + models: { + providers: { + kilocode: { + baseUrl: 'https://tunnel.example.com/', + headers: { + 'X-KiloCode-OrganizationId': 'org_old_stale', + 'X-Custom': 'preserved', + }, + models: [{ id: 'kept/model', name: 'Kept' }], + }, + }, + }, + }); + const { deps } = fakeDeps(existing); + // No KILOCODE_ORGANIZATION_ID in env + const config = generateBaseConfig(minimalEnv(), '/tmp/openclaw.json', deps); + + // Stale org header removed + expect(config.models.providers.kilocode.headers['X-KiloCode-OrganizationId']).toBeUndefined(); + // Other headers and config preserved + expect(config.models.providers.kilocode.headers['X-Custom']).toBe('preserved'); + expect(config.models.providers.kilocode.baseUrl).toBe('https://tunnel.example.com/'); + expect(config.models.providers.kilocode.models).toEqual([{ id: 'kept/model', name: 'Kept' }]); + }); + it('removes agents.defaults.models allowlist left by openclaw onboard', () => { const existing = JSON.stringify({ agents: { diff --git a/kiloclaw/controller/src/config-writer.ts b/kiloclaw/controller/src/config-writer.ts index 5f8d3aaa3c..ca0caaaec5 100644 --- a/kiloclaw/controller/src/config-writer.ts +++ b/kiloclaw/controller/src/config-writer.ts @@ -180,6 +180,24 @@ export function generateBaseConfig( console.log(`Overriding kilocode base URL: ${env.KILOCODE_API_BASE_URL}`); } + // Pass org scope to KiloCode provider as request header when available. + // This is used by OpenClaw provider requests (not Kilo CLI). + // Header name matches ORGANIZATION_ID_HEADER in src/lib/constants.ts. + if (env.KILOCODE_ORGANIZATION_ID) { + config.models = config.models ?? {}; + config.models.providers = config.models.providers ?? {}; + config.models.providers.kilocode = config.models.providers.kilocode ?? {}; + config.models.providers.kilocode.headers = config.models.providers.kilocode.headers ?? {}; + config.models.providers.kilocode.headers['X-KiloCode-OrganizationId'] = + env.KILOCODE_ORGANIZATION_ID; + config.models.providers.kilocode.models = config.models.providers.kilocode.models ?? []; + console.log('Configured KiloCode organization header from KILOCODE_ORGANIZATION_ID'); + } else { + // Remove stale org header from previous boots (e.g., instance was transferred + // from org to personal, or org was deleted). + delete config.models?.providers?.kilocode?.headers?.['X-KiloCode-OrganizationId']; + } + // User-selected default model override. if (env.KILOCODE_DEFAULT_MODEL) { config.agents = config.agents ?? {};