mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
fix: add contextInjection never mode (#65006) (thanks @xDarkicex)
This commit is contained in:
@@ -69,6 +69,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/DeepSeek: add DeepSeek V4 Flash and V4 Pro to the bundled catalog and make V4 Flash the onboarding default. Thanks @lsdsjy.
|
||||
- CLI/Gateway: make `gateway status` start faster by skipping plugin loading on the read-only status path. (#71364) Thanks @andyylin.
|
||||
- Plugins/compatibility: add a central plugin compatibility registry and docs for SDK/config/setup/runtime deprecation records, including dated migration metadata for legacy harness naming and other plugin-facing aliases. Thanks @vincentkoc.
|
||||
- Agents/bootstrap: add `agents.defaults.contextInjection: "never"` to disable workspace bootstrap file injection for agents that fully own their prompt lifecycle. (#65006) Thanks @xDarkicex.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -339,6 +339,7 @@ export async function loadCompactHooksHarness(): Promise<{
|
||||
|
||||
vi.doMock("../bootstrap-files.js", () => ({
|
||||
makeBootstrapWarn: vi.fn(() => () => {}),
|
||||
resolveContextInjectionMode: vi.fn(() => "always"),
|
||||
resolveBootstrapContextForRun: vi.fn(async () => ({ contextFiles: [] })),
|
||||
}));
|
||||
|
||||
|
||||
@@ -37,7 +37,11 @@ import { normalizeMessageChannel } from "../../utils/message-channel.js";
|
||||
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
|
||||
import { resolveOpenClawAgentDir } from "../agent-paths.js";
|
||||
import { resolveSessionAgentIds } from "../agent-scope.js";
|
||||
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../bootstrap-files.js";
|
||||
import {
|
||||
makeBootstrapWarn,
|
||||
resolveBootstrapContextForRun,
|
||||
resolveContextInjectionMode,
|
||||
} from "../bootstrap-files.js";
|
||||
import {
|
||||
listChannelSupportedActions,
|
||||
resolveChannelMessageToolCapabilities,
|
||||
@@ -471,17 +475,19 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
|
||||
const sessionLabel = params.sessionKey ?? params.sessionId;
|
||||
const resolvedMessageProvider = params.messageChannel ?? params.messageProvider;
|
||||
const { contextFiles } = await resolveBootstrapContextForRun({
|
||||
workspaceDir: effectiveWorkspace,
|
||||
config: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
warn: makeBootstrapWarn({
|
||||
sessionLabel,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
warn: (message) => log.warn(message),
|
||||
}),
|
||||
});
|
||||
const contextInjectionMode = resolveContextInjectionMode(params.config);
|
||||
const { contextFiles } = contextInjectionMode === "never"
|
||||
? { contextFiles: [] }
|
||||
: await resolveBootstrapContextForRun({
|
||||
workspaceDir: effectiveWorkspace,
|
||||
config: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
warn: makeBootstrapWarn({
|
||||
sessionLabel,
|
||||
warn: (message) => log.warn(message),
|
||||
}),
|
||||
});
|
||||
// Apply contextTokens cap to model so pi-coding-agent's auto-compaction
|
||||
// threshold uses the effective limit, not the native context window.
|
||||
const runtimeModelWithContext = runtimeModel as ProviderRuntimeModel;
|
||||
|
||||
@@ -13,23 +13,23 @@ export {
|
||||
|
||||
export type AttemptContextEngine = ContextEngine;
|
||||
|
||||
export type AttemptBootstrapContext = {
|
||||
bootstrapFiles: unknown[];
|
||||
contextFiles: unknown[];
|
||||
export type AttemptBootstrapContext<TBootstrapFile = unknown, TContextFile = unknown> = {
|
||||
bootstrapFiles: TBootstrapFile[];
|
||||
contextFiles: TContextFile[];
|
||||
};
|
||||
|
||||
export async function resolveAttemptBootstrapContext<
|
||||
TContext extends AttemptBootstrapContext,
|
||||
>(params: {
|
||||
contextInjectionMode: "always" | "continuation-skip";
|
||||
export async function resolveAttemptBootstrapContext<TBootstrapFile, TContextFile>(params: {
|
||||
contextInjectionMode: "always" | "continuation-skip" | "never";
|
||||
bootstrapContextMode?: string;
|
||||
bootstrapContextRunKind?: string;
|
||||
bootstrapMode?: BootstrapMode;
|
||||
sessionFile: string;
|
||||
hasCompletedBootstrapTurn: (sessionFile: string) => Promise<boolean>;
|
||||
resolveBootstrapContextForRun: () => Promise<TContext>;
|
||||
resolveBootstrapContextForRun: () => Promise<
|
||||
AttemptBootstrapContext<TBootstrapFile, TContextFile>
|
||||
>;
|
||||
}): Promise<
|
||||
TContext & {
|
||||
AttemptBootstrapContext<TBootstrapFile, TContextFile> & {
|
||||
isContinuationTurn: boolean;
|
||||
shouldRecordCompletedBootstrapTurn: boolean;
|
||||
}
|
||||
@@ -39,14 +39,16 @@ export async function resolveAttemptBootstrapContext<
|
||||
params.contextInjectionMode === "continuation-skip" &&
|
||||
params.bootstrapContextRunKind !== "heartbeat" &&
|
||||
(await params.hasCompletedBootstrapTurn(params.sessionFile));
|
||||
const shouldSkipBootstrapInjection =
|
||||
params.contextInjectionMode === "never" || isContinuationTurn;
|
||||
const shouldRecordCompletedBootstrapTurn =
|
||||
!isContinuationTurn &&
|
||||
!shouldSkipBootstrapInjection &&
|
||||
params.bootstrapContextMode !== "lightweight" &&
|
||||
params.bootstrapContextRunKind !== "heartbeat" &&
|
||||
params.bootstrapMode === "full";
|
||||
|
||||
const context = isContinuationTurn
|
||||
? ({ bootstrapFiles: [], contextFiles: [] } as unknown as TContext)
|
||||
const context = shouldSkipBootstrapInjection
|
||||
? { bootstrapFiles: [], contextFiles: [] }
|
||||
: await params.resolveBootstrapContextForRun();
|
||||
|
||||
return {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { resetEmbeddedAttemptHarness } from "./attempt.spawn-workspace.test-support.js";
|
||||
|
||||
async function resolveBootstrapContext(params: {
|
||||
contextInjectionMode?: "always" | "continuation-skip";
|
||||
contextInjectionMode?: "always" | "continuation-skip" | "never";
|
||||
bootstrapContextMode?: string;
|
||||
bootstrapContextRunKind?: string;
|
||||
bootstrapMode?: "full" | "limited" | "none";
|
||||
@@ -77,6 +77,22 @@ describe("embedded attempt context injection", () => {
|
||||
expect(resolver).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("disables bootstrap injection without marking the turn as a continuation", async () => {
|
||||
const { result, hasCompletedBootstrapTurn, resolveBootstrapContextForRun } =
|
||||
await resolveBootstrapContext({
|
||||
contextInjectionMode: "never",
|
||||
bootstrapMode: "full",
|
||||
completed: true,
|
||||
});
|
||||
|
||||
expect(result.isContinuationTurn).toBe(false);
|
||||
expect(result.shouldRecordCompletedBootstrapTurn).toBe(false);
|
||||
expect(result.bootstrapFiles).toEqual([]);
|
||||
expect(result.contextFiles).toEqual([]);
|
||||
expect(hasCompletedBootstrapTurn).not.toHaveBeenCalled();
|
||||
expect(resolveBootstrapContextForRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not let a stale completed marker suppress pending workspace bootstrap", async () => {
|
||||
const resolver = vi.fn(async () => ({
|
||||
bootstrapFiles: [{ name: "BOOTSTRAP.md" }],
|
||||
|
||||
@@ -3447,6 +3447,10 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
type: "string",
|
||||
const: "continuation-skip",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
const: "never",
|
||||
},
|
||||
],
|
||||
title: "Context Injection",
|
||||
description:
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
} from "./types.base.js";
|
||||
import type { MemorySearchConfig } from "./types.tools.js";
|
||||
|
||||
export type AgentContextInjection = "always" | "continuation-skip";
|
||||
export type AgentContextInjection = "always" | "continuation-skip" | "never";
|
||||
export type EmbeddedPiExecutionContract = "default" | "strict-agentic";
|
||||
|
||||
export type Gpt5PromptOverlayConfig = {
|
||||
|
||||
@@ -52,8 +52,13 @@ describe("agent defaults schema", () => {
|
||||
expect(result.contextInjection).toBe("continuation-skip");
|
||||
});
|
||||
|
||||
it("accepts contextInjection: never", () => {
|
||||
const result = AgentDefaultsSchema.parse({ contextInjection: "never" })!;
|
||||
expect(result.contextInjection).toBe("never");
|
||||
});
|
||||
|
||||
it("rejects invalid contextInjection values", () => {
|
||||
expect(() => AgentDefaultsSchema.parse({ contextInjection: "never" })).toThrow();
|
||||
expect(() => AgentDefaultsSchema.parse({ contextInjection: "unknown" })).toThrow();
|
||||
});
|
||||
|
||||
it("accepts embeddedPi.executionContract", () => {
|
||||
|
||||
@@ -83,7 +83,9 @@ export const AgentDefaultsSchema = z
|
||||
.strict()
|
||||
.optional(),
|
||||
skipBootstrap: z.boolean().optional(),
|
||||
contextInjection: z.union([z.literal("always"), z.literal("continuation-skip")]).optional(),
|
||||
contextInjection: z
|
||||
.union([z.literal("always"), z.literal("continuation-skip"), z.literal("never")])
|
||||
.optional(),
|
||||
bootstrapMaxChars: z.number().int().positive().optional(),
|
||||
bootstrapTotalMaxChars: z.number().int().positive().optional(),
|
||||
experimental: z
|
||||
|
||||
Reference in New Issue
Block a user