feat: add CLI session sharing functionality

- Implemented share token creation and revocation for CLI sessions.
- Added a new page for viewing shared CLI sessions with SSE support.
- Introduced hooks for fetching and managing CLI session shares.
- Enhanced the IssueTerminalTab component to handle share tokens and display active shares.
- Updated API routes to support fetching and revoking share tokens.
- Added unit tests for the CLI session share manager and rate limiter.
- Updated localization files to include new strings for sharing functionality.
This commit is contained in:
catlog22
2026-02-09 22:57:05 +08:00
parent 362f354f1c
commit d0cdee2e68
18 changed files with 748 additions and 23 deletions

View File

@@ -8,6 +8,7 @@ export type CliSessionAuditEventType =
| 'session_execute'
| 'session_resize'
| 'session_share_created'
| 'session_share_revoked'
| 'session_idle_reaped';
export interface CliSessionAuditEvent {

View File

@@ -22,6 +22,20 @@ function createTokenValue(): string {
export class CliSessionShareManager {
private tokens = new Map<string, InternalTokenRecord>();
listTokensForSession(sessionKey: string, projectRoot?: string): CliSessionShareTokenRecord[] {
this.cleanupExpired();
const records: CliSessionShareTokenRecord[] = [];
for (const record of this.tokens.values()) {
if (record.sessionKey !== sessionKey) continue;
if (projectRoot && record.projectRoot !== projectRoot) continue;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { expiresAtMs: _expiresAtMs, ...publicRecord } = record;
records.push(publicRecord);
}
records.sort((a, b) => a.expiresAt.localeCompare(b.expiresAt));
return records;
}
createToken(input: {
sessionKey: string;
projectRoot: string;
@@ -72,9 +86,20 @@ export class CliSessionShareManager {
}
let singleton: CliSessionShareManager | null = null;
let cleanupTimer: ReturnType<typeof setInterval> | null = null;
export function getCliSessionShareManager(): CliSessionShareManager {
if (!singleton) singleton = new CliSessionShareManager();
if (!singleton) {
singleton = new CliSessionShareManager();
cleanupTimer = setInterval(() => {
try {
singleton?.cleanupExpired();
} catch {
// ignore
}
}, 60_000);
cleanupTimer.unref?.();
}
return singleton;
}