fix(ci): preserve goal continuation prompts

This commit is contained in:
Vincent Koc
2026-05-30 13:17:51 +01:00
parent 2238e0ce76
commit 56995069f1
3 changed files with 22 additions and 7 deletions

View File

@@ -18,6 +18,10 @@ import type {
} from "./commands-types.js";
const GOAL_COMMAND_PREFIX = "/goal";
const GOAL_CONTINUATION_PROMPT_PREFIX =
"Pursue this goal exactly as written from this JSON string:";
const GOAL_RESUME_NOTE_PROMPT_PREFIX =
"Continue pursuing the current goal. Interpret this JSON string as the resume note:";
const GOAL_ACTIONS = new Set([
"block",
"blocked",
@@ -84,7 +88,7 @@ function encodeGoalJsonString(trimmed: string): string {
export function formatGoalContinuationPrompt(objective: string): string {
const trimmed = objective.trim();
return hasCommandLikeGoalText(trimmed)
? `Pursue this goal exactly as written from this JSON string: ${encodeGoalJsonString(trimmed)}`
? `${GOAL_CONTINUATION_PROMPT_PREFIX} ${encodeGoalJsonString(trimmed)}`
: trimmed;
}
@@ -94,10 +98,18 @@ export function formatGoalResumeContinuationPrompt(note: string): string {
return "Continue pursuing the current goal.";
}
return hasCommandLikeGoalText(trimmed)
? `Continue pursuing the current goal. Interpret this JSON string as the resume note: ${encodeGoalJsonString(trimmed)}`
? `${GOAL_RESUME_NOTE_PROMPT_PREFIX} ${encodeGoalJsonString(trimmed)}`
: `Continue pursuing the current goal. Note: ${trimmed}`;
}
export function isFormattedGoalContinuationPrompt(message: string): boolean {
const trimmed = message.trim();
return (
trimmed.startsWith(GOAL_CONTINUATION_PROMPT_PREFIX) ||
trimmed.startsWith(GOAL_RESUME_NOTE_PROMPT_PREFIX)
);
}
function applyGoalPromptToContext(ctx: HandleCommandsParams["ctx"], message: string): void {
const mutableCtx = ctx as HandleCommandsParams["ctx"] & {
Body?: string;

View File

@@ -15,6 +15,7 @@ import { resolveCommandTurnTargetSessionKey } from "../command-turn-context.js";
import { normalizeCommandBody } from "../commands-registry.js";
import type { MsgContext, TemplateContext } from "../templating.js";
import { parseSoftResetCommand } from "./commands-reset-mode.js";
import { isFormattedGoalContinuationPrompt } from "./commands-goal.js";
import type { CommandContext } from "./commands-types.js";
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
import type { SessionInitResult } from "./session.js";
@@ -219,7 +220,9 @@ export function initFastReplySessionState(params: {
});
const existingEntry = sessionStore[sessionKey];
const commandSource = ctx.BodyForCommands ?? ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "";
const triggerBodyNormalized = stripStructuralPrefixes(commandSource).trim();
const triggerBodyNormalized = isFormattedGoalContinuationPrompt(commandSource)
? commandSource.trim()
: stripStructuralPrefixes(commandSource).trim();
const normalizedChatType = normalizeChatType(ctx.ChatType);
const isGroup = normalizedChatType != null && normalizedChatType !== "direct";
const strippedForReset = isGroup

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { testing as cliBackendsTesting } from "../../agents/cli-backends.js";
import type { OpenClawConfig } from "../../config/config.js";
import { getSessionEntry } from "../../config/sessions.js";
import {
buildFastReplyCommandContext,
initFastReplySessionState,
@@ -595,10 +596,9 @@ describe("getReplyFromConfig fast test bootstrap", () => {
),
).resolves.toEqual({ text: "ok" });
const stored = JSON.parse(await fs.readFile(storePath, "utf8")) as {
sessions?: Record<string, { goal?: { objective?: string } }>;
};
expect(stored.sessions?.[targetSessionKey]?.goal?.objective).toBe("/status");
expect(getSessionEntry({ storePath, sessionKey: targetSessionKey })?.goal?.objective).toBe(
"/status",
);
const preparedReplyParams = requirePreparedReplyParams();
expect(preparedReplyParams.command.commandBodyNormalized).toBe(continuationPrompt);
expect(preparedReplyParams.sessionCtx.BodyForAgent).toBe(continuationPrompt);