mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
Found while verifying Auto Copy in a browser: a plain left click in a
claude/codex/gemini pane sent a synthetic SGR mouse report into the PTY
whether or not the program in that pane had ever enabled mouse tracking.
When the pane holds a plain shell (the CLI exited, or a shell was started
inside a session of that mode) readline prints the report as literal text
and it garbles the next line typed:
$ [<0;88;20Mecho hello
bash: 0: No such file or directory
The cause is that the browser could not know. The full strip
(isAltScreenStripMode) removes the mouse DECSETs from the stream, so
xterm's modes.mouseTrackingMode is permanently 'none' for those modes and
_sendSyntheticSgrTap() hand-encodes reports to stand in for xterm's own
encoder. With no state to consult it had to do that on every click.
What the strip removes, the server now remembers.
_recordStrippedMouseMode() records each sequence as it is stripped,
toState() publishes it as cliMouseTracking, and the browser's
_shouldReportMouseToCli() (renamed from _sessionUsesServerMouseStrip)
requires it at all three report sites: the desktop click, the touchend
tap, and the mobile tap classifier.
Details that are easy to get wrong:
* Only the tracking modes count (1000/1001/1002/1003). 1005/1006 select
an encoding and 1007 is alt-scroll; a CLI that picks SGR encoding
without turning tracking on is not asking about clicks, and counting
those would put the stray reports straight back.
* Modes are held in a Set, so a TUI disabling a mode it never enabled
cannot clear the ones that are really on.
* The change broadcasts immediately instead of through
broadcastSessionStateDebounced: the flag flips when a dialog opens, and
the user can click that dialog well inside the 500ms debounce window.
* It fails toward silence. After a server restart the flag is false until
the CLI re-emits its DECSET, which tmux does at client attach.
Verified against a live claude 2.x session: the CLI holds a tracking mode
on continuously, so its clicks are still reported byte for byte as
before, while a bash prompt in the same stripped mode now reports
nothing and types cleanly. The flag also propagates live over SSE in both
directions, checked by toggling ?1002h/?1002l from inside the pane.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
554 lines
22 KiB
TypeScript
554 lines
22 KiB
TypeScript
/**
|
|
* @fileoverview Session type definitions.
|
|
*
|
|
* Core domain type — SessionState is the primary entity in the system.
|
|
*
|
|
* Key exports:
|
|
* - SessionState — full session state (status, tokens, respawn, ralph, CLI metadata)
|
|
* - SessionConfig — creation-time config (id, workingDir, createdAt)
|
|
* - SessionOutput — captured stdout/stderr/exitCode
|
|
* - SessionStatus — 'idle' | 'busy' | 'stopped' | 'error'
|
|
* - SessionMode — 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' (which CLI backend)
|
|
* - ClaudeMode — CLI permission mode ('dangerously-skip-permissions' | 'auto' | 'normal' | 'allowedTools')
|
|
* - SessionColor — visual differentiation color
|
|
* - OpenCodeConfig — OpenCode-specific settings (model, autoAllowTools, continueSession)
|
|
* - CodexConfig — Codex (OpenAI CLI)-specific settings (model, resumeSessionId)
|
|
* - GeminiConfig — Gemini CLI-specific settings (model, approvalMode, resumeSession)
|
|
* - AntigravityConfig — Antigravity CLI (agy) settings (model, dangerouslySkipPermissions, resumeConversationId)
|
|
* - PiConfig — Pi CLI (pi.dev) settings (model, provider, thinking, resume/continue, project trust)
|
|
*
|
|
* Cross-domain relationships:
|
|
* - SessionState.respawnConfig embeds RespawnConfig (respawn domain)
|
|
* - SessionState.id is referenced by: RalphSessionState.sessionId (ralph),
|
|
* RunSummary.sessionId (run-summary), ActiveBashTool.sessionId (tools),
|
|
* TeamConfig.leadSessionId (teams), RespawnCycleMetrics.sessionId (respawn),
|
|
* TaskState.assignedSessionId (task)
|
|
*
|
|
* Persisted to `~/.codeman/state.json`. Served at `GET /api/sessions` and
|
|
* `GET /api/sessions/:id`.
|
|
*/
|
|
|
|
import type { RespawnConfig } from './respawn.js';
|
|
import type { AttachmentDetectedType } from './tools.js';
|
|
|
|
/** Status of a Claude session */
|
|
export type SessionStatus = 'idle' | 'busy' | 'stopped' | 'error';
|
|
|
|
/**
|
|
* Claude CLI startup permission mode.
|
|
* - `'dangerously-skip-permissions'`: Bypass all permission prompts (default)
|
|
* - `'auto'`: Anthropic's classifier-guarded low-prompt mode (`--permission-mode auto`)
|
|
* - `'normal'`: Standard mode with permission prompts
|
|
* - `'allowedTools'`: Only allow specific tools (requires allowedTools list)
|
|
*/
|
|
export type ClaudeMode = 'dangerously-skip-permissions' | 'auto' | 'normal' | 'allowedTools';
|
|
|
|
/** Session mode: which CLI backend a session runs */
|
|
export type SessionMode = 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi';
|
|
|
|
export type RemoteCommandMode = Extract<
|
|
SessionMode,
|
|
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi'
|
|
>;
|
|
|
|
/**
|
|
* Advanced SSH connection options shared by RemoteHost and SessionRemote.
|
|
*
|
|
* COD-107 — all fields are optional; every field absent reproduces today's
|
|
* behavior (port-22, default-identity, directly-SSH-able hosts). These describe
|
|
* HOW Codeman reaches the host (identity, proxy, jump host, arbitrary `-o`),
|
|
* letting it connect to e.g. a host fronted by a cloudflared SOCKS5 proxy on a
|
|
* custom port — the same connection `ssh-aa-desktop` makes — without a wrapper.
|
|
*/
|
|
export interface RemoteSshOptions {
|
|
/**
|
|
* Path to an SSH identity (private key) file — path ONLY, never key bytes.
|
|
* A leading `~`/`$HOME` is expanded to an absolute path at command-build time
|
|
* (ssh does not expand `~` in `-i`).
|
|
*/
|
|
identityFile?: string;
|
|
/**
|
|
* SOCKS5 proxy as `host:port` (e.g. `127.0.0.1:1080`). Expands to
|
|
* `-o ProxyCommand=nc -X 5 -x <host:port> %h %p` (the cloudflared/SOCKS5 case).
|
|
*/
|
|
socksProxy?: string;
|
|
/** SSH jump host (`[user@]host[:port]`) emitted as `-J <jumpHost>`. */
|
|
jumpHost?: string;
|
|
/** Arbitrary additional `-o KEY=VALUE` options (escape hatch). Each `KEY=VALUE`. */
|
|
extraSshOptions?: string[];
|
|
}
|
|
|
|
export interface RemoteHost extends RemoteSshOptions {
|
|
id: string;
|
|
label: string;
|
|
host: string;
|
|
username: string;
|
|
port?: number;
|
|
commands?: Partial<Record<RemoteCommandMode, string>>;
|
|
}
|
|
|
|
export interface RemoteCase {
|
|
name: string;
|
|
type: 'remote';
|
|
/** Owning username in multi-user mode; absent = legacy/unassigned (admin-only). */
|
|
owner?: string;
|
|
hostId: string;
|
|
remotePath: string;
|
|
}
|
|
|
|
export interface SessionRemote extends RemoteSshOptions {
|
|
hostId: string;
|
|
label: string;
|
|
host: string;
|
|
username: string;
|
|
port?: number;
|
|
remotePath: string;
|
|
commands?: Partial<Record<RemoteCommandMode, string>>;
|
|
/**
|
|
* COD-105 — whether THIS Codeman created the remote tmux session.
|
|
*
|
|
* - `true` (default for COD-104 launched sessions): we own the remote session;
|
|
* an explicit "kill" may propagate a remote `tmux kill-session`.
|
|
* - `false` (discovered + attached an existing remote session another Codeman
|
|
* created): closing the local tab must DETACH only — we must NEVER issue a
|
|
* remote `kill-session`, or we'd nuke work the remote's own Codeman (or
|
|
* another instance) still relies on. See `killSession()` gate.
|
|
*
|
|
* Absent is treated as owned (legacy/COD-104 sessions persisted before this
|
|
* field existed were all launched by us).
|
|
*/
|
|
owned?: boolean;
|
|
/**
|
|
* COD-105 — for a NON-owned (discovered + attached) session, the EXISTING
|
|
* remote tmux session name to `attach -t` (e.g. `codeman-disco1`). It differs
|
|
* from this Codeman's deterministic `codeman-<id>` name because the remote
|
|
* session was created elsewhere. Only meaningful when `owned === false`.
|
|
*/
|
|
remoteSessionName?: string;
|
|
}
|
|
|
|
/**
|
|
* COD-105 — a `codeman-*` tmux session discovered on a remote host's
|
|
* `tmux -L codeman` socket (may have been created by the remote's own Codeman,
|
|
* another instance, or this one). Returned by `listRemoteCodemanSessions`.
|
|
*/
|
|
export interface RemoteSessionInfo {
|
|
/** tmux session name (always starts `codeman-`). */
|
|
name: string;
|
|
/** Whether at least one client is currently attached to the remote session. */
|
|
attached: boolean;
|
|
/** COD-106 — number of clients attached (tmux `session_attached`); >1 = shared. */
|
|
attachedClients: number;
|
|
/** tmux `session_created` epoch seconds. */
|
|
created: number;
|
|
/** Number of windows in the remote session. */
|
|
windows: number;
|
|
}
|
|
|
|
// ========== Docker cases (COD-Docker) ==========
|
|
//
|
|
// Docker mode is a LOCATION OVERLAY on cases (never a 6th SessionMode), the exact
|
|
// analog of the remote-SSH feature above: instead of a local tmux pane running
|
|
// `ssh host` into a durable remote tmux server, a local tmux pane runs
|
|
// `docker exec -it` into a durable in-container tmux server. The container is
|
|
// scoped to the CASE (not the session), so multiple sessions can `docker exec`
|
|
// into the same long-lived container. See `docs/docker-cases-plan.md`.
|
|
|
|
/** Which CLI backends a Docker case can run (same set as remote). */
|
|
export type DockerCommandMode = Extract<
|
|
SessionMode,
|
|
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi'
|
|
>;
|
|
|
|
/** Container engine. Docker and Podman differ in the uid/userns + host-gateway alias. */
|
|
export type DockerEngine = 'docker' | 'podman';
|
|
|
|
/**
|
|
* Container network mode. `host` and any inbound `-p` publish are deliberately
|
|
* unrepresentable (never in this union, never emitted by the flag builder).
|
|
* - `bridge`: own netns, NAT egress, no inbound (default — every API CLI needs egress)
|
|
* - `none`: fully offline sandbox (breaks API CLIs; reserved for `shell`)
|
|
* - `custom`: a user-defined bridge `codeman-net-<slug>` (future egress-allowlist chokepoint)
|
|
*/
|
|
export type DockerNetworkMode = 'bridge' | 'none' | 'custom';
|
|
|
|
/** Per-container resource caps. Advisory under non-delegated rootless (see `capsEnforced`). */
|
|
export interface DockerResourceLimits {
|
|
/** e.g. '4g' -> --memory 4g --memory-swap 4g (swap==memory: a real OOM cap) */
|
|
memory?: string;
|
|
/** e.g. '2' -> --cpus 2 */
|
|
cpus?: string;
|
|
/** e.g. 512 -> --pids-limit 512 (fork-bomb guard) */
|
|
pidsLimit?: number;
|
|
/** e.g. '4096:8192' -> --ulimit nofile=4096:8192 */
|
|
nofile?: string;
|
|
/** e.g. '256m' -> --shm-size (only when a tool needs /dev/shm) */
|
|
shmSize?: string;
|
|
}
|
|
|
|
/** A reusable Docker engine/image/network/resource profile (mirror of RemoteHost). */
|
|
export interface DockerHost {
|
|
id: string;
|
|
label: string;
|
|
/** Engine; when absent the availability probe resolves it (docker, else podman). */
|
|
engine?: DockerEngine;
|
|
/** Base image ref (built locally by scripts/build-agent-image.mjs, e.g. codeman/agent:base). */
|
|
image: string;
|
|
/** Advanced: remote daemon (-H ssh://user@host or a DOCKER_HOST value). */
|
|
daemonHost?: string;
|
|
/** Advanced: docker `--context` name. */
|
|
context?: string;
|
|
/** Network mode (default 'bridge'). */
|
|
network?: DockerNetworkMode;
|
|
/** Custom bridge name when network === 'custom'. */
|
|
networkName?: string;
|
|
resources?: DockerResourceLimits;
|
|
/** GPU allocation, e.g. 'all' / '1' / 'device=0,1' -> `--gpus <value>` (needs the NVIDIA container toolkit). */
|
|
gpus?: string;
|
|
/** true (default) = convenient: bind-mount host cred dirs RW. false = sealed (blocks full-image export). */
|
|
mountCredentials?: boolean;
|
|
/** true (default) = wire in-container hooks (host-gateway callback + workspace scaffold). */
|
|
hooksEnabled?: boolean;
|
|
/** true (default) = a relaunch resumes the last conversation from the bind-mounted transcript. */
|
|
resumeOnStart?: boolean;
|
|
/** Per-mode command overrides (mirror RemoteHost.commands). */
|
|
commands?: Partial<Record<DockerCommandMode, string>>;
|
|
/** Escape hatch: extra `docker create` args (validated like extraSshOptions). */
|
|
extraCreateArgs?: string[];
|
|
/** Escape hatch: extra `docker exec` args. */
|
|
extraExecArgs?: string[];
|
|
}
|
|
|
|
/** A case linked to a Docker container (mirror of RemoteCase). */
|
|
export interface DockerCase {
|
|
name: string;
|
|
type: 'docker';
|
|
/** Owning username in multi-user mode; absent = legacy/unassigned (admin-only). */
|
|
owner?: string;
|
|
hostId: string;
|
|
/** Absolute HOST directory: the bind-mount source AND Session.workingDir (real host bytes). */
|
|
hostWorkspacePath: string;
|
|
/** Container path (default = hostWorkspacePath: mirror -> transcript projHash correlates). */
|
|
containerWorkdir?: string;
|
|
/** Container name (default codeman-case-<slug>). */
|
|
container?: string;
|
|
/** Last captured Claude conversation id, replayed via --resume on a fresh launch. */
|
|
lastClaudeSessionId?: string;
|
|
}
|
|
|
|
/**
|
|
* Flattened Docker execution metadata carried on a live session (mirror of
|
|
* SessionRemote). Round-trips through MuxSession/SessionState/mux-sessions.json.
|
|
*/
|
|
export interface SessionDocker {
|
|
hostId: string;
|
|
label: string;
|
|
engine: DockerEngine;
|
|
image: string;
|
|
/** Per-CASE container name (shared by all sessions of the case). */
|
|
containerName: string;
|
|
hostWorkspacePath: string;
|
|
containerWorkdir: string;
|
|
network: DockerNetworkMode;
|
|
networkName?: string;
|
|
resources?: DockerResourceLimits;
|
|
/** GPU allocation ('all' / '1' / 'device=0,1'). */
|
|
gpus?: string;
|
|
mountCredentials: boolean;
|
|
hooksEnabled: boolean;
|
|
resumeOnStart: boolean;
|
|
daemonHost?: string;
|
|
context?: string;
|
|
commands?: Partial<Record<DockerCommandMode, string>>;
|
|
extraCreateArgs?: string[];
|
|
extraExecArgs?: string[];
|
|
/** Stable hash of the drift-relevant create args (recreate-on-drift detection). */
|
|
configHash?: string;
|
|
}
|
|
|
|
/**
|
|
* Valid Claude CLI effort levels (claude >= 2.1.154).
|
|
* `ultracode` = xhigh effort + standing dynamic-workflow orchestration; it is a
|
|
* separate `ultracode` settings key rather than an `effortLevel` value.
|
|
*/
|
|
export const EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultracode'] as const;
|
|
|
|
/** Claude CLI effort level for new sessions (soft default, switchable via /effort in-session) */
|
|
export type EffortLevel = (typeof EFFORT_LEVELS)[number];
|
|
|
|
/** Type guard: is the string a valid EffortLevel? */
|
|
export function isEffortLevel(value: string | undefined): value is EffortLevel {
|
|
return value !== undefined && (EFFORT_LEVELS as readonly string[]).includes(value);
|
|
}
|
|
|
|
/** OpenCode session configuration */
|
|
export interface OpenCodeConfig {
|
|
/** Model identifier (e.g., "anthropic/claude-sonnet-4-5", "openai/gpt-5.2", "ollama/codellama") */
|
|
model?: string;
|
|
/** Whether to auto-allow all tool executions (sets permission.* = allow) */
|
|
autoAllowTools?: boolean;
|
|
/** Session ID to continue from */
|
|
continueSession?: string;
|
|
/** Whether to fork when continuing (branch the conversation) */
|
|
forkSession?: boolean;
|
|
/** Custom inline config JSON (passed via OPENCODE_CONFIG_CONTENT) */
|
|
configContent?: string;
|
|
}
|
|
|
|
/** Codex (OpenAI CLI) browser rendering strategy. Hybrid TUI is the only supported mode. */
|
|
export type CodexRenderMode = 'hybrid';
|
|
|
|
/** Codex (OpenAI CLI) session configuration */
|
|
export interface CodexConfig {
|
|
/** Model identifier (e.g., "gpt-5", "o4-mini"). Passed via --model. */
|
|
model?: string;
|
|
/** Resume a previous codex conversation by session id (passed via --resume) */
|
|
resumeSessionId?: string;
|
|
/** Bypass approval prompts (passes --dangerously-bypass-approvals-and-sandbox) */
|
|
dangerouslyBypassApprovals?: boolean;
|
|
/** Enable Codex's decorative TUI animations. Disable to reduce remote terminal redraws. */
|
|
animations?: boolean;
|
|
/** Browser rendering strategy for Codex sessions. Hybrid TUI is the only supported mode. */
|
|
renderMode?: CodexRenderMode;
|
|
}
|
|
|
|
/** Gemini CLI session configuration */
|
|
export interface GeminiConfig {
|
|
/** Model identifier (e.g., "gemini-2.5-pro"). Passed via --model. */
|
|
model?: string;
|
|
/** Gemini approval mode for tool calls. */
|
|
approvalMode?: 'default' | 'auto_edit' | 'yolo' | 'plan';
|
|
/** Resume a previous Gemini session ("latest", index, or session id). */
|
|
resumeSession?: string;
|
|
}
|
|
|
|
/** Antigravity CLI (agy) session configuration */
|
|
export interface AntigravityConfig {
|
|
/** Model identifier. Passed via --model. */
|
|
model?: string;
|
|
/** Auto-approve all tool permission requests (passes --dangerously-skip-permissions). Absent = agy's default prompting. */
|
|
dangerouslySkipPermissions?: boolean;
|
|
/** Resume a previous conversation by ID (passed via --conversation). */
|
|
resumeConversationId?: string;
|
|
}
|
|
|
|
/**
|
|
* Pi CLI (pi.dev) session configuration.
|
|
*
|
|
* Pi has NO permission prompts and no `--dangerously-skip-permissions` analog,
|
|
* so there is deliberately no bypass field here. The one privilege-shaped knob is
|
|
* `approveProjectTrust`, which controls whether pi loads and EXECUTES repo-local
|
|
* `.pi/` extensions (and installs missing project packages).
|
|
*/
|
|
export interface PiConfig {
|
|
/** Model pattern or ID. Supports `provider/id` and a `:<thinking>` suffix (e.g. `sonnet:high`). Passed via --model. */
|
|
model?: string;
|
|
/** Provider name (anthropic, openai, google, ...). Passed via --provider. */
|
|
provider?: string;
|
|
/** Reasoning level. Passed via --thinking. */
|
|
thinking?: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
/** Continue the most recent session (-c). Skipped when resumeSessionId is set (the two conflict). */
|
|
continueSession?: boolean;
|
|
/** Resume a specific session by ID or partial UUID (--session). Ids only, never paths. */
|
|
resumeSessionId?: string;
|
|
/**
|
|
* Tri-state project trust (repo-local `.pi/` settings/extensions/skills, plus
|
|
* installing missing project packages):
|
|
* true -> --approve (trust for this run; loads and EXECUTES repository TypeScript)
|
|
* false -> --no-approve (force-deny; the trust prompt never appears)
|
|
* absent -> pi's own defaultProjectTrust (ask).
|
|
* Multi-user: MATERIALIZED to false for non-granted owners, because pi's
|
|
* absent-config default is a prompt the session user could answer themselves.
|
|
*/
|
|
approveProjectTrust?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Configuration for creating a new session
|
|
*/
|
|
export interface SessionConfig {
|
|
/** Unique session identifier */
|
|
id: string;
|
|
/** Working directory for the session */
|
|
workingDir: string;
|
|
/** Timestamp when session was created */
|
|
createdAt: number;
|
|
}
|
|
|
|
/**
|
|
* Available session colors for visual differentiation
|
|
*/
|
|
export type SessionColor = 'default' | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink';
|
|
|
|
export type SessionAttachmentHistorySource = 'detected' | 'external';
|
|
|
|
/**
|
|
* Session-scoped attachment history entry.
|
|
*
|
|
* `externalPath` is server-private. It may be present in the internal persisted
|
|
* history copy, but API-bound session state must sanitize it before returning
|
|
* to the browser.
|
|
*/
|
|
export interface SessionAttachmentHistoryItem {
|
|
/** Stable history identity used for dedupe and list rendering */
|
|
id: string;
|
|
/** Codeman session ID this item belongs to */
|
|
sessionId: string;
|
|
/** Display filename */
|
|
fileName: string;
|
|
/** Lowercase extension without a leading dot */
|
|
extension: string;
|
|
/** Viewer category used by the web UI */
|
|
attachmentType: AttachmentDetectedType;
|
|
/** File size in bytes */
|
|
size: number;
|
|
/** Last modified timestamp in milliseconds, if known */
|
|
mtimeMs: number;
|
|
/** Last time this attachment was seen or explicitly published */
|
|
timestamp: number;
|
|
/** How the attachment entered the session */
|
|
source: SessionAttachmentHistorySource;
|
|
/** Workspace-relative path for detected session files */
|
|
relativePath?: string;
|
|
/** Server-private absolute path for explicitly published external files */
|
|
externalPath?: string;
|
|
}
|
|
|
|
/**
|
|
* Current state of a session
|
|
*/
|
|
export interface SessionState {
|
|
/** Unique session identifier */
|
|
id: string;
|
|
/** Process ID of the PTY process, null if not running */
|
|
pid: number | null;
|
|
/** Current session status */
|
|
status: SessionStatus;
|
|
/** Working directory path */
|
|
workingDir: string;
|
|
/** Remote execution metadata, present when this session runs over SSH through local tmux */
|
|
remote?: SessionRemote;
|
|
/** Docker execution metadata, present when this session runs inside a container via local tmux + docker exec */
|
|
docker?: SessionDocker;
|
|
/** Owning username in multi-user mode; undefined in single-user (ignored when the flag is off) */
|
|
owner?: string;
|
|
/**
|
|
* The Codeman session that spawned this one, supplied by the caller at create time
|
|
* (`parentSessionId` body field or the `X-Codeman-Parent-Session` header) and resolved
|
|
* against live sessions before being stored.
|
|
*
|
|
* ⚠️ UI DECORATION ONLY — it draws the lineage lines between tabs. It is never an
|
|
* ownership, permission, or lifecycle signal: a child outlives its parent, and an
|
|
* unresolvable value is dropped rather than failing the spawn.
|
|
*/
|
|
parentSessionId?: string;
|
|
/** ID of currently assigned task, null if none */
|
|
currentTaskId: string | null;
|
|
/** Timestamp when session was created */
|
|
createdAt: number;
|
|
/** Timestamp of last activity */
|
|
lastActivityAt: number;
|
|
/** Session display name */
|
|
name?: string;
|
|
/** Session mode */
|
|
mode?: SessionMode;
|
|
/** Auto-clear enabled */
|
|
autoClearEnabled?: boolean;
|
|
/** Auto-clear token threshold */
|
|
autoClearThreshold?: number;
|
|
/** Auto-compact enabled */
|
|
autoCompactEnabled?: boolean;
|
|
/** Auto-compact token threshold */
|
|
autoCompactThreshold?: number;
|
|
/** Auto-compact prompt */
|
|
autoCompactPrompt?: string;
|
|
/** Auto-resume on usage limit enabled */
|
|
autoResumeEnabled?: boolean;
|
|
/** Pending usage-limit auto-resume fire time (epoch ms), if armed */
|
|
autoResumeAt?: number;
|
|
/** Pinned to the top of the session manager list (COD-139) */
|
|
pinned?: boolean;
|
|
/** When the session was pinned (epoch ms) — orders the pinned group, most-recent-first */
|
|
pinnedAt?: number;
|
|
/** Image watcher enabled for this session */
|
|
imageWatcherEnabled?: boolean;
|
|
/** Total cost in USD */
|
|
totalCost?: number;
|
|
/** Input tokens used */
|
|
inputTokens?: number;
|
|
/** Output tokens used */
|
|
outputTokens?: number;
|
|
/** Whether respawn controller is currently enabled/running */
|
|
respawnEnabled?: boolean;
|
|
/** Respawn controller config (if enabled) */
|
|
respawnConfig?: RespawnConfig & { durationMinutes?: number };
|
|
/** Ralph / Todo tracker enabled */
|
|
ralphEnabled?: boolean;
|
|
/** Ralph auto-enable disabled (user explicitly turned off Ralph) */
|
|
ralphAutoEnableDisabled?: boolean;
|
|
/** Ralph completion phrase (if set) */
|
|
ralphCompletionPhrase?: string;
|
|
/** Parent agent ID if this session is a spawned agent */
|
|
parentAgentId?: string;
|
|
/** Child agent IDs spawned by this session */
|
|
childAgentIds?: string[];
|
|
/** Nice priority enabled */
|
|
niceEnabled?: boolean;
|
|
/** Nice value (-20 to 19) */
|
|
niceValue?: number;
|
|
/** User-assigned color for visual differentiation */
|
|
color?: SessionColor;
|
|
/** Flicker filter enabled (buffers output after screen clears) */
|
|
flickerFilterEnabled?: boolean;
|
|
/**
|
|
* True while the CLI in the pane has a mouse-tracking DECSET on, as observed
|
|
* by the server on its way out of the stream (those sequences are stripped for
|
|
* claude/codex/gemini, so the browser can never see them itself). The browser
|
|
* hand-encodes a click report ONLY when this is true; without it, every click
|
|
* sent mouse reports to a CLI that never asked for them.
|
|
*/
|
|
cliMouseTracking?: boolean;
|
|
/** Claude Code CLI version (parsed from terminal, e.g., "2.1.27") */
|
|
cliVersion?: string;
|
|
/** Claude model in use (parsed from terminal, e.g., "Opus 4.5") */
|
|
cliModel?: string;
|
|
/** Account type (parsed from terminal, e.g., "Claude Max", "API") */
|
|
cliAccountType?: string;
|
|
/** Latest CLI version available (parsed from version check) */
|
|
cliLatestVersion?: string;
|
|
/** OpenCode-specific configuration (only for mode === 'opencode') */
|
|
openCodeConfig?: OpenCodeConfig;
|
|
/** Codex-specific configuration (only for mode === 'codex') */
|
|
codexConfig?: CodexConfig;
|
|
/** Gemini-specific configuration (only for mode === 'gemini') */
|
|
geminiConfig?: GeminiConfig;
|
|
/** Antigravity-specific configuration (only for mode === 'antigravity') */
|
|
antigravityConfig?: AntigravityConfig;
|
|
/** Pi-specific configuration (only for mode === 'pi') */
|
|
piConfig?: PiConfig;
|
|
/** Claude conversation session ID to resume after reboot (set by restore script) */
|
|
resumeSessionId?: string;
|
|
/** Claude CLI effort level (soft default via --settings, switchable in-session via /effort) */
|
|
effort?: EffortLevel;
|
|
/** Sanitized per-session attachment history. */
|
|
attachmentHistory?: SessionAttachmentHistoryItem[];
|
|
/**
|
|
* Wall-clock ms of this pane's last Enter (Session.lastSubmitAt). Persisted
|
|
* because it is the response-viewer's only anchor for re-deriving the pane's
|
|
* live conversation after a Codeman restart: `start()` resets
|
|
* `claudeSessionId` to the launch id even when re-attaching to a mux session
|
|
* whose CLI has since moved on via `/clear`, and the correlation cannot run
|
|
* again until the pane's own Enter is known.
|
|
*/
|
|
lastSubmitAt?: number;
|
|
/**
|
|
* PTY-exit circuit breaker tripped — respawn blocked until an explicit restart
|
|
* (COD-118). Runtime-only: never restored on boot (fresh server = fresh breaker).
|
|
*/
|
|
respawnBlocked?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Output captured from a session
|
|
*/
|