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:
arkon
2026-01-19 13:08:53 +01:00
co-authored by Claude Opus 4.5
parent 0949b52830
commit d711ec8b3a
8 changed files with 255 additions and 29 deletions
+31 -4
View File
@@ -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`, {
+24 -9
View File
@@ -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">
+3
View File
@@ -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);
+28
View File
@@ -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 {