From 3198c10fba9da1ddeedb612f7d91b4688cf36f86 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 10 Apr 2026 19:25:35 +0100 Subject: [PATCH] fix: stabilize Codex harness landing checks --- scripts/check-no-raw-channel-fetch.mjs | 6 +- src/agents/btw.test.ts | 1 + .../compact.hooks.harness.ts | 2 + src/config/schema.base.generated.ts | 26 ++++++++ .../server-startup-post-attach.test.ts | 1 + src/gateway/server-startup.test.ts | 2 + src/infra/install-package-dir.ts | 8 ++- src/plugins/install-security-scan.runtime.ts | 60 ++++++------------- 8 files changed, 60 insertions(+), 46 deletions(-) diff --git a/scripts/check-no-raw-channel-fetch.mjs b/scripts/check-no-raw-channel-fetch.mjs index bf10113f9e..9e563a52f4 100644 --- a/scripts/check-no-raw-channel-fetch.mjs +++ b/scripts/check-no-raw-channel-fetch.mjs @@ -16,7 +16,7 @@ const sourceRoots = ["src/channels", "src/routing", "src/line", "extensions"]; const allowedRawFetchCallsites = new Set([ bundledPluginCallsite("bluebubbles", "src/test-harness.ts", 128), bundledPluginCallsite("bluebubbles", "src/types.ts", 181), - bundledPluginCallsite("browser", "src/browser/cdp.helpers.ts", 235), + bundledPluginCallsite("browser", "src/browser/cdp.helpers.ts", 253), bundledPluginCallsite("browser", "src/browser/client-fetch.ts", 192), bundledPluginCallsite("browser", "src/browser/test-fetch.ts", 24), bundledPluginCallsite("browser", "src/browser/test-fetch.ts", 27), @@ -41,8 +41,8 @@ const allowedRawFetchCallsites = new Set([ bundledPluginCallsite("minimax", "oauth.ts", 107), bundledPluginCallsite("minimax", "tts.ts", 52), bundledPluginCallsite("msteams", "src/graph.ts", 47), - bundledPluginCallsite("msteams", "src/sdk.ts", 292), - bundledPluginCallsite("msteams", "src/sdk.ts", 333), + bundledPluginCallsite("msteams", "src/sdk.ts", 329), + bundledPluginCallsite("msteams", "src/sdk.ts", 370), bundledPluginCallsite("ollama", "src/stream.ts", 649), bundledPluginCallsite("openai", "tts.ts", 133), bundledPluginCallsite("qa-channel", "src/bus-client.ts", 41), diff --git a/src/agents/btw.test.ts b/src/agents/btw.test.ts index 433692bb63..eb6ab72092 100644 --- a/src/agents/btw.test.ts +++ b/src/agents/btw.test.ts @@ -29,6 +29,7 @@ vi.mock("@mariozechner/pi-ai", async () => { }); vi.mock("@mariozechner/pi-coding-agent", () => ({ + generateSummary: vi.fn(async () => "summary"), SessionManager: { open: () => ({ getLeafEntry: getLeafEntryMock, diff --git a/src/agents/pi-embedded-runner/compact.hooks.harness.ts b/src/agents/pi-embedded-runner/compact.hooks.harness.ts index 103b6e5f14..03fe461c0a 100644 --- a/src/agents/pi-embedded-runner/compact.hooks.harness.ts +++ b/src/agents/pi-embedded-runner/compact.hooks.harness.ts @@ -269,6 +269,7 @@ export async function loadCompactHooksHarness(): Promise<{ create: vi.fn(() => ({})), }, estimateTokens: estimateTokensMock, + generateSummary: vi.fn(async () => "summary"), })); vi.doMock("../session-tool-result-guard-wrapper.js", () => ({ @@ -457,6 +458,7 @@ export async function loadCompactHooksHarness(): Promise<{ })); vi.doMock("../agent-scope.js", () => ({ + listAgentEntries: vi.fn(() => []), resolveSessionAgentId: resolveSessionAgentIdMock, resolveSessionAgentIds: vi.fn(() => ({ defaultAgentId: "main", sessionAgentId: "main" })), })); diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index 8b19d1bd9c..a16c353e30 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -2954,6 +2954,19 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { }, additionalProperties: {}, }, + embeddedHarness: { + type: "object", + properties: { + runtime: { + type: "string", + }, + fallback: { + type: "string", + enum: ["pi", "none"], + }, + }, + additionalProperties: false, + }, model: { anyOf: [ { @@ -5415,6 +5428,19 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { systemPromptOverride: { type: "string", }, + embeddedHarness: { + type: "object", + properties: { + runtime: { + type: "string", + }, + fallback: { + type: "string", + enum: ["pi", "none"], + }, + }, + additionalProperties: false, + }, model: { anyOf: [ { diff --git a/src/gateway/server-startup-post-attach.test.ts b/src/gateway/server-startup-post-attach.test.ts index 11894cde32..9c14686534 100644 --- a/src/gateway/server-startup-post-attach.test.ts +++ b/src/gateway/server-startup-post-attach.test.ts @@ -46,6 +46,7 @@ vi.mock("../agents/subagent-registry.js", () => ({ })); vi.mock("../config/paths.js", () => ({ + STATE_DIR: "/tmp/openclaw-state", resolveStateDir: vi.fn(() => "/tmp/openclaw-state"), })); diff --git a/src/gateway/server-startup.test.ts b/src/gateway/server-startup.test.ts index fe2e390561..e90bb07a63 100644 --- a/src/gateway/server-startup.test.ts +++ b/src/gateway/server-startup.test.ts @@ -143,6 +143,7 @@ describe("gateway startup primary model warmup", () => { expect(selectAgentHarnessMock).toHaveBeenCalledWith({ provider: "codex", modelId: "gpt-5.4", + config: cfg, }); expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled(); expect(resolveModelMock).not.toHaveBeenCalled(); @@ -188,6 +189,7 @@ describe("gateway startup primary model warmup", () => { expect(selectAgentHarnessMock).toHaveBeenCalledWith({ provider: "openai-codex", modelId: "gpt-5.4", + config: cfg, }); expect(ensureOpenClawModelsJsonMock).toHaveBeenCalledWith(cfg, "/tmp/agent"); expect(resolveModelMock).toHaveBeenCalled(); diff --git a/src/infra/install-package-dir.ts b/src/infra/install-package-dir.ts index ebbb291d8f..7703cd5739 100644 --- a/src/infra/install-package-dir.ts +++ b/src/infra/install-package-dir.ts @@ -218,7 +218,13 @@ export async function installPackageDir(params: { candidatePaths: [canonicalTargetDir], }); stageDir = await fs.mkdtemp(path.join(installBaseRealPath, ".openclaw-install-stage-")); - await fs.cp(params.sourceDir, stageDir, { recursive: true, verbatimSymlinks: true }); + await fs.cp(params.sourceDir, stageDir, { + recursive: true, + // Keep relative symlinks relative to the staged copy. Node's default + // rewrites them toward the source tree, which makes valid vendored + // package links look like install-root escapes during post-copy scans. + verbatimSymlinks: true, + }); } catch (err) { return await fail(`${params.copyErrorPrefix}: ${String(err)}`, err); } diff --git a/src/plugins/install-security-scan.runtime.ts b/src/plugins/install-security-scan.runtime.ts index f02a2d79b1..4cadf448b2 100644 --- a/src/plugins/install-security-scan.runtime.ts +++ b/src/plugins/install-security-scan.runtime.ts @@ -187,9 +187,11 @@ async function inspectNodeModulesSymlinkTarget(params: { const resolvedTargetStats = await fs.stat(resolvedTargetPath); const resolvedTargetRelativePath = path.relative(params.rootRealPath, resolvedTargetPath); return { - blockedDirectoryFinding: findBlockedPackageDirectoryInPath({ - pathRelativeToRoot: resolvedTargetRelativePath, - }), + blockedDirectoryFinding: resolvedTargetStats.isDirectory() + ? findBlockedPackageDirectoryInPath({ + pathRelativeToRoot: resolvedTargetRelativePath, + }) + : undefined, blockedFileFinding: resolvedTargetStats.isFile() ? findBlockedPackageFileAliasInPath({ pathRelativeToRoot: resolvedTargetRelativePath, @@ -262,16 +264,6 @@ function resolvePackageManifestTraversalLimits(): PackageManifestTraversalLimits }; } -async function resolvePackageManifestPath(dir: string): Promise { - const manifestPath = path.join(dir, "package.json"); - try { - const stats = await fs.stat(manifestPath); - return stats.isFile() ? manifestPath : undefined; - } catch { - return undefined; - } -} - async function collectPackageManifestPaths( rootDir: string, ): Promise { @@ -280,6 +272,8 @@ async function collectPackageManifestPaths( const queue: Array<{ depth: number; dir: string }> = [{ depth: 0, dir: rootDir }]; const packageManifestPaths: string[] = []; const visitedDirectories = new Set(); + let firstBlockedDirectoryFinding: BlockedPackageDirectoryFinding | undefined; + let firstBlockedFileFinding: BlockedPackageFileFinding | undefined; let queueIndex = 0; while (queueIndex < queue.length) { @@ -331,19 +325,13 @@ async function collectPackageManifestPaths( directoryRelativePath: relativeNextPath, }); if (blockedDirectoryFinding) { - return { - blockedDirectoryFinding, - packageManifestPaths, - }; + firstBlockedDirectoryFinding ??= blockedDirectoryFinding; } const blockedFileFinding = findBlockedNodeModulesFileAlias({ fileRelativePath: relativeNextPath, }); if (blockedFileFinding) { - return { - blockedFileFinding, - packageManifestPaths, - }; + firstBlockedFileFinding ??= blockedFileFinding; } if (pathContainsNodeModulesSegment(relativeNextPath)) { const symlinkTargetInspection = await inspectNodeModulesSymlinkTarget({ @@ -352,16 +340,10 @@ async function collectPackageManifestPaths( symlinkRelativePath: relativeNextPath, }); if (symlinkTargetInspection.blockedDirectoryFinding) { - return { - blockedDirectoryFinding: symlinkTargetInspection.blockedDirectoryFinding, - packageManifestPaths, - }; + firstBlockedDirectoryFinding ??= symlinkTargetInspection.blockedDirectoryFinding; } if (symlinkTargetInspection.blockedFileFinding) { - return { - blockedFileFinding: symlinkTargetInspection.blockedFileFinding, - packageManifestPaths, - }; + firstBlockedFileFinding ??= symlinkTargetInspection.blockedFileFinding; } } continue; @@ -371,14 +353,7 @@ async function collectPackageManifestPaths( directoryRelativePath: relativeNextPath, }); if (blockedDirectoryFinding) { - const manifestPath = await resolvePackageManifestPath(nextPath); - if (manifestPath) { - packageManifestPaths.push(manifestPath); - } - return { - blockedDirectoryFinding, - packageManifestPaths, - }; + firstBlockedDirectoryFinding ??= blockedDirectoryFinding; } queue.push({ depth: current.depth + 1, dir: nextPath }); continue; @@ -388,10 +363,7 @@ async function collectPackageManifestPaths( fileRelativePath: relativeNextPath, }); if (blockedFileFinding) { - return { - blockedFileFinding, - packageManifestPaths, - }; + firstBlockedFileFinding ??= blockedFileFinding; } } if (entry.isFile() && entry.name === "package.json") { @@ -405,7 +377,11 @@ async function collectPackageManifestPaths( } } - return { packageManifestPaths }; + return { + packageManifestPaths, + blockedDirectoryFinding: firstBlockedDirectoryFinding, + blockedFileFinding: firstBlockedFileFinding, + }; } async function scanManifestDependencyDenylist(params: {