mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-25 18:39:33 +01:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
551461cb31 | ||
|
|
c4bae75c59 | ||
|
|
88c415fc37 | ||
|
|
7175e4b350 | ||
|
|
08a417997f | ||
|
|
e6cb89b0cd |
No files matched your search
@@ -11,10 +11,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
cache: npm
|
cache: npm
|
||||||
registry-url: https://registry.npmjs.org
|
registry-url: https://registry.npmjs.org
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# aicodeman
|
# aicodeman
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Performance optimizations: V8 compile cache for 10-20% faster cold starts, lazy-load WebGL addon (244KB saved on mobile), preload hints for critical scripts, batch tmux reconciliation (N subprocess calls → 1). Also: WebSocket session lifecycle fixes, CJK IME input support, CI upgrade to Node 24/actions v6, install.sh fork support, and CLAUDE.md/README documentation refresh.
|
||||||
|
|
||||||
## 0.4.0
|
## 0.4.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ When user says "COM":
|
|||||||
4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json`
|
4. **Sync CLAUDE.md version**: Update the `**Version**` line below to match the new version from `package.json`
|
||||||
5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
|
5. **Commit and deploy**: `git add -A && git commit -m "chore: version packages" && git push && npm run build && systemctl --user restart codeman-web`
|
||||||
|
|
||||||
**Version**: 0.4.0 (must match `package.json`)
|
**Version**: 0.4.1 (must match `package.json`)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
@@ -108,8 +108,8 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
|||||||
| **State** | `src/state-store.ts`, `src/run-summary.ts`, `src/session-lifecycle-log.ts` | |
|
| **State** | `src/state-store.ts`, `src/run-summary.ts`, `src/session-lifecycle-log.ts` | |
|
||||||
| **Infra** | `src/hooks-config.ts`, `src/push-store.ts`, `src/tunnel-manager.ts`, `src/image-watcher.ts`, `src/file-stream-manager.ts` | |
|
| **Infra** | `src/hooks-config.ts`, `src/push-store.ts`, `src/tunnel-manager.ts`, `src/image-watcher.ts`, `src/file-stream-manager.ts` | |
|
||||||
| **Plan** | `src/plan-orchestrator.ts`, `src/prompts/*.ts`, `src/templates/claude-md.ts` | |
|
| **Plan** | `src/plan-orchestrator.ts`, `src/prompts/*.ts`, `src/templates/claude-md.ts` | |
|
||||||
| **Web** | `src/web/server.ts`, `src/web/sse-events.ts`, `src/web/routes/*.ts` (12 route modules + barrel), `src/web/ports/*.ts`, `src/web/middleware/auth.ts`, `src/web/schemas.ts` | |
|
| **Web** | `src/web/server.ts`, `src/web/sse-events.ts`, `src/web/routes/*.ts` (13 route modules incl. `ws-routes.ts` + barrel), `src/web/ports/*.ts`, `src/web/middleware/auth.ts`, `src/web/schemas.ts` | |
|
||||||
| **Frontend** | `src/web/public/app.js` ★ (~12.5K lines) + 9 JS modules (incl. `sw.js` service worker) | |
|
| **Frontend** | `src/web/public/app.js` ★ (~12.5K lines) + 11 JS modules (incl. `sw.js`, `input-cjk.js`) | |
|
||||||
| **Types** | `src/types/index.ts` → 13 domain files | See `@fileoverview` in index.ts |
|
| **Types** | `src/types/index.ts` → 13 domain files | See `@fileoverview` in index.ts |
|
||||||
|
|
||||||
★ = Large file (>50KB). All files have `@fileoverview` JSDoc — read that before diving in.
|
★ = Large file (>50KB). All files have `@fileoverview` JSDoc — read that before diving in.
|
||||||
@@ -118,7 +118,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
|||||||
|
|
||||||
**Config**: `src/config/` — 9 files. Import from specific files, not barrel.
|
**Config**: `src/config/` — 9 files. Import from specific files, not barrel.
|
||||||
|
|
||||||
**Utilities**: `src/utils/` — re-exported via index. Key: `CleanupManager`, `LRUMap`, `StaleExpirationMap`, `BufferAccumulator`, `stripAnsi`, `Debouncer`.
|
**Utilities**: `src/utils/` — re-exported via index. Key: `CleanupManager`, `LRUMap`, `StaleExpirationMap`, `BufferAccumulator`, `stripAnsi`, `Debouncer`, `KeyedDebouncer`. Also: `claude-cli-resolver`/`opencode-cli-resolver` (CLI path resolution), `string-similarity` (fuzzy matching), `regex-patterns` (ANSI/token/spinner patterns), `assertNever` (exhaustive checks).
|
||||||
|
|
||||||
### Data Flow
|
### Data Flow
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
|||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. Load order: `constants.js`(1) → `mobile-handlers.js`(2) → `voice-input.js`(3) → `notification-manager.js`(4) → `keyboard-accessory.js`(5) → `app.js`(6) → `ralph-wizard.js`(7) → `api-client.js`(8) → `subagent-windows.js`(9).
|
Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. Load order: `constants.js`(1) → `mobile-handlers.js`(2) → `voice-input.js`(3) → `notification-manager.js`(4) → `keyboard-accessory.js`(5) → `input-cjk.js`(5.5) → `app.js`(6) → `ralph-wizard.js`(7) → `api-client.js`(8) → `subagent-windows.js`(9). `input-cjk.js` handles CJK IME composition via an always-visible textarea below the terminal (`window.cjkActive` blocks xterm's onData).
|
||||||
|
|
||||||
**Z-index layers**: subagent windows (1000), plan agents (1100), log viewers (2000), image popups (3000), local echo overlay (7).
|
**Z-index layers**: subagent windows (1000), plan agents (1100), log viewers (2000), image popups (3000), local echo overlay (7).
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
|||||||
|
|
||||||
### API Routes
|
### API Routes
|
||||||
|
|
||||||
~111 handlers across 12 route files in `src/web/routes/`: system (35), sessions (24), ralph (9), plan (8), respawn (7), cases (7), files (5), mux (5), scheduled (4), push (4), teams (2), hooks (1). Each file has `@fileoverview` with endpoint details.
|
~114 handlers across 13 route files in `src/web/routes/`: system (36), sessions (25), ralph (9), plan (8), respawn (7), cases (7), files (5), mux (5), scheduled (4), push (4), teams (2), hooks (1), ws (1 WebSocket). Each file has `@fileoverview` with endpoint details.
|
||||||
|
|
||||||
## Adding Features
|
## Adding Features
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-1e3a5f?style=flat-square" alt="License: MIT"></a>
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-1e3a5f?style=flat-square" alt="License: MIT"></a>
|
||||||
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-18%2B-22c55e?style=flat-square&logo=node.js&logoColor=white" alt="Node.js 18+"></a>
|
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-18%2B-22c55e?style=flat-square&logo=node.js&logoColor=white" alt="Node.js 18+"></a>
|
||||||
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.5-3b82f6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript 5.5"></a>
|
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.9-3b82f6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript 5.9"></a>
|
||||||
<a href="https://fastify.dev/"><img src="https://img.shields.io/badge/Fastify-5.x-1e3a5f?style=flat-square&logo=fastify&logoColor=white" alt="Fastify"></a>
|
<a href="https://fastify.dev/"><img src="https://img.shields.io/badge/Fastify-5.x-1e3a5f?style=flat-square&logo=fastify&logoColor=white" alt="Fastify"></a>
|
||||||
<img src="https://img.shields.io/badge/Tests-1435%20total-22c55e?style=flat-square" alt="Tests">
|
<img src="https://img.shields.io/badge/Tests-1435%20total-22c55e?style=flat-square" alt="Tests">
|
||||||
</p>
|
</p>
|
||||||
@@ -499,9 +499,9 @@ The codebase went through a comprehensive 7-phase refactoring that eliminated go
|
|||||||
| Phase | What changed | Impact |
|
| Phase | What changed | Impact |
|
||||||
|-------|-------------|--------|
|
|-------|-------------|--------|
|
||||||
| **Performance** | Cached endpoints, SSE adaptive batching, buffer chunking | Sub-16ms terminal latency |
|
| **Performance** | Cached endpoints, SSE adaptive batching, buffer chunking | Sub-16ms terminal latency |
|
||||||
| **Route extraction** | `server.ts` split into 12 domain route modules + auth middleware + port interfaces | **−60%** server.ts LOC (6,736 → 2,697) |
|
| **Route extraction** | `server.ts` split into 13 domain route modules + auth middleware + port interfaces | **−60%** server.ts LOC (6,736 → 2,697) |
|
||||||
| **Domain splitting** | `types.ts` → 14 domain files, `ralph-tracker` → 7 files, `respawn-controller` → 5 files, `session` → 6 files | No more god files |
|
| **Domain splitting** | `types.ts` → 14 domain files, `ralph-tracker` → 7 files, `respawn-controller` → 5 files, `session` → 6 files | No more god files |
|
||||||
| **Frontend modules** | `app.js` → 8 extracted modules (constants, mobile, voice, notifications, keyboard, API, subagent windows) | **−24%** app.js LOC (15.2K → 11.5K) |
|
| **Frontend modules** | `app.js` → 9 extracted modules (constants, mobile, voice, notifications, keyboard, CJK input, API, Ralph wizard, subagent windows) | **−24%** app.js LOC (15.2K → 11.5K) |
|
||||||
| **Config consolidation** | ~70 scattered magic numbers → 9 domain-focused config files | Zero cross-file duplicates |
|
| **Config consolidation** | ~70 scattered magic numbers → 9 domain-focused config files | Zero cross-file duplicates |
|
||||||
| **Test infrastructure** | Shared mock library, 12 route test files, consolidated MockSession | Testable route handlers via `app.inject()` |
|
| **Test infrastructure** | Shared mock library, 12 route test files, consolidated MockSession | Testable route handlers via `app.inject()` |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Codeman Performance Optimization Plan
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
The backend is **already production-grade** — SSE broadcasting, state persistence, terminal batching, buffer management, and memory patterns are all well-optimized. The biggest gains are on the **frontend delivery** side.
|
||||||
|
|
||||||
|
## Implemented Optimizations
|
||||||
|
|
||||||
|
### 1. V8 Compile Cache (10-20% faster cold start)
|
||||||
|
|
||||||
|
**Files:** `scripts/codeman-web.service`, `package.json`
|
||||||
|
|
||||||
|
Node.js re-parses and compiles all JS on every cold start. `NODE_COMPILE_CACHE` caches V8 compiled bytecode to disk, reusing it on subsequent starts.
|
||||||
|
|
||||||
|
- Added `Environment=NODE_COMPILE_CACHE=/home/arkon/.codeman/compile-cache` to systemd service
|
||||||
|
- Added to `npm start` script for non-systemd usage
|
||||||
|
- Zero code changes, immediate win on every restart
|
||||||
|
|
||||||
|
### 2. WebGL Addon Lazy-Loading (244KB saved on mobile, non-blocking on desktop)
|
||||||
|
|
||||||
|
**Files:** `src/web/public/index.html`, `src/web/public/app.js`
|
||||||
|
|
||||||
|
`xterm-addon-webgl.min.js` (244KB) was loaded eagerly for all users via `<script defer>`, but only used on desktop with WebGL2 support.
|
||||||
|
|
||||||
|
- Removed `<script defer>` from `index.html`
|
||||||
|
- Added dynamic script loading in `app.js` — only downloads on desktop when WebGL is needed
|
||||||
|
- Mobile users never download the file at all (244KB saved)
|
||||||
|
- Desktop: loads in parallel with page rendering, addon initializes when ready
|
||||||
|
- Graceful fallback: canvas renderer used if WebGL unavailable or script fails
|
||||||
|
|
||||||
|
### 3. Preload Hints (~50-100ms faster perceived load)
|
||||||
|
|
||||||
|
**Files:** `src/web/public/index.html`
|
||||||
|
|
||||||
|
Browser discovers `<script defer>` tags only when the parser reaches them at the bottom of `<body>`. By then, the HTML parse has blocked for hundreds of lines.
|
||||||
|
|
||||||
|
- Added `<link rel="preload" as="script">` in `<head>` for `vendor/xterm.min.js`, `constants.js`, `app.js`
|
||||||
|
- Browser starts fetching critical scripts immediately during HTML parse (before reaching `<body>`)
|
||||||
|
- Zero runtime overhead — just hints for the browser's preload scanner
|
||||||
|
|
||||||
|
### 4. Batch Tmux Reconciliation (N subprocess calls → 1)
|
||||||
|
|
||||||
|
**Files:** `src/tmux-manager.ts`
|
||||||
|
|
||||||
|
`reconcileSessions()` previously called `tmux has-session` + `tmux display-message` per known session, plus `tmux list-sessions` for discovery, plus `tmux display-message` per discovered session. With 20 sessions: 41+ subprocess calls.
|
||||||
|
|
||||||
|
- Replaced with single `tmux list-panes -a -F '#{session_name}\t#{pane_pid}'` call
|
||||||
|
- Builds a Map from the result, then does O(1) lookups for both known and discovered sessions
|
||||||
|
- Also replaced inner O(n) `isKnown` scan with a Set lookup
|
||||||
|
- 20 sessions: 41 subprocess calls → 1, with faster lookups
|
||||||
|
|
||||||
|
### 5. Asset Hashing / Cache Busting (already implemented)
|
||||||
|
|
||||||
|
**Files:** `scripts/build.mjs` (pre-existing)
|
||||||
|
|
||||||
|
Content-hash cache busting was already implemented in the build script:
|
||||||
|
- All app JS/CSS files get content hashes (`app.abc123.js`)
|
||||||
|
- `index.html` rewritten to reference hashed filenames
|
||||||
|
- Pre-compressed with gzip + Brotli
|
||||||
|
- 1-year immutable cache works correctly — new deploys get new filenames
|
||||||
|
|
||||||
|
## Already Optimized (No Action Needed)
|
||||||
|
|
||||||
|
| Area | Why It's Fine |
|
||||||
|
|------|---------------|
|
||||||
|
| **SSE Broadcasting** | Single serialization per broadcast, preformatted frames, backpressure handling, session subscription filtering |
|
||||||
|
| **State Persistence** | 500ms debounce, incremental per-session JSON caching, async atomic writes, circuit breaker on failures |
|
||||||
|
| **Terminal Batching** | Adaptive intervals (16-50ms), per-session queues, immediate flush at 32KB, array-based accumulation |
|
||||||
|
| **Buffer Management** | BufferAccumulator (array-push, lazy join), auto-trim at 2MB/1MB, no string concatenation in hot paths |
|
||||||
|
| **ANSI Stripping** | Pre-compiled regex via factory functions, single-pass processing |
|
||||||
|
| **Static File Serving** | @fastify/static with 1-year cache, pre-compressed Brotli/gzip, no-cache for HTML |
|
||||||
|
| **Memory Management** | CleanupManager, LRUMap, StaleExpirationMap, bounded buffers, explicit listener cleanup |
|
||||||
|
| **Import Patterns** | Pure ESM, lazy web server import, no circular deps, no dynamic imports in hot paths |
|
||||||
|
| **Config Loading** | Small constant files, no I/O at import time, specific imports (no barrel) |
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aicodeman",
|
"name": "aicodeman",
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for AI coding agents - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node scripts/postinstall.js",
|
"postinstall": "node scripts/postinstall.js",
|
||||||
"build": "node scripts/build.mjs",
|
"build": "node scripts/build.mjs",
|
||||||
"start": "node dist/index.js",
|
"start": "NODE_COMPILE_CACHE=${HOME}/.codeman/compile-cache node dist/index.js",
|
||||||
"dev": "tsx src/index.ts web",
|
"dev": "tsx src/index.ts web",
|
||||||
"web": "node dist/index.js web",
|
"web": "node dist/index.js web",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ RestartSec=5
|
|||||||
KillMode=process
|
KillMode=process
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
Environment=HOME=/home/arkon
|
Environment=HOME=/home/arkon
|
||||||
|
Environment=NODE_COMPILE_CACHE=/home/arkon/.codeman/compile-cache
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
|
|||||||
+49
-48
@@ -905,13 +905,34 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
|||||||
const dead: string[] = [];
|
const dead: string[] = [];
|
||||||
const discovered: string[] = [];
|
const discovered: string[] = [];
|
||||||
|
|
||||||
// Check known sessions
|
// Batch: single tmux call to get all session names + pane PIDs (replaces N per-session subprocess calls)
|
||||||
|
const activeSessions = new Map<string, number>();
|
||||||
|
try {
|
||||||
|
const output = execSync("tmux list-panes -a -F '#{session_name}\t#{pane_pid}' 2>/dev/null || true", {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
timeout: EXEC_TIMEOUT_MS,
|
||||||
|
}).trim();
|
||||||
|
|
||||||
|
for (const line of output.split('\n')) {
|
||||||
|
if (!line) continue;
|
||||||
|
const sep = line.indexOf('\t');
|
||||||
|
if (sep === -1) continue;
|
||||||
|
const name = line.slice(0, sep);
|
||||||
|
const pid = parseInt(line.slice(sep + 1), 10);
|
||||||
|
if (name && !Number.isNaN(pid)) {
|
||||||
|
activeSessions.set(name, pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[TmuxManager] Failed to list tmux panes:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check known sessions against the batch result (O(1) map lookup instead of subprocess per session)
|
||||||
for (const [sessionId, session] of this.sessions) {
|
for (const [sessionId, session] of this.sessions) {
|
||||||
if (this.sessionExists(session.muxName)) {
|
const pid = activeSessions.get(session.muxName);
|
||||||
|
if (pid !== undefined) {
|
||||||
alive.push(sessionId);
|
alive.push(sessionId);
|
||||||
// Update PID if it changed
|
if (pid !== session.pid) {
|
||||||
const pid = this.getPanePid(session.muxName);
|
|
||||||
if (pid && pid !== session.pid) {
|
|
||||||
session.pid = pid;
|
session.pid = pid;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -921,51 +942,31 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discover unknown codeman sessions
|
// Discover unknown codeman/claudeman sessions from the same batch result
|
||||||
try {
|
const knownMuxNames = new Set<string>();
|
||||||
const output = execSync("tmux list-sessions -F '#{session_name}' 2>/dev/null || true", {
|
for (const session of this.sessions.values()) {
|
||||||
encoding: 'utf-8',
|
knownMuxNames.add(session.muxName);
|
||||||
timeout: EXEC_TIMEOUT_MS,
|
}
|
||||||
}).trim();
|
|
||||||
|
|
||||||
for (const line of output.split('\n')) {
|
for (const [sessionName, pid] of activeSessions) {
|
||||||
const sessionName = line.trim();
|
if (!sessionName.startsWith('codeman-') && !sessionName.startsWith('claudeman-')) continue;
|
||||||
if (!sessionName || (!sessionName.startsWith('codeman-') && !sessionName.startsWith('claudeman-'))) continue;
|
if (knownMuxNames.has(sessionName)) continue;
|
||||||
|
|
||||||
// Check if this session is already known
|
const fragment = sessionName.replace(/^(?:codeman|claudeman)-/, '');
|
||||||
let isKnown = false;
|
const sessionId = `restored-${fragment}`;
|
||||||
for (const session of this.sessions.values()) {
|
const session: MuxSession = {
|
||||||
if (session.muxName === sessionName) {
|
sessionId,
|
||||||
isKnown = true;
|
muxName: sessionName,
|
||||||
break;
|
pid,
|
||||||
}
|
createdAt: Date.now(),
|
||||||
}
|
workingDir: process.cwd(),
|
||||||
|
mode: 'claude',
|
||||||
if (!isKnown) {
|
attached: false,
|
||||||
// Extract session ID fragment from name
|
name: `Restored: ${sessionName}`,
|
||||||
const fragment = sessionName.replace(/^(?:codeman|claudeman)-/, '');
|
};
|
||||||
const sessionId = `restored-${fragment}`;
|
this.sessions.set(sessionId, session);
|
||||||
const pid = this.getPanePid(sessionName);
|
discovered.push(sessionId);
|
||||||
|
console.log(`[TmuxManager] Discovered unknown tmux session: ${sessionName} (PID ${pid})`);
|
||||||
if (pid) {
|
|
||||||
const session: MuxSession = {
|
|
||||||
sessionId,
|
|
||||||
muxName: sessionName,
|
|
||||||
pid,
|
|
||||||
createdAt: Date.now(),
|
|
||||||
workingDir: process.cwd(),
|
|
||||||
mode: 'claude',
|
|
||||||
attached: false,
|
|
||||||
name: `Restored: ${sessionName}`,
|
|
||||||
};
|
|
||||||
this.sessions.set(sessionId, session);
|
|
||||||
discovered.push(sessionId);
|
|
||||||
console.log(`[TmuxManager] Discovered unknown tmux session: ${sessionName} (PID ${pid})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[TmuxManager] Failed to discover sessions:', err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dead.length > 0 || discovered.length > 0) {
|
if (dead.length > 0 || discovered.length > 0) {
|
||||||
|
|||||||
+26
-11
@@ -571,6 +571,20 @@ class CodemanApp {
|
|||||||
document.body.classList.add('app-loaded');
|
document.body.classList.add('app-loaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initWebGL() {
|
||||||
|
if (typeof WebglAddon === 'undefined') return;
|
||||||
|
try {
|
||||||
|
this._webglAddon = new WebglAddon.WebglAddon();
|
||||||
|
this._webglAddon.onContextLoss(() => {
|
||||||
|
console.error('[CRASH-DIAG] WebGL context LOST — falling back to canvas renderer');
|
||||||
|
this._webglAddon.dispose();
|
||||||
|
this._webglAddon = null;
|
||||||
|
});
|
||||||
|
this.terminal.loadAddon(this._webglAddon);
|
||||||
|
console.log('[CRASH-DIAG] WebGL renderer enabled');
|
||||||
|
} catch (_e) { /* WebGL2 unavailable — canvas renderer used */ }
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
// Terminal Setup — xterm.js config and input handling
|
// Terminal Setup — xterm.js config and input handling
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
@@ -641,19 +655,20 @@ class CodemanApp {
|
|||||||
// but the 48KB/frame flush cap in flushPendingWrites() now prevents
|
// but the 48KB/frame flush cap in flushPendingWrites() now prevents
|
||||||
// oversized terminal.write() calls that triggered the stalls.
|
// oversized terminal.write() calls that triggered the stalls.
|
||||||
// Disable with ?nowebgl URL param if GPU issues return.
|
// Disable with ?nowebgl URL param if GPU issues return.
|
||||||
|
// Lazy-loaded: script downloaded only on desktop (saves 244KB on mobile).
|
||||||
this._webglAddon = null;
|
this._webglAddon = null;
|
||||||
const skipWebGL = MobileDetection.getDeviceType() !== 'desktop';
|
const skipWebGL = MobileDetection.getDeviceType() !== 'desktop';
|
||||||
if (!skipWebGL && !new URLSearchParams(location.search).has('nowebgl') && typeof WebglAddon !== 'undefined') {
|
if (!skipWebGL && !new URLSearchParams(location.search).has('nowebgl')) {
|
||||||
try {
|
if (typeof WebglAddon !== 'undefined') {
|
||||||
this._webglAddon = new WebglAddon.WebglAddon();
|
this._initWebGL();
|
||||||
this._webglAddon.onContextLoss(() => {
|
} else {
|
||||||
console.error('[CRASH-DIAG] WebGL context LOST — falling back to canvas renderer');
|
// Lazy-load WebGL addon — not bundled in <head> to avoid blocking mobile
|
||||||
this._webglAddon.dispose();
|
const wglScript = document.createElement('script');
|
||||||
this._webglAddon = null;
|
wglScript.src = 'vendor/xterm-addon-webgl.min.js';
|
||||||
});
|
wglScript.onload = () => this._initWebGL();
|
||||||
this.terminal.loadAddon(this._webglAddon);
|
wglScript.onerror = () => console.warn('[CRASH-DIAG] Failed to load WebGL addon — using canvas renderer');
|
||||||
console.log('[CRASH-DIAG] WebGL renderer enabled via ?webgl param');
|
document.head.appendChild(wglScript);
|
||||||
} catch (_e) { /* WebGL2 unavailable — canvas renderer used */ }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._localEchoOverlay = new LocalEchoOverlay(this.terminal);
|
this._localEchoOverlay = new LocalEchoOverlay(this.terminal);
|
||||||
|
|||||||
@@ -14,11 +14,16 @@
|
|||||||
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
||||||
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
|
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
|
||||||
|
<!-- Preload critical resources — lets browser discover these during HTML parse
|
||||||
|
instead of waiting until <script> tags at bottom-of-body are reached. -->
|
||||||
|
<link rel="preload" href="vendor/xterm.min.js" as="script">
|
||||||
|
<link rel="preload" href="constants.js" as="script">
|
||||||
|
<link rel="preload" href="app.js" as="script">
|
||||||
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
<!-- Self-hosted xterm.js — eliminates CDN DNS/TLS latency (~100ms).
|
||||||
'defer' preserves execution order (xterm loads before fit addon). -->
|
'defer' preserves execution order (xterm loads before fit addon). -->
|
||||||
<script defer src="vendor/xterm.min.js"></script>
|
<script defer src="vendor/xterm.min.js"></script>
|
||||||
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
||||||
<script defer src="vendor/xterm-addon-webgl.min.js"></script>
|
<!-- WebGL addon lazy-loaded by app.js on desktop only (skipped on mobile, saving 244KB) -->
|
||||||
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
|
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
|
||||||
<script defer src="vendor/xterm-zerolag-input.js"></script>
|
<script defer src="vendor/xterm-zerolag-input.js"></script>
|
||||||
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
||||||
|
|||||||
Reference in new issue
Block a user