diff --git a/scripts/load-channel-config-surface.ts b/scripts/load-channel-config-surface.ts index 855643fc2ad..d729a0ef8eb 100644 --- a/scripts/load-channel-config-surface.ts +++ b/scripts/load-channel-config-surface.ts @@ -281,17 +281,23 @@ export async function loadChannelConfigSurfaceModule( candidatePath: string, ): { schema: Record; uiHints?: Record } | null => { try { - const bunLoaded = loadViaBun(candidatePath); - if (bunLoaded && isBuiltChannelConfigSchema(bunLoaded)) { - return bunLoaded; + // Prefer the source-aware Jiti path so generated config metadata stays + // stable before and after build output exists in the repo. + const imported = loadViaJiti(candidatePath); + const resolved = resolveConfigSchemaExport(imported); + if (resolved) { + return resolved; } } catch { - // Bun is the fastest happy path, but some plugin config modules only load - // correctly through the source-aware Jiti alias setup. + // Fall back to Bun below when the source-aware loader cannot resolve the + // module graph in the current environment. } - const imported = loadViaJiti(candidatePath); - return resolveConfigSchemaExport(imported); + const bunLoaded = loadViaBun(candidatePath); + if (bunLoaded && isBuiltChannelConfigSchema(bunLoaded)) { + return bunLoaded; + } + return null; }; try { diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 4cc0110ca45..bad407fa1ab 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -731,6 +731,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", enum: ["open", "disabled", "allowlist"], }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, historyLimit: { type: "integer", minimum: 0, @@ -763,77 +767,84 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ exclusiveMinimum: 0, maximum: 9007199254740991, }, - chunkMode: { - type: "string", - enum: ["length", "newline"], - }, - blockStreaming: { - type: "boolean", - }, - blockStreamingCoalesce: { + streaming: { type: "object", properties: { - minChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, - }, - maxChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, - }, - idleMs: { - type: "integer", - minimum: 0, - maximum: 9007199254740991, - }, - }, - additionalProperties: false, - }, - streaming: { - anyOf: [ - { - type: "boolean", - }, - { + mode: { type: "string", enum: ["off", "partial", "block", "progress"], }, - ], - }, - streamMode: { - type: "string", - enum: ["partial", "block", "off"], - }, - draftChunk: { - type: "object", - properties: { - minChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, + chunkMode: { + type: "string", + enum: ["length", "newline"], }, - maxChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, + preview: { + type: "object", + properties: { + chunk: { + type: "object", + properties: { + minChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + maxChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + breakPreference: { + anyOf: [ + { + type: "string", + const: "paragraph", + }, + { + type: "string", + const: "newline", + }, + { + type: "string", + const: "sentence", + }, + ], + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, }, - breakPreference: { - anyOf: [ - { - type: "string", - const: "paragraph", + block: { + type: "object", + properties: { + enabled: { + type: "boolean", }, - { - type: "string", - const: "newline", + coalesce: { + type: "object", + properties: { + minChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + maxChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + idleMs: { + type: "integer", + minimum: 0, + maximum: 9007199254740991, + }, + }, + additionalProperties: false, }, - { - type: "string", - const: "sentence", - }, - ], + }, + additionalProperties: false, }, }, additionalProperties: false, @@ -950,6 +961,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", const: "all", }, + { + type: "string", + const: "batched", + }, ], }, dmPolicy: { @@ -959,14 +974,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ allowFrom: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, defaultTo: { @@ -985,14 +993,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ allowFrom: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, groupEnabled: { @@ -1001,14 +1002,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ groupChannels: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, }, @@ -1092,27 +1086,13 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ users: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, roles: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, channels: { @@ -1123,9 +1103,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ additionalProperties: { type: "object", properties: { - allow: { - type: "boolean", - }, requireMention: { type: "boolean", }, @@ -1198,27 +1175,13 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ users: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, roles: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, systemPrompt: { @@ -1299,14 +1262,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ approvers: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, agentFilter: { @@ -1939,6 +1895,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", enum: ["open", "disabled", "allowlist"], }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, historyLimit: { type: "integer", minimum: 0, @@ -1971,77 +1931,84 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ exclusiveMinimum: 0, maximum: 9007199254740991, }, - chunkMode: { - type: "string", - enum: ["length", "newline"], - }, - blockStreaming: { - type: "boolean", - }, - blockStreamingCoalesce: { + streaming: { type: "object", properties: { - minChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, - }, - maxChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, - }, - idleMs: { - type: "integer", - minimum: 0, - maximum: 9007199254740991, - }, - }, - additionalProperties: false, - }, - streaming: { - anyOf: [ - { - type: "boolean", - }, - { + mode: { type: "string", enum: ["off", "partial", "block", "progress"], }, - ], - }, - streamMode: { - type: "string", - enum: ["partial", "block", "off"], - }, - draftChunk: { - type: "object", - properties: { - minChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, + chunkMode: { + type: "string", + enum: ["length", "newline"], }, - maxChars: { - type: "integer", - exclusiveMinimum: 0, - maximum: 9007199254740991, + preview: { + type: "object", + properties: { + chunk: { + type: "object", + properties: { + minChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + maxChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + breakPreference: { + anyOf: [ + { + type: "string", + const: "paragraph", + }, + { + type: "string", + const: "newline", + }, + { + type: "string", + const: "sentence", + }, + ], + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, }, - breakPreference: { - anyOf: [ - { - type: "string", - const: "paragraph", + block: { + type: "object", + properties: { + enabled: { + type: "boolean", }, - { - type: "string", - const: "newline", + coalesce: { + type: "object", + properties: { + minChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + maxChars: { + type: "integer", + exclusiveMinimum: 0, + maximum: 9007199254740991, + }, + idleMs: { + type: "integer", + minimum: 0, + maximum: 9007199254740991, + }, + }, + additionalProperties: false, }, - { - type: "string", - const: "sentence", - }, - ], + }, + additionalProperties: false, }, }, additionalProperties: false, @@ -2158,6 +2125,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", const: "all", }, + { + type: "string", + const: "batched", + }, ], }, dmPolicy: { @@ -2167,14 +2138,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ allowFrom: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, defaultTo: { @@ -2193,14 +2157,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ allowFrom: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, groupEnabled: { @@ -2209,14 +2166,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ groupChannels: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, }, @@ -2300,27 +2250,13 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ users: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, roles: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, channels: { @@ -2331,9 +2267,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ additionalProperties: { type: "object", properties: { - allow: { - type: "boolean", - }, requireMention: { type: "boolean", }, @@ -2406,27 +2339,13 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ users: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, roles: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, systemPrompt: { @@ -2507,14 +2426,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ approvers: { type: "array", items: { - anyOf: [ - { - type: "string", - }, - { - type: "number", - }, - ], + type: "string", }, }, agentFilter: { @@ -4393,9 +4305,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ enabled: { type: "boolean", }, - allow: { - type: "boolean", - }, requireMention: { type: "boolean", }, @@ -4636,11 +4545,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, - streamMode: { - default: "replace", - type: "string", - enum: ["replace", "status_final", "append"], - }, mediaMaxMb: { type: "number", exclusiveMinimum: 0, @@ -4659,6 +4563,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", const: "all", }, + { + type: "string", + const: "batched", + }, ], }, actions: { @@ -4775,9 +4683,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ enabled: { type: "boolean", }, - allow: { - type: "boolean", - }, requireMention: { type: "boolean", }, @@ -5018,11 +4923,6 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, - streamMode: { - default: "replace", - type: "string", - enum: ["replace", "status_final", "append"], - }, mediaMaxMb: { type: "number", exclusiveMinimum: 0, @@ -5041,6 +4941,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", const: "all", }, + { + type: "string", + const: "batched", + }, ], }, actions: { @@ -5097,7 +5001,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", }, }, - required: ["groupPolicy", "streamMode"], + required: ["groupPolicy"], additionalProperties: false, }, }, @@ -5105,7 +5009,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", }, }, - required: ["groupPolicy", "streamMode"], + required: ["groupPolicy"], additionalProperties: false, }, }, @@ -7929,6 +7833,22 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ tenantId: { type: "string", }, + authType: { + type: "string", + enum: ["secret", "federated"], + }, + certificatePath: { + type: "string", + }, + certificateThumbprint: { + type: "string", + }, + useManagedIdentity: { + type: "boolean", + }, + managedIdentityClientId: { + type: "string", + }, webhook: { type: "object", properties: { @@ -7968,6 +7888,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", enum: ["open", "disabled", "allowlist"], }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, textChunkLimit: { type: "integer", exclusiveMinimum: 0, @@ -7977,6 +7901,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", enum: ["length", "newline"], }, + typingIndicator: { + type: "boolean", + }, blockStreaming: { type: "boolean", }, @@ -8250,6 +8177,33 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ minimum: 0, maximum: 9007199254740991, }, + delegatedAuth: { + type: "object", + properties: { + enabled: { + type: "boolean", + }, + scopes: { + type: "array", + items: { + type: "string", + }, + }, + }, + additionalProperties: false, + }, + sso: { + type: "object", + properties: { + enabled: { + type: "boolean", + }, + connectionName: { + type: "string", + }, + }, + additionalProperties: false, + }, }, required: ["dmPolicy", "groupPolicy"], additionalProperties: false, @@ -8546,6 +8500,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ minimum: 0, maximum: 9007199254740991, }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, dms: { type: "object", propertyNames: { @@ -8883,6 +8841,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ minimum: 0, maximum: 9007199254740991, }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, dms: { type: "object", propertyNames: { @@ -14965,6 +14927,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", enum: ["open", "disabled", "allowlist"], }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, historyLimit: { type: "integer", minimum: 0, @@ -15214,6 +15180,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ type: "string", enum: ["open", "disabled", "allowlist"], }, + contextVisibility: { + type: "string", + enum: ["all", "allowlist", "allowlist_quote"], + }, historyLimit: { type: "integer", minimum: 0, diff --git a/src/config/load-channel-config-surface.test.ts b/src/config/load-channel-config-surface.test.ts index c2771bfa517..dd77594b388 100644 --- a/src/config/load-channel-config-surface.test.ts +++ b/src/config/load-channel-config-surface.test.ts @@ -24,8 +24,105 @@ async function importLoaderWithMissingBun() { } } +async function importLoaderWithFailingJitiAndWorkingBun() { + const spawnSync = vi.fn(() => ({ + error: undefined, + status: 0, + stdout: JSON.stringify({ + schema: { + type: "object", + properties: { + ok: { type: "number" }, + }, + }, + }), + stderr: "", + })); + const createJiti = vi.fn(() => () => { + throw new Error("jiti failed"); + }); + vi.doMock("node:child_process", () => ({ spawnSync })); + vi.doMock("jiti", () => ({ createJiti })); + + try { + const imported = await importFreshModule< + typeof import("../../scripts/load-channel-config-surface.ts") + >(import.meta.url, "../../scripts/load-channel-config-surface.ts?scope=failing-jiti"); + return { + loadChannelConfigSurfaceModule: imported.loadChannelConfigSurfaceModule, + spawnSync, + createJiti, + }; + } finally { + vi.doUnmock("node:child_process"); + vi.doUnmock("jiti"); + } +} + describe("loadChannelConfigSurfaceModule", () => { - it("falls back to Jiti when bun is unavailable", async () => { + it("prefers the source-aware loader over bun when both succeed", async () => { + await withTempDir({ prefix: "openclaw-config-surface-" }, async (repoRoot) => { + const packageRoot = path.join(repoRoot, "extensions", "demo"); + const modulePath = path.join(packageRoot, "src", "config-schema.js"); + + fs.mkdirSync(path.join(packageRoot, "src"), { recursive: true }); + fs.writeFileSync( + path.join(packageRoot, "package.json"), + JSON.stringify({ name: "@openclaw/demo", type: "module" }, null, 2), + "utf8", + ); + fs.writeFileSync( + modulePath, + [ + "export const DemoChannelConfigSchema = {", + " schema: {", + " type: 'object',", + " properties: { ok: { type: 'string' } },", + " },", + "};", + "", + ].join("\n"), + "utf8", + ); + + const spawnSync = vi.fn(() => ({ + error: undefined, + status: 0, + stdout: JSON.stringify({ + schema: { + type: "object", + properties: { + ok: { type: "number" }, + }, + }, + }), + stderr: "", + })); + vi.doMock("node:child_process", () => ({ spawnSync })); + + try { + const imported = await importFreshModule< + typeof import("../../scripts/load-channel-config-surface.ts") + >(import.meta.url, "../../scripts/load-channel-config-surface.ts?scope=prefer-jiti"); + + await expect( + imported.loadChannelConfigSurfaceModule(modulePath, { repoRoot }), + ).resolves.toMatchObject({ + schema: { + type: "object", + properties: { + ok: { type: "string" }, + }, + }, + }); + expect(spawnSync).not.toHaveBeenCalled(); + } finally { + vi.doUnmock("node:child_process"); + } + }); + }); + + it("does not require bun when the source-aware loader succeeds", async () => { await withTempDir({ prefix: "openclaw-config-surface-" }, async (repoRoot) => { const packageRoot = path.join(repoRoot, "extensions", "demo"); const modulePath = path.join(packageRoot, "src", "config-schema.js"); @@ -61,6 +158,50 @@ describe("loadChannelConfigSurfaceModule", () => { }, }, }); + expect(spawnSync).not.toHaveBeenCalled(); + }); + }); + + it("falls back to bun when the source-aware loader fails", async () => { + await withTempDir({ prefix: "openclaw-config-surface-" }, async (repoRoot) => { + const packageRoot = path.join(repoRoot, "extensions", "demo"); + const modulePath = path.join(packageRoot, "src", "config-schema.js"); + + fs.mkdirSync(path.join(packageRoot, "src"), { recursive: true }); + fs.writeFileSync( + path.join(packageRoot, "package.json"), + JSON.stringify({ name: "@openclaw/demo", type: "module" }, null, 2), + "utf8", + ); + fs.writeFileSync( + modulePath, + [ + "export const DemoChannelConfigSchema = {", + " schema: {", + " type: 'object',", + " properties: { ok: { type: 'string' } },", + " },", + "};", + "", + ].join("\n"), + "utf8", + ); + + const { + loadChannelConfigSurfaceModule: loadWithFailingJiti, + spawnSync, + createJiti, + } = await importLoaderWithFailingJitiAndWorkingBun(); + + await expect(loadWithFailingJiti(modulePath, { repoRoot })).resolves.toMatchObject({ + schema: { + type: "object", + properties: { + ok: { type: "number" }, + }, + }, + }); + expect(createJiti).toHaveBeenCalled(); expect(spawnSync).toHaveBeenCalledWith("bun", expect.any(Array), expect.any(Object)); }); });