chore: snapshot working tree - pty_exited notifications + in-flight inference WIP
feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean). wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes. openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
This commit is contained in:
@@ -182,6 +182,7 @@ export async function sweepExpired(
|
||||
? 'idle timeout'
|
||||
: 'absolute timeout';
|
||||
log.info({ paneId: meta.paneId, reason }, 'sweeping expired PTY session');
|
||||
meta.timedOut = true;
|
||||
const sessionName = tmuxSessionName(meta.paneId);
|
||||
try {
|
||||
const ok = await killSession(tmuxConfPath, sessionName);
|
||||
@@ -191,7 +192,6 @@ export async function sweepExpired(
|
||||
} catch (err) {
|
||||
log.warn({ paneId: meta.paneId, err }, 'killSession threw during sweep');
|
||||
}
|
||||
registry.unregister(meta.paneId);
|
||||
killed.push(meta.paneId);
|
||||
}
|
||||
return killed;
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface SessionMeta {
|
||||
timeoutSeconds?: number;
|
||||
idleExpiresAt?: Date;
|
||||
absoluteExpiresAt?: Date;
|
||||
timedOut?: boolean;
|
||||
}
|
||||
|
||||
const sessions = new Map<string, SessionMeta>();
|
||||
@@ -115,6 +116,18 @@ export interface SearchMatch {
|
||||
|
||||
const ringBuffers = new Map<string, string[]>();
|
||||
|
||||
/**
|
||||
* Return the last N non-empty lines from the ring buffer for a pane.
|
||||
* ANSI escape sequences are preserved (xterm handles them).
|
||||
* Partial lines from mid-stream exit are included as-is.
|
||||
*/
|
||||
export function getLastLines(paneId: string, n: number): string[] {
|
||||
const buf = ringBuffers.get(paneId);
|
||||
if (!buf || buf.length === 0) return [];
|
||||
const nonEmpty = buf.filter(l => l.trim().length > 0);
|
||||
return nonEmpty.slice(-n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append raw PTY data to the ring buffer for a given pane.
|
||||
* Splits incoming data on newlines and pushes each line into the buffer,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../pty/manager.js';
|
||||
import { attachPty } from '../pty/pty.js';
|
||||
import { getUser } from '../auth.js';
|
||||
import { register, unregister, appendOutput, touchActivity, consumePendingMetadata } from '../pty/registry.js';
|
||||
import { register, unregister, appendOutput, touchActivity, consumePendingMetadata, get as getRegistry, getLastLines } from '../pty/registry.js';
|
||||
|
||||
export function registerWsAttachRoute(
|
||||
app: FastifyInstance,
|
||||
@@ -168,9 +168,22 @@ export function registerWsAttachRoute(
|
||||
});
|
||||
|
||||
handle.onExit(({ exitCode }) => {
|
||||
const meta = getRegistry(pid);
|
||||
const lastLines = getLastLines(pid, 5);
|
||||
const frame = {
|
||||
type: 'pty_exited' as const,
|
||||
session_id: sid,
|
||||
pane_id: pid,
|
||||
exit_code: exitCode,
|
||||
last_lines: lastLines,
|
||||
session_title: meta?.title ?? null,
|
||||
session_description: meta?.description ?? null,
|
||||
parent_agent: meta?.parentAgent ?? null,
|
||||
timed_out: meta?.timedOut ?? false,
|
||||
};
|
||||
try {
|
||||
if (socket.readyState === socket.OPEN) {
|
||||
socket.send(JSON.stringify({ type: 'exit', code: exitCode }));
|
||||
socket.send(JSON.stringify(frame));
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
|
||||
Reference in New Issue
Block a user