Skip to content

Commit a471327

Browse files
committed
fix(agents): skip continuation bootstrap preload
1 parent b405c6e commit a471327

3 files changed

Lines changed: 76 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai
5151
- Process execution: collapse case-insensitive duplicate child environment keys on Windows so caller-provided overrides such as `PATH` cannot be shadowed by host `Path`.
5252
- Browser CLI: request the existing `operator.admin` gateway scope explicitly for browser control commands, avoiding unnecessary scope-upgrade approval loops. Fixes #81555. (#81716) Thanks @joshavant.
5353
- Web: honor explicitly configured global `web_search` providers during provider ownership resolution while keeping sandboxed `web_fetch` limited to bundled providers.
54+
- Agents: skip bootstrap file and hook preload work on completed `continuation-skip` turns when no workspace bootstrap is pending, reducing isolated-agent prep latency without changing first-turn bootstrap behavior. Fixes #81548. Thanks @delizaran-unpa.
5455
- iOS: restore first-use Contacts, Calendar, and Reminders permission prompts and add Privacy & Access status/actions in Settings. Thanks @BunsDev.
5556
- Canvas: return not found for malformed percent-encoded Canvas/A2UI/document asset paths and keep decoded parent traversal blocked before path normalization.
5657
- Telegram: allow trusted local Bot API media files whose filenames start with dots instead of falling back to remote download.

src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,34 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
548548
expect(systemPrompt).toContain("Ask who I am before continuing.");
549549
});
550550

551+
it("skips bootstrap preload on completed continuation-skip turns", async () => {
552+
hoisted.resolveContextInjectionModeMock.mockReturnValue("continuation-skip");
553+
hoisted.hasCompletedBootstrapTurnMock.mockResolvedValue(true);
554+
hoisted.isWorkspaceBootstrapPendingMock.mockResolvedValue(false);
555+
556+
await createContextEngineAttemptRunner({
557+
contextEngine: createContextEngineBootstrapAndAssemble(),
558+
sessionKey,
559+
tempPaths,
560+
attemptOverrides: {
561+
prompt: "visible ask",
562+
transcriptPrompt: "visible ask",
563+
trigger: "user",
564+
},
565+
sessionPrompt: async (session) => {
566+
session.messages = [
567+
...session.messages,
568+
{ role: "assistant", content: "done", timestamp: 2 },
569+
];
570+
},
571+
});
572+
573+
expect(hoisted.hasCompletedBootstrapTurnMock).toHaveBeenCalledOnce();
574+
expect(hoisted.isWorkspaceBootstrapPendingMock).toHaveBeenCalledOnce();
575+
expect(hoisted.resolveBootstrapFilesForRunMock).not.toHaveBeenCalled();
576+
expect(hoisted.resolveBootstrapContextForRunMock).not.toHaveBeenCalled();
577+
});
578+
551579
it("adds current-turn context to the current model input without exposing internal runtime context", async () => {
552580
let seenPrompt: string | undefined;
553581

src/agents/pi-embedded-runner/run/attempt.ts

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ import {
189189
} from "../../tool-search.js";
190190
import { shouldAllowProviderOwnedThinkingReplay } from "../../transcript-policy.js";
191191
import { normalizeUsage, type NormalizedUsage } from "../../usage.js";
192-
import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
192+
import { DEFAULT_BOOTSTRAP_FILENAME, type WorkspaceBootstrapFile } from "../../workspace.js";
193193
import { isRunnerAbortError } from "../abort.js";
194194
import { isCacheTtlEligibleProvider, readLastCacheTtlTimestamp } from "../cache-ttl.js";
195195
import { resolveCompactionTimeoutMs } from "../compaction-safety-timeout.js";
@@ -1099,30 +1099,51 @@ export async function runEmbeddedAttempt(
10991099
workspaceDir: resolvedWorkspace,
11001100
warn: (message) => log.warn(message),
11011101
});
1102-
const preloadedBootstrapFiles =
1103-
isRawModelRun || contextInjectionMode === "never"
1104-
? undefined
1105-
: await resolveBootstrapFilesForRun({
1106-
workspaceDir: resolvedWorkspace,
1107-
config: params.config,
1108-
sessionKey: params.sessionKey,
1109-
sessionId: params.sessionId,
1110-
warn: bootstrapWarn,
1111-
contextMode: params.bootstrapContextMode,
1112-
runKind: params.bootstrapContextRunKind,
1113-
});
1114-
const bootstrapRouting = await resolveAttemptWorkspaceBootstrapRouting({
1115-
isWorkspaceBootstrapPending,
1116-
bootstrapFiles: preloadedBootstrapFiles,
1117-
bootstrapContextRunKind: params.bootstrapContextRunKind,
1118-
trigger: params.trigger,
1119-
sessionKey: params.sessionKey,
1120-
isPrimaryRun: isPrimaryBootstrapRun(params.sessionKey),
1121-
isCanonicalWorkspace: params.isCanonicalWorkspace,
1122-
effectiveWorkspace,
1123-
resolvedWorkspace,
1124-
hasBootstrapFileAccess: bootstrapHasFileAccess,
1125-
});
1102+
let completedBootstrapTurn: boolean | undefined;
1103+
const hasCompletedBootstrapTurnForAttempt = async (sessionFile: string) => {
1104+
completedBootstrapTurn ??= await hasCompletedBootstrapTurn(sessionFile);
1105+
return completedBootstrapTurn;
1106+
};
1107+
const resolveBootstrapRouting = (bootstrapFiles?: readonly WorkspaceBootstrapFile[]) =>
1108+
resolveAttemptWorkspaceBootstrapRouting({
1109+
isWorkspaceBootstrapPending,
1110+
bootstrapFiles,
1111+
bootstrapContextRunKind: params.bootstrapContextRunKind,
1112+
trigger: params.trigger,
1113+
sessionKey: params.sessionKey,
1114+
isPrimaryRun: isPrimaryBootstrapRun(params.sessionKey),
1115+
isCanonicalWorkspace: params.isCanonicalWorkspace,
1116+
effectiveWorkspace,
1117+
resolvedWorkspace,
1118+
hasBootstrapFileAccess: bootstrapHasFileAccess,
1119+
});
1120+
const shouldProbeContinuationSkip =
1121+
!isRawModelRun &&
1122+
contextInjectionMode === "continuation-skip" &&
1123+
(params.bootstrapContextRunKind ?? "default") !== "heartbeat" &&
1124+
(await hasCompletedBootstrapTurnForAttempt(params.sessionFile));
1125+
let preloadedBootstrapFiles: WorkspaceBootstrapFile[] | undefined;
1126+
let bootstrapRouting =
1127+
shouldProbeContinuationSkip || isRawModelRun || contextInjectionMode === "never"
1128+
? await resolveBootstrapRouting()
1129+
: undefined;
1130+
if (
1131+
!isRawModelRun &&
1132+
contextInjectionMode !== "never" &&
1133+
(bootstrapRouting === undefined || bootstrapRouting.bootstrapMode === "full")
1134+
) {
1135+
preloadedBootstrapFiles = await resolveBootstrapFilesForRun({
1136+
workspaceDir: resolvedWorkspace,
1137+
config: params.config,
1138+
sessionKey: params.sessionKey,
1139+
sessionId: params.sessionId,
1140+
warn: bootstrapWarn,
1141+
contextMode: params.bootstrapContextMode,
1142+
runKind: params.bootstrapContextRunKind,
1143+
});
1144+
bootstrapRouting = await resolveBootstrapRouting(preloadedBootstrapFiles);
1145+
}
1146+
bootstrapRouting ??= await resolveBootstrapRouting(preloadedBootstrapFiles);
11261147
const bootstrapMode = bootstrapRouting.bootstrapMode;
11271148
const {
11281149
bootstrapFiles: hookAdjustedBootstrapFiles,
@@ -1136,7 +1157,7 @@ export async function runEmbeddedAttempt(
11361157
bootstrapContextRunKind: params.bootstrapContextRunKind ?? "default",
11371158
bootstrapMode,
11381159
sessionFile: params.sessionFile,
1139-
hasCompletedBootstrapTurn,
1160+
hasCompletedBootstrapTurn: hasCompletedBootstrapTurnForAttempt,
11401161
resolveBootstrapContextForRun: async () => {
11411162
const bootstrapFiles =
11421163
preloadedBootstrapFiles ??

0 commit comments

Comments
 (0)