import * as readline from "node:readline"; import type { LLMClient } from "../providers/types.js"; import { RunSpawner } from "./run-spawner.js"; import { CompletionTailer } from "./completion-tailer.js"; import { ApprovalReader } from "./approval-reader.js"; export interface ChatSessionOptions { llm: LLMClient; projectRoot: string; sessionId?: string; /** Override readline interface for testing (omit to use stdin/stdout). */ rl?: readline.Interface; /** Injected RunSpawner for testing (omit to use a real instance). */ spawner?: RunSpawner; /** Injected clock for deterministic runId generation in tests. */ now?: () => number; /** Injected CompletionTailer for testing (omit to use a real instance). */ tailer?: CompletionTailer; /** Injected ApprovalReader for testing (omit to use a real instance). */ approvalReader?: ApprovalReader; /** * Memory namespace for the active team. Omit (or pass "") for the default * programming team, which resolves to the current .bober/memory/ path. * Sprint 2: threaded through buildMemoryDistill so a named team reads its own subdir. */ memoryNamespace?: string; } export declare class ChatSession { private readonly llm; private readonly projectRoot; private readonly sessionId; private readonly store; private readonly roster; private readonly classifier; private readonly answerer; private readonly spawner; private readonly tailer; private readonly approvalReader; private readonly approvalCursor; private readonly nowFn; private readonly model; /** Memory namespace for the active team; undefined means the default .bober/memory/ path. */ private readonly memoryNamespace; /** Phase 2: persists the per-session careful-mode toggle. */ private readonly carefulSidecar; constructor(opts: ChatSessionOptions); /** Generate a session-scoped runId using the injected clock. */ private nextRunId; /** * Handle a single turn: slash-dispatch or classify→answer→persist. * Returns the assistant reply string, or null for /exit. */ handleTurn(input: string): Promise; /** * Stop a run by resolving it against the current disk roster, then calling * RunSpawner.stop. Shared by /stop (deterministic slash path) and * classifier steer:stop (natural-language path). Never reaches the LLM. */ private handleStop; /** * Handle /careful [on|off]. * - "on" → set careful true, return confirmation * - "off" → set careful false, return confirmation * - undefined/other → report current state */ private handleCareful; /** * Approve a pending checkpoint: guard with pendingExists, write the marker, * clear the correlated RunState pending fields, and return an ack string. * Returns a "no pending checkpoint" message and writes nothing if the pending * file does not exist (sc-3-4). */ private handleApprove; /** * Reject a pending checkpoint: guard with pendingExists, write the marker * carrying the feedback string, clear the correlated RunState, and return ack. * Returns a "no pending checkpoint" message and writes nothing if absent (sc-3-4). */ private handleReject; /** * Queue free-text guidance for a run at the next pipeline boundary. * Shared by /tell (deterministic slash path) and * classifier tell (natural-language path). Never reaches the LLM. * * Guards: unknown run (not in roster) → clear error, writes nothing. * Path-traversal: appendGuidance validates runId via safeSegment first. * Does NOT require careful mode; guidance can be queued for any known run. */ private handleTell; /** * Soft-pause a run at the next checkpoint boundary. * * Distinct from handleStop: this does NOT send a kill signal — the process * stays alive and will hold at the next cooperative-pause gate in the pipeline. * * Guards: run must be found in the roster with status "running". * Side effects: writes paused.json marker + flips chat-owned RunState to * 'paused' (with pausedAt timestamp) via writeRunState. */ private handlePause; /** * Resume a soft-paused run. * * Clears the paused.json marker (best-effort) and flips RunState back * to 'running' (dropping the pausedAt field). The pipeline's cooperative * gate polls isPaused, so removing the marker allows the gate to advance. */ private handleResume; /** * Capture a plain task statement as an open action Finding in the hub pool. * Reuses captureTask (sprint 1) — the single write path; never re-implements it. * `now` is stamped here at the chat handler boundary (the only permitted * new Date() per principles); captureTask/the store stay clock-free. * Never throws: a persistence failure becomes an error reply, not an exception. */ private handleCaptureTask; /** * Handle /priority in a hub chat session. * * Gated on this.memoryNamespace === "hub" — returns an informative no-op * string for any other team without calling this.llm. */ private handleHubPriority; /** * Handle /decide vs in a hub chat session. * * Gated on this.memoryNamespace === "hub" — returns an informative no-op * string for any other team without calling this.llm. */ private handleHubDecide; /** * Collect sibling findings, rank via the hub judge, best-effort write priority.md, * and return a ranked summary string ("rank. title" per line). */ private rankAndRenderHub; /** * Clear pending fields from the RunState correlated to this checkpoint. * Inverse of the Sprint 2 reflection block: finds the input-required RunState * matching this checkpointId, drops the three pending fields, and sets * status back to "running". Idempotent — no-op if no correlated state exists. */ private clearPending; /** * Resolve a checkpoint id for NL approve/reject paths. * - If an id is provided, use it directly. * - If exactly one pending marker exists, use it. * - If zero or multiple pending markers exist (without a named id), return * an ambiguous result so the caller can ask the user instead of guessing. */ private resolveCheckpoint; /** * Start an interactive REPL loop reading from stdin. * Use handleTurn() directly in tests instead. */ start(): Promise; } //# sourceMappingURL=chat-session.d.ts.map