mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 14:44:05 +00:00
fix(tui): continue goal commands after creation
This commit is contained in:
@@ -4,8 +4,13 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { getSessionEntry, upsertSessionEntry } from "../../config/sessions.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { handleGoalCommand, parseGoalCommand } from "./commands-goal.js";
|
||||
import {
|
||||
formatGoalContinuationPrompt,
|
||||
handleGoalCommand,
|
||||
parseGoalCommand,
|
||||
} from "./commands-goal.js";
|
||||
import type { HandleCommandsParams } from "./commands-types.js";
|
||||
import { parseInlineDirectives } from "./directive-handling.parse.js";
|
||||
|
||||
const sessionKey = "agent:main:web:main";
|
||||
let tempRoots: string[] = [];
|
||||
@@ -76,6 +81,18 @@ describe("goal commands", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("formats command-looking continuation prompts so inline directives leave them intact", () => {
|
||||
const prompt = formatGoalContinuationPrompt("ship /fast off");
|
||||
expect(prompt).toBe(
|
||||
`Pursue this goal exactly as written from this JSON string: "ship \\/fast off"`,
|
||||
);
|
||||
|
||||
const directives = parseInlineDirectives(prompt);
|
||||
|
||||
expect(directives.cleaned).toBe(prompt);
|
||||
expect(directives.hasFastDirective).toBe(false);
|
||||
});
|
||||
|
||||
it("starts a goal from Codex-style bare /goal objective text", async () => {
|
||||
const storePath = await createStorePath();
|
||||
await upsertSessionEntry({
|
||||
@@ -84,13 +101,118 @@ describe("goal commands", () => {
|
||||
entry: { sessionId: "sess-main", updatedAt: 1, totalTokens: 0, totalTokensFresh: true },
|
||||
});
|
||||
|
||||
const result = await handleGoalCommand(
|
||||
buildGoalParams("/goal build a 3d game", storePath),
|
||||
true,
|
||||
);
|
||||
const params = buildGoalParams("/goal build a 3d game", storePath);
|
||||
const result = await handleGoalCommand(params, true);
|
||||
|
||||
expect(result?.shouldContinue).toBe(false);
|
||||
expect(result?.reply?.text).toBe("Goal started: build a 3d game");
|
||||
expect(result?.shouldContinue).toBe(true);
|
||||
expect(result?.reply).toBeUndefined();
|
||||
expect(params.command.commandBodyNormalized).toBe("build a 3d game");
|
||||
expect((params.ctx as { BodyForAgent?: string }).BodyForAgent).toBe("build a 3d game");
|
||||
expect(getSessionEntry({ storePath, sessionKey })?.goal?.objective).toBe("build a 3d game");
|
||||
});
|
||||
|
||||
it("wraps command-prefixed goal objectives before continuing", async () => {
|
||||
const storePath = await createStorePath();
|
||||
await upsertSessionEntry({
|
||||
storePath,
|
||||
sessionKey,
|
||||
entry: { sessionId: "sess-main", updatedAt: 1, totalTokens: 0, totalTokensFresh: true },
|
||||
});
|
||||
|
||||
const slashParams = buildGoalParams("/goal start /status", storePath);
|
||||
const slashResult = await handleGoalCommand(slashParams, true);
|
||||
const slashPrompt = `Pursue this goal exactly as written from this JSON string: "\\/status"`;
|
||||
|
||||
expect(slashResult?.shouldContinue).toBe(true);
|
||||
expect(slashParams.command.commandBodyNormalized).toBe(slashPrompt);
|
||||
expect((slashParams.ctx as { BodyForAgent?: string }).BodyForAgent).toBe(slashPrompt);
|
||||
expect(getSessionEntry({ storePath, sessionKey })?.goal?.objective).toBe("/status");
|
||||
|
||||
const bangStorePath = await createStorePath();
|
||||
await upsertSessionEntry({
|
||||
storePath: bangStorePath,
|
||||
sessionKey,
|
||||
entry: { sessionId: "sess-main", updatedAt: 1, totalTokens: 0, totalTokensFresh: true },
|
||||
});
|
||||
|
||||
const bangParams = buildGoalParams("/goal start !npm test", bangStorePath);
|
||||
const bangResult = await handleGoalCommand(bangParams, true);
|
||||
const bangPrompt = `Pursue this goal exactly as written from this JSON string: "!npm test"`;
|
||||
|
||||
expect(bangResult?.shouldContinue).toBe(true);
|
||||
expect(bangParams.command.commandBodyNormalized).toBe(bangPrompt);
|
||||
expect((bangParams.ctx as { BodyForAgent?: string }).BodyForAgent).toBe(bangPrompt);
|
||||
expect(getSessionEntry({ storePath: bangStorePath, sessionKey })?.goal?.objective).toBe(
|
||||
"!npm test",
|
||||
);
|
||||
});
|
||||
|
||||
it("resumes a goal and continues with a resume prompt", async () => {
|
||||
const storePath = await createStorePath();
|
||||
await upsertSessionEntry({
|
||||
storePath,
|
||||
sessionKey,
|
||||
entry: {
|
||||
sessionId: "sess-main",
|
||||
updatedAt: 1,
|
||||
goal: {
|
||||
schemaVersion: 1,
|
||||
id: "goal-1",
|
||||
objective: "finish the migration",
|
||||
status: "paused",
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
tokenStart: 0,
|
||||
tokenStartFresh: true,
|
||||
tokensUsed: 0,
|
||||
continuationTurns: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const params = buildGoalParams("/goal resume CI passed", storePath);
|
||||
const result = await handleGoalCommand(params, true);
|
||||
|
||||
expect(result?.shouldContinue).toBe(true);
|
||||
expect(params.command.commandBodyNormalized).toBe(
|
||||
"Continue pursuing the current goal. Note: CI passed",
|
||||
);
|
||||
expect(getSessionEntry({ storePath, sessionKey })?.goal?.status).toBe("active");
|
||||
});
|
||||
|
||||
it("wraps command-looking resume notes before continuing", async () => {
|
||||
const storePath = await createStorePath();
|
||||
await upsertSessionEntry({
|
||||
storePath,
|
||||
sessionKey,
|
||||
entry: {
|
||||
sessionId: "sess-main",
|
||||
updatedAt: 1,
|
||||
goal: {
|
||||
schemaVersion: 1,
|
||||
id: "goal-1",
|
||||
objective: "finish the migration",
|
||||
status: "paused",
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
tokenStart: 0,
|
||||
tokenStartFresh: true,
|
||||
tokensUsed: 0,
|
||||
continuationTurns: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const params = buildGoalParams("/goal resume /fast off", storePath);
|
||||
const result = await handleGoalCommand(params, true);
|
||||
const prompt = `Continue pursuing the current goal. Interpret this JSON string as the resume note: "\\/fast off"`;
|
||||
const directives = parseInlineDirectives(prompt);
|
||||
|
||||
expect(result?.shouldContinue).toBe(true);
|
||||
expect(params.command.commandBodyNormalized).toBe(prompt);
|
||||
expect((params.ctx as { BodyForAgent?: string }).BodyForAgent).toBe(prompt);
|
||||
expect(directives.cleaned).toBe(prompt);
|
||||
expect(directives.hasFastDirective).toBe(false);
|
||||
expect(getSessionEntry({ storePath, sessionKey })?.goal?.status).toBe("active");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,6 +73,61 @@ function goalReply(text: string): CommandHandlerResult {
|
||||
};
|
||||
}
|
||||
|
||||
function hasCommandLikeGoalText(trimmed: string): boolean {
|
||||
return /(?:^|\s)\//.test(trimmed) || trimmed.startsWith("!");
|
||||
}
|
||||
|
||||
function encodeGoalJsonString(trimmed: string): string {
|
||||
return JSON.stringify(trimmed).replaceAll("/", "\\/");
|
||||
}
|
||||
|
||||
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)}`
|
||||
: trimmed;
|
||||
}
|
||||
|
||||
export function formatGoalResumeContinuationPrompt(note: string): string {
|
||||
const trimmed = note.trim();
|
||||
if (!trimmed) {
|
||||
return "Continue pursuing the current goal.";
|
||||
}
|
||||
return hasCommandLikeGoalText(trimmed)
|
||||
? `Continue pursuing the current goal. Interpret this JSON string as the resume note: ${encodeGoalJsonString(trimmed)}`
|
||||
: `Continue pursuing the current goal. Note: ${trimmed}`;
|
||||
}
|
||||
|
||||
function applyGoalPromptToContext(ctx: HandleCommandsParams["ctx"], message: string): void {
|
||||
const mutableCtx = ctx as HandleCommandsParams["ctx"] & {
|
||||
Body?: string;
|
||||
RawBody?: string;
|
||||
CommandBody?: string;
|
||||
BodyForCommands?: string;
|
||||
BodyForAgent?: string;
|
||||
BodyStripped?: string;
|
||||
};
|
||||
mutableCtx.Body = message;
|
||||
mutableCtx.RawBody = message;
|
||||
mutableCtx.CommandBody = message;
|
||||
mutableCtx.BodyForCommands = message;
|
||||
mutableCtx.BodyForAgent = message;
|
||||
mutableCtx.BodyStripped = message;
|
||||
}
|
||||
|
||||
function applyGoalContinuationPrompt(params: HandleCommandsParams, message: string): void {
|
||||
applyGoalPromptToContext(params.ctx, message);
|
||||
if (params.rootCtx && params.rootCtx !== params.ctx) {
|
||||
applyGoalPromptToContext(params.rootCtx, message);
|
||||
}
|
||||
params.command.rawBodyNormalized = message;
|
||||
params.command.commandBodyNormalized = message;
|
||||
}
|
||||
|
||||
function goalContinuation(): CommandHandlerResult {
|
||||
return { shouldContinue: true };
|
||||
}
|
||||
|
||||
function goalErrorReply(error: unknown): CommandHandlerResult {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return goalReply(`Goal error: ${message}`);
|
||||
@@ -115,7 +170,8 @@ export const handleGoalCommand: CommandHandler = async (params, allowTextCommand
|
||||
fallbackEntry: params.sessionEntry,
|
||||
});
|
||||
syncGoalSessionEntry(params);
|
||||
return goalReply(`Goal started: ${goal.objective}`);
|
||||
applyGoalContinuationPrompt(params, formatGoalContinuationPrompt(goal.objective));
|
||||
return goalContinuation();
|
||||
}
|
||||
case "pause": {
|
||||
const goal = await updateSessionGoalStatus({
|
||||
@@ -128,14 +184,16 @@ export const handleGoalCommand: CommandHandler = async (params, allowTextCommand
|
||||
return goalReply(`Goal paused: ${goal.objective}`);
|
||||
}
|
||||
case "resume": {
|
||||
const goal = await updateSessionGoalStatus({
|
||||
await updateSessionGoalStatus({
|
||||
sessionKey: params.sessionKey,
|
||||
storePath: params.storePath,
|
||||
status: "active",
|
||||
...(parsed.text ? { note: parsed.text } : {}),
|
||||
});
|
||||
syncGoalSessionEntry(params);
|
||||
return goalReply(`Goal resumed: ${goal.objective}`);
|
||||
const message = formatGoalResumeContinuationPrompt(parsed.text);
|
||||
applyGoalContinuationPrompt(params, message);
|
||||
return goalContinuation();
|
||||
}
|
||||
case "complete":
|
||||
case "done": {
|
||||
|
||||
@@ -200,6 +200,7 @@ export async function maybeResolveNativeSlashCommandFastReply(params: {
|
||||
if (!commandResult.shouldContinue) {
|
||||
return { handled: true, reply: commandResult.reply };
|
||||
}
|
||||
const continuationTriggerBodyNormalized = command.rawBodyNormalized;
|
||||
|
||||
const directiveResult = await resolveReplyDirectives({
|
||||
ctx: params.ctx,
|
||||
@@ -216,7 +217,7 @@ export async function maybeResolveNativeSlashCommandFastReply(params: {
|
||||
sessionScope: sessionState.sessionScope,
|
||||
groupResolution: sessionState.groupResolution,
|
||||
isGroup: sessionState.isGroup,
|
||||
triggerBodyNormalized: sessionState.triggerBodyNormalized,
|
||||
triggerBodyNormalized: continuationTriggerBodyNormalized,
|
||||
resetTriggered: false,
|
||||
commandAuthorized: params.commandAuthorized,
|
||||
defaultProvider: params.defaultProvider,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "./get-reply-fast-path.js";
|
||||
import {
|
||||
buildGetReplyCtx,
|
||||
createGetReplyContinueDirectivesResult,
|
||||
createGetReplySessionState,
|
||||
expectResolvedTelegramTimezone,
|
||||
registerGetReplyRuntimeOverrides,
|
||||
@@ -28,6 +29,7 @@ function emptyAliasIndex(): ModelAliasIndex {
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
ensureAgentWorkspace: vi.fn(),
|
||||
handleInlineActions: vi.fn(),
|
||||
initSessionState: vi.fn(),
|
||||
loadModelCatalog: vi.fn<LoadModelCatalogFn>(async () => [
|
||||
{
|
||||
@@ -113,6 +115,8 @@ describe("getReplyFromConfig fast test bootstrap", () => {
|
||||
resolveRuntimeCliBackends: () => [],
|
||||
});
|
||||
mocks.ensureAgentWorkspace.mockReset();
|
||||
mocks.handleInlineActions.mockReset();
|
||||
mocks.handleInlineActions.mockResolvedValue({ kind: "reply", reply: { text: "ok" } });
|
||||
mocks.initSessionState.mockReset();
|
||||
mocks.loadModelCatalog.mockReset();
|
||||
mocks.loadModelCatalog.mockResolvedValue([
|
||||
@@ -525,6 +529,82 @@ describe("getReplyFromConfig fast test bootstrap", () => {
|
||||
expect(directiveParams.workspaceDir).toBe("/tmp/workspace");
|
||||
});
|
||||
|
||||
it("continues native slash goal starts with the rewritten command-safe prompt", async () => {
|
||||
const home = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-native-goal-fast-"));
|
||||
const targetSessionKey = "agent:main:telegram:123";
|
||||
const storePath = path.join(home, "sessions.json");
|
||||
const cfg = markCompleteReplyConfig({
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
workspace: path.join(home, "workspace"),
|
||||
},
|
||||
},
|
||||
session: { store: storePath },
|
||||
} as OpenClawConfig);
|
||||
const continuationPrompt = `Pursue this goal exactly as written from this JSON string: "\\/status"`;
|
||||
const continueDirectives = async (params: unknown) =>
|
||||
createGetReplyContinueDirectivesResult({
|
||||
body: (params as { triggerBodyNormalized: string }).triggerBodyNormalized,
|
||||
abortKey: targetSessionKey,
|
||||
from: "telegram:user:42",
|
||||
to: "telegram:123",
|
||||
senderId: "telegram:user:42",
|
||||
commandSource: (params as { triggerBodyNormalized: string }).triggerBodyNormalized,
|
||||
senderIsOwner: true,
|
||||
resetHookTriggered: false,
|
||||
});
|
||||
mocks.resolveReplyDirectives
|
||||
.mockImplementationOnce(continueDirectives)
|
||||
.mockImplementationOnce(async (params: unknown) => {
|
||||
expect((params as { triggerBodyNormalized: string }).triggerBodyNormalized).toBe(
|
||||
continuationPrompt,
|
||||
);
|
||||
return continueDirectives(params);
|
||||
});
|
||||
mocks.handleInlineActions.mockImplementation(async (params: unknown) => {
|
||||
expect(params).toMatchObject({
|
||||
command: {
|
||||
rawBodyNormalized: continuationPrompt,
|
||||
commandBodyNormalized: continuationPrompt,
|
||||
},
|
||||
cleanedBody: continuationPrompt,
|
||||
});
|
||||
return {
|
||||
kind: "continue",
|
||||
directives: {},
|
||||
abortedLastRun: false,
|
||||
cleanedBody: continuationPrompt,
|
||||
};
|
||||
});
|
||||
|
||||
await expect(
|
||||
getReplyFromConfig(
|
||||
buildGetReplyCtx({
|
||||
Body: "/goal start /status",
|
||||
BodyForAgent: "/goal start /status",
|
||||
RawBody: "/goal start /status",
|
||||
CommandBody: "/goal start /status",
|
||||
CommandSource: "native",
|
||||
CommandAuthorized: true,
|
||||
SessionKey: "telegram:slash:123",
|
||||
CommandTargetSessionKey: targetSessionKey,
|
||||
}),
|
||||
undefined,
|
||||
cfg,
|
||||
),
|
||||
).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");
|
||||
const preparedReplyParams = requirePreparedReplyParams();
|
||||
expect(preparedReplyParams.command.commandBodyNormalized).toBe(continuationPrompt);
|
||||
expect(preparedReplyParams.sessionCtx.BodyForAgent).toBe(continuationPrompt);
|
||||
expect(mocks.handleInlineActions).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("uses native command target session keys during fast bootstrap", () => {
|
||||
const result = initFastReplySessionState({
|
||||
ctx: buildGetReplyCtx({
|
||||
|
||||
@@ -268,9 +268,9 @@ describe("tui command handlers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("runs goal commands locally instead of sending them to the model", async () => {
|
||||
it("starts local goals and sends the objective to the model", async () => {
|
||||
const runGoalCommand = vi.fn().mockResolvedValue({ text: "Goal started: ship" });
|
||||
const { handleCommand, sendChat, addSystem, refreshSessionInfo } = createHarness({
|
||||
const { handleCommand, sendChat, addSystem, refreshSessionInfo, addUser } = createHarness({
|
||||
opts: { local: true },
|
||||
runGoalCommand,
|
||||
});
|
||||
@@ -282,11 +282,75 @@ describe("tui command handlers", () => {
|
||||
agentId: "main",
|
||||
command: "/goal start ship",
|
||||
});
|
||||
expect(sendChat).not.toHaveBeenCalled();
|
||||
expectSendChatFields(sendChat, {
|
||||
sessionKey: "agent:main:main",
|
||||
message: "ship",
|
||||
});
|
||||
expect(addUser).toHaveBeenCalledWith("ship");
|
||||
expect(addSystem).toHaveBeenCalledWith("Goal started: ship");
|
||||
expect(refreshSessionInfo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("wraps command-prefixed local goal objectives before sending", async () => {
|
||||
const slashRunGoalCommand = vi.fn().mockResolvedValue({ text: "Goal started" });
|
||||
const slashHarness = createHarness({
|
||||
opts: { local: true },
|
||||
runGoalCommand: slashRunGoalCommand,
|
||||
});
|
||||
|
||||
await slashHarness.handleCommand("/goal start /status");
|
||||
const slashPrompt = `Pursue this goal exactly as written from this JSON string: "\\/status"`;
|
||||
expectSendChatFields(slashHarness.sendChat, {
|
||||
sessionKey: "agent:main:main",
|
||||
message: slashPrompt,
|
||||
});
|
||||
expect(slashHarness.addUser).toHaveBeenCalledWith(slashPrompt);
|
||||
|
||||
const bangRunGoalCommand = vi.fn().mockResolvedValue({ text: "Goal started" });
|
||||
const bangHarness = createHarness({
|
||||
opts: { local: true },
|
||||
runGoalCommand: bangRunGoalCommand,
|
||||
});
|
||||
|
||||
await bangHarness.handleCommand("/goal start !npm test");
|
||||
const bangPrompt = `Pursue this goal exactly as written from this JSON string: "!npm test"`;
|
||||
expectSendChatFields(bangHarness.sendChat, {
|
||||
sessionKey: "agent:main:main",
|
||||
message: bangPrompt,
|
||||
});
|
||||
expect(bangHarness.addUser).toHaveBeenCalledWith(bangPrompt);
|
||||
});
|
||||
|
||||
it("keeps local goal status as a control command", async () => {
|
||||
const runGoalCommand = vi.fn().mockResolvedValue({ text: "Goal: ship" });
|
||||
const { handleCommand, sendChat, addSystem } = createHarness({
|
||||
opts: { local: true },
|
||||
runGoalCommand,
|
||||
});
|
||||
|
||||
await handleCommand("/goal status");
|
||||
|
||||
expect(sendChat).not.toHaveBeenCalled();
|
||||
expect(addSystem).toHaveBeenCalledWith("Goal: ship");
|
||||
});
|
||||
|
||||
it("wraps command-prefixed local goal resume notes before sending", async () => {
|
||||
const runGoalCommand = vi.fn().mockResolvedValue({ text: "Goal resumed: ship" });
|
||||
const { handleCommand, sendChat, addUser } = createHarness({
|
||||
opts: { local: true },
|
||||
runGoalCommand,
|
||||
});
|
||||
|
||||
await handleCommand("/goal resume /fast off");
|
||||
|
||||
const prompt = `Continue pursuing the current goal. Interpret this JSON string as the resume note: "\\/fast off"`;
|
||||
expectSendChatFields(sendChat, {
|
||||
sessionKey: "agent:main:main",
|
||||
message: prompt,
|
||||
});
|
||||
expect(addUser).toHaveBeenCalledWith(prompt);
|
||||
});
|
||||
|
||||
it("passes the selected agent for local global goal commands", async () => {
|
||||
const runGoalCommand = vi.fn().mockResolvedValue({ text: "Goal started: ship" });
|
||||
const { handleCommand } = createHarness({
|
||||
|
||||
@@ -3,6 +3,11 @@ import type { Component, SelectItem, TUI } from "@earendil-works/pi-tui";
|
||||
import type { SessionsPatchResult } from "../../packages/gateway-protocol/src/index.js";
|
||||
import { modelKey } from "../agents/model-ref-shared.js";
|
||||
import { normalizeGroupActivation } from "../auto-reply/group-activation.js";
|
||||
import {
|
||||
formatGoalContinuationPrompt,
|
||||
formatGoalResumeContinuationPrompt,
|
||||
parseGoalCommand,
|
||||
} from "../auto-reply/reply/commands-goal.js";
|
||||
import {
|
||||
formatThinkingLevels,
|
||||
normalizeUsageDisplay,
|
||||
@@ -69,6 +74,21 @@ function isSlashStopCommand(text: string): boolean {
|
||||
return trimmed.startsWith("/") && isChatStopCommandText(trimmed);
|
||||
}
|
||||
|
||||
function goalContinuationPrompt(text: string): string | null {
|
||||
const parsed = parseGoalCommand(text);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
const action = parsed.action;
|
||||
if (action === "start" || action === "set" || action === "create") {
|
||||
return formatGoalContinuationPrompt(parsed.text) || null;
|
||||
}
|
||||
if (action === "resume") {
|
||||
return formatGoalResumeContinuationPrompt(parsed.text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
const {
|
||||
client,
|
||||
@@ -396,6 +416,10 @@ export function createCommandHandlers(context: CommandHandlerContext) {
|
||||
});
|
||||
chatLog.addSystem(result.text);
|
||||
await refreshSessionInfo();
|
||||
const continuation = goalContinuationPrompt(raw);
|
||||
if (continuation) {
|
||||
await sendMessage(continuation);
|
||||
}
|
||||
} catch (err) {
|
||||
chatLog.addSystem(`goal failed: ${sanitizeRenderableText(String(err))}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user