From 41cefdff8fa8bcb671f4cb268bf0affb3e6049fa Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 06:04:28 -0400 Subject: [PATCH] docs: document bash process registry --- src/agents/bash-process-references.ts | 8 ++++-- .../bash-process-registry.test-helpers.ts | 6 ++++- src/agents/bash-process-registry.test.ts | 5 ++++ src/agents/bash-process-registry.ts | 25 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/agents/bash-process-references.ts b/src/agents/bash-process-references.ts index 54d3ca32b70..e1cec9669d2 100644 --- a/src/agents/bash-process-references.ts +++ b/src/agents/bash-process-references.ts @@ -1,11 +1,15 @@ +/** + * Compact references for active background bash sessions. + * These references are surfaced in agent context so follow-up turns can + * reconnect to prior long-running work. + */ import { listRunningSessions } from "./bash-process-registry.js"; import { deriveSessionName } from "./bash-tools.shared.js"; -// Builds compact references for currently running background process sessions. -// These references are surfaced to agents so they can reconnect to prior work. const DEFAULT_ACTIVE_PROCESS_LIMIT = 8; const MAX_COMMAND_LABEL_CHARS = 140; +/** Agent-facing summary of a reconnectable background process session. */ export type ActiveProcessSessionReference = { sessionId: string; status: "running"; diff --git a/src/agents/bash-process-registry.test-helpers.ts b/src/agents/bash-process-registry.test-helpers.ts index 14da00979f6..3f0f2dc880f 100644 --- a/src/agents/bash-process-registry.test-helpers.ts +++ b/src/agents/bash-process-registry.test-helpers.ts @@ -1,7 +1,11 @@ +/** + * Test fixtures for bash process registry state. + * Provides complete session objects so tests can focus on the field under + * inspection without repeating registry defaults. + */ import type { ChildProcessWithoutNullStreams } from "node:child_process"; import type { ProcessSession } from "./bash-process-registry.js"; -// Test fixtures for bash process registry state. /** Build a process-session fixture with safe defaults for registry tests. */ export function createProcessSessionFixture(params: { id: string; diff --git a/src/agents/bash-process-registry.test.ts b/src/agents/bash-process-registry.test.ts index ef6a07921ab..1771c420b58 100644 --- a/src/agents/bash-process-registry.test.ts +++ b/src/agents/bash-process-registry.test.ts @@ -1,3 +1,8 @@ +/** + * Bash process registry tests. + * Covers output caps, finished-session retention, cleanup, and PTY cursor mode + * state for background exec sessions. + */ import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ProcessSession } from "./bash-process-registry.js"; diff --git a/src/agents/bash-process-registry.ts b/src/agents/bash-process-registry.ts index 8e8ebf5103c..03487ca94f9 100644 --- a/src/agents/bash-process-registry.ts +++ b/src/agents/bash-process-registry.ts @@ -1,3 +1,8 @@ +/** + * In-memory registry for bash exec sessions. + * Tracks running/backgrounded sessions, bounded pending output, finished + * session retention, and process cleanup for reconnect/poll flows. + */ import type { ChildProcessWithoutNullStreams } from "node:child_process"; import type { EventSessionRoutingPolicy } from "../infra/event-session-routing.js"; import type { TerminationReason } from "../process/supervisor/types.js"; @@ -19,8 +24,10 @@ function clampTtl(value: number | undefined) { let jobTtlMs = clampTtl(readEnvInt("OPENCLAW_BASH_JOB_TTL_MS", "PI_BASH_JOB_TTL_MS")); +/** Lifecycle status recorded for background process sessions. */ export type ProcessStatus = "running" | "completed" | "failed" | "killed"; +/** Writable stdin surface shared by child-process and PTY-backed sessions. */ export type SessionStdin = { write: (data: string, cb?: (err?: Error | null) => void) => void; end: () => void; @@ -32,6 +39,7 @@ export type SessionStdin = { writableFinished?: boolean; }; +/** Mutable session state for a running bash exec process. */ export interface ProcessSession { id: string; command: string; @@ -78,6 +86,7 @@ export interface ProcessSession { cursorKeyMode: "unknown" | "normal" | "application"; } +/** Retained summary for a completed background session. */ export interface FinishedSession { id: string; command: string; @@ -104,28 +113,34 @@ function isSessionIdTaken(id: string) { return runningSessions.has(id) || finishedSessions.has(id); } +/** Creates a unique short session id that avoids running and retained sessions. */ export function createSessionSlug(): string { return createSessionSlugId(isSessionIdTaken); } +/** Adds a running session and starts retention sweeping if needed. */ export function addSession(session: ProcessSession) { runningSessions.set(session.id, session); startSweeper(); } +/** Returns a running session by id. */ export function getSession(id: string) { return runningSessions.get(id); } +/** Returns a retained finished background session by id. */ export function getFinishedSession(id: string) { return finishedSessions.get(id); } +/** Removes a session from both running and finished registries. */ export function deleteSession(id: string) { runningSessions.delete(id); finishedSessions.delete(id); } +/** Appends process output while enforcing aggregate and pending-output caps. */ export function appendOutput(session: ProcessSession, stream: "stdout" | "stderr", chunk: string) { session.pendingStdout ??= []; session.pendingStderr ??= []; @@ -156,6 +171,7 @@ export function appendOutput(session: ProcessSession, stream: "stdout" | "stderr session.tail = tail(session.aggregated, 2000); } +/** Drains pending stdout/stderr chunks returned by a process poll. */ export function drainSession(session: ProcessSession) { const stdout = session.pendingStdout.join(""); const stderr = session.pendingStderr.join(""); @@ -166,6 +182,7 @@ export function drainSession(session: ProcessSession) { return { stdout, stderr }; } +/** Moves a session to finished state and records exit metadata. */ export function markExited( session: ProcessSession, exitCode: number | null, @@ -181,6 +198,7 @@ export function markExited( moveToFinished(session, status); } +/** Marks a running session as reconnectable after the exec call returns. */ export function markBackgrounded(session: ProcessSession) { session.backgrounded = true; } @@ -240,6 +258,7 @@ function moveToFinished(session: ProcessSession, status: ProcessStatus) { }); } +/** Returns the last `max` characters of text without adding ellipses. */ export function tail(text: string, max = 2000) { if (text.length <= max) { return text; @@ -286,6 +305,7 @@ function capPendingBuffer(buffer: string[], pendingCharsInput: number, cap: numb return pendingChars; } +/** Keeps only the last `max` characters for bounded aggregate output storage. */ export function trimWithCap(text: string, max: number) { if (text.length <= max) { return text; @@ -293,24 +313,29 @@ export function trimWithCap(text: string, max: number) { return text.slice(text.length - max); } +/** Lists backgrounded running sessions visible to reconnect/poll callers. */ export function listRunningSessions() { return Array.from(runningSessions.values()).filter((s) => s.backgrounded); } +/** Lists retained finished background sessions. */ export function listFinishedSessions() { return Array.from(finishedSessions.values()); } +/** Clears retained finished sessions without touching running processes. */ export function clearFinished() { finishedSessions.clear(); } +/** Test-only reset for in-memory registry state and retention timers. */ export function resetProcessRegistryForTests() { runningSessions.clear(); finishedSessions.clear(); stopSweeper(); } +/** Overrides finished-session retention TTL, clamped to supported bounds. */ export function setJobTtlMs(value?: number) { if (value === undefined || Number.isNaN(value)) { return;