mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
Merge remote-tracking branch 'origin/master' into relprep/pr-510
Brings the 1.33.2 based adoption feature up to master. Conflicts were resolved by keeping master's intent and re-applying the feature on top: both sides kept for imports, the script tag and the stylesheet blocks; the trust-dialog guard keeps master's stopped check plus the adopted refusal; the Ralph and image watchers keep master's remote branch plus the adopted skip; the close confirmation keeps master's worktree option plus the adopted wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
57b2dcc466
858 files changed
+173626
-7078
No files matched your search
@@ -157,6 +157,13 @@ export interface CaseInfo {
|
||||
location?: 'local' | 'linked-local' | 'remote' | 'docker';
|
||||
/** Whether this is a linked local folder */
|
||||
linked?: boolean;
|
||||
/**
|
||||
* The case folder did not answer (an unreachable network mount, or an error other
|
||||
* than "no such file"), or its probe was refused because folders on other unreachable
|
||||
* mounts are still not answering, so whether it still exists is unknown. A refused
|
||||
* probe can set this on a healthy linked case. Absent = it answered.
|
||||
*/
|
||||
unreachable?: boolean;
|
||||
/**
|
||||
* Present when Codeman scaffolded this case directory for an AGENT-spawned session
|
||||
* (the packaged skill's workers, or any spawn naming a parent session), read back
|
||||
|
||||
@@ -121,6 +121,12 @@ export interface AppState {
|
||||
sessionOrder?: string[];
|
||||
/** Owner-scoped authoritative grouped tab layouts. */
|
||||
tabLayouts?: Record<string, TabLayout>;
|
||||
/**
|
||||
* Per-case default session group, keyed by the tab-layout owner key, then case
|
||||
* name. A group id from that owner's layout, or null for an intentional
|
||||
* Ungrouped choice. Read and written only through TabLayoutService.
|
||||
*/
|
||||
caseDefaultGroups?: Record<string, Record<string, string | null>>;
|
||||
}
|
||||
|
||||
// ========== Default Configuration ==========
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @fileoverview Discord bridge type definitions.
|
||||
*
|
||||
* The option lists and the config, result and status shapes of the two-way Discord bridge, kept apart
|
||||
* from the IO module (`src/discord-bridge.ts`, which pulls in `ws`) so the request schemas and the
|
||||
* route can import them alone, as the webhook's equivalents live in `push.ts`.
|
||||
*
|
||||
* Key exports:
|
||||
* - DISCORD_THREAD_CREATION, DISCORD_ON_DELETE, DISCORD_PING_MODES (+ their types): the option lists
|
||||
* - DiscordConfig: `~/.codeman/discord.json` (mode 0600; the bot token is a secret)
|
||||
* - DiscordResult, DiscordGatewayState, DiscordStatus: what `GET /api/discord` reports
|
||||
*
|
||||
* No dependencies on other domain modules but `push.ts` (the shared scope list).
|
||||
*/
|
||||
|
||||
import type { WebhookScope } from './push.js';
|
||||
|
||||
export const DISCORD_THREAD_CREATION = ['lazy', 'onSessionStart'] as const;
|
||||
export type DiscordThreadCreation = (typeof DISCORD_THREAD_CREATION)[number];
|
||||
export const DISCORD_ON_DELETE = ['keep', 'archive', 'delete'] as const;
|
||||
export type DiscordOnDelete = (typeof DISCORD_ON_DELETE)[number];
|
||||
export const DISCORD_PING_MODES = ['off', 'critical', 'all'] as const;
|
||||
export type DiscordPingMode = (typeof DISCORD_PING_MODES)[number];
|
||||
|
||||
export interface DiscordConfig {
|
||||
enabled: boolean;
|
||||
token: string;
|
||||
guildId: string;
|
||||
parentChannelId: string;
|
||||
allowedUserIds: string[];
|
||||
threadCreation: DiscordThreadCreation;
|
||||
onSessionDelete: DiscordOnDelete;
|
||||
/** What to do with threads the bridge made that no tab maps to any more: checked after boot, then daily. */
|
||||
staleThreads: DiscordOnDelete;
|
||||
pingMode: DiscordPingMode;
|
||||
/** `attention` skips info-level events (a finished response); `all` sends them too. */
|
||||
scope: WebhookScope;
|
||||
/** Tell each session how to reach the user (see DISCORD_STARTUP_HINT). On by default while the bridge is on. */
|
||||
startupHint: boolean;
|
||||
}
|
||||
|
||||
export interface DiscordResult {
|
||||
ok: boolean;
|
||||
/** True when the same event for the same tab was posted within the last few seconds, so nothing was sent. */
|
||||
deduped?: boolean;
|
||||
status?: number;
|
||||
error?: string;
|
||||
at: number;
|
||||
}
|
||||
|
||||
export type DiscordGatewayState = 'off' | 'connecting' | 'connected' | 'error';
|
||||
|
||||
/** `GET /api/discord`: the config without its token, plus what the bridge is doing. */
|
||||
export type DiscordStatus = Omit<DiscordConfig, 'token'> & {
|
||||
hasToken: boolean;
|
||||
tokenMasked: string;
|
||||
gateway: DiscordGatewayState;
|
||||
gatewayError: string | null;
|
||||
lastResult: DiscordResult | null;
|
||||
};
|
||||
+5
-1
@@ -24,9 +24,11 @@
|
||||
* | run-summary | RunSummary, RunSummaryEvent, RunSummaryStats | In-memory → `GET /api/sessions/:id/run-summary` |
|
||||
* | tools | ActiveBashTool, ImageDetectedEvent | In-memory, broadcast via SSE |
|
||||
* | teams | TeamConfig, TeamMember, TeamTask, InboxMessage, PaneInfo | `~/.claude/teams/`, `~/.claude/tasks/` → `GET /api/teams` |
|
||||
* | push | PushSubscriptionRecord, VapidKeys | `~/.codeman/push-keys.json`, `~/.codeman/push-subscriptions.json` |
|
||||
* | push | PushSubscriptionRecord, VapidKeys, WebhookConfig, WebhookStatus, WebhookResult | `~/.codeman/push-keys.json`, `~/.codeman/push-subscriptions.json`, `~/.codeman/webhook.json` |
|
||||
* | discord | DiscordConfig, DiscordResult, DiscordStatus, DiscordGatewayState | `~/.codeman/discord.json` → `GET`/`PUT /api/discord` |
|
||||
* | plan | PlanItem, PlanTaskStatus, TddPhase | In-memory → `GET /api/sessions/:id/plan/tasks` |
|
||||
* | orchestrator | OrchestratorState, OrchestratorPlan, OrchestratorConfig, OrchestratorPersistState | `~/.codeman/state.json` → `GET /api/orchestrator/status` |
|
||||
* | mcp-sync | McpSyncResult, McpSyncTargetResult | Other CLIs' own config files → `GET`/`POST /api/mcp-sync` |
|
||||
*
|
||||
* ## Cross-domain relationship map
|
||||
*
|
||||
@@ -64,6 +66,7 @@ export * from './run-summary.js';
|
||||
export * from './tools.js';
|
||||
export * from './teams.js';
|
||||
export * from './push.js';
|
||||
export * from './discord.js';
|
||||
export * from './plan.js';
|
||||
export * from './orchestrator.js';
|
||||
export * from './update.js';
|
||||
@@ -73,3 +76,4 @@ export * from './user.js';
|
||||
export * from './webview.js';
|
||||
export * from './intent.js';
|
||||
export * from './foreign-tmux.js';
|
||||
export * from './mcp-sync.js';
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @fileoverview Response types for MCP server sync (`GET`/`POST /api/mcp-sync`, src/mcp-sync.ts).
|
||||
*
|
||||
* These are returned over HTTP, so they carry server NAMES only: never env values or headers,
|
||||
* and never file content (a parse failure is reported by position, see `describeMcpSyncError`).
|
||||
*/
|
||||
|
||||
/** One participating CLI in a sync result. */
|
||||
export interface McpSyncTargetResult {
|
||||
id: string;
|
||||
label: string;
|
||||
/** The config file read (and written). For a `skipped` target, the unresolved location. */
|
||||
file: string;
|
||||
/**
|
||||
* `absent`: not installed and no config file, so neither read nor created.
|
||||
* `skipped`: the CLI's config location could not be resolved safely (e.g. its relocation env
|
||||
* var is a relative path), so it is neither read nor written; `error` says why.
|
||||
* `unreadable`: the file exists but cannot be parsed safely, so it is not written.
|
||||
* `failed`: a read or write error (the file may be unchanged).
|
||||
*/
|
||||
status: 'ok' | 'absent' | 'skipped' | 'unreadable' | 'failed';
|
||||
/** Why the target is not `ok`. Position or category only, never file content. */
|
||||
error?: string;
|
||||
servers: string[];
|
||||
/** Servers added (apply) or that would be added (plan). */
|
||||
added: string[];
|
||||
/** Missing servers this dialect cannot express. */
|
||||
skipped: string[];
|
||||
}
|
||||
|
||||
/** The `data` of `GET`/`POST /api/mcp-sync`. */
|
||||
export interface McpSyncResult {
|
||||
applied: boolean;
|
||||
targets: McpSyncTargetResult[];
|
||||
/** Names defined differently by different CLIs; existing definitions are left untouched. */
|
||||
conflicts: string[];
|
||||
/** Names left out because the only definitions are switched off in their own CLI. */
|
||||
disabled: string[];
|
||||
/** Installed, enabled agent CLIs with no known MCP config file, so sync cannot touch them. */
|
||||
unsupported: string[];
|
||||
}
|
||||
+43
-2
@@ -6,13 +6,17 @@
|
||||
* Key exports:
|
||||
* - PushSubscriptionRecord — a registered push endpoint with per-event preferences
|
||||
* - VapidKeys — VAPID key pair (public + private) for Web Push authentication
|
||||
* - WebhookConfig, WebhookStatus, WebhookResult (+ the kind/scope lists): the webhook channel
|
||||
* (ntfy, Slack, Discord, generic JSON) that carries the same events as Web Push
|
||||
*
|
||||
* Persistence:
|
||||
* - VAPID keys: `~/.codeman/push-keys.json` (auto-generated on first use)
|
||||
* - Subscriptions: `~/.codeman/push-subscriptions.json` (expired auto-cleaned on 410/404)
|
||||
* - Webhook: `~/.codeman/webhook.json` (mode 0600; the URL is a bearer secret)
|
||||
*
|
||||
* Managed by PushStore (`src/push-store.ts`). Served at `GET /api/push/vapid-key`,
|
||||
* `POST /api/push/subscribe`. No dependencies on other domain modules.
|
||||
* Push is managed by PushStore (`src/push-store.ts`), served at `GET /api/push/vapid-key`,
|
||||
* `POST /api/push/subscribe`. The webhook is managed by `src/webhook-notify.ts`, served at
|
||||
* `GET`/`PUT /api/webhook` and `POST /api/webhook/test`. No dependencies on other domain modules.
|
||||
*/
|
||||
|
||||
/** A registered push subscription */
|
||||
@@ -32,3 +36,40 @@ export interface VapidKeys {
|
||||
privateKey: string;
|
||||
generatedAt: number;
|
||||
}
|
||||
|
||||
/** Services the webhook channel can format a message for. */
|
||||
export const WEBHOOK_KINDS = ['ntfy', 'slack', 'discord', 'generic'] as const;
|
||||
export type WebhookKind = (typeof WEBHOOK_KINDS)[number];
|
||||
|
||||
/** `attention`: only events that need a human (critical / warning). `all`: also "response complete". */
|
||||
export const WEBHOOK_SCOPES = ['attention', 'all'] as const;
|
||||
export type WebhookScope = (typeof WEBHOOK_SCOPES)[number];
|
||||
|
||||
export type WebhookUrgency = 'critical' | 'warning' | 'info';
|
||||
|
||||
/** The stored webhook config (`~/.codeman/webhook.json`). `url` is a secret and is never returned. */
|
||||
export interface WebhookConfig {
|
||||
enabled: boolean;
|
||||
kind: WebhookKind;
|
||||
url: string;
|
||||
scope: WebhookScope;
|
||||
}
|
||||
|
||||
/** One delivery attempt. `error` never contains the URL. */
|
||||
export interface WebhookResult {
|
||||
ok: boolean;
|
||||
status?: number;
|
||||
error?: string;
|
||||
at: number;
|
||||
}
|
||||
|
||||
/** `GET /api/webhook`: the config without its URL, plus the last delivery result. */
|
||||
export interface WebhookStatus {
|
||||
enabled: boolean;
|
||||
kind: WebhookKind;
|
||||
scope: WebhookScope;
|
||||
hasUrl: boolean;
|
||||
/** Scheme + host only; the path and query are the secret. */
|
||||
urlMasked: string;
|
||||
lastResult: WebhookResult | null;
|
||||
}
|
||||
+232
-7
@@ -8,15 +8,16 @@
|
||||
* - 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' | 'grok' | 'deepseek' | 'omp' (which CLI backend)
|
||||
* - SessionMode — 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp' | 'copilot' (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)
|
||||
* - CodexConfig — Codex (OpenAI CLI)-specific settings (model, reasoningEffort, resumeSessionId, bypass, animations, renderMode)
|
||||
* - 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)
|
||||
* - GrokConfig — Grok Build CLI (xAI `grok`) settings (model, alwaysApprove, resume/continue)
|
||||
* - CopilotConfig — GitHub Copilot CLI settings (model, allowAll, resume/continue)
|
||||
* - DeepSeekConfig — DeepSeek Harness (`dsh`) settings (profile, permissionMode, resume, status bridge)
|
||||
*
|
||||
* Cross-domain relationships:
|
||||
@@ -32,6 +33,7 @@
|
||||
|
||||
import type { RespawnConfig } from './respawn.js';
|
||||
import type { AttachmentDetectedType } from './tools.js';
|
||||
import type { GitFetchStatus } from '../git-fetch.js';
|
||||
|
||||
/** Status of a Claude session */
|
||||
export type SessionStatus = 'idle' | 'busy' | 'stopped' | 'error';
|
||||
@@ -56,7 +58,8 @@ export type SessionMode =
|
||||
| 'pi'
|
||||
| 'grok'
|
||||
| 'deepseek'
|
||||
| 'omp';
|
||||
| 'omp'
|
||||
| 'copilot';
|
||||
|
||||
/**
|
||||
* Who owns a session's name. `placeholder`: Codeman's own `w<n>-<case>` (or no
|
||||
@@ -78,7 +81,7 @@ export interface SessionWriteOptions {
|
||||
|
||||
export type RemoteCommandMode = Extract<
|
||||
SessionMode,
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp'
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp' | 'copilot'
|
||||
>;
|
||||
|
||||
/**
|
||||
@@ -213,7 +216,7 @@ export interface RemoteSessionInfo {
|
||||
/** Which CLI backends a Docker case can run (same set as remote). */
|
||||
export type DockerCommandMode = Extract<
|
||||
SessionMode,
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp'
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp' | 'copilot'
|
||||
>;
|
||||
|
||||
/** Container engine. Docker and Podman differ in the uid/userns + host-gateway alias. */
|
||||
@@ -445,6 +448,36 @@ export function isEffortLevel(value: string | undefined): value is EffortLevel {
|
||||
return value !== undefined && (EFFORT_LEVELS as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reasoning effort levels codex accepts as `model_reasoning_effort` (codex-cli 0.154.0).
|
||||
* Which of them a given model honours is codex's business; Codeman only keeps the value
|
||||
* to a known word, since it lands in the launch argv.
|
||||
*/
|
||||
export const CODEX_REASONING_EFFORTS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const;
|
||||
|
||||
/** Codex reasoning effort for a session, passed as `--config model_reasoning_effort=<level>` */
|
||||
export type CodexReasoningEffort = (typeof CODEX_REASONING_EFFORTS)[number];
|
||||
|
||||
/**
|
||||
* Model aliases Claude Code accepts for its advisor tool (a stronger model the session's
|
||||
* main model consults at decision points; code.claude.com/docs/en/advisor). Haiku is left
|
||||
* out on purpose: it can call an advisor but never act as one.
|
||||
*/
|
||||
export const ADVISOR_MODEL_ALIASES = ['fable', 'opus', 'sonnet'] as const;
|
||||
|
||||
/** A full model id in one of the advisor-capable families, e.g. `claude-opus-5-5`. */
|
||||
const ADVISOR_MODEL_ID_PATTERN = /^claude-(?:fable|opus|sonnet)-[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
|
||||
/**
|
||||
* Type guard: is the value an advisor model Codeman will pass to claude? An alias from
|
||||
* ADVISOR_MODEL_ALIASES or a full fable/opus/sonnet model id. ⚠️ This allowlist is also the
|
||||
* injection guard: the value is rendered inside the single-quoted `--settings` JSON argument.
|
||||
*/
|
||||
export function isAdvisorModel(value: unknown): value is string {
|
||||
if (typeof value !== 'string' || value.length > 64) return false;
|
||||
return (ADVISOR_MODEL_ALIASES as readonly string[]).includes(value) || ADVISOR_MODEL_ID_PATTERN.test(value);
|
||||
}
|
||||
|
||||
/** OpenCode session configuration */
|
||||
export interface OpenCodeConfig {
|
||||
/** Model identifier (e.g., "anthropic/claude-sonnet-4-5", "openai/gpt-5.2", "ollama/codellama") */
|
||||
@@ -466,6 +499,8 @@ export type CodexRenderMode = 'hybrid';
|
||||
export interface CodexConfig {
|
||||
/** Model identifier (e.g., "gpt-5", "o4-mini"). Passed via --model. */
|
||||
model?: string;
|
||||
/** Reasoning effort for this session. Passed via --config model_reasoning_effort=<level>. */
|
||||
reasoningEffort?: CodexReasoningEffort;
|
||||
/** Resume a previous codex conversation by session id (passed via --resume) */
|
||||
resumeSessionId?: string;
|
||||
/** Bypass approval prompts (passes --dangerously-bypass-approvals-and-sandbox) */
|
||||
@@ -561,6 +596,32 @@ export interface GrokConfig {
|
||||
resumeSessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub Copilot CLI (`copilot`) session configuration.
|
||||
*
|
||||
* Copilot asks before running tools ("Manual Approval") and before touching paths
|
||||
* outside the trusted folder. `--yolo` is its allow-all switch (tools, paths and URLs),
|
||||
* the one the Run button opts into like the other agent CLIs' bypass switches. Verified
|
||||
* against copilot 1.0.94.
|
||||
*/
|
||||
export interface CopilotConfig {
|
||||
/** Model ID passed via --model (e.g. "claude-sonnet-5.5", or "auto"). */
|
||||
model?: string;
|
||||
/**
|
||||
* Allow every tool, path and URL without asking (passes --yolo). Absent = Copilot's own
|
||||
* Manual Approval mode. Multi-user: forced off for non-granted owners by the only-if-sent
|
||||
* clamp branch, like codex/antigravity/grok.
|
||||
*/
|
||||
allowAll?: boolean;
|
||||
/** Resume the most recent session (--continue). Skipped when resumeSessionId is set. */
|
||||
continueSession?: boolean;
|
||||
/**
|
||||
* Resume a specific session (--resume <value>): an id, id prefix or session name, as ONE plain word
|
||||
* starting with a letter or digit (a leading `-` would make the value its own flag).
|
||||
*/
|
||||
resumeSessionId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DeepSeek Harness (`dsh`) session configuration.
|
||||
*
|
||||
@@ -677,6 +738,24 @@ export interface CustomModelSelection {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a session's {@link DisplayModel} came from (src/session-display-model.ts):
|
||||
* - `custom-endpoint`: the Custom Model Endpoint Profile's model, which wins.
|
||||
* - `statusline`: the CLI reported it (claude's statusLine exporter), follows a switch.
|
||||
* - `screen`: read off the CLI's own footer (`capabilities.modelDetect`), follows a switch.
|
||||
* - `config`: what the CLI's own config pins for this session
|
||||
* (`capabilities.modelDetect.configResolver`), while its screen names none.
|
||||
* - `launch`: what the session was launched with; nothing has reported since.
|
||||
*/
|
||||
export type DisplayModelSource = 'custom-endpoint' | 'statusline' | 'screen' | 'config' | 'launch';
|
||||
|
||||
/** The model a session runs as far as the server knows, for a session header. */
|
||||
export interface DisplayModel {
|
||||
/** Display text: sanitized (no control characters) and at most 64 characters. */
|
||||
model: string;
|
||||
source: DisplayModelSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The full custom-model selection a session keeps: the public selection plus the
|
||||
* bookkeeping `Session.setCustomModel()` needs to UNDO it later without guessing what
|
||||
@@ -742,6 +821,78 @@ export interface PaneExit {
|
||||
at: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalized prompt-cache state of a session's main conversation, from the statusline's
|
||||
* `prompt_cache` object (`parsePromptCache` in usage-telemetry.ts). `warm` is the only
|
||||
* field Claude always reports: once the prefix has gone cold it sends `warm:false` with a
|
||||
* null `expires_at`.
|
||||
*/
|
||||
export interface PromptCacheStatus {
|
||||
warm: boolean;
|
||||
/** Cache lifetime of the current prefix; Claude Code only ever reports these two. */
|
||||
ttl?: '5m' | '1h';
|
||||
/** Epoch MILLISECONDS when the cached prefix goes cold (the statusline reports seconds). */
|
||||
expiresAt?: number;
|
||||
/** Tokens the next request re-writes if the cache has gone cold by then. */
|
||||
recacheTokensIfCold?: number;
|
||||
/** Requests this session that re-processed content the cache already held. */
|
||||
misses?: number;
|
||||
/** Cache rebuilds that followed a compaction or a clearing of old tool results. */
|
||||
expectedRebuilds?: number;
|
||||
/** Cache read tokens as a fraction of all input tokens this session, 0-1. */
|
||||
hitRatio?: number;
|
||||
/** Claude Code's diagnosis of the last miss, e.g. `ttl_expired_1h` or `tools_changed`. */
|
||||
lastMissCauses?: string[];
|
||||
/**
|
||||
* Epoch MS when Codeman saw the context compacted (`/compact`, Claude Code's own idle
|
||||
* compaction, or a clearing of old tool results): the first warm report whose
|
||||
* `recacheTokensIfCold` is null after one that carried it. Carried while the null window holds,
|
||||
* dropped at the next request. Derived in `Session.setPromptCache`, not reported by the
|
||||
* statusline, so it is never set by `parsePromptCache`. Lets the readout say "Context compacted
|
||||
* at HH:MM"; it is never an alert.
|
||||
*/
|
||||
compactedAt?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this launch carries Codeman's statusLine exporter, the only source of
|
||||
* `promptCache`, and why not when it does not. Decided once per claude spawn
|
||||
* (`TmuxManager`), so a setting flipped later does not change it until the next launch.
|
||||
* - `injected`: the exporter is on the launch.
|
||||
* - `collection-off`: plan-usage collection (`showPlanUsageLimits`) was off at launch.
|
||||
* - `workspace-statusline`: the workspace's own `.claude/settings.local.json` sets a
|
||||
* statusLine (or could not be read), and Codeman never replaces one there.
|
||||
* - `remote`: remote and Docker launches never carry the exporter.
|
||||
*/
|
||||
export type StatusLineExporter = 'injected' | 'collection-off' | 'workspace-statusline' | 'remote';
|
||||
|
||||
/**
|
||||
* What the prompt-cache keep-warm (`prompt-cache-keepwarm.ts`) is doing for a session, for
|
||||
* the Session Options readout. Ephemeral: published through `toState()` and the
|
||||
* `session:keepWarm` event, never restored.
|
||||
*/
|
||||
export interface KeepWarmStatus {
|
||||
/** Effective opt-in: the session's own flag, else the app-wide default (see `keepWarmOn`). */
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Whether this launch carries `idleCompaction: false` (`MuxSession.idleCompactionOff`). False
|
||||
* on a session switched on after launch: Claude Code's own idle compaction may still run
|
||||
* until its next launch.
|
||||
*/
|
||||
idleCompactionOff: boolean;
|
||||
/** `armed` = waiting for the due time, `waiting` = due but deferred by a guard, `nudged` = sent, waiting for the next cache report. */
|
||||
state: 'off' | 'armed' | 'waiting' | 'nudged' | 'stopped';
|
||||
/** Next evaluation, epoch ms (armed, waiting, nudged). */
|
||||
nextCheckAt?: number;
|
||||
/** Why it is waiting or stopped, in words. */
|
||||
reason?: string;
|
||||
/** Nudges sent this session so far. */
|
||||
nudges: number;
|
||||
lastNudgeAt?: number;
|
||||
/** When the keep-warm window after the last real prompt closes, epoch ms. */
|
||||
budgetEndsAt?: number;
|
||||
}
|
||||
|
||||
export interface SessionState {
|
||||
/** Unique session identifier */
|
||||
id: string;
|
||||
@@ -751,6 +902,19 @@ export interface SessionState {
|
||||
status: SessionStatus;
|
||||
/** Working directory path */
|
||||
workingDir: string;
|
||||
/**
|
||||
* The directory the session's local tmux pane is in NOW (`#{pane_current_path}`),
|
||||
* when known. `workingDir` is fixed at create time and never follows a `cd`;
|
||||
* this is what a relative path printed in the terminal is relative to.
|
||||
*
|
||||
* Runtime-only: refreshed from the pane-exit watcher's read and never restored
|
||||
* from disk, so a restart starts blank. Absent for remote and docker sessions
|
||||
* (their local pane runs the ssh / docker-exec client) and for direct-PTY or
|
||||
* rediscovered sessions. NOT a confinement root: the file routes stay confined
|
||||
* to `workingDir`, and a path resolved outside it goes through the attachment
|
||||
* routes like any other out-of-workspace path.
|
||||
*/
|
||||
paneCwd?: 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 */
|
||||
@@ -770,7 +934,9 @@ export interface SessionState {
|
||||
*
|
||||
* ⚠️ 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.
|
||||
* unresolvable value is dropped rather than failing the spawn. Its one non-visual reader
|
||||
* picks a default, nothing more: a session an agent spawned does not inherit the app-wide
|
||||
* prompt-cache keep-warm default (`keepWarmOn`), though its own switch still works.
|
||||
*/
|
||||
parentSessionId?: string;
|
||||
/** ID of currently assigned task, null if none */
|
||||
@@ -799,6 +965,34 @@ export interface SessionState {
|
||||
autoResumeEnabled?: boolean;
|
||||
/** Pending usage-limit auto-resume fire time (epoch ms), if armed */
|
||||
autoResumeAt?: number;
|
||||
/**
|
||||
* Prompt-cache state of the main conversation as Claude Code last reported it on the
|
||||
* statusline (v2.1.251+), fed by `POST /api/status-telemetry`. Absent until the first
|
||||
* report. Restored on a restart (the Session constructor's `promptCache`) so the readout
|
||||
* is not blank until the pane's next render; the readout is time-aware, so a value whose
|
||||
* `expiresAt` has passed reads as cold rather than as a stale warm.
|
||||
*/
|
||||
promptCache?: PromptCacheStatus;
|
||||
/** Why a cache report will or will not arrive (see `StatusLineExporter`); absent when unknown. */
|
||||
statusLineExporter?: StatusLineExporter;
|
||||
/** Prompt-cache keep-warm opt-in; absent = follow the app-wide `promptCacheKeepWarm` default. */
|
||||
keepWarmEnabled?: boolean;
|
||||
/** What the keep-warm is doing right now; ephemeral, never restored. */
|
||||
keepWarm?: KeepWarmStatus;
|
||||
/**
|
||||
* Git fetch on an interval for this session's repositories; absent = follow the app-wide
|
||||
* `gitFetchEnabled` default. Restored on a restart, like `keepWarmEnabled`, because the choice is the
|
||||
* user's and outlives the run.
|
||||
*/
|
||||
gitFetchEnabled?: boolean;
|
||||
/** Seconds between this session's runs; absent = follow the app-wide `gitFetchIntervalSeconds` default. */
|
||||
gitFetchIntervalSeconds?: number;
|
||||
/**
|
||||
* What this session's last fetch did. Written to disk with the rest of the record, but the restore copies
|
||||
* only the fields its own config lists and this is not one of them, so it is the panel's truth for the
|
||||
* life of the session, not across a restart.
|
||||
*/
|
||||
gitFetch?: GitFetchStatus;
|
||||
/** 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 */
|
||||
@@ -836,7 +1030,8 @@ export interface SessionState {
|
||||
/**
|
||||
* 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
|
||||
* the strip-full and strip-mux-and-mouse modes, claude/codex/gemini and opencode
|
||||
* under tmux, 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.
|
||||
*/
|
||||
@@ -861,6 +1056,8 @@ export interface SessionState {
|
||||
piConfig?: PiConfig;
|
||||
/** Grok-specific configuration (only for mode === 'grok') */
|
||||
grokConfig?: GrokConfig;
|
||||
/** GitHub Copilot CLI configuration (only for mode === 'copilot') */
|
||||
copilotConfig?: CopilotConfig;
|
||||
/** DeepSeek Harness configuration (only for mode === 'deepseek') */
|
||||
deepSeekConfig?: DeepSeekConfig;
|
||||
/** OMP-specific configuration (only for mode === 'omp') */
|
||||
@@ -869,6 +1066,17 @@ export interface SessionState {
|
||||
resumeSessionId?: string;
|
||||
/** Claude CLI effort level (soft default via --settings, switchable in-session via /effort) */
|
||||
effort?: EffortLevel;
|
||||
/** Claude advisor model (`advisorModel` in the launch `--settings`, switchable in-session via /advisor) */
|
||||
advisorModel?: string;
|
||||
/**
|
||||
* The model the session was LAUNCHED with (`--model`): the caller's per-session `model`, or
|
||||
* the app-wide default when there was none. Persisted so a recovered session relaunches on
|
||||
* the same model rather than whatever the default is by then. Not `cliModel`, which is what
|
||||
* the CLI's banner reports. Claude sessions only (`cliTakesSessionModel()`): every other CLI
|
||||
* keeps its model in its own config object (`codexConfig.model` and so on), and this is
|
||||
* absent for them.
|
||||
*/
|
||||
model?: string;
|
||||
/**
|
||||
* Custom Model Endpoint Profiles (docs/custom-model-endpoints-plan.md): the custom
|
||||
* OpenAI-compatible endpoint (local or cloud) this session's CLI is currently pointed
|
||||
@@ -878,6 +1086,16 @@ export interface SessionState {
|
||||
* written) is {@link CustomModelBookkeeping}, persisted disk-only like `__envOverrides`.
|
||||
*/
|
||||
customModel?: CustomModelSelection;
|
||||
/**
|
||||
* The model this session runs, as far as the server knows it, and where that came from
|
||||
* (src/session-display-model.ts): the custom endpoint's model, else the newest report
|
||||
* from the CLI itself (statusline or its own footer), else the model its config pins,
|
||||
* else the launch model. Absent when
|
||||
* none is known; a session header then shows the harness alone. Untrusted display text
|
||||
* (pane-derived for `screen`): render it as text. Persisted, and a `statusline`/`screen`
|
||||
* value is restored after a restart until the next report replaces it.
|
||||
*/
|
||||
displayModel?: DisplayModel;
|
||||
/** Sanitized per-session attachment history. */
|
||||
attachmentHistory?: SessionAttachmentHistoryItem[];
|
||||
/**
|
||||
@@ -889,6 +1107,13 @@ export interface SessionState {
|
||||
* again until the pane's own Enter is known.
|
||||
*/
|
||||
lastSubmitAt?: number;
|
||||
/**
|
||||
* Wall-clock ms of the last submit that was NOT the prompt-cache keep-alive
|
||||
* (Session.lastRealPromptAt): the keep-warm cap's clock. Persisted so a restart
|
||||
* does not reopen the window, which `lastSubmitAt` cannot do since every nudge
|
||||
* moves it.
|
||||
*/
|
||||
lastRealPromptAt?: number;
|
||||
/**
|
||||
* Claude conversations this pane has been on, oldest first, current last.
|
||||
* Written ONLY from a first-hand `UserPromptSubmit`/`Stop` hook payload —
|
||||
|
||||
@@ -70,6 +70,7 @@ export type AttachmentDetectedType =
|
||||
| 'pdf'
|
||||
| 'document'
|
||||
| 'presentation'
|
||||
| 'spreadsheet'
|
||||
| 'markdown'
|
||||
| 'text';
|
||||
|
||||
|
||||
Reference in new issue
Block a user