diff --git a/Packages/macOS/CmuxCloud/Sources/CmuxCloud/VMClient/VMClient.swift b/Packages/macOS/CmuxCloud/Sources/CmuxCloud/VMClient/VMClient.swift index 2d6f6432f20d..f65feba4df9d 100644 --- a/Packages/macOS/CmuxCloud/Sources/CmuxCloud/VMClient/VMClient.swift +++ b/Packages/macOS/CmuxCloud/Sources/CmuxCloud/VMClient/VMClient.swift @@ -772,6 +772,10 @@ public struct VMCloudSession: Sendable { public let title: String? public let kind: String public let status: String + /// Lifetime attaches for this session, not the number of clients attached + /// now. The control plane only ever adds to it, so it never returns to + /// zero. Socket clients receive it as `attachment_count`; do not present it + /// as a live viewer count. public let attachmentCount: Int public let effectiveCols: Int? public let effectiveRows: Int? diff --git a/web/app/api/vm/[id]/sessions/route.ts b/web/app/api/vm/[id]/sessions/route.ts index 838c3b083e73..bb20b5bd3daa 100644 --- a/web/app/api/vm/[id]/sessions/route.ts +++ b/web/app/api/vm/[id]/sessions/route.ts @@ -97,6 +97,7 @@ function sessionPayload(session: CloudVmSessionRow) { title: session.title, kind: session.kind, status: session.status, + // Cumulative attaches over the session's life, never a live client count. attachmentCount: session.attachmentCount, effectiveCols: session.effectiveCols, effectiveRows: session.effectiveRows, diff --git a/web/db/schema.ts b/web/db/schema.ts index 0c7a69e6e096..0acb3b9b9c82 100644 --- a/web/db/schema.ts +++ b/web/db/schema.ts @@ -907,6 +907,11 @@ export const cloudVmSessions = pgTable( title: text("title"), kind: text("kind").notNull().default("terminal"), status: cloudVmSessionStatus("status").notNull().default("running"), + // Lifetime number of attaches, not the number of clients attached now. + // upsertVmSession is the only writer and there is no detach writer at all, + // so the count only grows and never returns to zero. Pair it with + // lastAttachedAt to reason about recency; do not present it as a live + // viewer or participant count. attachmentCount: integer("attachment_count").notNull().default(0), effectiveCols: integer("effective_cols"), effectiveRows: integer("effective_rows"), diff --git a/web/services/vms/repository.ts b/web/services/vms/repository.ts index 050d240b7f92..d26d71595168 100644 --- a/web/services/vms/repository.ts +++ b/web/services/vms/repository.ts @@ -579,6 +579,11 @@ export type VmRepositoryShape = { readonly providerSessionId: string; readonly title?: string | null; readonly status?: CloudVmSessionStatus; + /** + * Attaches contributed by this call, defaulting to 1; pass a positive count. + * The insert branch stores it as the session's first count and the conflict + * branch adds it to the existing total, so this is never a replacement value. + */ readonly attachmentCount?: number; readonly effectiveCols?: number | null; readonly effectiveRows?: number | null;