Skip to content

Commit dfe58a1

Browse files
committed
fix(agents): treat TUI client label as current session
1 parent 27ee5c0 commit dfe58a1

8 files changed

Lines changed: 102 additions & 2 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai
4141
- 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.
4242
- 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.
4343
- 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.
44+
- 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.
4445
- 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.
4546
- 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.
4647
- 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.

docs/concepts/session-tool.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ agents alternate messages (up to 5 turns). The target agent can reply
8282
or another visible session. It reports usage, time, model/runtime state, and
8383
linked background-task context when present. Like `/status`, it can backfill
8484
sparse token/cache counters from the latest transcript usage entry, and
85-
`model=default` clears a per-session override.
85+
`model=default` clears a per-session override. Use `sessionKey="current"` for
86+
the caller's current session; visible client labels such as `openclaw-tui` are
87+
not session keys.
8688

8789
`sessions_yield` intentionally ends the current turn so the next message can be
8890
the follow-up event you are waiting for. Use it after spawning sub-agents when

src/agents/openclaw-tools.session-status.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,22 @@ describe("session_status tool", () => {
468468
expect(details.sessionKey).toBe("main");
469469
});
470470

471+
it("treats the TUI client label as the current requester session", async () => {
472+
resetSessionStore({
473+
"agent:main:main": {
474+
sessionId: "s-main",
475+
updatedAt: 10,
476+
},
477+
});
478+
479+
const tool = getSessionStatusTool("agent:main:main");
480+
481+
const result = await tool.execute("call-tui-label", { sessionKey: "openclaw-tui" });
482+
const details = result.details as { ok?: boolean; sessionKey?: string };
483+
expect(details.ok).toBe(true);
484+
expect(details.sessionKey).toBe("agent:main:main");
485+
});
486+
471487
it("falls back from implicit default-account direct policy keys to persisted direct sessions", async () => {
472488
resetSessionStore({
473489
"agent:main:telegram:direct:1053274893": {

src/agents/tool-description-presets.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function describeSessionsSpawnTool(options?: { acpAvailable?: boolean }):
6060
export function describeSessionStatusTool(): string {
6161
return [
6262
"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.",
63+
'Use `sessionKey="current"` for the current session; do not use UI/client labels such as `openclaw-tui` as session keys.',
6364
"Optional `model` sets a per-session model override; `model=default` resets overrides.",
6465
"Use this for questions like what model is active or how a session is configured.",
6566
].join(" ");

src/agents/tools/session-status-tool.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
createSessionVisibilityGuard,
4646
shouldResolveSessionIdInput,
4747
createAgentToAgentPolicy,
48+
resolveCurrentSessionClientAlias,
4849
resolveEffectiveSessionToolsVisibility,
4950
resolveInternalSessionKey,
5051
resolveSessionReference,
@@ -322,6 +323,13 @@ export function createSessionStatusTool(opts?: {
322323

323324
const requestedKeyParam = readStringParam(params, "sessionKey");
324325
let requestedKeyRaw = requestedKeyParam ?? opts?.agentSessionKey;
326+
const currentSessionAlias = resolveCurrentSessionClientAlias({
327+
key: requestedKeyRaw ?? "",
328+
requesterInternalKey: effectiveRequesterKey,
329+
});
330+
if (currentSessionAlias) {
331+
requestedKeyRaw = currentSessionAlias;
332+
}
325333
const requestedKeyInput = requestedKeyRaw?.trim() ?? "";
326334
let resolvedViaSessionId = false;
327335
let resolvedViaImplicitCurrentFallback = false;

src/agents/tools/sessions-helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {
2020
listSpawnedSessionKeys,
2121
looksLikeSessionId,
2222
looksLikeSessionKey,
23+
resolveCurrentSessionClientAlias,
2324
resolveDisplaySessionKey,
2425
resolveInternalSessionKey,
2526
resolveMainSessionAlias,

src/agents/tools/sessions-resolution.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ vi.mock("../../gateway/call.js", () => ({
77
let isResolvedSessionVisibleToRequester: typeof import("./sessions-resolution.js").isResolvedSessionVisibleToRequester;
88
let looksLikeSessionId: typeof import("./sessions-resolution.js").looksLikeSessionId;
99
let looksLikeSessionKey: typeof import("./sessions-resolution.js").looksLikeSessionKey;
10+
let resolveCurrentSessionClientAlias: typeof import("./sessions-resolution.js").resolveCurrentSessionClientAlias;
1011
let resolveDisplaySessionKey: typeof import("./sessions-resolution.js").resolveDisplaySessionKey;
1112
let resolveInternalSessionKey: typeof import("./sessions-resolution.js").resolveInternalSessionKey;
1213
let resolveMainSessionAlias: typeof import("./sessions-resolution.js").resolveMainSessionAlias;
@@ -19,6 +20,7 @@ beforeAll(async () => {
1920
isResolvedSessionVisibleToRequester,
2021
looksLikeSessionId,
2122
looksLikeSessionKey,
23+
resolveCurrentSessionClientAlias,
2224
resolveDisplaySessionKey,
2325
resolveInternalSessionKey,
2426
resolveMainSessionAlias,
@@ -105,6 +107,22 @@ describe("session key display/internal mapping", () => {
105107
"current",
106108
);
107109
});
110+
111+
it("maps interactive client ids to the requester session", () => {
112+
expect(
113+
resolveCurrentSessionClientAlias({
114+
key: "openclaw-tui",
115+
requesterInternalKey: "agent:main:main",
116+
}),
117+
).toBe("agent:main:main");
118+
expect(resolveCurrentSessionClientAlias({ key: "openclaw-tui" })).toBeUndefined();
119+
expect(
120+
resolveCurrentSessionClientAlias({
121+
key: "node-host",
122+
requesterInternalKey: "agent:main:main",
123+
}),
124+
).toBeUndefined();
125+
});
108126
});
109127

110128
describe("session reference shape detection", () => {
@@ -303,4 +321,22 @@ describe("resolveSessionReference", () => {
303321
});
304322
expect(callGatewayMock).toHaveBeenCalledTimes(1);
305323
});
324+
325+
it("treats the TUI client label as the requester session", async () => {
326+
await expect(
327+
resolveSessionReference({
328+
sessionKey: "openclaw-tui",
329+
alias: "main",
330+
mainKey: "main",
331+
requesterInternalKey: "agent:main:main",
332+
restrictToSpawned: false,
333+
}),
334+
).resolves.toMatchObject({
335+
ok: true,
336+
key: "agent:main:main",
337+
displayKey: "agent:main:main",
338+
resolvedViaSessionId: false,
339+
});
340+
expect(callGatewayMock).not.toHaveBeenCalled();
341+
});
306342
});

src/agents/tools/sessions-resolution.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { OpenClawConfig } from "../../config/types.openclaw.js";
22
import { callGateway } from "../../gateway/call.js";
3+
import {
4+
GATEWAY_CLIENT_IDS,
5+
normalizeGatewayClientId,
6+
} from "../../gateway/protocol/client-info.js";
37
import { formatErrorMessage } from "../../infra/errors.js";
48
import {
59
listSpawnedSessionKeys,
@@ -15,6 +19,16 @@ const defaultSessionsResolutionDeps = {
1519
callGateway,
1620
};
1721

22+
const CURRENT_SESSION_CLIENT_ALIAS_IDS = new Set<string>([
23+
GATEWAY_CLIENT_IDS.TUI,
24+
GATEWAY_CLIENT_IDS.CLI,
25+
GATEWAY_CLIENT_IDS.WEBCHAT_UI,
26+
GATEWAY_CLIENT_IDS.CONTROL_UI,
27+
GATEWAY_CLIENT_IDS.MACOS_APP,
28+
GATEWAY_CLIENT_IDS.IOS_APP,
29+
GATEWAY_CLIENT_IDS.ANDROID_APP,
30+
]);
31+
1832
let sessionsResolutionDeps: {
1933
callGateway: GatewayCaller;
2034
} = defaultSessionsResolutionDeps;
@@ -51,6 +65,23 @@ export function resolveInternalSessionKey(params: {
5165
return params.key;
5266
}
5367

68+
export function resolveCurrentSessionClientAlias(params: {
69+
key: string;
70+
requesterInternalKey?: string;
71+
}): string | undefined {
72+
const requesterKey = normalizeOptionalString(params.requesterInternalKey);
73+
if (!requesterKey) {
74+
return undefined;
75+
}
76+
const clientId = normalizeGatewayClientId(params.key);
77+
if (!clientId || !CURRENT_SESSION_CLIENT_ALIAS_IDS.has(clientId)) {
78+
return undefined;
79+
}
80+
// UI/client labels can appear next to the real session key in status text.
81+
// Treat them as the current requester instead of probing them as sessionIds.
82+
return requesterKey;
83+
}
84+
5485
export { listSpawnedSessionKeys };
5586

5687
export async function isRequesterSpawnedSessionVisible(params: {
@@ -361,7 +392,11 @@ export async function resolveSessionReference(params: {
361392
requesterInternalKey?: string;
362393
restrictToSpawned: boolean;
363394
}): Promise<SessionReferenceResolution> {
364-
const rawInput = params.sessionKey.trim();
395+
const rawInput =
396+
resolveCurrentSessionClientAlias({
397+
key: params.sessionKey,
398+
requesterInternalKey: params.requesterInternalKey,
399+
}) ?? params.sessionKey.trim();
365400
if (rawInput === "current") {
366401
const resolvedCurrent = await resolveSessionReferenceByKeyOrSessionId({
367402
raw: rawInput,

0 commit comments

Comments
 (0)