mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:40:44 +00:00
Tests: narrow bootstrap routing coverage
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import type { BootstrapMode } from "../../bootstrap-mode.js";
|
||||
import { resolveBootstrapMode } from "../../bootstrap-mode.js";
|
||||
import { buildAgentUserPromptPrefix } from "../../system-prompt.js";
|
||||
|
||||
export type AttemptBootstrapRoutingInput = {
|
||||
workspaceBootstrapPending: boolean;
|
||||
bootstrapContextRunKind?: "default" | "heartbeat" | "cron";
|
||||
trigger?: string;
|
||||
sessionKey?: string;
|
||||
isPrimaryRun: boolean;
|
||||
isCanonicalWorkspace?: boolean;
|
||||
effectiveWorkspace: string;
|
||||
resolvedWorkspace: string;
|
||||
hasBootstrapFileAccess: boolean;
|
||||
};
|
||||
|
||||
export type AttemptBootstrapRouting = {
|
||||
bootstrapMode: BootstrapMode;
|
||||
shouldStripBootstrapFromContext: boolean;
|
||||
userPromptPrefixText?: string;
|
||||
};
|
||||
|
||||
export type AttemptWorkspaceBootstrapRoutingInput = Omit<
|
||||
AttemptBootstrapRoutingInput,
|
||||
"workspaceBootstrapPending"
|
||||
> & {
|
||||
isWorkspaceBootstrapPending: (workspaceDir: string) => Promise<boolean>;
|
||||
};
|
||||
|
||||
export function shouldStripBootstrapFromEmbeddedContext(_params: {
|
||||
bootstrapMode: BootstrapMode;
|
||||
}): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resolveAttemptBootstrapRouting(
|
||||
params: AttemptBootstrapRoutingInput,
|
||||
): AttemptBootstrapRouting {
|
||||
const bootstrapMode = resolveBootstrapMode({
|
||||
bootstrapPending: params.workspaceBootstrapPending,
|
||||
runKind: params.bootstrapContextRunKind ?? "default",
|
||||
isInteractiveUserFacing: params.trigger === "user" || params.trigger === "manual",
|
||||
isPrimaryRun: params.isPrimaryRun,
|
||||
isCanonicalWorkspace:
|
||||
(params.isCanonicalWorkspace ?? true) &&
|
||||
params.effectiveWorkspace === params.resolvedWorkspace,
|
||||
hasBootstrapFileAccess: params.hasBootstrapFileAccess,
|
||||
});
|
||||
|
||||
return {
|
||||
bootstrapMode,
|
||||
shouldStripBootstrapFromContext: shouldStripBootstrapFromEmbeddedContext({
|
||||
bootstrapMode,
|
||||
}),
|
||||
userPromptPrefixText: buildAgentUserPromptPrefix({
|
||||
bootstrapMode,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveAttemptWorkspaceBootstrapRouting(
|
||||
params: AttemptWorkspaceBootstrapRoutingInput,
|
||||
): Promise<AttemptBootstrapRouting> {
|
||||
const workspaceBootstrapPending = await params.isWorkspaceBootstrapPending(
|
||||
params.resolvedWorkspace,
|
||||
);
|
||||
return resolveAttemptBootstrapRouting({
|
||||
...params,
|
||||
workspaceBootstrapPending,
|
||||
});
|
||||
}
|
||||
@@ -1,60 +1,28 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
cleanupTempPaths,
|
||||
createContextEngineAttemptRunner,
|
||||
getHoisted,
|
||||
resetEmbeddedAttemptHarness,
|
||||
} from "./attempt.spawn-workspace.test-support.js";
|
||||
|
||||
const hoisted = getHoisted();
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveAttemptWorkspaceBootstrapRouting } from "./attempt-bootstrap-routing.js";
|
||||
|
||||
describe("runEmbeddedAttempt bootstrap routing", () => {
|
||||
const tempPaths: string[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
resetEmbeddedAttemptHarness();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupTempPaths(tempPaths);
|
||||
});
|
||||
|
||||
it("resolves bootstrap pending from the canonical workspace instead of a copied sandbox", async () => {
|
||||
const sandboxWorkspace = "/tmp/openclaw-sandbox-copy";
|
||||
let capturedPrompt = "";
|
||||
|
||||
hoisted.resolveSandboxContextMock.mockResolvedValue({
|
||||
enabled: true,
|
||||
workspaceAccess: "ro",
|
||||
workspaceDir: sandboxWorkspace,
|
||||
});
|
||||
hoisted.isWorkspaceBootstrapPendingMock.mockImplementation(async (workspaceDir: string) => {
|
||||
const canonicalWorkspace = "/tmp/openclaw-canonical-workspace";
|
||||
const isWorkspaceBootstrapPending = vi.fn(async (workspaceDir: string) => {
|
||||
return workspaceDir === sandboxWorkspace;
|
||||
});
|
||||
|
||||
await createContextEngineAttemptRunner({
|
||||
sessionKey: "agent:main:bootstrap-canonical-workspace",
|
||||
tempPaths,
|
||||
contextEngine: {
|
||||
assemble: async ({ messages }) => ({
|
||||
messages,
|
||||
estimatedTokens: 1,
|
||||
}),
|
||||
},
|
||||
attemptOverrides: {
|
||||
disableTools: true,
|
||||
},
|
||||
sessionPrompt: async (session, prompt) => {
|
||||
capturedPrompt = prompt;
|
||||
session.messages = [
|
||||
...session.messages,
|
||||
{ role: "assistant", content: "done", timestamp: 2 } as never,
|
||||
];
|
||||
},
|
||||
const routing = await resolveAttemptWorkspaceBootstrapRouting({
|
||||
isWorkspaceBootstrapPending,
|
||||
trigger: "user",
|
||||
isPrimaryRun: true,
|
||||
isCanonicalWorkspace: true,
|
||||
effectiveWorkspace: sandboxWorkspace,
|
||||
resolvedWorkspace: canonicalWorkspace,
|
||||
hasBootstrapFileAccess: true,
|
||||
});
|
||||
|
||||
expect(hoisted.isWorkspaceBootstrapPendingMock).toHaveBeenCalledTimes(1);
|
||||
expect(hoisted.isWorkspaceBootstrapPendingMock).not.toHaveBeenCalledWith(sandboxWorkspace);
|
||||
expect(capturedPrompt).not.toContain("[Bootstrap pending]");
|
||||
expect(isWorkspaceBootstrapPending).toHaveBeenCalledOnce();
|
||||
expect(isWorkspaceBootstrapPending).toHaveBeenCalledWith(canonicalWorkspace);
|
||||
expect(isWorkspaceBootstrapPending).not.toHaveBeenCalledWith(sandboxWorkspace);
|
||||
expect(routing.bootstrapMode).toBe("none");
|
||||
expect(routing.userPromptPrefixText).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import {
|
||||
createAgentSession,
|
||||
@@ -52,7 +52,6 @@ import {
|
||||
resolveBootstrapContextForRun,
|
||||
resolveContextInjectionMode,
|
||||
} from "../../bootstrap-files.js";
|
||||
import { resolveBootstrapMode } from "../../bootstrap-mode.js";
|
||||
import { createCacheTrace } from "../../cache-trace.js";
|
||||
import {
|
||||
listChannelSupportedActions,
|
||||
@@ -114,7 +113,6 @@ import {
|
||||
import { resolveSystemPromptOverride } from "../../system-prompt-override.js";
|
||||
import { buildSystemPromptParams } from "../../system-prompt-params.js";
|
||||
import { buildSystemPromptReport } from "../../system-prompt-report.js";
|
||||
import { buildAgentUserPromptPrefix } from "../../system-prompt.js";
|
||||
import { resolveAgentTimeoutMs } from "../../timeout.js";
|
||||
import { UNKNOWN_TOOL_THRESHOLD } from "../../tool-loop-detection.js";
|
||||
import {
|
||||
@@ -182,6 +180,11 @@ import { splitSdkTools } from "../tool-split.js";
|
||||
import { mapThinkingLevel } from "../utils.js";
|
||||
import { flushPendingToolResultsAfterIdle } from "../wait-for-idle-before-flush.js";
|
||||
export { buildContextEnginePromptCacheInfo } from "./attempt.context-engine-helpers.js";
|
||||
import {
|
||||
resolveAttemptWorkspaceBootstrapRouting,
|
||||
shouldStripBootstrapFromEmbeddedContext,
|
||||
} from "./attempt-bootstrap-routing.js";
|
||||
export { shouldStripBootstrapFromEmbeddedContext } from "./attempt-bootstrap-routing.js";
|
||||
import { configureEmbeddedAttemptHttpRuntime } from "./attempt-http-runtime.js";
|
||||
import {
|
||||
assembleAttemptContextEngine,
|
||||
@@ -318,12 +321,6 @@ export function resolveUnknownToolGuardThreshold(loopDetection?: {
|
||||
return UNKNOWN_TOOL_THRESHOLD;
|
||||
}
|
||||
|
||||
export function shouldStripBootstrapFromEmbeddedContext(_params: {
|
||||
bootstrapMode: "full" | "limited" | "none";
|
||||
}): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isPrimaryBootstrapRun(sessionKey?: string): boolean {
|
||||
return !isSubagentSessionKey(sessionKey) && !isAcpSessionKey(sessionKey);
|
||||
}
|
||||
@@ -338,8 +335,7 @@ export function remapInjectedContextFilesToWorkspace(params: {
|
||||
}
|
||||
return params.files.map((file) => {
|
||||
const relative = path.relative(params.sourceWorkspaceDir, file.path);
|
||||
const canRemap =
|
||||
relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
const canRemap = relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
return canRemap
|
||||
? {
|
||||
...file,
|
||||
@@ -484,8 +480,6 @@ export async function runEmbeddedAttempt(
|
||||
|
||||
const sessionLabel = params.sessionKey ?? params.sessionId;
|
||||
const contextInjectionMode = resolveContextInjectionMode(params.config);
|
||||
// Bootstrap lifecycle is owned by the canonical workspace, not a copied sandbox view.
|
||||
const workspaceBootstrapPending = await isWorkspaceBootstrapPending(resolvedWorkspace);
|
||||
const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
|
||||
const toolsRaw = params.disableTools
|
||||
? []
|
||||
@@ -555,20 +549,20 @@ export async function runEmbeddedAttempt(
|
||||
return allTools;
|
||||
})();
|
||||
const toolsEnabled = supportsModelTools(params.model);
|
||||
const bootstrapRunKind = params.bootstrapContextRunKind ?? "default";
|
||||
const bootstrapHasFileAccess = toolsEnabled && toolsRaw.some((tool) => tool.name === "read");
|
||||
const bootstrapMode = resolveBootstrapMode({
|
||||
bootstrapPending: workspaceBootstrapPending,
|
||||
runKind: bootstrapRunKind,
|
||||
isInteractiveUserFacing: params.trigger === "user" || params.trigger === "manual",
|
||||
const bootstrapRouting = await resolveAttemptWorkspaceBootstrapRouting({
|
||||
isWorkspaceBootstrapPending,
|
||||
bootstrapContextRunKind: params.bootstrapContextRunKind,
|
||||
trigger: params.trigger,
|
||||
sessionKey: params.sessionKey,
|
||||
isPrimaryRun: isPrimaryBootstrapRun(params.sessionKey),
|
||||
isCanonicalWorkspace:
|
||||
(params.isCanonicalWorkspace ?? true) && effectiveWorkspace === resolvedWorkspace,
|
||||
isCanonicalWorkspace: params.isCanonicalWorkspace,
|
||||
effectiveWorkspace,
|
||||
resolvedWorkspace,
|
||||
hasBootstrapFileAccess: bootstrapHasFileAccess,
|
||||
});
|
||||
const shouldStripBootstrapFromContext = shouldStripBootstrapFromEmbeddedContext({
|
||||
bootstrapMode,
|
||||
});
|
||||
const bootstrapMode = bootstrapRouting.bootstrapMode;
|
||||
const shouldStripBootstrapFromContext = bootstrapRouting.shouldStripBootstrapFromContext;
|
||||
const {
|
||||
bootstrapFiles: hookAdjustedBootstrapFiles,
|
||||
contextFiles: resolvedContextFiles,
|
||||
@@ -576,7 +570,7 @@ export async function runEmbeddedAttempt(
|
||||
} = await resolveAttemptBootstrapContext({
|
||||
contextInjectionMode,
|
||||
bootstrapContextMode: params.bootstrapContextMode,
|
||||
bootstrapContextRunKind: bootstrapRunKind,
|
||||
bootstrapContextRunKind: params.bootstrapContextRunKind ?? "default",
|
||||
bootstrapMode,
|
||||
sessionFile: params.sessionFile,
|
||||
hasCompletedBootstrapTurn,
|
||||
@@ -927,9 +921,7 @@ export async function runEmbeddedAttempt(
|
||||
});
|
||||
const systemPromptOverride = createSystemPromptOverride(appendPrompt);
|
||||
let systemPromptText = systemPromptOverride();
|
||||
const userPromptPrefixText = buildAgentUserPromptPrefix({
|
||||
bootstrapMode,
|
||||
});
|
||||
const userPromptPrefixText = bootstrapRouting.userPromptPrefixText;
|
||||
|
||||
let sessionManager: ReturnType<typeof guardSessionManager> | undefined;
|
||||
let session: Awaited<ReturnType<typeof createAgentSession>>["session"] | undefined;
|
||||
|
||||
Reference in New Issue
Block a user