fix(sessions): leave a pending idle alert to the user through a keep-alive turn

Only a human spends an idle alert, but the keep-alive turn resolved an unseen one three ways: its working transition, its stop hook, and its own idle prompt replacing the item with a pre-acknowledged one. While the turn runs, a pending idle item is now left exactly as found, the page's stop handler returns on the keep-alive reason before clearing hooks or raising Response Complete, and session:working/session:idle carry keepAlive so the stuck-detection timer neither restarts nor re-arms.

The window that marks the turn closes on its own idle prompt or any other submit instead of a fixed three minutes, which a slow turn's idle prompt (sent a minute after the turn ends) could outlast. The service keeps the default and cap in memory, read through a strict reader so a mid-write read is retried rather than kept, and the cap is clamped before the strict settings save.
This commit is contained in:
JD committed 2026-10-19 01:42:00 -04:00
1 parent 71a51df2e2
commit 5db6c633d3
22 files changed
+441 -41

No files matched your search

+20 -2
View File
@@ -456,6 +456,15 @@ export interface ClaudeMessage {
duration_ms?: number;
}
/**
* Whether a programmatic write leaves the keep-alive turn running: the nudge itself, or a bare
* Enter (which submits nothing new). Anything else carrying an Enter starts a turn of its own.
*/
function isKeepAliveWrite(data: string): boolean {
const text = data.replace(/[\r\n]/g, '').trim();
return text === '' || text === KEEP_WARM_NUDGE;
}
/**
* Event signatures emitted by the Session class.
* Subscribe using `session.on('eventName', handler)`.
@@ -1562,8 +1571,10 @@ export class Session extends EventEmitter {
/**
* True while the running turn is Codeman's own keep-alive nudge: its stop hook and idle
* prompt open pre-acknowledged, and its submit moves `lastSubmitAt` (it IS a submit, and the
* response viewer's conversation correlation needs it) but not `lastRealPromptAt`. Any
* `fromUser` write ends it early, so a person's prompt right after a nudge counts and alerts normally.
* response viewer's conversation correlation needs it) but not `lastRealPromptAt`. The turn's
* own idle prompt ends it (`endKeepAliveTurn()`), and so does any other submit: a `fromUser`
* write, a programmatic write of anything but the nudge, or a `prompt_submitted` hook for
* another prompt, so a prompt right after a nudge counts and alerts normally.
*/
get keepAliveTurnActive(): boolean {
return Date.now() < this._keepAliveTurnUntil;
@@ -1573,6 +1584,10 @@ export class Session extends EventEmitter {
this._keepAliveTurnUntil = Date.now() + windowMs;
}
endKeepAliveTurn(): void {
this._keepAliveTurnUntil = 0;
}
/** Last keystroke a person sent (`fromUser` write), epoch ms; 0 when none this process. */
get lastHumanInputAt(): number {
return this._lastHumanInputAt;
@@ -4741,6 +4756,9 @@ export class Session extends EventEmitter {
this._lastHumanInputAt = Date.now();
// A person is here: whatever turn follows is theirs, not the keep-alive's.
this._keepAliveTurnUntil = 0;
} else if (hasEnter && !isKeepAliveWrite(data)) {
// Anything else submitted (an agent, respawn, auto-resume) starts a turn of its own.
this._keepAliveTurnUntil = 0;
}
if (hasEnter) this._stampSubmit();
return submitted;