mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
feat: add kickstart prompt and auto-compact context options
Kickstart Prompt: - Remove Idle Timeout setting from respawn UI (uses 10s default) - Add Kickstart Prompt field sent when /init doesn't trigger work - New states: monitoring_init, sending_kickstart, waiting_kickstart - Monitors 3s after /init to detect if Claude started working Auto-Compact Context: - Add auto-compact at 110k tokens (sends /compact with optional prompt) - Preserves summarized context unlike /clear which wipes everything - Optional focus prompt to guide what compact should preserve - Blocks auto-clear while compacting to prevent conflicts Also updates auto-clear default threshold from 100k to 140k tokens. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,9 +73,9 @@ src/
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Session** (`src/session.ts`): Wraps Claude CLI as PTY subprocess. Two modes: `runPrompt(prompt)` for one-shot, `startInteractive()` for persistent terminal. Emits events: `output`, `terminal`, `message`, `completion`, `exit`, `idle`, `working`, `autoClear`, `clearTerminal`.
|
||||
- **Session** (`src/session.ts`): Wraps Claude CLI as PTY subprocess. Two modes: `runPrompt(prompt)` for one-shot, `startInteractive()` for persistent terminal. Emits events: `output`, `terminal`, `message`, `completion`, `exit`, `idle`, `working`, `autoClear`, `autoCompact`, `clearTerminal`.
|
||||
|
||||
- **RespawnController** (`src/respawn-controller.ts`): State machine that keeps sessions productive. Detects idle → sends update prompt → optionally `/clear` → optionally `/init` → repeats. State flow: `WATCHING → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT → WATCHING`
|
||||
- **RespawnController** (`src/respawn-controller.ts`): State machine that keeps sessions productive. Detects idle → sends update prompt → optionally `/clear` → optionally `/init` → optionally kickstart prompt → repeats. State flow: `WATCHING → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT → MONITORING_INIT → (optional) SENDING_KICKSTART → WAITING_KICKSTART → WATCHING`
|
||||
|
||||
- **ScreenManager** (`src/screen-manager.ts`): Wraps sessions in GNU screen for persistence across server restarts. On startup, reconciles with `screen -ls` to restore sessions and discovers unknown "ghost" screens. Uses 4-strategy kill process to prevent orphaned claude processes.
|
||||
|
||||
@@ -140,6 +140,15 @@ pty.spawn('claude', ['--dangerously-skip-permissions'], { ... })
|
||||
- **One-shot mode**: Uses `--output-format stream-json` for detailed token usage from JSON
|
||||
- **Interactive mode**: Parses tokens from Claude's status line (e.g., "123.4k tokens"), estimates 60/40 input/output split
|
||||
|
||||
### Auto-Compact & Auto-Clear
|
||||
|
||||
Sessions support automatic context management when token thresholds are reached:
|
||||
|
||||
- **Auto-Compact** (default: 110k tokens): Sends `/compact` command with optional prompt to summarize context
|
||||
- **Auto-Clear** (default: 140k tokens): Sends `/clear` to reset context entirely
|
||||
|
||||
Both wait for Claude to be idle before executing. Auto-compact runs first if both are enabled. Configured via `session.setAutoCompact(enabled, threshold?, prompt?)` and `session.setAutoClear(enabled, threshold?)`.
|
||||
|
||||
### Terminal Display Fix (Tab Switch & New Session)
|
||||
|
||||
When switching tabs or creating new sessions, terminal may be rendered at wrong size. Fix sequence:
|
||||
@@ -154,7 +163,7 @@ Uses `pendingCtrlL` Set to track sessions needing the fix. Waits for `session:id
|
||||
|
||||
All events broadcast to `/api/events` with format: `{ type: string, sessionId?: string, data: any }`.
|
||||
|
||||
Event prefixes: `session:`, `task:`, `respawn:`, `scheduled:`, `case:`, `init`. Key events: `session:idle`, `session:working`, `session:terminal`, `session:clearTerminal`, `session:completion`.
|
||||
Event prefixes: `session:`, `task:`, `respawn:`, `scheduled:`, `case:`, `screen:`, `init`. Key events: `session:idle`, `session:working`, `session:terminal`, `session:clearTerminal`, `session:completion`, `session:autoClear`, `session:autoCompact`.
|
||||
|
||||
### Frontend (app.js)
|
||||
|
||||
|
||||
@@ -17,17 +17,20 @@ const READY_INDICATOR = '↵ send';
|
||||
* Respawn sequence states
|
||||
*
|
||||
* The controller cycles through these states:
|
||||
* WATCHING → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT → WATCHING
|
||||
* WATCHING → SENDING_UPDATE → WAITING_UPDATE → SENDING_CLEAR → WAITING_CLEAR → SENDING_INIT → WAITING_INIT → MONITORING_INIT → (maybe SENDING_KICKSTART → WAITING_KICKSTART) → WATCHING
|
||||
*/
|
||||
export type RespawnState =
|
||||
| 'watching' // Watching for idle, ready to start respawn sequence
|
||||
| 'sending_update' // About to send the update docs prompt
|
||||
| 'waiting_update' // Waiting for update to complete
|
||||
| 'sending_clear' // About to send /clear
|
||||
| 'waiting_clear' // Waiting for clear to complete
|
||||
| 'sending_init' // About to send /init
|
||||
| 'waiting_init' // Waiting for init to complete
|
||||
| 'stopped'; // Controller stopped
|
||||
| 'watching' // Watching for idle, ready to start respawn sequence
|
||||
| 'sending_update' // About to send the update docs prompt
|
||||
| 'waiting_update' // Waiting for update to complete
|
||||
| 'sending_clear' // About to send /clear
|
||||
| 'waiting_clear' // Waiting for clear to complete
|
||||
| 'sending_init' // About to send /init
|
||||
| 'waiting_init' // Waiting for init to complete
|
||||
| 'monitoring_init' // Monitoring if /init triggered work
|
||||
| 'sending_kickstart' // About to send kickstart prompt
|
||||
| 'waiting_kickstart' // Waiting for kickstart to complete
|
||||
| 'stopped'; // Controller stopped
|
||||
|
||||
export interface RespawnConfig {
|
||||
/** How long to wait after seeing prompt before considering truly idle (ms) */
|
||||
@@ -42,6 +45,8 @@ export interface RespawnConfig {
|
||||
sendClear: boolean;
|
||||
/** Whether to send /init after /clear */
|
||||
sendInit: boolean;
|
||||
/** Optional prompt to send if /init doesn't trigger work */
|
||||
kickstartPrompt?: string;
|
||||
}
|
||||
|
||||
export interface RespawnEvents {
|
||||
@@ -210,6 +215,13 @@ export class RespawnController extends EventEmitter {
|
||||
this.promptDetected = false;
|
||||
this.lastActivityTime = Date.now();
|
||||
this.clearIdleTimer();
|
||||
|
||||
// If we're monitoring init and work started, go to watching (no kickstart needed)
|
||||
if (this._state === 'monitoring_init') {
|
||||
this.log('/init triggered work, skipping kickstart');
|
||||
this.emit('stepCompleted', 'init');
|
||||
this.completeCycle();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,6 +247,12 @@ export class RespawnController extends EventEmitter {
|
||||
case 'waiting_init':
|
||||
this.checkInitComplete();
|
||||
break;
|
||||
case 'monitoring_init':
|
||||
this.checkMonitoringInitIdle();
|
||||
break;
|
||||
case 'waiting_kickstart':
|
||||
this.checkKickstartComplete();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -287,7 +305,61 @@ export class RespawnController extends EventEmitter {
|
||||
private checkInitComplete(): void {
|
||||
this.clearIdleTimer();
|
||||
this.log('/init completed (ready indicator)');
|
||||
|
||||
// If kickstart prompt is configured, monitor to see if /init triggered work
|
||||
if (this.config.kickstartPrompt) {
|
||||
this.startMonitoringInit();
|
||||
} else {
|
||||
this.emit('stepCompleted', 'init');
|
||||
this.completeCycle();
|
||||
}
|
||||
}
|
||||
|
||||
private startMonitoringInit(): void {
|
||||
this.setState('monitoring_init');
|
||||
this.terminalBuffer = '';
|
||||
this.workingDetected = false;
|
||||
this.log('Monitoring if /init triggered work...');
|
||||
|
||||
// Give Claude a moment to start working before checking for idle
|
||||
this.stepTimer = setTimeout(() => {
|
||||
// If still in monitoring state and no work detected, consider it idle
|
||||
if (this._state === 'monitoring_init' && !this.workingDetected) {
|
||||
this.checkMonitoringInitIdle();
|
||||
}
|
||||
}, 3000); // 3 second grace period for /init to trigger work
|
||||
}
|
||||
|
||||
private checkMonitoringInitIdle(): void {
|
||||
this.clearIdleTimer();
|
||||
if (this.stepTimer) {
|
||||
clearTimeout(this.stepTimer);
|
||||
this.stepTimer = null;
|
||||
}
|
||||
this.log('/init did not trigger work, sending kickstart prompt');
|
||||
this.emit('stepCompleted', 'init');
|
||||
this.sendKickstart();
|
||||
}
|
||||
|
||||
private sendKickstart(): void {
|
||||
this.setState('sending_kickstart');
|
||||
this.terminalBuffer = '';
|
||||
|
||||
this.stepTimer = setTimeout(() => {
|
||||
const prompt = this.config.kickstartPrompt!;
|
||||
this.log(`Sending kickstart prompt: "${prompt}"`);
|
||||
this.session.write(prompt + '\n');
|
||||
this.emit('stepSent', 'kickstart', prompt);
|
||||
this.setState('waiting_kickstart');
|
||||
this.promptDetected = false;
|
||||
this.workingDetected = false;
|
||||
}, this.config.interStepDelayMs);
|
||||
}
|
||||
|
||||
private checkKickstartComplete(): void {
|
||||
this.clearIdleTimer();
|
||||
this.log('Kickstart completed (ready indicator)');
|
||||
this.emit('stepCompleted', 'kickstart');
|
||||
this.completeCycle();
|
||||
}
|
||||
|
||||
|
||||
+74
-4
@@ -62,6 +62,8 @@ export interface SessionEvents {
|
||||
taskFailed: (task: BackgroundTask, error: string) => void;
|
||||
// Auto-clear event
|
||||
autoClear: (data: { tokens: number; threshold: number }) => void;
|
||||
// Auto-compact event
|
||||
autoCompact: (data: { tokens: number; threshold: number; prompt?: string }) => void;
|
||||
}
|
||||
|
||||
export type SessionMode = 'claude' | 'shell';
|
||||
@@ -97,10 +99,16 @@ export class Session extends EventEmitter {
|
||||
// Token tracking for auto-clear
|
||||
private _totalInputTokens: number = 0;
|
||||
private _totalOutputTokens: number = 0;
|
||||
private _autoClearThreshold: number = 100000; // Default 100k tokens
|
||||
private _autoClearThreshold: number = 140000; // Default 140k tokens
|
||||
private _autoClearEnabled: boolean = false;
|
||||
private _isClearing: boolean = false; // Prevent recursive clearing
|
||||
|
||||
// Auto-compact settings
|
||||
private _autoCompactThreshold: number = 110000; // Default 110k tokens (lower than clear)
|
||||
private _autoCompactEnabled: boolean = false;
|
||||
private _autoCompactPrompt: string = ''; // Optional prompt for compact
|
||||
private _isCompacting: boolean = false; // Prevent recursive compacting
|
||||
|
||||
// Screen session support
|
||||
private _screenManager: ScreenManager | null = null;
|
||||
private _screenSession: ScreenSession | null = null;
|
||||
@@ -235,6 +243,28 @@ export class Session extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
get autoCompactThreshold(): number {
|
||||
return this._autoCompactThreshold;
|
||||
}
|
||||
|
||||
get autoCompactEnabled(): boolean {
|
||||
return this._autoCompactEnabled;
|
||||
}
|
||||
|
||||
get autoCompactPrompt(): string {
|
||||
return this._autoCompactPrompt;
|
||||
}
|
||||
|
||||
setAutoCompact(enabled: boolean, threshold?: number, prompt?: string): void {
|
||||
this._autoCompactEnabled = enabled;
|
||||
if (threshold !== undefined) {
|
||||
this._autoCompactThreshold = threshold;
|
||||
}
|
||||
if (prompt !== undefined) {
|
||||
this._autoCompactPrompt = prompt;
|
||||
}
|
||||
}
|
||||
|
||||
isIdle(): boolean {
|
||||
return this._status === 'idle';
|
||||
}
|
||||
@@ -694,7 +724,8 @@ export class Session extends EventEmitter {
|
||||
this._totalInputTokens += msg.message.usage.input_tokens || 0;
|
||||
this._totalOutputTokens += msg.message.usage.output_tokens || 0;
|
||||
|
||||
// Check if we should auto-clear
|
||||
// Check if we should auto-compact or auto-clear
|
||||
this.checkAutoCompact();
|
||||
this.checkAutoClear();
|
||||
}
|
||||
}
|
||||
@@ -748,15 +779,54 @@ export class Session extends EventEmitter {
|
||||
this._totalInputTokens += Math.round(delta * 0.6);
|
||||
this._totalOutputTokens += Math.round(delta * 0.4);
|
||||
|
||||
// Check if we should auto-clear
|
||||
// Check if we should auto-compact or auto-clear
|
||||
this.checkAutoCompact();
|
||||
this.checkAutoClear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should auto-compact based on token threshold
|
||||
private checkAutoCompact(): void {
|
||||
if (!this._autoCompactEnabled || this._isCompacting || this._isClearing) return;
|
||||
|
||||
const totalTokens = this._totalInputTokens + this._totalOutputTokens;
|
||||
if (totalTokens >= this._autoCompactThreshold) {
|
||||
this._isCompacting = true;
|
||||
console.log(`[Session] Auto-compact triggered: ${totalTokens} tokens >= ${this._autoCompactThreshold} threshold`);
|
||||
|
||||
// Wait for Claude to be idle before compacting
|
||||
const checkAndCompact = () => {
|
||||
if (!this._isWorking) {
|
||||
// Send /compact command with optional prompt
|
||||
const compactCmd = this._autoCompactPrompt
|
||||
? `/compact ${this._autoCompactPrompt}\n`
|
||||
: '/compact\n';
|
||||
this.write(compactCmd);
|
||||
this.emit('autoCompact', {
|
||||
tokens: totalTokens,
|
||||
threshold: this._autoCompactThreshold,
|
||||
prompt: this._autoCompactPrompt || undefined
|
||||
});
|
||||
|
||||
// Wait a moment then re-enable (longer than clear since compact takes time)
|
||||
setTimeout(() => {
|
||||
this._isCompacting = false;
|
||||
}, 10000);
|
||||
} else {
|
||||
// Check again in 2 seconds
|
||||
setTimeout(checkAndCompact, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
// Start checking after a short delay
|
||||
setTimeout(checkAndCompact, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should auto-clear based on token threshold
|
||||
private checkAutoClear(): void {
|
||||
if (!this._autoClearEnabled || this._isClearing) return;
|
||||
if (!this._autoClearEnabled || this._isClearing || this._isCompacting) return;
|
||||
|
||||
const totalTokens = this._totalInputTokens + this._totalOutputTokens;
|
||||
if (totalTokens >= this._autoClearThreshold) {
|
||||
|
||||
@@ -74,6 +74,8 @@ export interface RespawnConfig {
|
||||
sendClear: boolean;
|
||||
/** Whether to send /init after /clear */
|
||||
sendInit: boolean;
|
||||
/** Optional prompt to send if /init doesn't trigger work */
|
||||
kickstartPrompt?: string;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
|
||||
+31
-4
@@ -1264,22 +1264,32 @@ class ClaudemanApp {
|
||||
// Get respawn config from modal inputs
|
||||
getModalRespawnConfig() {
|
||||
const updatePrompt = document.getElementById('modalRespawnPrompt').value;
|
||||
const idleTimeout = parseInt(document.getElementById('modalRespawnIdleTimeout').value) || 5;
|
||||
const sendClear = document.getElementById('modalRespawnSendClear').checked;
|
||||
const sendInit = document.getElementById('modalRespawnSendInit').checked;
|
||||
const kickstartPrompt = document.getElementById('modalRespawnKickstart').value.trim() || undefined;
|
||||
const durationStr = document.getElementById('modalRespawnDuration').value;
|
||||
const durationMinutes = durationStr ? parseInt(durationStr) : null;
|
||||
|
||||
// Auto-compact settings
|
||||
const autoCompactEnabled = document.getElementById('modalAutoCompactEnabled').checked;
|
||||
const autoCompactThreshold = parseInt(document.getElementById('modalAutoCompactThreshold').value) || 110000;
|
||||
const autoCompactPrompt = document.getElementById('modalAutoCompactPrompt').value.trim() || undefined;
|
||||
|
||||
// Auto-clear settings
|
||||
const autoClearEnabled = document.getElementById('modalAutoClearEnabled').checked;
|
||||
const autoClearThreshold = parseInt(document.getElementById('modalAutoClearThreshold').value) || 100000;
|
||||
const autoClearThreshold = parseInt(document.getElementById('modalAutoClearThreshold').value) || 140000;
|
||||
|
||||
return {
|
||||
respawnConfig: {
|
||||
updatePrompt,
|
||||
idleTimeoutMs: idleTimeout * 1000,
|
||||
sendClear,
|
||||
sendInit,
|
||||
kickstartPrompt,
|
||||
},
|
||||
durationMinutes,
|
||||
autoCompactEnabled,
|
||||
autoCompactThreshold,
|
||||
autoCompactPrompt,
|
||||
autoClearEnabled,
|
||||
autoClearThreshold
|
||||
};
|
||||
@@ -1291,7 +1301,15 @@ class ClaudemanApp {
|
||||
return;
|
||||
}
|
||||
|
||||
const { respawnConfig, durationMinutes, autoClearEnabled, autoClearThreshold } = this.getModalRespawnConfig();
|
||||
const {
|
||||
respawnConfig,
|
||||
durationMinutes,
|
||||
autoCompactEnabled,
|
||||
autoCompactThreshold,
|
||||
autoCompactPrompt,
|
||||
autoClearEnabled,
|
||||
autoClearThreshold
|
||||
} = this.getModalRespawnConfig();
|
||||
|
||||
try {
|
||||
// Enable respawn on the session
|
||||
@@ -1303,6 +1321,15 @@ class ClaudemanApp {
|
||||
const data = await res.json();
|
||||
if (data.error) throw new Error(data.error);
|
||||
|
||||
// Set auto-compact if enabled
|
||||
if (autoCompactEnabled) {
|
||||
await fetch(`/api/sessions/${this.editingSessionId}/auto-compact`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled: true, threshold: autoCompactThreshold, prompt: autoCompactPrompt })
|
||||
});
|
||||
}
|
||||
|
||||
// Set auto-clear if enabled
|
||||
if (autoClearEnabled) {
|
||||
await fetch(`/api/sessions/${this.editingSessionId}/auto-clear`, {
|
||||
|
||||
@@ -204,14 +204,6 @@
|
||||
<input type="text" id="modalRespawnPrompt" value="update all the docs and CLAUDE.md" placeholder="Prompt to send when idle">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Idle Timeout</label>
|
||||
<div class="input-suffix">
|
||||
<input type="number" id="modalRespawnIdleTimeout" value="5" min="1" max="60">
|
||||
<span>sec</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Run Duration</label>
|
||||
<div class="input-suffix">
|
||||
@@ -235,6 +227,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Kickstart Prompt</label>
|
||||
<input type="text" id="modalRespawnKickstart" value="" placeholder="Optional: prompt if /init doesn't trigger work">
|
||||
<span class="form-hint">Sent only when /init completes but Claude stays idle</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Auto-Compact Context</label>
|
||||
<div class="auto-clear-row">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="modalAutoCompactEnabled">
|
||||
<span>Enable at</span>
|
||||
</label>
|
||||
<div class="input-suffix input-suffix-sm">
|
||||
<input type="number" id="modalAutoCompactThreshold" value="110000" min="10000" max="500000" step="10000">
|
||||
<span>tokens</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="modalAutoCompactPrompt" value="" placeholder="Optional: focus prompt for compact" class="mt-sm">
|
||||
<span class="form-hint">Summarizes context while preserving key information</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Auto-Clear Context</label>
|
||||
<div class="auto-clear-row">
|
||||
@@ -243,10 +257,11 @@
|
||||
<span>Enable at</span>
|
||||
</label>
|
||||
<div class="input-suffix input-suffix-sm">
|
||||
<input type="number" id="modalAutoClearThreshold" value="100000" min="10000" max="500000" step="10000">
|
||||
<input type="number" id="modalAutoClearThreshold" value="140000" min="10000" max="500000" step="10000">
|
||||
<span>tokens</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="form-hint">Completely resets context (use higher threshold than compact)</span>
|
||||
</div>
|
||||
|
||||
<div class="respawn-actions">
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
/* Utility classes */
|
||||
.mt-sm { margin-top: 0.5rem; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: var(--bg-dark);
|
||||
|
||||
@@ -515,6 +515,29 @@ export class WebServer extends EventEmitter {
|
||||
};
|
||||
});
|
||||
|
||||
// Set auto-compact on a session
|
||||
this.app.post('/api/sessions/:id/auto-compact', async (req) => {
|
||||
const { id } = req.params as { id: string };
|
||||
const body = req.body as { enabled: boolean; threshold?: number; prompt?: string };
|
||||
const session = this.sessions.get(id);
|
||||
|
||||
if (!session) {
|
||||
return { error: 'Session not found' };
|
||||
}
|
||||
|
||||
session.setAutoCompact(body.enabled, body.threshold, body.prompt);
|
||||
this.broadcast('session:updated', session.toDetailedState());
|
||||
|
||||
return {
|
||||
success: true,
|
||||
autoCompact: {
|
||||
enabled: session.autoCompactEnabled,
|
||||
threshold: session.autoCompactThreshold,
|
||||
prompt: session.autoCompactPrompt,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Quick run (create session, run prompt, return result, then cleanup)
|
||||
this.app.post('/api/run', async (req) => {
|
||||
// Prevent unbounded session creation
|
||||
@@ -912,6 +935,11 @@ export class WebServer extends EventEmitter {
|
||||
this.broadcast('session:autoClear', { sessionId: session.id, ...data });
|
||||
this.broadcast('session:updated', session.toDetailedState());
|
||||
});
|
||||
|
||||
session.on('autoCompact', (data: { tokens: number; threshold: number; prompt?: string }) => {
|
||||
this.broadcast('session:autoCompact', { sessionId: session.id, ...data });
|
||||
this.broadcast('session:updated', session.toDetailedState());
|
||||
});
|
||||
}
|
||||
|
||||
private setupRespawnListeners(sessionId: string, controller: RespawnController): void {
|
||||
|
||||
Reference in New Issue
Block a user