






















@@ -4,8 +4,13 @@ import path from "node:path";
44import { afterEach, describe, expect, it } from "vitest";
55import { getSessionEntry, upsertSessionEntry } from "../../config/sessions.js";
66import type { OpenClawConfig } from "../../config/types.openclaw.js";
7-import { handleGoalCommand, parseGoalCommand } from "./commands-goal.js";
7+import {
8+formatGoalContinuationPrompt,
9+handleGoalCommand,
10+parseGoalCommand,
11+} from "./commands-goal.js";
812import type { HandleCommandsParams } from "./commands-types.js";
13+import { parseInlineDirectives } from "./directive-handling.parse.js";
9141015const sessionKey = "agent:main:web:main";
1116let tempRoots: string[] = [];
@@ -76,6 +81,18 @@ describe("goal commands", () => {
7681});
7782});
788384+it("formats command-looking continuation prompts so inline directives leave them intact", () => {
85+const prompt = formatGoalContinuationPrompt("ship /fast off");
86+expect(prompt).toBe(
87+`Pursue this goal exactly as written from this JSON string: "ship \\/fast off"`,
88+);
89+90+const directives = parseInlineDirectives(prompt);
91+92+expect(directives.cleaned).toBe(prompt);
93+expect(directives.hasFastDirective).toBe(false);
94+});
95+7996it("starts a goal from Codex-style bare /goal objective text", async () => {
8097const storePath = await createStorePath();
8198await upsertSessionEntry({
@@ -84,13 +101,118 @@ describe("goal commands", () => {
84101entry: { sessionId: "sess-main", updatedAt: 1, totalTokens: 0, totalTokensFresh: true },
85102});
8610387-const result = await handleGoalCommand(
88-buildGoalParams("/goal build a 3d game", storePath),
89-true,
90-);
104+const params = buildGoalParams("/goal build a 3d game", storePath);
105+const result = await handleGoalCommand(params, true);
9110692-expect(result?.shouldContinue).toBe(false);
93-expect(result?.reply?.text).toBe("Goal started: build a 3d game");
107+expect(result?.shouldContinue).toBe(true);
108+expect(result?.reply).toBeUndefined();
109+expect(params.command.commandBodyNormalized).toBe("build a 3d game");
110+expect((params.ctx as { BodyForAgent?: string }).BodyForAgent).toBe("build a 3d game");
94111expect(getSessionEntry({ storePath, sessionKey })?.goal?.objective).toBe("build a 3d game");
95112});
113+114+it("wraps command-prefixed goal objectives before continuing", async () => {
115+const storePath = await createStorePath();
116+await upsertSessionEntry({
117+ storePath,
118+ sessionKey,
119+entry: { sessionId: "sess-main", updatedAt: 1, totalTokens: 0, totalTokensFresh: true },
120+});
121+122+const slashParams = buildGoalParams("/goal start /status", storePath);
123+const slashResult = await handleGoalCommand(slashParams, true);
124+const slashPrompt = `Pursue this goal exactly as written from this JSON string: "\\/status"`;
125+126+expect(slashResult?.shouldContinue).toBe(true);
127+expect(slashParams.command.commandBodyNormalized).toBe(slashPrompt);
128+expect((slashParams.ctx as { BodyForAgent?: string }).BodyForAgent).toBe(slashPrompt);
129+expect(getSessionEntry({ storePath, sessionKey })?.goal?.objective).toBe("/status");
130+131+const bangStorePath = await createStorePath();
132+await upsertSessionEntry({
133+storePath: bangStorePath,
134+ sessionKey,
135+entry: { sessionId: "sess-main", updatedAt: 1, totalTokens: 0, totalTokensFresh: true },
136+});
137+138+const bangParams = buildGoalParams("/goal start !npm test", bangStorePath);
139+const bangResult = await handleGoalCommand(bangParams, true);
140+const bangPrompt = `Pursue this goal exactly as written from this JSON string: "!npm test"`;
141+142+expect(bangResult?.shouldContinue).toBe(true);
143+expect(bangParams.command.commandBodyNormalized).toBe(bangPrompt);
144+expect((bangParams.ctx as { BodyForAgent?: string }).BodyForAgent).toBe(bangPrompt);
145+expect(getSessionEntry({ storePath: bangStorePath, sessionKey })?.goal?.objective).toBe(
146+"!npm test",
147+);
148+});
149+150+it("resumes a goal and continues with a resume prompt", async () => {
151+const storePath = await createStorePath();
152+await upsertSessionEntry({
153+ storePath,
154+ sessionKey,
155+entry: {
156+sessionId: "sess-main",
157+updatedAt: 1,
158+goal: {
159+schemaVersion: 1,
160+id: "goal-1",
161+objective: "finish the migration",
162+status: "paused",
163+createdAt: 1,
164+updatedAt: 1,
165+tokenStart: 0,
166+tokenStartFresh: true,
167+tokensUsed: 0,
168+continuationTurns: 0,
169+},
170+},
171+});
172+173+const params = buildGoalParams("/goal resume CI passed", storePath);
174+const result = await handleGoalCommand(params, true);
175+176+expect(result?.shouldContinue).toBe(true);
177+expect(params.command.commandBodyNormalized).toBe(
178+"Continue pursuing the current goal. Note: CI passed",
179+);
180+expect(getSessionEntry({ storePath, sessionKey })?.goal?.status).toBe("active");
181+});
182+183+it("wraps command-looking resume notes before continuing", async () => {
184+const storePath = await createStorePath();
185+await upsertSessionEntry({
186+ storePath,
187+ sessionKey,
188+entry: {
189+sessionId: "sess-main",
190+updatedAt: 1,
191+goal: {
192+schemaVersion: 1,
193+id: "goal-1",
194+objective: "finish the migration",
195+status: "paused",
196+createdAt: 1,
197+updatedAt: 1,
198+tokenStart: 0,
199+tokenStartFresh: true,
200+tokensUsed: 0,
201+continuationTurns: 0,
202+},
203+},
204+});
205+206+const params = buildGoalParams("/goal resume /fast off", storePath);
207+const result = await handleGoalCommand(params, true);
208+const prompt = `Continue pursuing the current goal. Interpret this JSON string as the resume note: "\\/fast off"`;
209+const directives = parseInlineDirectives(prompt);
210+211+expect(result?.shouldContinue).toBe(true);
212+expect(params.command.commandBodyNormalized).toBe(prompt);
213+expect((params.ctx as { BodyForAgent?: string }).BodyForAgent).toBe(prompt);
214+expect(directives.cleaned).toBe(prompt);
215+expect(directives.hasFastDirective).toBe(false);
216+expect(getSessionEntry({ storePath, sessionKey })?.goal?.status).toBe("active");
217+});
96218});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。