mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(agents): treat TUI client label as current session
This commit is contained in:
@@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Bedrock: stop heartbeat runs from persisting blank user transcript turns and repair existing blank user text messages before replay, preventing AWS Bedrock `ContentBlock` blank-text validation failures. Fixes #72640 and #72622. Thanks @goldzulu.
|
||||
- Agents/LM Studio: promote standalone bracketed local-model tool requests into registered tool calls and hide unsupported bracket blocks from visible replies, so MemPalace MCP lookups do not print raw `[tool]` JSON scaffolding in chat. Fixes #66178. Thanks @detroit357.
|
||||
- Local models: warn when an assistant reply looks like a tool call but the provider emitted plain text instead of a structured tool invocation, making fake/non-executed tool calls visible in logs. Fixes #51332. Thanks @emilclaw.
|
||||
- TUI/local models: treat visible gateway client labels such as `openclaw-tui` as the current requester session for session-aware tools, so Ollama tool calls no longer fail by resolving the UI label as a session id. Fixes #66391. Thanks @kickingzebra.
|
||||
- Local models: route self-hosted OpenAI-compatible model discovery through the guarded fetch path pinned to the configured host, covering vLLM and SGLang setup without reopening local/LAN SSRF probes. Supersedes #46359. Thanks @cdxiaodong.
|
||||
- Local models: classify terminated, reset, closed, timeout, and aborted model-call failures and attach a process memory snapshot to the diagnostic event, making LM Studio/Ollama RAM-pressure failures easier to prove from stability bundles. Refs #65551. Thanks @BigWiLLi111.
|
||||
- Local models: pass configured provider request timeouts through OpenAI SDK transports and the model idle watchdog so long-running local or custom OpenAI-compatible streams use one timeout knob instead of hitting the SDK's 10-minute default or the 120s idle default. Fixes #63663. Thanks @aidiffuser.
|
||||
|
||||
@@ -82,7 +82,9 @@ agents alternate messages (up to 5 turns). The target agent can reply
|
||||
or another visible session. It reports usage, time, model/runtime state, and
|
||||
linked background-task context when present. Like `/status`, it can backfill
|
||||
sparse token/cache counters from the latest transcript usage entry, and
|
||||
`model=default` clears a per-session override.
|
||||
`model=default` clears a per-session override. Use `sessionKey="current"` for
|
||||
the caller's current session; visible client labels such as `openclaw-tui` are
|
||||
not session keys.
|
||||
|
||||
`sessions_yield` intentionally ends the current turn so the next message can be
|
||||
the follow-up event you are waiting for. Use it after spawning sub-agents when
|
||||
|
||||
@@ -468,6 +468,22 @@ describe("session_status tool", () => {
|
||||
expect(details.sessionKey).toBe("main");
|
||||
});
|
||||
|
||||
it("treats the TUI client label as the current requester session", async () => {
|
||||
resetSessionStore({
|
||||
"agent:main:main": {
|
||||
sessionId: "s-main",
|
||||
updatedAt: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const tool = getSessionStatusTool("agent:main:main");
|
||||
|
||||
const result = await tool.execute("call-tui-label", { sessionKey: "openclaw-tui" });
|
||||
const details = result.details as { ok?: boolean; sessionKey?: string };
|
||||
expect(details.ok).toBe(true);
|
||||
expect(details.sessionKey).toBe("agent:main:main");
|
||||
});
|
||||
|
||||
it("falls back from implicit default-account direct policy keys to persisted direct sessions", async () => {
|
||||
resetSessionStore({
|
||||
"agent:main:telegram:direct:1053274893": {
|
||||
|
||||
@@ -60,6 +60,7 @@ export function describeSessionsSpawnTool(options?: { acpAvailable?: boolean }):
|
||||
export function describeSessionStatusTool(): string {
|
||||
return [
|
||||
"Show a /status-equivalent session status card for the current or another visible session, including usage, time, cost when available, and linked background task context.",
|
||||
'Use `sessionKey="current"` for the current session; do not use UI/client labels such as `openclaw-tui` as session keys.',
|
||||
"Optional `model` sets a per-session model override; `model=default` resets overrides.",
|
||||
"Use this for questions like what model is active or how a session is configured.",
|
||||
].join(" ");
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
createSessionVisibilityGuard,
|
||||
shouldResolveSessionIdInput,
|
||||
createAgentToAgentPolicy,
|
||||
resolveCurrentSessionClientAlias,
|
||||
resolveEffectiveSessionToolsVisibility,
|
||||
resolveInternalSessionKey,
|
||||
resolveSessionReference,
|
||||
@@ -322,6 +323,13 @@ export function createSessionStatusTool(opts?: {
|
||||
|
||||
const requestedKeyParam = readStringParam(params, "sessionKey");
|
||||
let requestedKeyRaw = requestedKeyParam ?? opts?.agentSessionKey;
|
||||
const currentSessionAlias = resolveCurrentSessionClientAlias({
|
||||
key: requestedKeyRaw ?? "",
|
||||
requesterInternalKey: effectiveRequesterKey,
|
||||
});
|
||||
if (currentSessionAlias) {
|
||||
requestedKeyRaw = currentSessionAlias;
|
||||
}
|
||||
const requestedKeyInput = requestedKeyRaw?.trim() ?? "";
|
||||
let resolvedViaSessionId = false;
|
||||
let resolvedViaImplicitCurrentFallback = false;
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
listSpawnedSessionKeys,
|
||||
looksLikeSessionId,
|
||||
looksLikeSessionKey,
|
||||
resolveCurrentSessionClientAlias,
|
||||
resolveDisplaySessionKey,
|
||||
resolveInternalSessionKey,
|
||||
resolveMainSessionAlias,
|
||||
|
||||
@@ -7,6 +7,7 @@ vi.mock("../../gateway/call.js", () => ({
|
||||
let isResolvedSessionVisibleToRequester: typeof import("./sessions-resolution.js").isResolvedSessionVisibleToRequester;
|
||||
let looksLikeSessionId: typeof import("./sessions-resolution.js").looksLikeSessionId;
|
||||
let looksLikeSessionKey: typeof import("./sessions-resolution.js").looksLikeSessionKey;
|
||||
let resolveCurrentSessionClientAlias: typeof import("./sessions-resolution.js").resolveCurrentSessionClientAlias;
|
||||
let resolveDisplaySessionKey: typeof import("./sessions-resolution.js").resolveDisplaySessionKey;
|
||||
let resolveInternalSessionKey: typeof import("./sessions-resolution.js").resolveInternalSessionKey;
|
||||
let resolveMainSessionAlias: typeof import("./sessions-resolution.js").resolveMainSessionAlias;
|
||||
@@ -19,6 +20,7 @@ beforeAll(async () => {
|
||||
isResolvedSessionVisibleToRequester,
|
||||
looksLikeSessionId,
|
||||
looksLikeSessionKey,
|
||||
resolveCurrentSessionClientAlias,
|
||||
resolveDisplaySessionKey,
|
||||
resolveInternalSessionKey,
|
||||
resolveMainSessionAlias,
|
||||
@@ -105,6 +107,22 @@ describe("session key display/internal mapping", () => {
|
||||
"current",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps interactive client ids to the requester session", () => {
|
||||
expect(
|
||||
resolveCurrentSessionClientAlias({
|
||||
key: "openclaw-tui",
|
||||
requesterInternalKey: "agent:main:main",
|
||||
}),
|
||||
).toBe("agent:main:main");
|
||||
expect(resolveCurrentSessionClientAlias({ key: "openclaw-tui" })).toBeUndefined();
|
||||
expect(
|
||||
resolveCurrentSessionClientAlias({
|
||||
key: "node-host",
|
||||
requesterInternalKey: "agent:main:main",
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("session reference shape detection", () => {
|
||||
@@ -303,4 +321,22 @@ describe("resolveSessionReference", () => {
|
||||
});
|
||||
expect(callGatewayMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("treats the TUI client label as the requester session", async () => {
|
||||
await expect(
|
||||
resolveSessionReference({
|
||||
sessionKey: "openclaw-tui",
|
||||
alias: "main",
|
||||
mainKey: "main",
|
||||
requesterInternalKey: "agent:main:main",
|
||||
restrictToSpawned: false,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
ok: true,
|
||||
key: "agent:main:main",
|
||||
displayKey: "agent:main:main",
|
||||
resolvedViaSessionId: false,
|
||||
});
|
||||
expect(callGatewayMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_IDS,
|
||||
normalizeGatewayClientId,
|
||||
} from "../../gateway/protocol/client-info.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import {
|
||||
listSpawnedSessionKeys,
|
||||
@@ -15,6 +19,16 @@ const defaultSessionsResolutionDeps = {
|
||||
callGateway,
|
||||
};
|
||||
|
||||
const CURRENT_SESSION_CLIENT_ALIAS_IDS = new Set<string>([
|
||||
GATEWAY_CLIENT_IDS.TUI,
|
||||
GATEWAY_CLIENT_IDS.CLI,
|
||||
GATEWAY_CLIENT_IDS.WEBCHAT_UI,
|
||||
GATEWAY_CLIENT_IDS.CONTROL_UI,
|
||||
GATEWAY_CLIENT_IDS.MACOS_APP,
|
||||
GATEWAY_CLIENT_IDS.IOS_APP,
|
||||
GATEWAY_CLIENT_IDS.ANDROID_APP,
|
||||
]);
|
||||
|
||||
let sessionsResolutionDeps: {
|
||||
callGateway: GatewayCaller;
|
||||
} = defaultSessionsResolutionDeps;
|
||||
@@ -51,6 +65,23 @@ export function resolveInternalSessionKey(params: {
|
||||
return params.key;
|
||||
}
|
||||
|
||||
export function resolveCurrentSessionClientAlias(params: {
|
||||
key: string;
|
||||
requesterInternalKey?: string;
|
||||
}): string | undefined {
|
||||
const requesterKey = normalizeOptionalString(params.requesterInternalKey);
|
||||
if (!requesterKey) {
|
||||
return undefined;
|
||||
}
|
||||
const clientId = normalizeGatewayClientId(params.key);
|
||||
if (!clientId || !CURRENT_SESSION_CLIENT_ALIAS_IDS.has(clientId)) {
|
||||
return undefined;
|
||||
}
|
||||
// UI/client labels can appear next to the real session key in status text.
|
||||
// Treat them as the current requester instead of probing them as sessionIds.
|
||||
return requesterKey;
|
||||
}
|
||||
|
||||
export { listSpawnedSessionKeys };
|
||||
|
||||
export async function isRequesterSpawnedSessionVisible(params: {
|
||||
@@ -361,7 +392,11 @@ export async function resolveSessionReference(params: {
|
||||
requesterInternalKey?: string;
|
||||
restrictToSpawned: boolean;
|
||||
}): Promise<SessionReferenceResolution> {
|
||||
const rawInput = params.sessionKey.trim();
|
||||
const rawInput =
|
||||
resolveCurrentSessionClientAlias({
|
||||
key: params.sessionKey,
|
||||
requesterInternalKey: params.requesterInternalKey,
|
||||
}) ?? params.sessionKey.trim();
|
||||
if (rawInput === "current") {
|
||||
const resolvedCurrent = await resolveSessionReferenceByKeyOrSessionId({
|
||||
raw: rawInput,
|
||||
|
||||
Reference in New Issue
Block a user