♻️ feat: implement session management for PTY sessions in the server

- Add SessionManager class to handle PTY sessions with WebSocket connections.
- Implement methods for creating, retrieving, and destroying sessions.
- Handle PTY output and WebSocket messages for terminal interaction.
- Ensure graceful session destruction and cleanup.

feat: initialize web application with Next.js and Tailwind CSS

- Create initial Next.js application structure with TypeScript support.
- Set up Tailwind CSS for styling with custom theme configurations.
- Add ESLint configuration for code quality and consistency.

feat: implement chat API and UI components

- Create chat API route to handle chat requests and responses.
- Develop chat layout with sidebar, header, chat window, and input components.
- Integrate Zustand for state management of conversations and messages.
- Add utility functions for formatting dates and managing class names.

chore: add environment variables and configuration files

- Create .env.example for environment variable setup.
- Add configuration files for PostCSS, Tailwind CSS, and TypeScript.
- Set up package.json with necessary dependencies and scripts for development.
This commit is contained in:
nirholas
2026-03-31 12:35:31 +00:00
parent d31c2bec03
commit 38648ae5f4
53 changed files with 4177 additions and 4 deletions

View File

@@ -0,0 +1,66 @@
/**
* Circular byte buffer for PTY scrollback replay.
* Stores the last `capacity` bytes of PTY output; oldest bytes are silently
* discarded when the buffer is full.
*/
export class ScrollbackBuffer {
private buf: Buffer;
private writePos = 0; // next write position in the ring
private stored = 0; // bytes currently stored (≤ capacity)
readonly capacity: number;
constructor(capacityBytes = 100 * 1024) {
this.capacity = capacityBytes;
this.buf = Buffer.allocUnsafe(capacityBytes);
}
/**
* Append PTY output to the buffer.
* Uses 'binary' (latin1) encoding to preserve raw byte values from node-pty.
*/
write(data: string): void {
const src = Buffer.from(data, "binary");
const len = src.length;
if (len === 0) return;
if (len >= this.capacity) {
// Incoming chunk larger than the whole buffer — keep only the tail
src.copy(this.buf, 0, len - this.capacity);
this.writePos = 0;
this.stored = this.capacity;
return;
}
const end = this.writePos + len;
if (end <= this.capacity) {
src.copy(this.buf, this.writePos);
} else {
// Wrap around the ring boundary
const tailLen = this.capacity - this.writePos;
src.copy(this.buf, this.writePos, 0, tailLen);
src.copy(this.buf, 0, tailLen);
}
this.writePos = end % this.capacity;
this.stored = Math.min(this.stored + len, this.capacity);
}
/**
* Returns all buffered bytes in chronological order (oldest first).
* The returned Buffer is a copy and safe to send over a WebSocket.
*/
read(): Buffer {
if (this.stored === 0) return Buffer.alloc(0);
if (this.stored < this.capacity) {
// Buffer hasn't wrapped yet — contiguous region starting at index 0
return Buffer.from(this.buf.subarray(0, this.stored));
}
// Buffer is full and has wrapped — oldest data starts at writePos
const out = Buffer.allocUnsafe(this.capacity);
const tailLen = this.capacity - this.writePos;
this.buf.copy(out, 0, this.writePos); // tail (oldest)
this.buf.copy(out, tailLen, 0, this.writePos); // head (newest)
return out;
}
}