diff --git a/CHANGELOG.md b/CHANGELOG.md index 25332e961af..07fc11827a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ Docs: https://docs.openclaw.ai - Providers/Codex: pass agent and workspace directories into provider stream wrappers so Codex native `web_search` activation can evaluate the correct auth context, and smoke-test the built status-message runtime by resolving the emitted bundle name. Carries forward #67843; refs #65909. Thanks @neilofneils404. - Cron/models: keep `payload.model` as a per-job primary that can use configured fallbacks, while still letting `payload.fallbacks: []` make cron runs strict and avoid hidden agent-primary retries. Refs #73023. Thanks @pavelyortho-cyber. - Models/fallbacks: treat user-selected session models as exact choices, so `/model ollama/...` and model-picker switches fail visibly when the selected provider is unreachable instead of answering from an unrelated configured fallback. Fixes #73023. Thanks @pavelyortho-cyber. -- Codex harness: keep ChatGPT subscription app-server runs from inheriting `CODEX_API_KEY` or `OPENAI_API_KEY`, and fall back to `CODEX_API_KEY` / `OPENAI_API_KEY` app-server login only when no Codex account is available. Fixes #73057. Thanks @holgergruenhagen. +- Codex harness: keep ChatGPT subscription app-server runs from inheriting `CODEX_API_KEY` or `OPENAI_API_KEY`, and fall back to `CODEX_API_KEY` / `OPENAI_API_KEY` app-server login only when no Codex account is available. Fixes #73057. Thanks @holgergruenhagen and @pashpashpash. - CLI/model probes: fail local `infer model run` probes when the provider returns no text output, so unreachable local providers and empty completions no longer look like successful smoke tests. Refs #73023. Thanks @pavelyortho-cyber. - CLI/Ollama: run local `infer model run` through the lean provider completion path and skip global model discovery for one-shot local probes, so Ollama smoke tests no longer pay full chat-agent/tool startup cost or hang before the native `/api/chat` request. Fixes #72851. Thanks @TotalRes2020. - Doctor/gateway services: ignore launchd/systemd companion services that only reference the gateway as a dependency, suppress inactive Linux extra-service warnings, and avoid rewriting a running systemd gateway command/entrypoint during doctor repair. Carries forward #39118. Thanks @therk. diff --git a/src/plugins/contracts/boundary-invariants.test.ts b/src/plugins/contracts/boundary-invariants.test.ts index c6f7ddf8c46..98ee6173e3c 100644 --- a/src/plugins/contracts/boundary-invariants.test.ts +++ b/src/plugins/contracts/boundary-invariants.test.ts @@ -1,6 +1,7 @@ import { readFileSync, readdirSync } from "node:fs"; import { dirname, relative, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; +import ts from "typescript"; import { describe, expect, it } from "vitest"; const SRC_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); @@ -165,14 +166,46 @@ function isAllowedBundledExtensionImport(specifier: string): boolean { } function collectBundledExtensionImports(source: string): string[] { - const matches = [ - ...source.matchAll(/from\s+["']([^"']*extensions\/[^"']+)["']/gu), - ...source.matchAll(/vi\.(?:mock|doMock)\(\s*["']([^"']*extensions\/[^"']+)["']/gu), - ...source.matchAll(/importActual(?:<[^>]*>)?\(\s*["']([^"']*extensions\/[^"']+)["']/gu), - ]; - return matches - .map((match) => match[1]) - .filter((specifier): specifier is string => typeof specifier === "string"); + const sourceFile = ts.createSourceFile( + "boundary-invariants-input.ts", + source, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ); + const specifiers: string[] = []; + + function visit(node: ts.Node): void { + if ( + (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && + node.moduleSpecifier && + ts.isStringLiteralLike(node.moduleSpecifier) + ) { + specifiers.push(node.moduleSpecifier.text); + } + if (ts.isCallExpression(node) && isBundledExtensionImportHelperCall(node.expression)) { + const firstArgument = node.arguments[0]; + if (firstArgument && ts.isStringLiteralLike(firstArgument)) { + specifiers.push(firstArgument.text); + } + } + ts.forEachChild(node, visit); + } + + visit(sourceFile); + return specifiers.filter((specifier) => specifier.includes("extensions/")); +} + +function isBundledExtensionImportHelperCall(expression: ts.Expression): boolean { + if (ts.isPropertyAccessExpression(expression)) { + return ( + ((expression.name.text === "mock" || expression.name.text === "doMock") && + ts.isIdentifier(expression.expression) && + expression.expression.text === "vi") || + expression.name.text === "importActual" + ); + } + return ts.isIdentifier(expression) && expression.text === "importActual"; } function collectTypedHookNames(source: string): string[] {