mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 19:01:47 +03: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 { buildInboundMetaSystemPrompt, buildInboundUserContextPrefix } from "./inbound-meta.js";
|
||||||
import { resolveQueueSettings } from "./queue.js";
|
import { resolveQueueSettings } from "./queue.js";
|
||||||
import { routeReply } from "./route-reply.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 { ensureSkillSnapshot, prependSystemEvents } from "./session-updates.js";
|
||||||
import { resolveTypingMode } from "./typing-mode.js";
|
import { resolveTypingMode } from "./typing-mode.js";
|
||||||
import { appendUntrustedContext } from "./untrusted-context.js";
|
import { appendUntrustedContext } from "./untrusted-context.js";
|
||||||
@@ -50,9 +51,6 @@ import { appendUntrustedContext } from "./untrusted-context.js";
|
|||||||
type AgentDefaults = NonNullable<OpenClawConfig["agents"]>["defaults"];
|
type AgentDefaults = NonNullable<OpenClawConfig["agents"]>["defaults"];
|
||||||
type ExecOverrides = Pick<ExecToolDefaults, "host" | "security" | "ask" | "node">;
|
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 = {
|
type RunPreparedReplyParams = {
|
||||||
ctx: MsgContext;
|
ctx: MsgContext;
|
||||||
sessionCtx: TemplateContext;
|
sessionCtx: TemplateContext;
|
||||||
|
|||||||
@@ -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 { 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 { 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 { agentCommand } from "../../commands/agent.js";
|
||||||
import { loadConfig } from "../../config/config.js";
|
import { loadConfig } from "../../config/config.js";
|
||||||
import {
|
import {
|
||||||
@@ -47,9 +48,106 @@ import {
|
|||||||
import { formatForLog } from "../ws-log.js";
|
import { formatForLog } from "../ws-log.js";
|
||||||
import { waitForAgentJob } from "./agent-job.js";
|
import { waitForAgentJob } from "./agent-job.js";
|
||||||
import { injectTimestamp, timestampOptsFromConfig } from "./agent-timestamp.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 = {
|
export const agentHandlers: GatewayRequestHandlers = {
|
||||||
agent: async ({ params, respond, context, client }) => {
|
agent: async ({ params, respond, context, client, isWebchatConnect }) => {
|
||||||
const p = params;
|
const p = params;
|
||||||
if (!validateAgentParams(p)) {
|
if (!validateAgentParams(p)) {
|
||||||
respond(
|
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 isKnownGatewayChannel = (value: string): boolean => isGatewayMessageChannel(value);
|
||||||
const channelHints = [request.channel, request.replyChannel]
|
const channelHints = [request.channel, request.replyChannel]
|
||||||
.filter((value): value is string => typeof value === "string")
|
.filter((value): value is string => typeof value === "string")
|
||||||
@@ -194,7 +286,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
typeof request.sessionKey === "string" && request.sessionKey.trim()
|
typeof request.sessionKey === "string" && request.sessionKey.trim()
|
||||||
? request.sessionKey.trim()
|
? request.sessionKey.trim()
|
||||||
: undefined;
|
: undefined;
|
||||||
const requestedSessionKey =
|
let requestedSessionKey =
|
||||||
requestedSessionKeyRaw ??
|
requestedSessionKeyRaw ??
|
||||||
resolveExplicitAgentSessionKey({
|
resolveExplicitAgentSessionKey({
|
||||||
cfg,
|
cfg,
|
||||||
@@ -219,6 +311,43 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
let bestEffortDeliver = false;
|
let bestEffortDeliver = false;
|
||||||
let cfgForAgent: ReturnType<typeof loadConfig> | undefined;
|
let cfgForAgent: ReturnType<typeof loadConfig> | undefined;
|
||||||
let resolvedSessionKey = requestedSessionKey;
|
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) {
|
if (requestedSessionKey) {
|
||||||
const { cfg, storePath, entry, canonicalKey } = loadSessionEntry(requestedSessionKey);
|
const { cfg, storePath, entry, canonicalKey } = loadSessionEntry(requestedSessionKey);
|
||||||
|
|||||||
Reference in New Issue
Block a user