Reference table of all modules in the Helm application.
| Module | File | Responsibility |
|---|---|---|
| BrowserGamepad | renderer/gamepad.ts |
Browser Gamepad API polling (250ms debounce), button-press events via IPC, analog stick events, D-pad and stick auto-repeat engine. Supports standard mapping (Xbox, buttons 12-15) and generic/DirectInput gamepads (axes-based D-pad: dual-axis pairs + hat switch). Sole gamepad input source. |
| SessionManager | src/session/manager.ts |
Track sessions, switch active, rename sessions, emit session:added/removed/changed. Calls persistence after every state change. restoreSessions() reloads saved sessions at startup (skipping duplicates). Dead processes are cleaned up via PTY exit events. |
| SessionPersistence | src/session/persistence.ts |
saveSessions(), loadSessions(), clearPersistedSessions() to config/sessions.yaml. saveDrafts()/loadDrafts() to config/drafts.yaml. Plan file I/O: savePlanFile()/loadPlanFile()/deletePlanFile()/listPlanFiles() for config/plans/*.json; saveDependencies()/loadDependencies()/cleanupOrphanDependencies() for config/plan-dependencies.json. File-level I/O used by SessionManager, DraftManager, and PlanManager for persist/restore operations. |
| PtyManager | src/session/pty-manager.ts |
PTY process lifecycle — spawn via node-pty (cmd.exe on Windows, bash on Unix), write to stdin, resize, kill. One PTY per embedded terminal session. deliverText() is the single programmatic delivery path: it frames text with buildPastePayload using the owned BracketedPasteTracker and writes the submit suffix separately after SUBMIT_SETTLE_DELAY_MS, with no IPC involved. write() marks session activity for every caller and stays outside the delivery gate so user keystrokes are never queued. |
| DeliveryLock | src/session/delivery-lock.ts |
Per-session promise-chain gate serializing the delivery transaction (nudge → payload → settle → submit). Acquired in deliverPromptSequenceToSession, the single choke point for every programmatic sender. Release is structural, so a thrown transaction neither wedges the session nor leaks into the next caller; drained sessions are pruned from the map. Deliberately excludes PtyManager.write (user keystrokes) and the delivery-verification polling window (recovery re-acquires it as a fresh acquisition — holding it across verification would deadlock). |
| BracketedPasteTracker | src/session/bracketed-paste-tracker.ts |
Per-session DEC 2004 (bracketed paste) state, scanned incrementally from PTY output (ESC[?2004h / ESC[?2004l) across chunk boundaries. Owned by PtyManager; the main process's source of truth for framing, so default pty delivery needs no renderer round trip. waitUntilEnabled() spends the shared readiness budget on a just-spawned CLI and abandons the wait if the PTY exits. Cleared on exit/kill/killAll. |
| StateDetector | src/session/state-detector.ts |
Tracks PTY I/O activity and question markers. AIAGENT phase state is not scraped from terminal text; agents update durable aiagentState through session_set_aiagent_state. Emits activity-change events with levels: active (producing output or receiving user input), inactive (>10s silence), idle (>5min silence). Five entry points: processOutput() for PTY stdout (question markers + activity), markActive() for PTY stdin (activity only, invoked from PtyManager.write for every writer, not just the pty:write IPC handler), markScrolling(sessionId) for scroll input, markResizing(sessionId) for resize input (suppresses activity promotion for 1s, called from pty:resize and pty:markSwitching IPC handlers), markRestored(sessionId) for restored sessions (suppresses activity promotion for 3s grace period, prevents shell startup output from promoting restored sessions to green). Configurable timeouts via ActivityTimeouts. getLastOutputTime(sessionId) for query. |
| PipelineQueue | src/session/pipeline-queue.ts |
Auto-handoff queue — routes tasks to waiting sessions. Handoff triggers when a session enters completed or idle state. |
| NotificationManager | src/session/notification-manager.ts |
Windows toast notifications via Electron Notification API. Triggers on activity changes: active → inactive (>10s silence) or idle (>5min silence), only when session is in implementing/planning state. Dedup guard (15s), window-focus check, settings toggle. Click → focus window + switch session. |
| MessManager | src/session/mess-manager.ts |
Project-scoped durable coordination domain: validates local project membership and same-project targets, appends ordered entries, advances bounded unread cursors, serves cursor-neutral history, and emits project append events. |
| MessPersistence | src/session/mess-persistence.ts |
Per-project JSONL log plus atomic cursor/metadata persistence under app data. Uses monotonic sequence counters, compaction recovery, retention pruning, malformed-line diagnostics, and an explicit single-writer assumption. |
| MessPersistencePaths | src/session/persistence-paths.ts |
Derives stable per-user app-data locations for Mess from immutable project UUIDs, keeping logs and cursors outside the repository and independent of project names or paths. |
| MessNotifier | src/session/mess-notifier.ts |
Best-effort one-line [HELM_MESS] reminders for visible unread entries when a running session falls quiet (inactive or idle). Handles append-while-quiet, catch-up for sessions busy at post time, the one-time join line, cooldown, retry, delivery verification, session removal, and disposal. IDLE is not readiness. |
| HandoverDelivery | src/session/handover-delivery.ts |
Holds a session's session_compact handover note across its own compaction and pastes it back on the first active → inactive edge after arming. Floor (15s) ignores a pre-compaction stall; ceiling (5min) delivers anyway when the CLI never falls silent. In-memory only; cancel and session close report the loss. See handover.md. |
| InitialPrompt | src/session/initial-prompt.ts |
Per-CLI prompt pre-loading — converts sequence parser syntax to PTY escape codes, sends to newly spawned PTY after configurable delay. Optional onComplete callback fires after all prompt items execute (used by pty-handlers to write context text after prompt finishes). |
| PatternMatcher | src/session/pattern-matcher.ts |
Core pattern engine — scans PTY output against per-CLI regex rules, deduplicates matches with per-session per-rule cooldown, executes send-text sequences immediately, and schedules wait-until sequences via TimeParser. cancelSchedule(sessionId) cancels any pending wait-until for a session; emits schedule-state changes so the UI can render the ⏰ chip. |
| TimeParser | src/utils/time-parser.ts |
Parses human-readable time strings into scheduled Date values — supports 12-hour clock (9pm, 9:30am), 24-hour clock (21:00), and relative durations (in 30 minutes, in 1 hour). Used exclusively by PatternMatcher for wait-until schedule computation. |
| SequenceParser | src/input/sequence-parser.ts |
Parses sequence format strings ({Enter}, {Ctrl+C}, {Wait 500}, {Mod Down/Up}, {{/}} escapes, plain text) into typed SequenceAction arrays. Used by both button bindings and initial prompts. |
| ConfigLoader | src/config/loader.ts |
Self-contained profile YAML loading + profile/tools/directory/bindings CRUD. Auto-migration from legacy tools.yaml/directories.yaml. StickConfig types, StickVirtualButton, getStickConfig(), getHapticFeedback(), setHapticFeedback(), getNotifications(), setNotifications(), SidebarPrefs, getSidebarPrefs(), setSidebarPrefs(), SessionGroupPrefs, getSessionGroupPrefs(), setSessionGroupPrefs(), addBookmarkedDir(), removeBookmarkedDir(). ActionType = 'keyboard' | 'voice' | 'scroll' | 'context-menu' | 'prompt-tree' | 'new-draft'. Binding union includes ContextMenuBinding, PromptTreeBinding, NewDraftBinding. PromptTreeBinding (renamed from the removed SequenceListBinding) carries no payload — it opens the global prompt-template picker tree (see PromptTemplateManager). CliTypeConfig retains optional sequences?: Record<string, SequenceListItem[]> (read-only migration input only — folded into the global prompt-template tree on first load; no longer surfaced in the UI), optional handoffCommand for auto-handoff pipeline, optional spawnCommand for fresh spawn with session UUID, renameCommand, resumeCommand, continueCommand for session resume. getSequences(cliType) still reads legacy sequence groups for migration. copyCliBindings() deep-copies both bindings and sequences from source to target CLI type. addCliType() and updateCliType() accept an optional options param ({ handoffCommand?, renameCommand?, spawnCommand?, resumeCommand?, continueCommand? }). updateCliType() merges rather than replacing — preserves existing optional fields like sequences; within options, undefined = preserve existing, empty string = clear, value = set. ChipbarAction { label: string; sequence: string } interface + chipActions?: ChipbarAction[] on ProfileConfig (profile root, not per-CLI-type). getChipbarActions() returns { actions, inboxDir } for the active profile; inboxDir is config/plans/incoming/ resolved at call time. |
| CliTypeMigration | src/config/cli-type-migration.ts |
One-time, idempotent migration of pre-UUID CLI types. Mints a UUID id per slug-keyed entry, sets displayName from the old name, records legacyKey, then rekeys bindings.yaml, persisted sessions, recycle-bin entries and scheduled tasks (plus history) from slug to UUID. Staged write → verify → swap; a failure leaves the originals untouched. See config-system.md. |
| CliTypeStore | src/config/cli-type-store.ts |
cli-types.yaml persistence, keyed by CLI type UUID. resolve(ref) is the single resolution implementation — uuid id → legacyKey slug → displayName (trimmed, case-insensitive) — surfaced as ConfigLoader.resolveCliType. An ambiguous displayName throws AmbiguousCliTypeError naming the conflicting ids rather than picking one. |
| TelegramCliLabel | src/telegram/cli-label.ts |
cliLabel(cliType) for Telegram text. The notifier, topic manager and keyboard builders hold no ConfigLoader, so the resolver is injected once via setCliLabelResolver in initTelegramModules. Guarantees a human label, never a UUID. |
| ElectronMain | src/electron/main.ts |
Window creation, IPC setup, app lifecycle. Renderer crash recovery (auto-reloads on render-process-gone — safe because session state lives in main process). Delegates power monitoring to setupPowerMonitor(). |
| PowerMonitor | src/session/power-monitor.ts |
Logs detailed session/PTY diagnostics on suspend/resume/shutdown via Electron powerMonitor. Reports session counts, PTY IDs, and PTY survival status on resume. Called from main.ts with sessionManager + ptyManager. |
| IPC Handlers | src/electron/ipc/*.ts |
Orchestrator + domain handler files (session, config, profile, tools, keyboard, pty, system, telegram, mess) plus draft, plan, prompt-template, and other feature handlers. The Mess handler exposes cursor-neutral mess:history and project-scoped mess:appended; dependencies are injected and cleanup removes the owned listeners. |
| Renderer | renderer/*.ts |
Modular UI: Vue entry point, shared state, utilities, bindings, paste delivery, navigation, screens, modals, drafts, planner, sidebar, and terminal modules. Browser Gamepad API drives button navigation. Session cards show elapsed timer since last CLI output (formatElapsed() in sessions.ts, driven by lastOutputAt from pty:activity-change, refreshed every 10s). Session list shows embedded terminals only. D-pad navigation auto-selects terminals. |
| TerminalView | renderer/terminal/terminal-view.ts |
xterm.js wrapper — one Terminal instance per session with fit/search/weblinks addons (scrollback: 10,000 lines). Forwards user input, resize, and title-change events via callbacks. Optional onScrollInput callback for scroll-specific PTY writes. Unified scroll(direction, lines) method handles both buffer modes: alternate buffer → PageUp/PageDown escape sequences to PTY via onScrollInput (falls back to onData); normal buffer → scrollLines() directly (bypasses SmoothScrollableElement — xterm.js #5620). Capture-phase wheel handler and gamepad scroll bindings both delegate to scroll(). Selection API: getSelection(), hasSelection(), clearSelection(). |
| TerminalManager | renderer/terminal/terminal-manager.ts |
Multi-terminal orchestrator — create, switch, resize, rename, PTY IPC data routing, cleanup. Exposes onSwitch/onEmpty callbacks. getActiveView() returns current TerminalView. deselect() nulls activeSessionId without destroying the terminal — stops keyboard relay, used by group overview to pause input during preview grid. renameSession() updates the display name persisted across UI reloads. Right-click contextmenu listener on terminal area shows context menu overlay. Capture-phase mousedown listener on terminal elements blocks right-click (button 2) from reaching xterm.js paste handling via stopPropagation(). createTerminal() accepts optional contextText forwarded through ptySpawn() to the main process. adoptTerminal(sessionId, cliType, cwd?) creates a TerminalView for an externally-spawned PTY session (e.g. Telegram bot) without calling pty:spawn — wires up data/resize/title IPC but does not auto-switch to the new terminal. switchTo() calls pty:markSwitching IPC before fit() to suppress false activity promotion during terminal switching. writeToTerminal() writes PTY output directly to xterm.js (no filtering by default). Owns a PtyOutputBuffer instance — feeds all PTY data to the ring buffer for preview display; exposes via getOutputBuffer(). setOnTitleChange() routes OSC title events to renderer state. |
| PtyOutputBuffer | renderer/terminal/pty-output-buffer.ts |
Ring buffer storing last N lines (default 50) per session as ANSI-stripped plain text. append() strips ANSI sequences, splits on newlines, handles \r overwrites, trims to max lines, and notifies update callbacks. getLastLines(sessionId, count) returns the most recent lines (including any partial line). onUpdate()/offUpdate() for live subscription. Used by GroupOverview for preview content. |
| PtyFilter | renderer/terminal/pty-filter.ts |
Optionally strips alternate-screen ANSI escape sequences from PTY output. applyPtyFilters(data, opts?) — conditionally strips alt screen modes (47/1047/1048/1049) and ED 3 (\x1b[3J) when stripAltScreen option is true. ED 2 (\x1b[2J) is intentionally preserved — xterm.js pushes viewport content into scrollback on ED 2, enabling scrollbar for full-screen TUI CLIs. stripAltScreen() convenience wrapper. Fast-path skips regex when no escape sequences present. Mouse tracking sequences pass through to xterm.js for native handling. |
| GroupOverview | renderer/screens/group-overview.ts |
Single-column scrollable session preview grid with two modes: global (all eye-visible sessions across all folders, with folder break marks, activated by the dock Overview pane) and group (single-directory sessions, activated by D-pad Right on a group header). Live PTY output previews, terminal deselect while open. showOverview(groupDirPath, initialSessionId?) — pass null for global mode. isOverviewVisible() / refreshOverview() / hideOverview(). handleOverviewInput(button) — handles Left/B (dismiss), A (select), X (collapse), Right (no-op); returns false for Up/Down so callers fall through to sidebar navigation. selectOverviewCard(sessionId) — exits overview and switches to session; sets selectedOnExit flag to suppress session restore on unmount. setSelectCardCallback(fn) — wired from useAppBootstrap.ts (not the dead navigation.ts). setOverviewDismissCallback(fn) — fired on non-selection dismiss (B/Left); used by useAppBootstrap.ts to call updateSessionsFocus() for sidebar scroll sync. Dependency-injected PtyOutputBuffer and session state getter to avoid circular imports. See docs/group-overview.md for full documentation. |
| SessionGroups | renderer/session-groups.ts |
Pure grouping logic — groups sessions by working directory. Types (SessionGroup with displayName: string (resolved custom name, formerly dirName), NavItem with NavItemType = 'group-header' | 'session-card', SessionGroupPrefs) and functions (groupSessionsByDirectory, buildFlatNavList, moveGroupUp/Down, toggleCollapse, findNavIndexBySessionId, resolveGroupDisplayName(dirPath, directories) — resolves the configured custom display name for a directory path, used by group headers, plan screen title, and group overview break marks). SessionGroupPrefs includes optional bookmarked?: string[] (bookmarked directories appear as empty groups) and optional overviewHidden?: string[] (stable session keys hidden from the global overview — falls back to session id). getVisibleSessions(), isSessionHiddenFromOverview(), getSessionOverviewKey() for eye-toggle support. Group order + collapse state persisted in settings.yaml. |
| SortLogic | renderer/sort-logic.ts |
Pure sort functions for sessions (by state priority + alphabetical) and bindings. No side effects — easy to test. |
| TabCycling | renderer/tab-cycling.ts |
Resolves next/previous terminal for Ctrl+Tab cycling using sorted display order so tab switching matches what the user sees. |
| SortControl | renderer/components/sort-control.ts |
Reusable sort control widget — dropdown for field selection + direction toggle button. |
| ScheduledTaskHistoryManager | src/session/scheduled-task-history-manager.ts |
Rolling 7-day run log for scheduled tasks. EventEmitter — append(entry) assigns a UUID id, prunes entries older than HISTORY_WINDOW_MS (7 days), saves, and emits history:changed; list() returns newest-first; clear() empties. Injectable clock for testable pruning. Persisted via scheduled-task-history-persistence.ts to config/scheduled-task-history.yaml (defensive stale-filter on load). ScheduledTaskManager injects it and records a setup-snapshot (NO stdout) at each run-completion point with outcome done/failed/cancelled. |
| DraftManager | src/session/draft-manager.ts |
Per-session draft prompt CRUD (create/update/delete/get/count). EventEmitter, emits draft:changed. Persisted to config/drafts.yaml via SessionPersistence. |
| RuntimeGroupManager | src/session/runtime-group-manager.ts |
Custom cross-directory session groups with exclusive (one-group-max) membership. EventEmitter (runtime-group:changed), injected persist + injectable clock. create/rename/setCollapsed/addSession (evicts from any prior group) /removeSessionEverywhere/closeGroup/ensureGroup/groupForSession/list/exportAll/importAll. Does not load from disk itself (orchestrator hydrates via importAll(loadRuntimeGroups())). Persistence: runtime-group-persistence.ts → config/runtime-groups.yaml ({ groups }). Restore helper: runtime-group-restore.ts reattachRestoredSession() re-adds a restored session to its group, recreating it by id+name if closed. IPC: runtime-group-handlers.ts (8 channels). |
| useRuntimeGroups / useRuntimeGroupActions | renderer/composables/useRuntimeGroups.ts, useRuntimeGroupActions.ts |
Module-singleton reactive live groups (subscribes to runtime-group:changed) + the create/move/close/remove action flows shared by the split button, headers, and context menu. Close-all uses close-group-plan.ts (buildCloseGroupPlan) — closes members via the canonical sessionClose first (tags recycle-bin entries) then removes the group. Drag rules: renderer/runtime-group-drop.ts dropVerdict. Drag state: useSessionDrag.ts. Modals: RuntimeGroupNameModal.vue, RuntimeGroupCloseDialog.vue, RuntimeGroupMoveSubmenu.vue. |
| peer-crypto | src/mcp/peer/peer-crypto.ts |
Pure crypto primitives for the peer handshake: stable machine identity (RSA-2048) + self-signed cert (RSA-2048/SHA-256, persisted), PSK/nonce generation, certFingerprint, canonical length-prefixed handshake transcript, and per-role PSK-keyed HMAC (computeHandshakeMac/verifyHandshakeMac). No sockets. |
| pairing-crypto | src/mcp/peer/pairing-crypto.ts |
Pure crypto for SAS pairing: ephemeral X25519 keygen, commit-then-reveal commitment, canonical pairing transcript, and three HKDF derivations from the ECDH shared secret — the 6-digit SAS (user compares), confirm-MAC key, final PSK. No sockets/persistence. |
| PinnedCertStore | src/mcp/peer/pinned-cert-store.ts |
TOFU cert-fingerprint pin store, keyed by peerId. recordIfAbsent (write-once), constant-time verify, removePin (only way a pin changes — user unpair). Hard-rejects a changed fingerprint (MITM). Injected persist; EventEmitter (peer-pins:changed). |
| SecretStore | src/mcp/peer/secret-store.ts |
The ONLY home for raw PSK bytes, keyed by pskRef, stored base64. set/get/has/remove/exportAll/importAll. Never logs/echoes secret values; rejects non-canonical base64. Injected persist; EventEmitter (peer-secrets:changed). |
| RemoteLinkServer | src/mcp/peer/remote-link-server.ts |
Inbound mTLS-WebSocket listener (default :47474, separate from the localhost MCP server). Per connection: channel-binding via RFC-5705 exporter → peer cert fp → responder PSK handshake → TOFU pin (record/verify) → build PeerLink. Never leaks the reject reason. |
| RemoteLinkClient | src/mcp/peer/remote-link-client.ts |
Outbound dialer with a reconnecting generation state machine. Pin-verifies the server cert before the WS upgrade, runs the initiator PSK handshake, then builds a PeerLink. Injectable timeouts/backoff/rng; backoff resets after stability. |
| remote-link-handshake | src/mcp/peer/remote-link-handshake.ts |
The wire framing for the initiator/responder PSK handshake (runInitiatorHandshake/runResponderHandshake) — exchanges nonces + role-labelled MACs bound to the TLS channel binding, returns the proven peerMachineId. |
| PeerLink | src/mcp/peer/peer-link.ts |
JSON-RPC 2.0 multiplexer over an already-authenticated ws socket. Frames/correlates/times-out requests, heartbeat ping/pong, strict inbound discrimination, max 256 in-flight, idempotent dispose. Owns NO trust decisions. |
| PeerLinkManager | src/mcp/peer/peer-link-manager.ts |
Orchestrates transport for every peer: ONE RemoteLinkServer + ONE RemoteLinkClient per outbound/bidirectional peer, keeping exactly one authenticated link per peer (dedup by min(machineId)). start/stop/addPeer/disposePeer/status/list/call. Injectable server/client factories. |
| InboundCallGate | src/mcp/peer/inbound-call-gate.ts |
The onCall security boundary for inbound peer calls: hard-deny set → per-peer allow-list → rate-limit → dispatch through the UNCHANGED callMcpTool under a proxy identity (caller-identity args stripped) → audit every outcome. Uniform non-leaky deny message. Answers the reserved __peer_tools__ discovery method in-gate. |
| proxy-identity | src/mcp/peer/proxy-identity.ts |
Synthesizes the peer:<peerId> AuthContext a remote peer's calls run under — deterministic, never a real UUID session (isProxySessionId guard). Prevents impersonation of local sessions. |
| PeerRateLimiter | src/mcp/peer/rate-limiter.ts |
Per-peer token-bucket limiter (lazy bucket per peer, injectable clock). createDefaultPeerRateLimiter bakes in ~30 req/min with a burst of 30. |
| PeerAuditLog | src/mcp/peer/peer-audit-log.ts |
Rolling 7-day trail of every inbound-call decision (ok/denied/rate-limited/error). Stores method + argument KEY NAMES only — never values or secrets. Mirrors ScheduledTaskHistoryManager; injectable persist/clock. Persistence: peer-audit-persistence.ts → config/peer-audit.yaml. |
| PeerDiscovery | src/mcp/peer/peer-discovery.ts |
LAN presence via mDNS (_helm._tcp) — advertises + browses, self-filters own machineId. Injected bonjour backend. Reveals only presence (machineId/alias/address); grants NO access. |
| PeerPairing | src/mcp/peer/peer-pairing.ts |
Per-attempt SAS numeric-comparison pairing state machine (commit → reveal → derive → SAS → accept → finalize/abort). Channel-agnostic (injected channel.send). On success atomically persists pin + PSK + bidirectional PeerConfig (all-or-nothing rollback). |
| PairingCoordinator | src/mcp/peer/pairing-coordinator.ts |
Global UX/safety gatekeeper around PeerPairing: exactly one active session, 180s expiry, one accept/reject per session, and rate caps (3 fails/source/10min → 15min cooldown; PLUS a global 10-starts/10-min backstop). Injected pairing factory + clock. |
| fleet-startup | src/mcp/peer/fleet-startup.ts |
Thin glue that stands up the PeerLinkManager only when fleet is enabled in settings (else returns null, binds no port). Builds identity/cert, hydrates pin + secret stores, wires the onCall sink. |
| HelmPeerService | src/mcp/services/helm-peer-service.ts |
Local surface behind peer_list/peer_tools/peer_call. Delegates to PeerLinkManager (list/call); tools uses the reserved discovery method. call trims + client-side rejects empty/peer_*/sentinel/hard-deny tools. Throws "Fleet is not enabled" when the manager is absent (OFF). |
| PeerConfigManager | src/session/peer-config-manager.ts |
In-memory peer registry (id, alias, address, pskRef, allow glob list, direction, machineId, enabled). Pure model + authorization: isToolAllowed (deny-by-default glob match), add/upsertByMachineId/update/remove/get*/list/exportAll/importAll. Never holds secret material. Injected persist + clock. |
| PeersTab / PeerPairingDialog / PeerAuditModal / usePeers | renderer/components/sidebar/PeersTab.vue, renderer/components/modals/PeerPairingDialog.vue, renderer/components/modals/PeerAuditModal.vue, renderer/composables/usePeers.ts |
Peers settings tab (list/enable/allow-list/unpair) + SAS pair-by-code dialog (6-digit compare) + 7-day audit viewer. usePeers is the module-singleton reactive state bridging the IPC peer surface to the UI. |
| DraftStrip | renderer/drafts/draft-strip.ts |
Draft strip above terminal. Shows draft labels as pills (click opens editor directly), badge count on session cards, plan chips via renderPlanChips(), and right-aligned chip-bar action buttons via renderActionButtons(). invalidateChipActionCache() clears the per-page-load actions cache (forcing re-fetch from configGetChipbarActions IPC on next strip render). resolveTemplates() substitutes {cwd}, {cliType}, {sessionName}, {plansDir}, and {inboxDir} in action sequences at click time using the active session context; both path variables resolve to config/plans/incoming/. |
| DraftEditor | renderer/drafts/draft-editor.ts |
Slide-down editor panel for composing/editing drafts. Title + content fields, Save/Apply/Delete/Cancel buttons, smart keyboard routing. |
| DraftSubmenu | renderer/modals/draft-submenu.ts |
Drafts submenu from context menu. New Draft + per-draft Apply/Edit/Delete action picker. |
| PlanManager | src/session/plan-manager.ts |
Per-directory acyclic directed graph of work items. CRUD operations, dependency management with cycle prevention (DFS), ready-state computation. Items stored in Map<id, PlanItem>, deps as flat PlanDependency[] array. EventEmitter — emits plan:changed on every mutation. Self-saves to individual JSON files in config/plans/ and dependency edges in config/plan-dependencies.json on every mutation via persistence functions. Current lifecycle is planning/ready/coding/review/blocked/done; legacy IPC names still expose startable/doing counts for UI compatibility. Validates: no self-loops, no cross-directory deps, no duplicate edges, no cycles. exportAll() used by plan:deps IPC handler. |
| PlanLayout | renderer/plans/plan-layout.ts |
Sugiyama-style left-to-right layered auto-layout for plan DAGs. Pipeline: topological sort (Kahn's algorithm) → layer assignment (longest path from roots) → within-layer ordering (barycenter heuristic, 2-pass) → coordinate assignment. Exports computeLayout(items, deps, options?) → LayoutResult { nodes, width, height }. Configurable spacing via LayoutOptions. Handles disconnected components. |
| PlanScreen | renderer/plans/plan-screen.ts |
SVG canvas screen for plan DAG visualisation. Renders inside #mainArea as .plan-screen overlay. ViewBox-based pan (mouse drag) and zoom (mouse wheel). Nodes rendered as SVG <g> with status-colored borders, status dots, title/description text, and right-edge connectors. Dependency arrows as quadratic bezier <path> with #arrowhead markers. Click-to-select nodes opens bottom editor. Add Node button in header. Keyboard shortcuts: Ctrl+N (add node), Escape (close screen, gated on editor not visible). Gamepad D-pad navigation: left/right moves between layers, up/down moves within a layer (closest-Y selection across layers). Action buttons: A (open editor for selected node), X (delete selected node), Y (add new node), B (exit). Auto-selects first node (layer 0, order 0) on open. showPlanScreen(dirPath)/hidePlanScreen()/isPlanScreenVisible()/handlePlanScreenDpad(dir)/handlePlanScreenAction(button)/getSelectedPlanId() public API. setPlanScreenFitCallback(fn) re-fits the active terminal when the plan screen closes. |
| PlanChips | renderer/plans/plan-chips.ts |
Plan badges on session cards (createPlanBadge(codingCount, readyCount, blockedCount=0, reviewCount=0)) and plan chips in draft strip (renderPlanChips(sessionId) — uses generation counter to prevent stale async renders from appending duplicate chips). Coding chips (green) and review chips (purple) show map + count. Ready chips (blue) show map + count. Chip clicks: ready -> send description to PTY + transition to coding, coding/review -> re-send description to PTY (no status change). Updates plan count caches in sessions state. |
| SessionsPlans | renderer/screens/sessions-plans.ts |
Folder planner grid service for the 3rd navigation zone below spawn. handlePlansZone(button, dir) handles 2-column D-pad navigation. handlePlansZoneButton(button) handles A (open plan screen) and B (back to sessions zone). updatePlansFocus() keeps the focused Vue-rendered row visible/highlighted. refreshPlanBadges() populates per-dir count maps for ready, coding, blocked, review, and planning. |
| EditorPopup | renderer/components/modals/EditorPopup.vue (bridged via renderer/editor/editor-popup.ts) |
In-app Prompt Editor modal — the target of Ctrl+G and the prompt-template apply flow. Multi-line textarea + last-10 recent-prompts list + a PromptManagementTree pane for browsing/managing the global prompt-template library. When opened from the apply flow it is PREFILLED with the chosen template body (caret at end). Submit via Ctrl+Enter or Send — delivers via deliverPromptSequence(). showEditorPopup(onSubmit, initialText?, templateId?, hasPrefill?) resolves once the editor closes; hasPrefill=true forces the prefill to override any saved Ctrl+G draft. |
| EditorHistory | renderer/editor/editor-history.ts |
Persistent storage for Ctrl+G prompt history (max 10 entries). Primary: IPC editorGetHistory/editorSetHistory channels. Fallback: localStorage key gamepad-cli-editor-history. loadEditorHistory(), saveEditorHistory(), addEditorHistoryEntry(), getEditorHistoryPreview() (first line, truncated). |
| HelmControlService | src/mcp/helm-control-service.ts |
Compatibility facade for MCP domain services. Delegates plan/session/context/sequence/attachment/Telegram/scheduler/project/directory behavior to src/mcp/services/*, exposes the stable method names used by MCP dispatch, and makes optional scheduler/project service absence explicit. |
| LocalhostMcpServer | src/mcp/localhost-mcp-server.ts |
JSON-RPC 2.0 HTTP MCP server bound to 127.0.0.1. Owns HTTP transport, auth, JSON-RPC framing, initialize/tools/list/tools/call handling, and structured-content wrapping. Tool definitions, reminders, validation, and dispatch live in src/mcp/tools/*. Bearer-token auth uses timing-safe comparison, plus Helm session-token decoding so trusted Helm-spawned CLIs can infer sender identity for session_send_text. Configurable via McpConfig (enabled, port, authToken). |
| SettingsPanel | renderer/components/sidebar/SettingsPanel.vue |
Vue-owned settings shell. Hosts tools, directories, quick actions, Telegram, MCP, profiles, and CLI bindings tabs via sidebar components and emits tab/close events back to useSettingsController(). |
| — | — | Vue Layer (renderer/stores/, composables/, components/) |
| AppStore | renderer/stores/app.ts |
Pinia store wrapping state.ts reactive singleton — currentScreen, gamepadCount, eventLog, activeProfile. |
| SessionsScreenStore | renderer/stores/sessions-screen.ts |
Pinia store wrapping sessions-state.ts — zone, focusIndex, cardColumn, overviewGroup, plansFocusIndex. |
| ConfigStore | renderer/stores/config.ts |
Pinia store — CLI types, bindings, sequences, tools caches. |
| DraftsStore | renderer/stores/drafts.ts |
Pinia store — draft counts, active draft, editor visibility. |
| PlansStore | renderer/stores/plans.ts |
Pinia store — plan counts per directory for ready, coding, blocked, review, and planning. |
| NavigationStore | renderer/stores/navigation.ts |
Pinia store — centralized view routing (terminal/overview/plan), active session switching, identity-based sidebar focus, overlay open/close lifecycle with restore context. Sole write authority for panel view + sidebar focus. |
| ChipBarStore | renderer/stores/chip-bar.ts |
Pinia store — chip bar action state and refresh for the active session. |
| useHandover / PendingHandoverModal | renderer/composables/useHandover.ts, renderer/components/modals/PendingHandoverModal.vue |
Reactive mirror of pending compaction handovers + the terminal lock shown while one waits. The lock is mechanical: keystrokes are PTY output and reset the silence timer the delivery waits on. Claims the keyboard only while the pending session's terminal is focused. See handover.md. |
| useModalStack | renderer/composables/useModalStack.ts |
Reactive push/pop modal stack replacing 11-deep if-chain. Module-level singleton shared across all callers. |
| useIpc | renderer/composables/useIpc.ts |
Typed IPC wrappers with auto-cleanup on onUnmounted. |
| useGamepad | renderer/composables/useGamepad.ts |
Gamepad polling setup + connection events. |
| usePanelResize | renderer/composables/usePanelResize.ts |
Splitter drag resize via Vue template refs + lifecycle hooks. |
| useKeyboardRelay | renderer/composables/useKeyboardRelay.ts |
Ctrl+V → PTY, Ctrl+G → Prompt Editor document-level intercepts. |
| usePromptApplyFlow | renderer/composables/usePromptApplyFlow.ts |
Single prompt-template apply path shared by the main + popout windows: open PromptTreeModal picker → on pick, open the Prompt Editor PREFILLED with the template body (caret at end) → Ctrl+Enter delivers via deliverPromptSequence(). Picking never sends directly. |
| useTerminals | renderer/composables/useTerminals.ts |
Terminal create/switch/destroy lifecycle composable. |
| useNavigation | renderer/composables/useNavigation.ts |
Navigation routing: sandwich → modal stack → view → screen → config binding fallback. |
| useMessPane | renderer/composables/useMessPane.ts |
Project-following reactive Mess history: generation-guarded loads, append subscription, live session-label joins, sender/broadcast/unread filters, bounded backscroll, and scroll-following. |
| MessPane | renderer/components/dock/MessPane.vue |
Read-only project conversation observer with time/address/body rows, closed-session labels, undelivered hints, filters, and cursor-neutral Older control. |
| Vue Modal SFCs | renderer/components/modals/*.vue |
Modal Vue components (CloseConfirm, PlanDeleteConfirm, PromptTreeModal — prompt-template picker tree, EditorPopup — Prompt Editor, QuickSpawn, DirPicker, ContextMenu, DraftSubmenu, FormModal, BindingEditor) — Teleport + modal stack. |
| PromptManagementTree | renderer/components/panels/PromptManagementTree.vue |
Left-pane tree inside the Prompt Editor for browsing + managing the global prompt-template library (folders + template leaves) backed by PromptTemplateManager via the prompt-template:* IPC channels. |
| Vue Sidebar SFCs | renderer/components/sidebar/*.vue |
Sidebar and settings components (SessionCard, SessionGroup, SpawnGrid, SortBar, PlansGrid, StatusStrip, SettingsPanel, ProfilesTab, BindingsTab, ToolsTab, ChipbarActionsTab, TelegramTab, McpTab, ScheduledTasksTab, ScheduledTaskHistoryModal — Past Schedules modal listing the 7-day run log grouped by day with outcome badges + "Recreate as new"). |
| Vue Panel SFCs | renderer/components/panels/*.vue |
6 right-panel components (TerminalPane, OverviewCard, OverviewGrid, PlanScreen, MainView, ChipBar). |