fix(agents): resolve webchat current session status

* fix(agents): resolve webchat current session status

* fix(agents): resolve webchat current session status

---------

Co-authored-by: Cornna <96944678+ymylive@users.noreply.github.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
This commit is contained in:
cornna
2026-06-23 06:26:49 +08:00
committed by GitHub
parent a1c2454b08
commit ef62076789
2 changed files with 133 additions and 21 deletions
@@ -538,6 +538,99 @@ describe("session_status tool", () => {
expect(details.sessionKey).toBe("main");
});
it("resolves webchat sessionKey=current to the full requester main key (#89773)", async () => {
resetSessionStore({
main: {
sessionId: "s-fallback-main",
updatedAt: 5,
thinkingLevel: "high",
},
"agent:admin:main": {
sessionId: "s-admin-main",
updatedAt: 10,
thinkingLevel: "low",
},
});
const tool = createSessionStatusTool({
agentSessionKey: "agent:admin:main",
activeDeliveryContext: {
channel: "webchat",
to: "control-ui-conversation",
},
config: mockConfig as never,
});
const result = await tool.execute("call-current-webchat-main", { sessionKey: "current" });
const details = result.details as { ok?: boolean; sessionKey?: string };
expect(details.ok).toBe(true);
expect(details.sessionKey).toBe("agent:admin:main");
const statusArg = mockCallArg(buildStatusMessageMock) as Record<string, unknown>;
expectRecordFields(statusArg.sessionEntry, {
sessionId: "s-admin-main",
thinkingLevel: "low",
});
});
it("resolves whitespace-decorated webchat sessionKey=current to the full requester main key (#89800)", async () => {
resetSessionStore({
main: {
sessionId: "s-fallback-main",
updatedAt: 5,
thinkingLevel: "high",
},
"agent:admin:main": {
sessionId: "s-admin-main",
updatedAt: 10,
thinkingLevel: "low",
},
});
const tool = createSessionStatusTool({
agentSessionKey: "agent:admin:main",
activeDeliveryContext: {
channel: "webchat",
to: "control-ui-conversation",
},
config: mockConfig as never,
});
const result = await tool.execute("call-current-webchat-main-spaced", {
sessionKey: " current ",
});
const details = result.details as { ok?: boolean; sessionKey?: string };
expect(details.ok).toBe(true);
expect(details.sessionKey).toBe("agent:admin:main");
const statusArg = mockCallArg(buildStatusMessageMock) as Record<string, unknown>;
expectRecordFields(statusArg.sessionEntry, {
sessionId: "s-admin-main",
thinkingLevel: "low",
});
});
it("synthesizes webchat sessionKey=current from the full requester main key (#89773)", async () => {
resetSessionStore({});
const tool = createSessionStatusTool({
agentSessionKey: "agent:admin:main",
activeDeliveryContext: {
channel: "webchat",
to: "control-ui-conversation",
},
config: mockConfig as never,
});
const result = await tool.execute("call-current-webchat-main-unpersisted", {
sessionKey: "current",
});
const details = result.details as { ok?: boolean; sessionKey?: string; statusText?: string };
expect(details.ok).toBe(true);
expect(details.sessionKey).toBe("agent:admin:main");
expect(details.statusText).toContain("OpenClaw");
});
it("uses runSessionKey thinking level for implicit no-arg status lookups (#82669)", async () => {
resetSessionStore({
"agent:main:telegram:default:direct:1234": {
+40 -21
View File
@@ -574,15 +574,16 @@ export function createSessionStatusTool(opts?: {
if (isImplicitRunSessionStatus) {
requestedKeyRaw = opts?.runSessionKey;
}
let requestedKeyInput = requestedKeyRaw?.trim() ?? "";
// Track whether this is a semantic-current request (literal "current" or a
// current-client alias) BEFORE any rewrite, so visibility treats it as self.
const isSemanticCurrentRequest =
requestedKeyRaw === "current" ||
requestedKeyInput === "current" ||
isImplicitRunSessionStatus ||
Boolean(
resolveCurrentSessionClientAlias({
key: requestedKeyRaw ?? "",
key: requestedKeyInput,
requesterInternalKey: effectiveRequesterKey,
}),
);
@@ -590,23 +591,26 @@ export function createSessionStatusTool(opts?: {
// Resolve semantic "current" to the live run session key for lookup purposes (#76708).
// In sandboxed channel runs there may be no separate runSessionKey because the sandbox
// key already is the live requester; avoid probing literal "current" through the gateway.
if (requestedKeyRaw === "current" && (opts?.runSessionKey || opts?.sandboxed === true)) {
if (requestedKeyInput === "current" && (opts?.runSessionKey || opts?.sandboxed === true)) {
requestedKeyRaw = opts.runSessionKey ?? effectiveRequesterKey;
requestedKeyInput = requestedKeyRaw?.trim() ?? "";
}
const currentSessionAlias = resolveCurrentSessionClientAlias({
key: requestedKeyRaw ?? "",
key: requestedKeyInput,
requesterInternalKey: effectiveRequesterKey,
});
if (currentSessionAlias) {
requestedKeyRaw = opts?.runSessionKey ?? currentSessionAlias;
requestedKeyInput = requestedKeyRaw?.trim() ?? "";
}
const requestedKeyInput = requestedKeyRaw?.trim() ?? "";
const effectiveRequesterLookupKey = effectiveRequesterKey.trim();
let resolvedViaSessionId = false;
let resolvedViaImplicitCurrentFallback = false;
if (!requestedKeyRaw?.trim()) {
if (!requestedKeyInput) {
throw new Error("sessionKey required");
}
requestedKeyRaw = requestedKeyInput;
const ensureAgentAccess = (targetAgentId: string) => {
if (targetAgentId === requesterAgentId) {
return;
@@ -622,20 +626,20 @@ export function createSessionStatusTool(opts?: {
}
};
if (requestedKeyRaw.startsWith("agent:") && !isSemanticCurrentRequest) {
const requestedAgentId = resolveAgentIdFromSessionKey(requestedKeyRaw);
if (requestedKeyInput.startsWith("agent:") && !isSemanticCurrentRequest) {
const requestedAgentId = resolveAgentIdFromSessionKey(requestedKeyInput);
ensureAgentAccess(requestedAgentId);
const access = visibilityGuard.check(
normalizeVisibilityTargetSessionKey(requestedKeyRaw, requestedAgentId),
normalizeVisibilityTargetSessionKey(requestedKeyInput, requestedAgentId),
);
if (!access.allowed) {
throw new Error(access.error);
}
}
const isExplicitAgentKey = requestedKeyRaw.startsWith("agent:");
const isExplicitAgentKey = requestedKeyInput.startsWith("agent:");
let agentId = isExplicitAgentKey
? resolveAgentIdFromSessionKey(requestedKeyRaw)
? resolveAgentIdFromSessionKey(requestedKeyInput)
: requesterAgentId;
let storePath = resolveStorePath(cfg.session?.store, { agentId });
let store = loadSessionStore(storePath);
@@ -652,15 +656,15 @@ export function createSessionStatusTool(opts?: {
alias,
mainKey,
requesterInternalKey: storeScopedRequesterKey,
includeAliasFallback: requestedKeyRaw !== "current",
includeAliasFallback: requestedKeyInput !== "current",
});
if (
!resolved &&
(requestedKeyRaw === "current" || shouldResolveSessionIdInput(requestedKeyRaw))
(requestedKeyInput === "current" || shouldResolveSessionIdInput(requestedKeyInput))
) {
const resolvedSession = await resolveSessionReference({
sessionKey: requestedKeyRaw,
sessionKey: requestedKeyInput,
alias,
mainKey,
requesterInternalKey: effectiveRequesterKey,
@@ -671,7 +675,7 @@ export function createSessionStatusTool(opts?: {
resolvedSession,
requesterSessionKey: effectiveRequesterKey,
restrictToSpawned: opts?.sandboxed === true,
visibilitySessionKey: requestedKeyRaw,
visibilitySessionKey: requestedKeyInput,
});
if (!visibleSession.ok) {
throw new Error("Session status visibility is restricted to the current session tree.");
@@ -680,6 +684,7 @@ export function createSessionStatusTool(opts?: {
ensureAgentAccess(resolveAgentIdFromSessionKey(visibleSession.key));
resolvedViaSessionId = true;
requestedKeyRaw = visibleSession.key;
requestedKeyInput = requestedKeyRaw.trim();
agentId = resolveAgentIdFromSessionKey(visibleSession.key);
storePath = resolveStorePath(cfg.session?.store, { agentId });
store = loadSessionStore(storePath);
@@ -700,7 +705,18 @@ export function createSessionStatusTool(opts?: {
}
}
if (!resolved && requestedKeyRaw === "current") {
if (!resolved && requestedKeyInput === "current" && effectiveRequesterLookupKey) {
resolved = resolveSessionEntry({
store,
keyRaw: effectiveRequesterLookupKey,
alias,
mainKey,
requesterInternalKey: storeScopedRequesterKey,
includeAliasFallback: false,
});
}
if (!resolved && requestedKeyInput === "current") {
resolved = resolveSessionEntry({
store,
keyRaw: requestedKeyRaw,
@@ -732,12 +748,15 @@ export function createSessionStatusTool(opts?: {
}
if (!resolved) {
const runSessionFallbackKey = opts?.runSessionKey?.trim();
const fallback = resolveImplicitCurrentSessionFallback({
allowFallback: isSemanticCurrentRequest || requestedKeyParam === undefined,
fallbackKey:
(isSemanticCurrentRequest || isImplicitRunSessionStatus) && opts?.runSessionKey
? opts.runSessionKey
: storeScopedRequesterKey,
(isSemanticCurrentRequest || isImplicitRunSessionStatus) && runSessionFallbackKey
? runSessionFallbackKey
: isSemanticCurrentRequest
? effectiveRequesterLookupKey
: storeScopedRequesterKey,
});
if (fallback) {
resolved = fallback;
@@ -746,8 +765,8 @@ export function createSessionStatusTool(opts?: {
}
if (!resolved) {
const kind = shouldResolveSessionIdInput(requestedKeyRaw) ? "sessionId" : "sessionKey";
throw new Error(`Unknown ${kind}: ${requestedKeyRaw}`);
const kind = shouldResolveSessionIdInput(requestedKeyInput) ? "sessionId" : "sessionKey";
throw new Error(`Unknown ${kind}: ${requestedKeyInput}`);
}
// Preserve caller-scoped raw-key/current lookups as "self" for visibility checks.