Skip to content

Commit 36cee42

Browse files
committed
fix(agents): avoid duplicate runtime event prompts
1 parent 61f8934 commit 36cee42

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

src/agents/embedded-agent-runner/run/runtime-context-prompt.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,24 @@ describe("runtime context prompt submission", () => {
159159
});
160160
});
161161

162+
it("does not duplicate implicit runtime context in a hook-expanded model prompt", () => {
163+
const effectivePrompt = "exec completed (abc123, code 0) :: output text";
164+
const transcriptPrompt = "[OpenClaw heartbeat poll]";
165+
166+
expect(
167+
resolveRuntimeContextPromptParts({
168+
effectivePrompt,
169+
transcriptPrompt,
170+
modelPrompt: ["hook prefix", effectivePrompt, "hook suffix"].join("\n\n"),
171+
unmatchedTranscriptMode: "runtime-context",
172+
}),
173+
).toEqual({
174+
prompt: transcriptPrompt,
175+
modelPrompt: ["hook prefix", transcriptPrompt, "hook suffix"].join("\n\n"),
176+
runtimeContext: effectivePrompt,
177+
});
178+
});
179+
162180
it("does not treat unmatched user prompt text as implicit runtime context", () => {
163181
const effectivePrompt = ["media note", "reply hint", "user body"].join("\n\n");
164182

src/agents/embedded-agent-runner/run/runtime-context-prompt.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ function removeLastPromptOccurrence(text: string, prompt: string): string | null
7676
.trim();
7777
}
7878

79+
function replaceLastPromptOccurrence(
80+
text: string,
81+
prompt: string,
82+
replacement: string,
83+
): string | null {
84+
const index = text.lastIndexOf(prompt);
85+
if (index === -1) {
86+
return null;
87+
}
88+
return `${text.slice(0, index)}${replacement}${text.slice(index + prompt.length)}`;
89+
}
90+
7991
/**
8092
* Separates user-authored prompt text from hidden runtime context. Transcript
8193
* prompt stays user-visible; model prompt may carry runtime-only additions that
@@ -99,7 +111,7 @@ export function resolveRuntimeContextPromptParts(params: {
99111
: shouldExtractInternalRuntimeContext
100112
? extractInternalRuntimeContext(params.modelPrompt)
101113
: { text: params.modelPrompt };
102-
const modelPromptText = modelPrompt?.text ?? transcriptPrompt ?? extracted.text;
114+
let modelPromptText = modelPrompt?.text ?? transcriptPrompt ?? extracted.text;
103115
const prompt = transcriptPrompt ?? extracted.text;
104116
if (!prompt.trim() && params.emptyTranscriptMode === "model-prompt") {
105117
return {
@@ -128,6 +140,11 @@ export function resolveRuntimeContextPromptParts(params: {
128140
params.unmatchedTranscriptMode === "runtime-context" && hiddenRuntimeContext === undefined
129141
? extracted.text.trim()
130142
: undefined;
143+
if (implicitRuntimeContext && modelPrompt && transcriptPrompt !== undefined) {
144+
modelPromptText =
145+
replaceLastPromptOccurrence(modelPrompt.text, extracted.text, transcriptPrompt) ??
146+
modelPromptText;
147+
}
131148
const runtimeContext =
132149
[hiddenRuntimeContext, implicitRuntimeContext, extracted.runtimeContext]
133150
.filter((value): value is string => Boolean(value?.trim()))

0 commit comments

Comments
 (0)