Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@ yarn-error.log*
/docs/marketing/
/docs/tasks/
/.gradle-user-home-fix/
/.intellijPlatform/
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **`sleep` tool** — Pause agent execution for up to 30 seconds; useful for rate limiting or waiting on external processes.
- **`ask_user` tool** — Agent can ask the user a question mid-execution and wait for a response; supports free-text and predefined options. Backed by `UserQuestionService` with CompletableDeferred pattern (same as ToolApprovalService).
- **`web_search` tool** — Search the web via Brave Search, SerpAPI, or DuckDuckGo Instant Answers; configurable provider and API key in `config.yaml`.
- **`fetch_webpage` tool** — Fetch a URL, convert HTML to Markdown (via Jsoup), then process with the weak LLM model using a custom prompt; for extracting specific information from web pages.
- **`run_process_background` tool** — Start a shell command in the background and return immediately with a `process_id`; for long-running builds or dev servers.
- **`monitor_process` tool** — Read stdout from a background process started with `run_process_background`; call repeatedly to stream output.
- **`code_intelligence` tool** — Analyze code structure without IDE: find symbol usages (grep), find definitions (ctags/grep), list symbols (ctags), run compiler diagnostics; works in CLI and plugin.
- `ProcessManager` service for managing long-running background processes with start/stop/monitor lifecycle.
- `UserQuestionService` for suspending the agent loop while waiting for user answers.
- `CommandLimits` helper for centralized terminal command output/size limits.
- DB migration `V3RenameSlashCommandToSlashPrompt` that remaps existing `prompts.type='SLASH_COMMAND'` rows to `SLASH_PROMPT` in-place; preserves user-defined and built-in entries.
- **Multi-agent orchestration pipeline** — dedicated `core/agents/orchestration/` package with `OrchestrationDispatcher`, `TaskDecomposer`, and `ResultMerger`. Supports three strategies: `ORCHESTRATOR` (single turn with `multi-agent-coordinator` subagent dynamically spawning via `invoke_subagent`), `PARALLEL` (independent specs executed concurrently via `MultiAgentRunner`), and `PIPELINE` (linear chain using `{{prev.output}}` placeholder between stages).
- **`CoreSessionService` in `:core`** — session lifecycle, state, and dispatch moved out of the IntelliJ plugin. Backed by `CoreSessionServiceFactory` and `DefaultWorkflowStreamingListener`. The IntelliJ `SessionManager` is now a thin adapter (886 → 704 LOC).
- **Reusable tool-call listeners** — `AbstractToolCallLifecycleListener` base plus `CoreMessageToolCallListener` (core persistence) and `TuiToolCallListener` (TUI rendering); eliminates duplicated tool-call plumbing between plugin and CLI.
- `BuiltinSubagentOverrides` — user-level override mechanism for built-in subagent definitions without forking the Markdown source.
- `example-config.yaml` shipped under `core/src/main/resources/config/` as a reference for the new typed YAML schema.
- `OpenAICompatibleAdapter` base class consolidating shared `/v1/chat/completions` protocol, SSE streaming, and per-provider quirks for all OpenAI-compatible backends.
- Test fixtures `SubtaskResponseFixtures` and `TaskResponseFixtures` plus new adapter characterization tests (`OpenAICompatibleBaseTest`, `GenericOpenAIAdapterMalformedTest`).

### Changed

- **Renamed feature "Slash Commands" → "Prompts"** across code and UI. The `/name` invocation syntax is unchanged; these are prompt templates (not plugin/CLI commands), so everything now reflects that:
- API model `SlashCommand` → `SlashPrompt` (`core/src/main/kotlin/pl/jclab/refio/api/models/SlashPrompt.kt`)
- DB enum `PromptType.SLASH_COMMAND` → `SLASH_PROMPT` (migrated automatically via V3)
- Router API: `getEnabledCommands`/`findCommand`/`saveCommand` → `getEnabledSlashPrompts`/`findSlashPrompt`/`saveSlashPrompt`; request model `SaveCommandRequest` → `SaveSlashPromptRequest`
- IntelliJ dialog `CommandEditDialog` → `PromptEditDialog`; intention action `RefioSlashCommandIntentionAction` → `RefioSlashPromptIntentionAction`
- Settings UI: tab label `Commands` → `Prompts`, column `Command` → `Prompt`
- Chat input placeholder now reads `(@context, /prompt, !subagent)`
- CLI/TUI methods `getSlashCommands`/`processSlashCommands` → `getSlashPrompts`/`processSlashPrompts`
- **CommandWhitelist system replaced by unified `CommandRule`** (regex-based `ALLOW` / `BLOCK` / `ASK`). The legacy `CommandWhitelist`, `CommandWhitelistConfig`, `CommandWhitelistDefaults`, and `CommandDenylist` classes were removed; default ruleset is expanded in `CommandRuleDefaults`. Terminal command decisions are now a single-pass regex evaluation with a catch-all `ASK` rule.
- **Models settings tab no longer auto-refreshes from providers on open.** The panel now reads from the in-memory model cache (or DB visibility settings) and never blocks the UI on slow/unreachable providers. Remote model discovery is triggered only by the **Refresh** button. New `fetchIfMissing: Boolean` parameter was added to `ModelRegistry.getAllModels` / `ConfigRouter.getModelsWithVisibility` / `CoreApiClient.getModelsWithVisibility` (defaults preserve previous behavior for other callers). `ModelRegistry.getCachedModelsSnapshot()` exposes the cache without triggering fetches.
- **Shorter `listModels` timeout for local providers.** Ollama and LM Studio now use a 3-second per-provider timeout when listing models (down from 15s) since they are localhost services and 15s just extended UI freezes when the daemon wasn't running. Cloud providers keep 15s.
- **Tools settings tab layout is now responsive.** Fixed column widths (180/320/90/90) were replaced with flexible bounds: `Plan Mode` and `Agent Mode` columns are clamped to 80–110 px, while Tool Name and Description stretch with the panel. The Terminal Command Rules table uses the same flexible layout. Removes the forced 700 px horizontal overflow that cropped column headers on narrow sidebars.
- Built-in system-agent prompt (`system-agent.md`) refreshed.
- `ToolDescriptionBuilder` adjusted for the new tool set and prompt-template renames.
- **`CoreApiRouter` modularized from ~987 → ~300 LOC.** Composition-root concerns split into dedicated modules under `core/api/modules/`: `CoreApiRouterBootstrap` (system tool registration, Ollama concurrency, core init), `DomainRouters` (12 domain router wiring), `AgentExecutionModule`, `ChatPlanningModule`, `PersistenceModule`, `ProjectRouterFactory`, `SupportServicesModule`, `EmbeddingProviderFactory`, `AnalysisStack`, and `AgentTurnLoopFactory`. `CoreApiRouter` is now a readable composition root with no business logic.
- **`ConfigService` split (2175 → 290 LOC, –87%).** Responsibilities extracted to focused services: `ModelSelectionService` (logical-slot resolution, model visibility), `ConfigResolver` (precedence-aware reads), `ConfigValidator`, `ConfigDefaultsInitializer`, `ConfigYamlApplier` (DB ← YAML application), and `ContextBudgetResolver`. Public API remains stable via `ConfigService` delegation.
- **`ConfigYaml` redesigned around a typed schema.** The 1300+ LOC monolithic loader/emitter replaced by `ConfigYamlModel` (`kotlinx.serialization` data classes with typed sections), `ConfigYamlEmitter`, `ConfigYamlIO`, `ConfigYamlMerger`, and `YamlSanitizer`. `ConfigYaml.kt` shrank to 76 LOC. Unknown keys now fail loud (aligned with the no-fallbacks policy).
- **OpenAI-compatible adapter consolidation.** Per-provider classes now extend the new `OpenAICompatibleAdapter` base:
- `GenericOpenAIAdapter` 547 → 189 LOC
- `LMStudioAdapter` 461 → 108 LOC
- `OpenRouterAdapter` 869 → 180 LOC (keeps its `data:`-prefix/mid-stream-error quirks)
- `ZAIAdapter` reduced to 23 LOC with endpoint resolution delegated to the new `ZAIUrls` helper
- `OpenAICompatibleHelpers` gained `consumeChatCompletionsSSE`, `buildMessages`, `addCommonKwargs`, and `resolveEffectiveMaxTokens` used across the family.
- **Coverage gate moved to `:core`.** `:core:jacocoTestCoverageVerification` enforces a 35% instructions minimum and is wired into `:core:check`. `:intellij-plugin:jacocoTestReport` still produces HTML/XML but no longer enforces a threshold (tests live in `:core`).
- **Session layer migrated from `:intellij-plugin` to `:core`.** `SessionStateManager`, `SessionLifecycleService`, `MessageDispatcher`, `SubtaskTracker`, `PromptStateTracker`, and `ExecutionMonitor` live in `core/session/` and are reused by both the IntelliJ plugin and the TUI.
- **TUI state layer simplified.** `TuiViewModel`, `TuiSessionViewModel`, `TuiChatViewModel`, `TuiState`, `TuiStepsView`, and `TuiWorkflowListener` aligned with the new core session service; silent subtask-operation fallbacks removed; shared factory companions (`TuiSessionEntry.fromTaskResponse`, `TuiSubtask.fromSubtaskResponse`) eliminate inline DTO mapping.
- Settings panels (`AdvancedSettingsPanel`, `ContextSettingsPanel`, `GeneralSettingsPanel`, `ModelsSettingsPanel`, `ProvidersSettingsPanel`, `SettingsView`) rewritten against the typed config model.
- `AgentTurnLoop` + `TurnLLMCaller`/`TurnToolExecutor`/`TurnFinalizer`/`TurnEventListener`/`TurnPromptBuilder`/`TurnPrompt` updated for listener-based tool-call lifecycle, cleaner prompt assembly, and richer event propagation.

### Removed

- `core/src/main/kotlin/pl/jclab/refio/api/models/TaskPlanModels.kt` — superseded by the subtask pipeline.
- `TurnLoopConfigAliases.kt` and `TurnPromptAliases.kt` backcompat shims (replaced by a single `TurnPrompt.kt`).
- Legacy `CoreApiClient` passthroughs and `DualLogger` backcompat shim under `services/logging/` (call sites migrated to domain routers / `core.logging.dualLogger`).

### Fixed

- Four EDT-blocking `runBlocking` calls in `SessionLifecycleService` converted to `withContext`, eliminating UI-thread stalls during session persistence.
- Silent fallbacks removed from OpenAI response parsing, `CommandRule` regex compilation, and `HttpRequestTool` content-type detection — errors now surface instead of masking bad input.
- Duplicate `runTurnCallback` wiring in `CoreApiRouter` (InvokeSubagent + DelegateToStrong) consolidated into a single source of truth for agent turn dispatch.

## [0.0.1.6] - 2025-04-12

### Added
Expand Down
13 changes: 7 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ Local-first AI coding assistant for IntelliJ IDEA and the terminal. Kotlin/JVM p

# Quality
./gradlew :intellij-plugin:check # Includes detectSensitiveLogging task
./gradlew :intellij-plugin:jacocoTestReport # Coverage report (40% minimum)
./gradlew :core:jacocoTestCoverageVerification # Coverage gate (35% minimum instructions, wired into :core:check)
./gradlew :intellij-plugin:jacocoTestReport # Plugin coverage report (HTML/XML only, no threshold — tests live in :core)
```

## Module Architecture
Expand All @@ -46,17 +47,17 @@ UI (IntelliJ Swing / TUI Mordant)
→ Domain Routers (12 routers: Task, Chat, Agent, Subtask, Config, Prompts, Tool, RAG, ApiLogs, MultiAgent, ProjectContext, Subagent)
→ CoreApiRouter (composition root — creates dependencies, exposes routers, no business logic)
→ Execution (WorkflowOrchestrator → ChatService for CHAT | AgentTurnLoop for PLAN/AGENT)
→ LLMClient (8 provider adapters) + ToolRegistry (15 tools) + ContextService (14 providers)
→ LLMClient (8 provider adapters) + ToolRegistry (24 tools) + ContextService (14 providers)
→ Infrastructure (SQLite via Exposed ORM, Ktor HTTP, Caffeine cache)
```

Callers access domain routers directly via `coreApiRouter.taskRouter`, `coreApiRouter.chatRouter`, etc. CoreApiRouter itself is a thin composition root (~987 LOC) with no facade methods.
Callers access domain routers directly via `coreApiRouter.taskRouter`, `coreApiRouter.chatRouter`, etc. CoreApiRouter itself is a thin composition root (~300 LOC) with no facade methods.

## Three Execution Modes

- **CHAT** — No tools. Conversation-only via WorkflowOrchestrator → ChatService.
- **PLAN** — Read-only tools (6). AgentTurnLoop with max 25 iterations.
- **AGENT** — Full read/write tools (15). AgentTurnLoop with max 50 iterations. File snapshots before edits.
- **PLAN** — Read-only tools (14). AgentTurnLoop with max 25 iterations.
- **AGENT** — Full read/write tools (24). AgentTurnLoop with max 50 iterations. File snapshots before edits.

Subagents use a nested invocation model (max depth 3) with custom system prompts and tool filtering.

Expand Down Expand Up @@ -100,7 +101,7 @@ JUnit 5 + MockK + Turbine (Flow testing). Tests mirror source structure under `s

## Important Patterns

- **Thin router pattern**: CoreApiRouter is a composition root (~987 LOC) that creates dependencies and exposes 12 domain routers. Callers use domain routers directly (e.g., `coreApiRouter.taskRouter.createTask()`). No facade methods — zero business logic in CoreApiRouter.
- **Thin router pattern**: CoreApiRouter is a composition root (~300 LOC) that creates dependencies and exposes 12 domain routers. Callers use domain routers directly (e.g., `coreApiRouter.taskRouter.createTask()`). No facade methods — zero business logic in CoreApiRouter.
- **StateFlow reactivity**: SessionManager exposes 11 StateFlows; UI observes via `Flow.collect`.
- **Separate source trees**: Each module has its own `src/main/kotlin`. When adding new core files, ensure they don't depend on IntelliJ Platform APIs — the `:core` module has no IntelliJ dependency.
- **Security layers**: PathSandbox restricts file ops to project root; CommandRule (regex-based ALLOW/BLOCK/ASK) replaces legacy CommandWhitelist for terminal commands; FileLimits enforces size/extension restrictions. ToolPermissionsService provides 3-level (ON/ASK/OFF) per-mode access control. ToolApprovalService handles user approval flow with session trust rules.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Refio includes a standalone CLI with a full-screen TUI that mirrors the IntelliJ

- **Split-pane layout** — Chat on the left (55%), active tab on the right (45%). Full-width when no tab is selected.
- **8 tabs + 2 screens** — F1 Help, F2-F7 tabs (Steps, Context, RAG, Logs, Debug, API), F8 Files, F9 Settings. Tab bar shows mode, cost, and streaming status.
- **Two input modes** — Raw TTY (real terminal: F-keys, Shift+Tab, single-char dispatch) and line mode (IDE terminal, pipes: /commands, :tab shortcuts).
- **Two input modes** — Raw TTY (real terminal: F-keys, Shift+Tab, single-char dispatch) and line mode (IDE terminal, pipes: `/prompt`, `:tab` shortcuts).
- **@context autocomplete** — Type `@` for a popup with context prefixes (file, folder, grep, diff, etc.). 9 of 14 providers available in CLI mode.
- **Settings screen** — 11 sub-tabs covering providers, models, prompts, context/RAG, MCP, tools, subagents, and more.
- **Resize-responsive** — UI adapts to terminal window size changes in real time.
Expand Down Expand Up @@ -199,7 +199,7 @@ Refio includes a standalone CLI with a full-screen TUI that mirrors the IntelliJ
|-----|--------|
| `@` | Open context autocomplete (@file, @folder, @grep, @diff, etc.) |
| `!` | Open subagent autocomplete (!review, !security, etc.) |
| `/` | Open slash command autocomplete (/explain, /refactor, etc.) |
| `/` | Open slash prompt autocomplete (/explain, /refactor, etc.) |
| Tab / Arrow Down | Next autocomplete candidate |
| Arrow Up | Previous autocomplete candidate |
| Enter | Accept autocomplete selection |
Expand All @@ -209,7 +209,7 @@ Refio includes a standalone CLI with a full-screen TUI that mirrors the IntelliJ

- **Multi-line input** — input expands up to 4 visible lines as you type
- **Paste support** — large pastes (>200 chars) show a preview marker
- **Slash commands** — type `/` at the start for prompt templates (/explain, /test, /fix, /refactor, etc.)
- **Slash prompts** — type `/` at the start for reusable prompt templates (/explain, /test, /fix, /refactor, etc.). Manage them under **Settings → Prompts**.
- **System commands** — /help, /quit, /clear, /history, /export, /resend, /rewind, /edit, and more (type `/help` for full list)

### Architecture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class StandaloneCoreBootstrap(
val projectRouter = appRouter.createProjectRouter(
projectRoot = absolutePath,
projectHandle = projectHandle,
ideProject = null
platformProject = null
)
projectRouter.initialize(dbPath)
this.projectRouter = projectRouter
Expand Down
14 changes: 7 additions & 7 deletions cli/src/main/kotlin/pl/jclab/refio/cli/tui/input/TuiCompleter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import org.jline.reader.Candidate
import org.jline.reader.Completer
import org.jline.reader.LineReader
import org.jline.reader.ParsedLine
import pl.jclab.refio.api.models.SlashCommand
import pl.jclab.refio.api.models.SlashPrompt

/**
* JLine3 Completer for TUI prompt input.
* Provides completion for @mentions, /slash commands (prompt templates), and !subagents.
* Provides completion for @mentions, /slash prompts (prompt templates), and !subagents.
*/
class TuiCompleter : Completer {

/** System commands removed — all operations accessed through GUI keybindings/screens */
private val systemCommands = emptyList<String>()

/** Slash command prompt templates from SlashCommand.BUILTINS */
private val slashCommands: List<Pair<String, String>> by lazy {
/** Slash prompt templates from SlashPrompt.BUILTINS */
private val slashPrompts: List<Pair<String, String>> by lazy {
try {
SlashCommand.BUILTINS.map { "/${it.name}" to it.description }
SlashPrompt.BUILTINS.map { "/${it.name}" to it.description }
} catch (_: Exception) {
emptyList()
}
Expand All @@ -34,8 +34,8 @@ class TuiCompleter : Completer {

when {
word.startsWith("/") -> {
// Slash commands (prompt templates) first
slashCommands.filter { it.first.startsWith(word) }
// Slash prompts (prompt templates) first
slashPrompts.filter { it.first.startsWith(word) }
.forEach { candidates.add(Candidate(it.first, it.first, null, it.second, null, null, true)) }
// Then system commands
systemCommands.filter { it.startsWith(word) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,17 +666,17 @@ class TuiInputHandler(private val terminal: Terminal) {
}

/**
* Handle slash commands (prompt templates only).
* Handle slash prompts (prompt templates only).
* Returns true if the input was handled.
*
* All system operations (history, settings, export, etc.) are accessed
* through GUI keybindings and screens, not slash commands.
* Slash commands: /explain, /refactor, etc. — prompt templates from SlashCommand.BUILTINS
* through GUI keybindings and screens, not slash prompts.
* Slash prompts: /explain, /refactor, etc. — prompt templates from SlashPrompt.BUILTINS
*/
internal fun handleCommand(input: String, viewModel: TuiViewModel): Boolean {
// Slash commands (prompt templates) are NOT handled here.
// They are processed inline in TuiViewModel.sendMessage() — same as the plugin.
// Unknown /commands are passed through as normal messages.
// Slash prompts (prompt templates) are NOT handled here.
// They are expanded inline in TuiViewModel.sendMessage() — same as the plugin.
// Unknown /names are passed through as normal messages.
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ object TuiSettingsScreen {
"lmstudio.lmstudio_context_size" to "Context size"
),
"Custom OpenAI" to listOf(
"custom_openai.custom_openai_base_url" to "Base URL",
"custom_openai.custom_openai_api_key" to "API Key",
"custom_openai.custom_openai_model" to "Model"
"generic_openai.generic_openai_base_url" to "Base URL",
"generic_openai.generic_openai_api_key" to "API Key",
"generic_openai.generic_openai_model" to "Model"
),
"Z.AI" to listOf(
"zai.zai_base_url" to "Base URL",
Expand Down
Loading
Loading