mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 05:54:43 +00:00
fix (gateway/agent): route bare /new and /reset through sessions.reset
This commit is contained in:
@@ -43,6 +43,7 @@ import { buildGroupIntro } from "./groups.js";
|
||||
import { buildInboundMetaSystemPrompt, buildInboundUserContextPrefix } from "./inbound-meta.js";
|
||||
import { resolveQueueSettings } from "./queue.js";
|
||||
import { routeReply } from "./route-reply.js";
|
||||
import { BARE_SESSION_RESET_PROMPT } from "./session-reset-prompt.js";
|
||||
import { ensureSkillSnapshot, prependSystemEvents } from "./session-updates.js";
|
||||
import { resolveTypingMode } from "./typing-mode.js";
|
||||
import { appendUntrustedContext } from "./untrusted-context.js";
|
||||
@@ -50,9 +51,6 @@ import { appendUntrustedContext } from "./untrusted-context.js";
|
||||
type AgentDefaults = NonNullable<OpenClawConfig["agents"]>["defaults"];
|
||||
type ExecOverrides = Pick<ExecToolDefaults, "host" | "security" | "ask" | "node">;
|
||||
|
||||
const BARE_SESSION_RESET_PROMPT =
|
||||
"A new session was started via /new or /reset. Greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.";
|
||||
|
||||
type RunPreparedReplyParams = {
|
||||
ctx: MsgContext;
|
||||
sessionCtx: TemplateContext;
|
||||
|
||||
2
src/auto-reply/reply/session-reset-prompt.ts
Normal file
2
src/auto-reply/reply/session-reset-prompt.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const BARE_SESSION_RESET_PROMPT =
|
||||
"A new session was started via /new or /reset. Greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.";
|
||||
@@ -1,6 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { GatewayRequestHandlers } from "./types.js";
|
||||
import type { GatewayRequestHandlerOptions, GatewayRequestHandlers } from "./types.js";
|
||||
import { listAgentIds } from "../../agents/agent-scope.js";
|
||||
import { BARE_SESSION_RESET_PROMPT } from "../../auto-reply/reply/session-reset-prompt.js";
|
||||
import { agentCommand } from "../../commands/agent.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import {
|
||||
@@ -47,9 +48,106 @@ import {
|
||||
import { formatForLog } from "../ws-log.js";
|
||||
import { waitForAgentJob } from "./agent-job.js";
|
||||
import { injectTimestamp, timestampOptsFromConfig } from "./agent-timestamp.js";
|
||||
import { sessionsHandlers } from "./sessions.js";
|
||||
|
||||
const RESET_COMMAND_RE = /^\/(new|reset)(?:\s+([\s\S]*))?$/i;
|
||||
|
||||
function isGatewayErrorShape(value: unknown): value is { code: string; message: string } {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
const candidate = value as { code?: unknown; message?: unknown };
|
||||
return typeof candidate.code === "string" && typeof candidate.message === "string";
|
||||
}
|
||||
|
||||
async function runSessionResetFromAgent(params: {
|
||||
key: string;
|
||||
reason: "new" | "reset";
|
||||
idempotencyKey: string;
|
||||
context: GatewayRequestHandlerOptions["context"];
|
||||
client: GatewayRequestHandlerOptions["client"];
|
||||
isWebchatConnect: GatewayRequestHandlerOptions["isWebchatConnect"];
|
||||
}): Promise<
|
||||
| { ok: true; key: string; sessionId?: string }
|
||||
| { ok: false; error: ReturnType<typeof errorShape> }
|
||||
> {
|
||||
return await new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const settle = (
|
||||
result:
|
||||
| { ok: true; key: string; sessionId?: string }
|
||||
| { ok: false; error: ReturnType<typeof errorShape> },
|
||||
) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const respond: GatewayRequestHandlerOptions["respond"] = (ok, payload, error) => {
|
||||
if (!ok) {
|
||||
settle({
|
||||
ok: false,
|
||||
error: isGatewayErrorShape(error)
|
||||
? error
|
||||
: errorShape(ErrorCodes.UNAVAILABLE, String(error ?? "sessions.reset failed")),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const payloadObj = payload as
|
||||
| {
|
||||
key?: unknown;
|
||||
entry?: {
|
||||
sessionId?: unknown;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
const key = typeof payloadObj?.key === "string" ? payloadObj.key : params.key;
|
||||
const sessionId =
|
||||
payloadObj?.entry && typeof payloadObj.entry.sessionId === "string"
|
||||
? payloadObj.entry.sessionId
|
||||
: undefined;
|
||||
settle({ ok: true, key, sessionId });
|
||||
};
|
||||
|
||||
void sessionsHandlers["sessions.reset"]({
|
||||
req: {
|
||||
type: "req",
|
||||
id: `${params.idempotencyKey}:reset`,
|
||||
method: "sessions.reset",
|
||||
},
|
||||
params: {
|
||||
key: params.key,
|
||||
reason: params.reason,
|
||||
},
|
||||
context: params.context,
|
||||
client: params.client,
|
||||
isWebchatConnect: params.isWebchatConnect,
|
||||
respond,
|
||||
})
|
||||
.then(() => {
|
||||
if (!settled) {
|
||||
settle({
|
||||
ok: false,
|
||||
error: errorShape(
|
||||
ErrorCodes.UNAVAILABLE,
|
||||
"sessions.reset completed without returning a response",
|
||||
),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
settle({
|
||||
ok: false,
|
||||
error: errorShape(ErrorCodes.UNAVAILABLE, String(err)),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const agentHandlers: GatewayRequestHandlers = {
|
||||
agent: async ({ params, respond, context, client }) => {
|
||||
agent: async ({ params, respond, context, client, isWebchatConnect }) => {
|
||||
const p = params;
|
||||
if (!validateAgentParams(p)) {
|
||||
respond(
|
||||
@@ -147,12 +245,6 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
}
|
||||
|
||||
// Inject timestamp into messages that don't already have one.
|
||||
// Channel messages (Discord, Telegram, etc.) get timestamps via envelope
|
||||
// formatting in a separate code path — they never reach this handler.
|
||||
// See: https://github.com/moltbot/moltbot/issues/3658
|
||||
message = injectTimestamp(message, timestampOptsFromConfig(cfg));
|
||||
|
||||
const isKnownGatewayChannel = (value: string): boolean => isGatewayMessageChannel(value);
|
||||
const channelHints = [request.channel, request.replyChannel]
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
@@ -194,7 +286,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
typeof request.sessionKey === "string" && request.sessionKey.trim()
|
||||
? request.sessionKey.trim()
|
||||
: undefined;
|
||||
const requestedSessionKey =
|
||||
let requestedSessionKey =
|
||||
requestedSessionKeyRaw ??
|
||||
resolveExplicitAgentSessionKey({
|
||||
cfg,
|
||||
@@ -219,6 +311,43 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
let bestEffortDeliver = false;
|
||||
let cfgForAgent: ReturnType<typeof loadConfig> | undefined;
|
||||
let resolvedSessionKey = requestedSessionKey;
|
||||
let skipTimestampInjection = false;
|
||||
|
||||
const resetCommandMatch = message.match(RESET_COMMAND_RE);
|
||||
if (resetCommandMatch && requestedSessionKey) {
|
||||
const resetReason = resetCommandMatch[1]?.toLowerCase() === "new" ? "new" : "reset";
|
||||
const resetResult = await runSessionResetFromAgent({
|
||||
key: requestedSessionKey,
|
||||
reason: resetReason,
|
||||
idempotencyKey: idem,
|
||||
context,
|
||||
client,
|
||||
isWebchatConnect,
|
||||
});
|
||||
if (!resetResult.ok) {
|
||||
respond(false, undefined, resetResult.error);
|
||||
return;
|
||||
}
|
||||
requestedSessionKey = resetResult.key;
|
||||
resolvedSessionId = resetResult.sessionId ?? resolvedSessionId;
|
||||
const postResetMessage = resetCommandMatch[2]?.trim() ?? "";
|
||||
if (postResetMessage) {
|
||||
message = postResetMessage;
|
||||
} else {
|
||||
// Keep bare /new and /reset behavior aligned with chat.send:
|
||||
// reset first, then run a fresh-session greeting prompt in-place.
|
||||
message = BARE_SESSION_RESET_PROMPT;
|
||||
skipTimestampInjection = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject timestamp into user-authored messages that don't already have one.
|
||||
// Channel messages (Discord, Telegram, etc.) get timestamps via envelope
|
||||
// formatting in a separate code path — they never reach this handler.
|
||||
// See: https://github.com/moltbot/moltbot/issues/3658
|
||||
if (!skipTimestampInjection) {
|
||||
message = injectTimestamp(message, timestampOptsFromConfig(cfg));
|
||||
}
|
||||
|
||||
if (requestedSessionKey) {
|
||||
const { cfg, storePath, entry, canonicalKey } = loadSessionEntry(requestedSessionKey);
|
||||
|
||||
Reference in New Issue
Block a user