fix: preserve linked install unsafe flag and baseline regressions

This commit is contained in:
Peter Steinberger
2026-04-04 12:26:42 +09:00
parent 8a8ea94228
commit 406f06dcc5
6 changed files with 87 additions and 16 deletions

View File

@@ -120,6 +120,7 @@ Docs: https://docs.openclaw.ai
- Providers/OpenAI Codex: add forward-compat `openai-codex/gpt-5.4-mini` synthesis across provider runtime, model catalog, and model listing so Codex mini works before bundled Pi catalog updates land. - Providers/OpenAI Codex: add forward-compat `openai-codex/gpt-5.4-mini` synthesis across provider runtime, model catalog, and model listing so Codex mini works before bundled Pi catalog updates land.
- Plugins/marketplace: block remote marketplace symlink escapes without rewriting ordinary local marketplace install paths. (#60556) Thanks @eleqtrizit. - Plugins/marketplace: block remote marketplace symlink escapes without rewriting ordinary local marketplace install paths. (#60556) Thanks @eleqtrizit.
- Plugins/Kimi Coding: keep native Anthropic tool payloads on the Kimi coding endpoint while still parsing tagged tool-call text on the response path, so tool calls execute again instead of echoing raw markup. (#60391) Thanks @Eric-Guo. - Plugins/Kimi Coding: keep native Anthropic tool payloads on the Kimi coding endpoint while still parsing tagged tool-call text on the response path, so tool calls execute again instead of echoing raw markup. (#60391) Thanks @Eric-Guo.
- Plugins/install: preserve `--dangerously-force-unsafe-install` across linked plugin probes and linked hook-pack fallback probes so local `--link` installs honor the documented unsafe override. (#60624) Thanks @JerrettDavis.
## 2026.4.2 ## 2026.4.2

View File

@@ -183,7 +183,7 @@ export function normalizeCompatibilityConfig({
} }
} }
if (!changed) { if (!changed && changes.length === 0) {
return { config: cfg, changes: [] }; return { config: cfg, changes: [] };
} }
return { return {

View File

@@ -12,12 +12,31 @@ export const unsupportedSecretRefSurfacePatterns = [
"channels.whatsapp.accounts.*.creds.json", "channels.whatsapp.accounts.*.creds.json",
] as const; ] as const;
export { canonicalizeLegacySessionKey, isLegacyGroupSessionKey } from "./src/session-contract.js"; import { whatsappCommandPolicy as whatsappCommandPolicyImpl } from "./src/command-policy.js";
export { createWhatsAppPollFixture, expectWhatsAppPollSent } from "./src/outbound-test-support.js"; import { resolveLegacyGroupSessionKey as resolveLegacyGroupSessionKeyImpl } from "./src/group-session-contract.js";
export { whatsappCommandPolicy } from "./src/command-policy.js"; import { __testing as whatsappAccessControlTestingImpl } from "./src/inbound/access-control.js";
export { resolveLegacyGroupSessionKey } from "./src/group-session-contract.js"; import {
export { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "./src/normalize-target.js"; isWhatsAppGroupJid as isWhatsAppGroupJidImpl,
export { __testing as whatsappAccessControlTesting } from "./src/inbound/access-control.js"; normalizeWhatsAppTarget as normalizeWhatsAppTargetImpl,
} from "./src/normalize-target.js";
import {
createWhatsAppPollFixture as createWhatsAppPollFixtureImpl,
expectWhatsAppPollSent as expectWhatsAppPollSentImpl,
} from "./src/outbound-test-support.js";
import {
canonicalizeLegacySessionKey as canonicalizeLegacySessionKeyImpl,
isLegacyGroupSessionKey as isLegacyGroupSessionKeyImpl,
} from "./src/session-contract.js";
export const canonicalizeLegacySessionKey = canonicalizeLegacySessionKeyImpl;
export const createWhatsAppPollFixture = createWhatsAppPollFixtureImpl;
export const expectWhatsAppPollSent = expectWhatsAppPollSentImpl;
export const isLegacyGroupSessionKey = isLegacyGroupSessionKeyImpl;
export const isWhatsAppGroupJid = isWhatsAppGroupJidImpl;
export const normalizeWhatsAppTarget = normalizeWhatsAppTargetImpl;
export const resolveLegacyGroupSessionKey = resolveLegacyGroupSessionKeyImpl;
export const whatsappAccessControlTesting = whatsappAccessControlTestingImpl;
export const whatsappCommandPolicy = whatsappCommandPolicyImpl;
export function collectUnsupportedSecretRefConfigCandidates( export function collectUnsupportedSecretRefConfigCandidates(
raw: unknown, raw: unknown,

View File

@@ -46,17 +46,29 @@ function createModuleLoader() {
const loadModule = createModuleLoader(); const loadModule = createModuleLoader();
function resolveContractSurfaceModulePath(rootDir: string | undefined): string | null { function resolveContractSurfaceModulePaths(rootDir: string | undefined): string[] {
if (typeof rootDir !== "string" || rootDir.length === 0) { if (typeof rootDir !== "string" || rootDir.length === 0) {
return null; return [];
} }
const modulePaths: string[] = [];
for (const basename of CONTRACT_SURFACE_BASENAMES) { for (const basename of CONTRACT_SURFACE_BASENAMES) {
const modulePath = path.join(rootDir, basename); const modulePath = path.join(rootDir, basename);
if (fs.existsSync(modulePath)) { if (!fs.existsSync(modulePath)) {
return modulePath; continue;
} }
const compiledDistModulePath = modulePath.replace(
`${path.sep}dist-runtime${path.sep}`,
`${path.sep}dist${path.sep}`,
);
// Prefer the compiled dist module over the dist-runtime shim so Jiti sees
// the full named export surface instead of only local wrapper exports.
if (compiledDistModulePath !== modulePath && fs.existsSync(compiledDistModulePath)) {
modulePaths.push(compiledDistModulePath);
continue;
}
modulePaths.push(modulePath);
} }
return null; return modulePaths;
} }
function loadBundledChannelContractSurfaces(): unknown[] { function loadBundledChannelContractSurfaces(): unknown[] {
@@ -79,14 +91,18 @@ function loadBundledChannelContractSurfaceEntries(): Array<{
if (manifest.origin !== "bundled" || manifest.channels.length === 0) { if (manifest.origin !== "bundled" || manifest.channels.length === 0) {
continue; continue;
} }
const modulePath = resolveContractSurfaceModulePath(manifest.rootDir); const modulePaths = resolveContractSurfaceModulePaths(manifest.rootDir);
if (!modulePath) { if (modulePaths.length === 0) {
continue; continue;
} }
try { try {
const surface = Object.assign(
{},
...modulePaths.map((modulePath) => loadModule(modulePath)(modulePath) as object),
);
surfaces.push({ surfaces.push({
pluginId: manifest.id, pluginId: manifest.id,
surface: loadModule(modulePath)(modulePath), surface,
}); });
} catch { } catch {
continue; continue;

View File

@@ -897,8 +897,14 @@ function patchConfigForScopedAccount(params: {
ensureEnabled: boolean; ensureEnabled: boolean;
}): OpenClawConfig { }): OpenClawConfig {
const { cfg, channel, accountId, patch, ensureEnabled } = params; const { cfg, channel, accountId, patch, ensureEnabled } = params;
const channelConfig = cfg.channels?.[channel] as
| { accounts?: Record<string, unknown> }
| undefined;
const hasExistingAccounts = Boolean(
channelConfig?.accounts && Object.keys(channelConfig.accounts).length > 0,
);
const seededCfg = const seededCfg =
accountId === DEFAULT_ACCOUNT_ID accountId === DEFAULT_ACCOUNT_ID || hasExistingAccounts
? cfg ? cfg
: moveSingleAccountChannelSectionToDefaultAccount({ : moveSingleAccountChannelSectionToDefaultAccount({
cfg, cfg,

View File

@@ -567,11 +567,20 @@ function discoverInDirectory(params: {
candidates: PluginCandidate[]; candidates: PluginCandidate[];
diagnostics: PluginDiagnostic[]; diagnostics: PluginDiagnostic[];
seen: Set<string>; seen: Set<string>;
recurseDirectories?: boolean;
skipDirectories?: Set<string>; skipDirectories?: Set<string>;
visitedDirectories?: Set<string>;
}) { }) {
if (!fs.existsSync(params.dir)) { if (!fs.existsSync(params.dir)) {
return; return;
} }
const resolvedDir = safeRealpathSync(params.dir) ?? path.resolve(params.dir);
if (params.recurseDirectories) {
if (params.visitedDirectories?.has(resolvedDir)) {
return;
}
params.visitedDirectories?.add(resolvedDir);
}
let entries: fs.Dirent[] = []; let entries: fs.Dirent[] = [];
try { try {
entries = fs.readdirSync(params.dir, { withFileTypes: true }); entries = fs.readdirSync(params.dir, { withFileTypes: true });
@@ -695,6 +704,14 @@ function discoverInDirectory(params: {
manifest, manifest,
packageDir: fullPath, packageDir: fullPath,
}); });
continue;
}
if (params.recurseDirectories) {
discoverInDirectory({
...params,
dir: fullPath,
});
} }
} }
} }
@@ -894,6 +911,18 @@ export function discoverOpenClawPlugins(params: {
}); });
} }
if (roots.workspace && workspaceRoot) { if (roots.workspace && workspaceRoot) {
discoverInDirectory({
dir: workspaceRoot,
origin: "workspace",
ownershipUid: params.ownershipUid,
workspaceDir: workspaceRoot,
candidates,
diagnostics,
seen,
recurseDirectories: true,
skipDirectories: new Set([".openclaw"]),
visitedDirectories: new Set<string>(),
});
discoverInDirectory({ discoverInDirectory({
dir: roots.workspace, dir: roots.workspace,
origin: "workspace", origin: "workspace",