mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
246 lines
6.4 KiB
TypeScript
246 lines
6.4 KiB
TypeScript
import {
|
|
storeCreateSession,
|
|
storeGetSession,
|
|
storeIsSessionOwner,
|
|
storeGetSessionOwners,
|
|
storeBindSession,
|
|
storeUpdateSession,
|
|
storeListSessions,
|
|
storeListSessionsByUsername,
|
|
storeListSessionsByEnvironment,
|
|
storeListSessionsByOwnerUuid,
|
|
} from '../store'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { getAllEventBuses, removeEventBus } from '../transport/event-bus'
|
|
import type {
|
|
CreateSessionRequest,
|
|
CreateCodeSessionRequest,
|
|
SessionResponse,
|
|
SessionSummaryResponse,
|
|
} from '../types/api'
|
|
|
|
const CODE_SESSION_PREFIX = 'cse_'
|
|
const WEB_SESSION_PREFIX = 'session_'
|
|
const CLOSED_SESSION_STATUSES = new Set(['archived', 'inactive'])
|
|
|
|
function toResponse(row: {
|
|
id: string
|
|
environmentId: string | null
|
|
title: string | null
|
|
status: string
|
|
source: string
|
|
permissionMode: string | null
|
|
workerEpoch: number
|
|
username: string | null
|
|
createdAt: Date
|
|
updatedAt: Date
|
|
}): SessionResponse {
|
|
return {
|
|
id: row.id,
|
|
environment_id: row.environmentId,
|
|
title: row.title,
|
|
status: row.status,
|
|
source: row.source,
|
|
permission_mode: row.permissionMode,
|
|
worker_epoch: row.workerEpoch,
|
|
username: row.username,
|
|
created_at: row.createdAt.getTime() / 1000,
|
|
updated_at: row.updatedAt.getTime() / 1000,
|
|
}
|
|
}
|
|
|
|
export function toWebSessionId(sessionId: string): string {
|
|
if (!sessionId.startsWith(CODE_SESSION_PREFIX)) return sessionId
|
|
return `${WEB_SESSION_PREFIX}${sessionId.slice(CODE_SESSION_PREFIX.length)}`
|
|
}
|
|
|
|
function toCompatibleCodeSessionId(sessionId: string): string | null {
|
|
if (!sessionId.startsWith(WEB_SESSION_PREFIX)) return null
|
|
return `${CODE_SESSION_PREFIX}${sessionId.slice(WEB_SESSION_PREFIX.length)}`
|
|
}
|
|
|
|
export function toWebSessionResponse(
|
|
session: SessionResponse,
|
|
): SessionResponse {
|
|
return { ...session, id: toWebSessionId(session.id) }
|
|
}
|
|
|
|
function toWebSessionSummaryResponse(
|
|
session: SessionSummaryResponse,
|
|
): SessionSummaryResponse {
|
|
return { ...session, id: toWebSessionId(session.id) }
|
|
}
|
|
|
|
export function createSession(
|
|
req: CreateSessionRequest & { username?: string },
|
|
): SessionResponse {
|
|
const record = storeCreateSession({
|
|
environmentId: req.environment_id,
|
|
title: req.title,
|
|
source: req.source,
|
|
permissionMode: req.permission_mode,
|
|
username: req.username,
|
|
})
|
|
return toResponse(record)
|
|
}
|
|
|
|
export function createCodeSession(
|
|
req: CreateCodeSessionRequest,
|
|
): SessionResponse {
|
|
const record = storeCreateSession({
|
|
idPrefix: 'cse_',
|
|
title: req.title,
|
|
source: req.source,
|
|
permissionMode: req.permission_mode,
|
|
})
|
|
return toResponse(record)
|
|
}
|
|
|
|
export function getSession(sessionId: string): SessionResponse | null {
|
|
const record = storeGetSession(sessionId)
|
|
return record ? toResponse(record) : null
|
|
}
|
|
|
|
export function isSessionClosedStatus(
|
|
status: string | null | undefined,
|
|
): boolean {
|
|
return !!status && CLOSED_SESSION_STATUSES.has(status)
|
|
}
|
|
|
|
export function resolveExistingSessionId(sessionId: string): string | null {
|
|
if (storeGetSession(sessionId)) {
|
|
return sessionId
|
|
}
|
|
|
|
const compatibleCodeSessionId = toCompatibleCodeSessionId(sessionId)
|
|
if (compatibleCodeSessionId && storeGetSession(compatibleCodeSessionId)) {
|
|
return compatibleCodeSessionId
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export function resolveExistingWebSessionId(sessionId: string): string | null {
|
|
return resolveExistingSessionId(sessionId)
|
|
}
|
|
|
|
export function resolveOwnedWebSessionId(
|
|
sessionId: string,
|
|
uuid: string,
|
|
): string | null {
|
|
if (storeIsSessionOwner(sessionId, uuid)) {
|
|
return sessionId
|
|
}
|
|
|
|
const compatibleCodeSessionId = toCompatibleCodeSessionId(sessionId)
|
|
if (
|
|
compatibleCodeSessionId &&
|
|
storeIsSessionOwner(compatibleCodeSessionId, uuid)
|
|
) {
|
|
return compatibleCodeSessionId
|
|
}
|
|
|
|
// Auto-bind: if the session exists but has no owner, claim it for the requesting user
|
|
const existingId = resolveExistingSessionId(sessionId)
|
|
if (existingId) {
|
|
const owners = storeGetSessionOwners(existingId)
|
|
if (!owners || owners.size === 0) {
|
|
storeBindSession(existingId, uuid)
|
|
return existingId
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export function listWebSessionsByOwnerUuid(uuid: string): SessionResponse[] {
|
|
return storeListSessionsByOwnerUuid(uuid)
|
|
.filter(session => !isSessionClosedStatus(session.status))
|
|
.map(toResponse)
|
|
.map(toWebSessionResponse)
|
|
}
|
|
|
|
export function listWebSessionSummariesByOwnerUuid(
|
|
uuid: string,
|
|
): SessionSummaryResponse[] {
|
|
return storeListSessionsByOwnerUuid(uuid)
|
|
.filter(session => !isSessionClosedStatus(session.status))
|
|
.map(toSummaryResponse)
|
|
.map(toWebSessionSummaryResponse)
|
|
}
|
|
|
|
export function updateSessionTitle(sessionId: string, title: string) {
|
|
storeUpdateSession(sessionId, { title })
|
|
}
|
|
|
|
export function updateSessionStatus(sessionId: string, status: string) {
|
|
storeUpdateSession(sessionId, { status })
|
|
const bus = getAllEventBuses().get(sessionId)
|
|
if (!bus) return
|
|
|
|
bus.publish({
|
|
id: randomUUID(),
|
|
sessionId,
|
|
type: 'session_status',
|
|
payload: { status },
|
|
direction: 'inbound',
|
|
})
|
|
}
|
|
|
|
export function touchSession(sessionId: string) {
|
|
storeUpdateSession(sessionId, {})
|
|
}
|
|
|
|
export function archiveSession(sessionId: string) {
|
|
updateSessionStatus(sessionId, 'archived')
|
|
removeEventBus(sessionId)
|
|
}
|
|
|
|
export function incrementEpoch(sessionId: string): number {
|
|
const record = storeGetSession(sessionId)
|
|
if (!record) throw new Error('Session not found')
|
|
const newEpoch = record.workerEpoch + 1
|
|
storeUpdateSession(sessionId, { workerEpoch: newEpoch })
|
|
return newEpoch
|
|
}
|
|
|
|
export function listSessions() {
|
|
return storeListSessions().map(toResponse)
|
|
}
|
|
|
|
function toSummaryResponse(row: {
|
|
id: string
|
|
title: string | null
|
|
status: string
|
|
username: string | null
|
|
updatedAt: Date
|
|
}): SessionSummaryResponse {
|
|
return {
|
|
id: row.id,
|
|
title: row.title,
|
|
status: row.status,
|
|
username: row.username,
|
|
updated_at: row.updatedAt.getTime() / 1000,
|
|
}
|
|
}
|
|
|
|
export function listSessionSummaries(): SessionSummaryResponse[] {
|
|
return storeListSessions().map(toSummaryResponse)
|
|
}
|
|
|
|
export function listSessionSummariesByOwnerUuid(
|
|
uuid: string,
|
|
): SessionSummaryResponse[] {
|
|
return storeListSessionsByOwnerUuid(uuid).map(toSummaryResponse)
|
|
}
|
|
|
|
export function listSessionSummariesByUsername(
|
|
username: string,
|
|
): SessionSummaryResponse[] {
|
|
return storeListSessionsByUsername(username).map(toSummaryResponse)
|
|
}
|
|
|
|
export function listSessionsByEnvironment(envId: string) {
|
|
return storeListSessionsByEnvironment(envId).map(toResponse)
|
|
}
|