From e67dc2b6eb9ec6ea8a755360f7223d307f329b54 Mon Sep 17 00:00:00 2001 From: D-K-P <8297864+D-K-P@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:31:59 +0100 Subject: [PATCH] fix(webapp): keep the close action available for idle sessions Address review on the sessions status change: - Keep the "Close session" action on the detail page for Idle sessions; they are open, only Closed and Expired are terminal. - Rename the status helper input from currentRunId to hasCurrentRun, since the detail page passes a run friendlyId, not the session's currentRunId. - Restore the Active tooltip copy so it stays accurate now that the Active filter also returns open, idle sessions. - Align the sessions docs example so the listed tag matches a top-level tag set at start time. --- .../app/components/sessions/v1/SessionStatus.tsx | 2 +- .../presenters/v3/SessionListPresenter.server.ts | 2 +- .../app/presenters/v3/deriveSessionStatus.test.ts | 14 +++++++------- .../app/presenters/v3/deriveSessionStatus.ts | 10 +++++----- .../route.tsx | 4 ++-- docs/ai-chat/sessions.mdx | 5 ++++- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/webapp/app/components/sessions/v1/SessionStatus.tsx b/apps/webapp/app/components/sessions/v1/SessionStatus.tsx index 27f27dda2..fdc4bb6b1 100644 --- a/apps/webapp/app/components/sessions/v1/SessionStatus.tsx +++ b/apps/webapp/app/components/sessions/v1/SessionStatus.tsx @@ -13,7 +13,7 @@ export const allSessionStatuses = ["ACTIVE", "CLOSED", "EXPIRED"] as const satis >; const descriptions: Record = { - ACTIVE: "The session has a run currently executing.", + ACTIVE: "The session is open and can receive input or schedule new runs.", IDLE: "The session is open but has no run currently executing.", CLOSED: "The session was closed; no further input or runs can be triggered against it.", EXPIRED: "The session passed its expiry time without being closed explicitly.", diff --git a/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts b/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts index c820e44c1..74b057efe 100644 --- a/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/SessionListPresenter.server.ts @@ -214,7 +214,7 @@ export class SessionListPresenter { const status = deriveSessionStatus({ closedAt: session.closedAt, expiresAt: session.expiresAt, - currentRunId: session.currentRunId, + hasCurrentRun: session.currentRunId != null, currentRunStatus: currentRun?.status, now, }); diff --git a/apps/webapp/app/presenters/v3/deriveSessionStatus.test.ts b/apps/webapp/app/presenters/v3/deriveSessionStatus.test.ts index 02da3dbf2..04be73ec9 100644 --- a/apps/webapp/app/presenters/v3/deriveSessionStatus.test.ts +++ b/apps/webapp/app/presenters/v3/deriveSessionStatus.test.ts @@ -11,7 +11,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: PAST, expiresAt: null, - currentRunId: "run_1", + hasCurrentRun: true, currentRunStatus: "EXECUTING", now: NOW, }) @@ -23,7 +23,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: PAST, expiresAt: PAST, - currentRunId: null, + hasCurrentRun: false, currentRunStatus: undefined, now: NOW, }) @@ -35,7 +35,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: null, expiresAt: PAST, - currentRunId: "run_1", + hasCurrentRun: true, currentRunStatus: "EXECUTING", now: NOW, }) @@ -47,7 +47,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: null, expiresAt: FUTURE, - currentRunId: "run_1", + hasCurrentRun: true, currentRunStatus: "EXECUTING", now: NOW, }) @@ -59,7 +59,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: null, expiresAt: null, - currentRunId: "run_1", + hasCurrentRun: true, currentRunStatus: "EXPIRED", now: NOW, }) @@ -71,7 +71,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: null, expiresAt: null, - currentRunId: null, + hasCurrentRun: false, currentRunStatus: undefined, now: NOW, }) @@ -83,7 +83,7 @@ describe("deriveSessionStatus", () => { deriveSessionStatus({ closedAt: null, expiresAt: null, - currentRunId: "run_missing", + hasCurrentRun: true, currentRunStatus: undefined, now: NOW, }) diff --git a/apps/webapp/app/presenters/v3/deriveSessionStatus.ts b/apps/webapp/app/presenters/v3/deriveSessionStatus.ts index 64adac437..23ab5b5f0 100644 --- a/apps/webapp/app/presenters/v3/deriveSessionStatus.ts +++ b/apps/webapp/app/presenters/v3/deriveSessionStatus.ts @@ -7,11 +7,11 @@ export type DeriveSessionStatusInput = { closedAt: Date | null; /** `Session.expiresAt` — retention deadline, if any. */ expiresAt: Date | null; - /** `Session.currentRunId` — pointer to the current run (no FK). */ - currentRunId: string | null; + /** Whether the session points at a current run at all. */ + hasCurrentRun: boolean; /** - * Status of the run named by `currentRunId`. `undefined` when there is no - * current run, or the pointer couldn't be resolved (stale / cross-env). + * Status of the current run. `undefined` when there is no current run, or the + * pointer couldn't be resolved (stale / cross-env). */ currentRunStatus: TaskRunStatus | undefined; /** `Date.now()` at the time of derivation. */ @@ -38,7 +38,7 @@ export function deriveSessionStatus(input: DeriveSessionStatusInput): SessionDis } const hasLiveRun = - input.currentRunId != null && + input.hasCurrentRun && input.currentRunStatus !== undefined && !isFinalRunStatus(input.currentRunStatus); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx index 4c0612c8e..426049ada 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.sessions.$sessionParam/route.tsx @@ -119,7 +119,7 @@ export default function Page() { const status = deriveSessionStatus({ closedAt: session.closedAt ? new Date(session.closedAt) : null, expiresAt: session.expiresAt ? new Date(session.expiresAt) : null, - currentRunId: session.currentRun?.friendlyId ?? null, + hasCurrentRun: session.currentRun != null, currentRunStatus: session.currentRun?.status, now: Date.now(), }); @@ -791,7 +791,7 @@ function OverviewTab({ - {status === "ACTIVE" && ( + {(status === "ACTIVE" || status === "IDLE") && ( diff --git a/docs/ai-chat/sessions.mdx b/docs/ai-chat/sessions.mdx index 2b15439d2..bb2a2d84c 100644 --- a/docs/ai-chat/sessions.mdx +++ b/docs/ai-chat/sessions.mdx @@ -99,7 +99,10 @@ const { id, runId, publicAccessToken, isCached } = await sessions.start({ type: "chat.agent", externalId: chatId, taskIdentifier: "my-chat", + // Top-level tags live on the Session row and are what `sessions.list({ tag })` filters on. + tags: [`chat:${chatId}`], triggerConfig: { + // triggerConfig.tags tag each run the session schedules, not the session row. tags: [`chat:${chatId}`], basePayload: { /* whatever your task's payload shape is */ }, }, @@ -153,7 +156,7 @@ Cursor-paginated list of Sessions in the current environment. Returns a `CursorP ```ts for await (const s of sessions.list({ type: "chat.agent", - tag: `user:${userId}`, + tag: `chat:${chatId}`, status: "ACTIVE", limit: 50, })) {