mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(sessions): read the composer from a styled capture so a prompt suggestion is not a draft
Claude Code draws its prompt suggestion in an empty composer, the first letter in the reverse-video cursor cell and the rest faint, so a plain capture read it as typed text and keep-warm never nudged those sessions. The keep-warm pane read now uses capture-pane -e and drops the suggestion before checking the composer. Also from review: an off status carries no window, automation and an unreadable composer retry instead of stopping, a failed evaluation re-arms a retry, a trigger during an evaluation queues one follow-up that remove() cancels, and the cap's clock is seeded from lastSubmitAt for pre-upgrade state.
This commit is contained in:
1 parent
5db6c633d3
commit
83d80578e4
13 files changed
+292
-37
No files matched your search
@@ -773,7 +773,7 @@ function formatKeepWarmStatus(status, now, statusLine) {
|
||||
if (PROMPT_CACHE_NOT_COLLECTED[statusLine]) return '';
|
||||
if (!status || typeof status.state !== 'string') return '';
|
||||
const clock = (ms) => new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
const window = Number.isFinite(status.budgetEndsAt)
|
||||
const keepWarmWindow = Number.isFinite(status.budgetEndsAt)
|
||||
? status.budgetEndsAt > now
|
||||
? `stops ${clock(status.budgetEndsAt)}`
|
||||
: 'window closed'
|
||||
@@ -794,13 +794,13 @@ function formatKeepWarmStatus(status, now, statusLine) {
|
||||
break;
|
||||
}
|
||||
case 'armed':
|
||||
parts = [`next ${clock(status.nextCheckAt)}`, window, idleCompaction];
|
||||
parts = [`next ${clock(status.nextCheckAt)}`, keepWarmWindow, idleCompaction];
|
||||
break;
|
||||
case 'waiting':
|
||||
parts = ['waiting', status.reason || 'retrying', `retry ${clock(status.nextCheckAt)}`, window, idleCompaction];
|
||||
parts = ['waiting', status.reason || 'retrying', `retry ${clock(status.nextCheckAt)}`, keepWarmWindow, idleCompaction];
|
||||
break;
|
||||
case 'nudged':
|
||||
parts = [`sent ${clock(status.lastNudgeAt)} (${status.nudges}\u00d7)`, window, idleCompaction];
|
||||
parts = [`sent ${clock(status.lastNudgeAt)} (${status.nudges}\u00d7)`, keepWarmWindow, idleCompaction];
|
||||
break;
|
||||
default:
|
||||
parts = ['stopped', status.reason || 'nothing to do', idleCompaction];
|
||||
|
||||
+5
-4
@@ -119,7 +119,7 @@ import { AI_CHECK_MODEL } from '../config/ai-defaults.js';
|
||||
import { approvalInbox } from './approval-inbox.js';
|
||||
import { SCREEN_BLOCKED_SOURCE } from './screen-blocked.js';
|
||||
import { PromptCacheKeepWarmService } from '../prompt-cache-keepwarm.js';
|
||||
import { composerIsEmpty } from '../session-submit-verifier.js';
|
||||
import { composerIsEmpty, plainFrame } from '../session-submit-verifier.js';
|
||||
import { detectUsageLimitPause } from '../usage-limit-patterns.js';
|
||||
import { stopDeepSeekWeb } from '../deepseek-web-server.js';
|
||||
import {
|
||||
@@ -348,10 +348,11 @@ export class WebServer extends EventEmitter {
|
||||
readPane: (view) => {
|
||||
const session = view as Session;
|
||||
const muxName = session.muxName;
|
||||
// `capture-pane -p`: the visible frame as plain text.
|
||||
const text = muxName ? (this.mux.capturePaneText?.(muxName) ?? null) : null;
|
||||
// `capture-pane -p -e`: the visible frame with its styles, which is how `composerIsEmpty()`
|
||||
// tells Claude Code's faint prompt suggestion from a typed draft.
|
||||
const text = muxName ? (this.mux.capturePaneText?.(muxName, undefined, { styles: true }) ?? null) : null;
|
||||
if (!text) return { composerEmpty: null, limitShown: false };
|
||||
const limit = detectUsageLimitPause(text);
|
||||
const limit = detectUsageLimitPause(plainFrame(text));
|
||||
return {
|
||||
composerEmpty: composerIsEmpty(text, getCli(session.mode)?.capabilities.workDetect?.promptGlyph ?? '') ?? null,
|
||||
limitShown: limit !== null && limit.resetAt > Date.now(),
|
||||
|
||||
Reference in new issue
Block a user