mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-16 23:41:06 +00:00
9.8 KiB
9.8 KiB
ARCHITECTURE_NOTES
1. High-level overview — How the VS Code extension works
- Activation
- VS Code activates the extension via
package.jsonactivation events. - Extension creates singletons:
ClineProvider, API adapters, managers (ProviderSettingsManager, CustomModesManager), and registers webview providers / commands.
- VS Code activates the extension via
- UI
- Sidebar/tab webview(s) host the chat UI.
- Webview <-> extension comms use postMessage handlers implemented in
webviewMessageHandler. - Settings views must bind inputs to a local
cachedState(see AGENTS.md) and only persist on explicit Save.
- Task model
Taskis the runtime unit for an agent conversation/workflow: orchestrates prompt building, streaming, tool execution, retries, checkpoints, and persistence.- A
ClineProvidermanages tasks and exposes methods for creating/finding the visible task.
- API & streaming
- Provider-specific API adapters (Anthropic/OpenAI-like) implement
createMessagereturning a stream. - Task creates an
AbortControllerper call, streams the response, parses events (text chunks, tool calls, usage), and renders partial assistant messages to the webview.
- Provider-specific API adapters (Anthropic/OpenAI-like) implement
- Persistence & state
contextProxyis used for global state persistence (settings, stored messages, conversation history).- Checkpoints are created by the task on user sends / important state transitions.
2. Core components and responsibilities
-
ClineProvider
- Lifetime manager for tasks and UI provider for the webview.
- Exposes contextProxy, settings managers, and task creation.
-
Task
- submitUserMessage(), handleWebviewAskResponse(), ask(), recursivelyMakeClineRequests(), abortTask(), checkpointSave(), saveClineMessages().
- Maintains: clineMessages, assistantMessageContent, userMessageContent, currentRequest controller, abort flags, usage counters, autoApproval timers.
-
API Adapter
- Abstracted provider interface to make streaming requests and parse provider-specific events into a normalized internal event stream.
-
MessageQueueService
- Serializes transport of messages to UI / persistence to avoid races.
-
Managers
- ProviderSettingsManager, CustomModesManager — config and mode lifecycle.
-
WebviewMessageHandler
- Normalizes incoming UI messages and routes to provider or Task methods (e.g., askResponse → Task.submitUserMessage).
-
Tool Executors
- Execute tool calls (file read/write, shell, formatters) as requested by the model; results are injected back into the task loop.
3. The agent loop: recursivelyMakeClineRequests — conceptual steps
- Build or pop a userContent stack item to process.
- Check abort/paused/reset flags and backoff state.
- Compose prompt: conversation messages, tool metadata, file details (optional), environment hints, mode-specific system content.
- Create an
api_req_startedplaceholder message in UI and start streaming via provider API with an AbortController. - Stream parse:
- On text chunks: append to assistant buffer and present partial assistant message.
- On tool-call events: execute tool immediately or schedule; push tool results to user content buffer.
- On usage/grounding events: aggregate telemetry/usage.
- When assistant completes:
- Convert assistant output and tool results into user content blocks and push back to the stack.
- If stack not empty → recurse (continue loop).
- Handle error paths:
- Rate limits → exponential backoff and retry.
- Context window truncate → condense context and retry (MAX_CONTEXT_WINDOW_RETRIES).
- Network/first-chunk failures → retry with exponential backoff.
- Abort → update UI row with cancel reason and possibly call abortTask().
- Persist checkpoints and telemetry periodically and on state transitions.
Return semantics:
- Returns false on normal termination (stack empty).
- Returns true/throws on unexpected error forcing outer stop.
4. Message flow (sequence diagram)
sequenceDiagram
participant User
participant Webview
participant WebviewHandler
participant ClineProvider
participant Task
participant APIAdapter
participant ToolExecutor
User->>Webview: type + send
Webview->>WebviewHandler: postMessage("askResponse")
WebviewHandler->>ClineProvider: getVisibleInstance() / getTask
WebviewHandler->>Task: submitUserMessage(text, images)
Task->>Task: handleWebviewAskResponse(...)
Note over Task: ask() awaiting predicate resolves
Task->>APIAdapter: createMessage(prompt, controller)
APIAdapter-->>Task: stream chunks (text/tool/usage)
Task->>Webview: presentAssistantMessage(partial)
alt tool call event
Task->>ToolExecutor: execute(toolCall)
ToolExecutor-->>Task: toolResult
Task->>Task: push toolResult into userContent
end
Task->>Task: push userContent to stack -> continue loop
5. Hook system: purpose and architecture
Purpose: provide extension points for cross-cutting concerns without leaking internal Task implementation. Hooks enable logging, telemetry, testing, customization (modes/providers), and third-party integrations.
Design goals:
- Minimal surface area: well-defined hook types for Task lifecycle and stream events.
- Async-capable: hooks can be async and must not block the critical fast-path; use awaited or fire-and-forget based on hook type.
- Backpressure-safe: streaming hooks receive deltas; heavy processing should be offloaded.
- Idempotent & resilient: hooks must not mutate core state in ways that affect correctness; errors should be captured and logged, not crash the task.
- Observability-first: hooks expose granular events for debugging and telemetry.
Hook categories:
- Lifecycle hooks (synchronous optional await):
- onTaskStart(taskMeta)
- onTaskStop(taskMeta, reason)
- onCheckpointSaved(checkpointMeta)
- Ask/Response hooks:
- beforeAsk(promptContext) — may mutate promptContext copy
- afterAsk(responseSummary)
- Streaming hooks (should be non-blocking):
- onStreamChunk(chunk)
- onStreamComplete(assistantMessage)
- Tool hooks:
- onToolCall(toolRequest)
- onToolResult(toolResult)
- Persistence hooks:
- onSaveMessages(messages)
- Admin hooks:
- onAbort(reason)
Hook registration API (concept):
- Task.hooks.register(name, fn, { priority = 0, awaitable = false })
- Task.hooks.unregister(id)
- Invocation: Task.hooks.invoke(name, payload) — wraps calls in try/catch and observes awaitable flag.
Decision: streaming hooks default to non-awaitable to avoid blocking the parse -> render loop. Lifecycle hooks default to awaitable.
6. Hook invocation schema (mermaid)
flowchart TD
A[Task Event Occurs] --> B{Registered Hooks?}
B -- Yes --> C[Sort by priority]
C --> D{awaitable?}
D -- true --> E[await hook(payload)]
D -- false --> F[call hook(payload) in microtask / Promise.resolve()]
E --> G[collect results / errors]
F --> G
G --> H[continue core logic]
B -- No --> H
7. Component diagram (mermaid)
classDiagram
class Webview {
+postMessage()
+onMessage()
}
class WebviewHandler {
+handle(message)
}
class ClineProvider {
+createTask()
+getVisibleInstance()
}
class Task {
+submitUserMessage()
+ask()
+recursivelyMakeClineRequests()
+abortTask()
+checkpointSave()
+hooks
}
class APIAdapter {
+createMessage()
}
class ToolExecutor {
+execute()
}
Webview --> WebviewHandler
WebviewHandler --> ClineProvider
ClineProvider --> Task
Task --> APIAdapter
Task --> ToolExecutor
Task --> Webview
8. Architectural decisions & rationale
- Event-driven task loop: a streaming, event-based loop simplifies partial UI updates and tool interleaving; streaming allows progressive display and early tool execution.
- Isolation of UI -> Task pathway: UI writes are normalized through
webviewMessageHandler→Task.submitUserMessage()→Task.handleWebviewAskResponse()to avoid races and ensure canonical state changes. - Per-request AbortController: enables precise cancellation of single API calls; Task-level
abortTask()sets task abort state and coordinates higher-level shutdown. - Hooks with priority & await semantics: gives control to extensions/internals for synchronous lifecycle needs while protecting the stream path from blocking.
- Checkpointing on user sends: safety and reproducibility for long-running tasks and file operations.
- Separate managers for settings/modes: keep config, mode logic, and UI concerns decoupled from Task runtime.
- Use cachedState in SettingsView: prevents race conditions between UI edits and ContextProxy live state.
9. Implementation notes and best practices
- Always write hooks defensively: catch and log errors.
- Keep streaming hooks lightweight; delegate heavy processing to worker tasks or background jobs.
- Respect task abort and per-request AbortController to avoid leaking tool executions.
- When adding a UI input to SettingsView follow AGENTS.md: bind to
cachedStateand persist tocontextProxyonly on explicit Save. - When modifying prompt composition, prefer creating a copy of
clineMessagesto avoid concurrent mutation issues. - Use messageQueueService for UI and persistence writes to serialize state transitions.
10. Example hook registration (conceptual)
// Example (conceptual) — register a non-blocking stream logger
Task.hooks.register(
"onStreamChunk",
(chunk) => {
// lightweight logging
console.debug("stream chunk", chunk.type, chunk.length)
},
{ awaitable: false, priority: 10 },
)
11. Appendix — Recap of critical constants & limits
- MAX_CONTEXT_WINDOW_RETRIES = 3
- MAX_EXPONENTIAL_BACKOFF_SECONDS = 600
- FORCED_CONTEXT_REDUCTION_PERCENT = 75
End of ARCHITECTURE_NOTES.md