diff --git a/CHANGELOG.md b/CHANGELOG.md index d51f5383c70..cc7dc8b5ccd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai - Gateway/plugins: always send a non-empty `idempotencyKey` for plugin subagent runs, so dreaming narrative jobs stop failing gateway schema validation. (#65354) Thanks @CodeForgeNet and @vincentkoc. - Cron/isolated sessions: persist the right transcript path for each isolated run, including fresh session rollovers, so cron runs stop appending to stale session files. Thanks @samrusani and @vincentkoc. - Dreaming/cron: wake managed dreaming jobs immediately instead of waiting for the next heartbeat, so scheduled dreaming runs start when the cron fires. (#65053) Thanks @l0cka and @vincentkoc. +- QA/packaging: stop packaged QA helpers from crashing when optional scenario execution config is unavailable, so npm distributions can skip the repo-only scenario pack without breaking completion-cache and startup paths. (#65118) Thanks @EdderTalmor and @vincentkoc. ## 2026.4.11 diff --git a/extensions/qa-lab/src/scenario-catalog.test.ts b/extensions/qa-lab/src/scenario-catalog.test.ts index cd5ea2682c9..13b670e44dd 100644 --- a/extensions/qa-lab/src/scenario-catalog.test.ts +++ b/extensions/qa-lab/src/scenario-catalog.test.ts @@ -119,4 +119,8 @@ describe("qa scenario catalog", () => { }), ).toThrow(/gracefulFallbackAny entries must be strings/); }); + + it("returns undefined execution config for an unknown scenario id", () => { + expect(readQaScenarioExecutionConfig("missing-scenario-id")).toBeUndefined(); + }); }); diff --git a/extensions/qa-lab/src/scenario-catalog.ts b/extensions/qa-lab/src/scenario-catalog.ts index ffaca54946e..c20161a31ef 100644 --- a/extensions/qa-lab/src/scenario-catalog.ts +++ b/extensions/qa-lab/src/scenario-catalog.ts @@ -278,7 +278,15 @@ export function readQaScenarioPackMarkdown(): string { export function readQaScenarioPack(): QaScenarioPack { const packMarkdown = readTextFile(QA_SCENARIO_PACK_INDEX_PATH).trim(); if (!packMarkdown) { - throw new Error(`qa scenario pack not found: ${QA_SCENARIO_PACK_INDEX_PATH}`); + // The QA scenario pack is optional in npm distributions. Return an empty + // pack so completion cache updates and other consumers don't crash when + // the qa/scenarios/ directory is not shipped with the package. + return { + version: 1, + agent: { identityMarkdown: DEFAULT_QA_AGENT_IDENTITY_MARKDOWN }, + kickoffTask: "QA scenarios not available in this distribution.", + scenarios: [], + }; } const parsedPack = parseQaYamlWithContext( qaScenarioPackSchema, @@ -344,7 +352,7 @@ export function readQaScenarioById(id: string): QaSeedScenario { } export function readQaScenarioExecutionConfig(id: string): Record | undefined { - return readQaScenarioById(id).execution?.config; + return readQaScenarioPack().scenarios.find((candidate) => candidate.id === id)?.execution?.config; } export function validateQaScenarioExecutionConfig(config: Record) {