diff --git a/CHANGELOG.md b/CHANGELOG.md index c939796b009..3d83305b6fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ Docs: https://docs.openclaw.ai - Channels/streaming: expose `streaming.progress.label`, `labels`, `maxLines`, and `toolProgress` in bundled channel config metadata so progress draft settings appear in config, docs, and control surfaces. Thanks @vincentkoc. - Channels/streaming: normalize whitespace and case for `streaming.progress.label: "auto"` so progress draft labels keep using the built-in label pool instead of rendering a literal `auto` title. Thanks @vincentkoc. - Plugins/Codex: preserve Codex-native OAuth routing for `/codex bind` app-server turns so bound sessions keep the selected Codex auth profile instead of falling back to public OpenAI credentials. (#76714) Thanks @keshavbotagent. +- Fix `session_status` with `sessionKey: "current"` resolving to stale Telegram direct session instead of live run session (#76708). Thanks @amknight. - Gateway/install: prefer supported system Node over nvm/fnm/volta/asdf/mise when regenerating managed gateway services, so `gateway install --force` no longer recreates service definitions that doctor immediately flags as version-manager-backed. Fixes #76339. Thanks @brokemac79. - Cron/status: render explicit `delivery.mode: "none"` jobs as no-delivery previews and label cron session history distinctly instead of showing fallback delivery or direct-session rows. Fixes #76945. - Gateway/usage: serve `usage.cost` and `sessions.usage` from a durable transcript aggregate cache with lock-safe background refreshes and localized stale-cache status, so large usage views avoid repeated full scans. (#76650) Thanks @Marvinthebored. diff --git a/src/agents/openclaw-tools.session-status.test.ts b/src/agents/openclaw-tools.session-status.test.ts index fb7d2c2c0d5..9396354f3be 100644 --- a/src/agents/openclaw-tools.session-status.test.ts +++ b/src/agents/openclaw-tools.session-status.test.ts @@ -492,6 +492,37 @@ describe("session_status tool", () => { expect(details.sessionKey).toBe("main"); }); + it("resolves sessionKey=current to runSessionKey when sandbox key differs from live session (#76708)", async () => { + resetSessionStore({ + "agent:main:telegram:default:direct:1234": { + sessionId: "s-tg-direct", + updatedAt: 5, + status: "done", + }, + "agent:main:main": { + sessionId: "s-main", + updatedAt: 10, + status: "running", + }, + }); + + // The tool is constructed with the Telegram sandbox key as agentSessionKey + // but the actual live run session key as runSessionKey. + const tool = createSessionStatusTool({ + agentSessionKey: "agent:main:telegram:default:direct:1234", + runSessionKey: "agent:main:main", + config: { + ...mockConfig, + tools: { ...mockConfig.tools, sessions: { visibility: "all" } }, + } as never, + }); + + const result = await tool.execute("call-current-run-session", { sessionKey: "current" }); + const details = result.details as { ok?: boolean; sessionKey?: string }; + expect(details.ok).toBe(true); + expect(details.sessionKey).toBe("agent:main:main"); + }); + it("treats the TUI client label as the current requester session", async () => { resetSessionStore({ "agent:main:main": { diff --git a/src/agents/openclaw-tools.ts b/src/agents/openclaw-tools.ts index f7f20194cd1..79d9629983b 100644 --- a/src/agents/openclaw-tools.ts +++ b/src/agents/openclaw-tools.ts @@ -260,6 +260,12 @@ export function createOpenClawTools( sandboxBrowserBridgeUrl?: string; allowHostBrowserControl?: boolean; agentSessionKey?: string; + /** + * The actual live run session key. When the tool is constructed with a sandbox/policy + * session key, this allows `session_status({sessionKey:"current"})` to resolve to + * the live run session instead of the stale sandbox key. + */ + runSessionKey?: string; agentChannel?: GatewayMessageChannel; agentAccountId?: string; /** Delivery target for topic/thread routing. */ @@ -588,6 +594,7 @@ export function createOpenClawTools( }), createSessionStatusTool({ agentSessionKey: options?.agentSessionKey, + runSessionKey: options?.runSessionKey, config: resolvedConfig, sandboxed: options?.sandboxed, }), diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index bdf51e6daa1..2c63232c19c 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -664,6 +664,10 @@ async function compactEmbeddedPiSessionDirectOnce( messageProvider: resolvedMessageProvider, agentAccountId: params.agentAccountId, sessionKey: sandboxSessionKey, + runSessionKey: + params.sessionKey && params.sessionKey !== sandboxSessionKey + ? params.sessionKey + : undefined, sessionId: params.sessionId, runId: params.runId, groupId: params.groupId, diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 27a8e11e9d1..2b7f2322cf9 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -885,6 +885,13 @@ export async function runEmbeddedAttempt( ownerOnlyToolAllowlist: params.ownerOnlyToolAllowlist, allowGatewaySubagentBinding: params.allowGatewaySubagentBinding, sessionKey: sandboxSessionKey, + // When sandboxSessionKey differs from the real run session key (e.g. Telegram + // direct peer key vs agent:main:main), pass the live key so session_status + // "current" resolves to the active run session, not the stale sandbox key. + runSessionKey: + params.sessionKey && params.sessionKey !== sandboxSessionKey + ? params.sessionKey + : undefined, sessionId: params.sessionId, runId: params.runId, agentDir, diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index db233bc9165..451efa8a6e7 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -274,6 +274,12 @@ export function createOpenClawCodingTools(options?: { messageThreadId?: string | number; sandbox?: SandboxContext | null; sessionKey?: string; + /** + * The actual live run session key. When the tool set is constructed with a + * sandbox/policy session key, this allows `session_status({sessionKey:"current"})` + * to resolve to the live run session instead of the stale sandbox key. + */ + runSessionKey?: string; /** Ephemeral session UUID — regenerated on /new and /reset. */ sessionId?: string; /** Stable run identifier for this agent invocation. */ @@ -698,6 +704,7 @@ export function createOpenClawCodingTools(options?: { sandboxBrowserBridgeUrl: sandbox?.browser?.bridgeUrl, allowHostBrowserControl: sandbox ? sandbox.browserAllowHostControl : true, agentSessionKey: options?.sessionKey, + runSessionKey: options?.runSessionKey, agentChannel: resolveGatewayMessageChannel(options?.messageProvider), agentAccountId: options?.agentAccountId, agentTo: options?.messageTo, diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 495f6a0e582..22bdf8f7bf4 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -276,6 +276,12 @@ async function resolveModelOverride(params: { export function createSessionStatusTool(opts?: { agentSessionKey?: string; + /** + * The actual live run session key. When the tool is constructed with a sandbox/policy + * session key (e.g. a Telegram direct peer key), this allows `session_status({sessionKey: + * "current"})` to resolve to the live run session instead of the stale sandbox key. + */ + runSessionKey?: string; config?: OpenClawConfig; sandboxed?: boolean; }): AnyAgentTool { @@ -346,12 +352,23 @@ export function createSessionStatusTool(opts?: { const requestedKeyParam = readStringParam(params, "sessionKey"); let requestedKeyRaw = requestedKeyParam ?? opts?.agentSessionKey; + + // When sessionKey is literally "current" and a runSessionKey is provided, + // resolve directly to the live run session instead of falling through to + // stale sandbox/policy key resolution (#76708). + if (requestedKeyRaw === "current" && opts?.runSessionKey) { + requestedKeyRaw = opts.runSessionKey; + } + const currentSessionAlias = resolveCurrentSessionClientAlias({ key: requestedKeyRaw ?? "", requesterInternalKey: effectiveRequesterKey, }); if (currentSessionAlias) { - requestedKeyRaw = currentSessionAlias; + // When a runSessionKey is provided (e.g. the live run session key), prefer it + // over the sandbox/policy key so "current" resolves to the active run session + // instead of a stale sandbox key (e.g. a Telegram direct peer key). + requestedKeyRaw = opts?.runSessionKey ?? currentSessionAlias; } const requestedKeyInput = requestedKeyRaw?.trim() ?? ""; let resolvedViaSessionId = false;