Skip to content

Conversation

@shwuhk
Copy link

@shwuhk shwuhk commented Nov 14, 2025

Summary

Update saveHomeSettings to create the settings directory using USER_CONFIG_DIR instead of deriving it from USER_SETTINGS_FILE. This ensures the correct user config directory is created before writing settings.

Background

Previously, the directory creation logic in saveHomeSettings used the path derived from USER_SETTINGS_FILE. In some environments this could result in creating an unexpected directory or failing to create the intended config directory, leading to issues when persisting user settings.

Changes

  • Change directory creation in saveHomeSettings to use USER_CONFIG_DIR as the base path.
  • Keep using USER_SETTINGS_FILE only as the full file path for writing the settings file.

Risks

  • Low: the change only affects which directory is created before writing settings, and aligns it with the intended USER_CONFIG_DIR.

Testing

  • Manual / automated tests were not run in this environment when preparing this PR.
  • Please run the existing test suite in CI to validate.

shtse8 added 30 commits November 4, 2025 22:27
Fixed multiple missing exports preventing TUI from launching:

1. Fixed calculateScrollViewport import
   - Changed SelectionUI.tsx to import from local utils
   - Changed PendingCommandSelection.tsx to import from local utils
   - calculateScrollViewport is a UI-specific utility, not core logic

2. Added missing agent manager export
   - Exported switchAgent from agent-manager

3. Added missing configuration exports
   - Exported AI_PROVIDERS constant
   - Exported getConfiguredProviders function
   - Exported ProviderId type

4. Added missing model fetcher exports
   - Exported fetchModels function
   - Exported ModelInfo type

Result: TUI now launches successfully
- Can run `bun run ./packages/code/src/index.ts`
- Shows welcome screen and input prompt
- All imports resolved correctly

Known remaining issues (non-blocking):
- React duplicate key warning in component render
- Additional text input utilities need to be added to code-core
  (Cursor, Wrapping, TextOps namespaces - ~20 functions)
…ent configuration

BREAKING CHANGE: Remove global agent state management in favor of stateless per-request architecture

## Overview
Complete architectural refactoring to support stateless server design where:
- Server has NO global "current agent" state
- Each session stores its own agent/provider/model configuration
- Clients manage UI selection state independently
- All context passed via explicit parameters

This enables true multi-session and multi-client independence.

## Changes by Package

### code-core
- **Database Schema**: Add agentId column to sessions table with default 'coder'
- **Agent Manager**: Remove switchAgent, getCurrentAgent, getCurrentAgentId, setAppStoreGetter
  - Keep pure functions: initializeAgentManager, getAllAgents, getAgentById, reloadAgents
- **NEW: system-prompt-builder.ts**: Pure function buildSystemPrompt(agentId) replaces global state
- **Session Repository**: Add agentId parameter to createSession, updateSession methods
- **Session Type**: Add agentId field to Session interface
- **AI SDK**: Deprecate getSystemPrompt() in favor of buildSystemPrompt(agentId)
- **Export**: Add DEFAULT_AGENT_ID export

### code-server
- **Streaming Service**:
  - Add agentId parameter to StreamAIResponseOptions
  - Use buildSystemPrompt(agentId) instead of global getCurrentSystemPrompt()
  - Pass system prompt explicitly to createAIStream
  - Determine agentId from: input parameter > session.agentId > DEFAULT_AGENT_ID
- **Session Router**: Add agentId parameter to session.create input schema
- **Message Router**: Add agentId parameter to message.streamResponse input schema

### code-client
- **App Store**: Rename currentAgentId → selectedAgentId, setCurrentAgentId → setSelectedAgent
  - Clarify that this is UI selection state, not server state
  - Pass selectedAgentId to session.create

### code (TUI)
- **Dashboard**:
  - Remove switchAgent import
  - Use setSelectedAgent instead of switchAgent
  - Update variable names: currentAgentId → selectedAgentId

### Documentation
- **NEW: docs/STATELESS_SERVER_REFACTOR.md**: Complete refactoring plan and design principles

## Migration

### Database
Generated migration: drizzle/0001_wet_randall_flagg.sql
```sql
ALTER TABLE `sessions` ADD `agent_id` text DEFAULT 'coder' NOT NULL;
```

Existing sessions will use default 'coder' agent.
New sessions will use client-selected agent.

### API Changes
**Session Creation**:
```typescript
// Before: No agent parameter
client.session.create({ provider, model })

// After: Optional agentId parameter
client.session.create({ provider, model, agentId: selectedAgentId })
```

**Message Streaming**:
```typescript
// Before: Uses global agent state
client.message.streamResponse({ sessionId, userMessage })

// After: Can override session agent
client.message.streamResponse({ sessionId, userMessage, agentId })
```

## Benefits
1. **Stateless Server**: No global state = easier scaling, testing, debugging
2. **Multi-Session Support**: Each session has independent configuration
3. **Multi-Client Support**: Different clients can have different selected agents
4. **Explicit Parameters**: All context passed via parameters, not hidden state
5. **Type Safety**: Parameters enforce correct usage at compile time

## Testing
- ✅ Build successful for code-core, code-server packages
- ✅ Type safety verified (no getCurrentAgent references)
- ✅ Migration generated and validated

## Architecture
See docs/STATELESS_SERVER_REFACTOR.md for full design principles and implementation details.
Fixed incorrect tRPC client calls that were causing runtime errors:
- TypeError: client[procedureType] is not a function

Changes:
- useSessionPersistence: client.session.getLast() → client.session.getLast.query()
- useInputState: client.message.getRecentUserMessages({...}) → client.message.getRecentUserMessages.query({...})
- Removed unnecessary await from getTRPCClient() calls (synchronous function)

tRPC v11 requires explicit .query()/.mutate()/.subscribe() suffixes
for all procedure calls.

Verified: TUI now launches successfully without client errors.
Fixed runtime error in ControlledTextInput component that was trying
to import text operation functions from code-core that didn't exist:
- TypeError: TextOps.normalizeLineEndings is not a function

Created three utility modules with pure functions:
1. text-ops.ts - Text manipulation (insert, delete, transpose, yank)
2. cursor-ops.ts - Cursor movement and word boundary detection
3. wrapping-ops.ts - Text wrapping and physical line navigation

Updated ControlledTextInput.tsx to import from local utils instead
of trying to import from @sylphx/code-core.

These utilities implement readline-compatible text editing operations
with support for:
- Character and word-level editing
- Kill/yank buffer operations
- Multiline text with proper wrapping
- Physical line navigation (accounting for wrapped text)

Verified: TUI now launches successfully and input handling works.
Fixed all remaining tRPC client calls in app-store.ts:
- Removed unnecessary await from getTRPCClient() (synchronous function)
- Added .query() suffix to all query procedures
- Added .mutate() suffix to all mutation procedures

Affected methods:
- setCurrentSession: client.session.getById.query()
- refreshCurrentSession: client.session.getById.query()
- createSession: client.session.create.mutate()
- updateSessionModel: client.session.updateModel.mutate()
- updateSessionProvider: client.session.updateProvider.mutate()
- updateSessionTitle: client.session.updateTitle.mutate()
- deleteSession: client.session.delete.mutate()
- addMessage: client.message.add.mutate()
- updateTodos: client.todo.update.mutate()

tRPC v11 requires explicit .query()/.mutate() suffixes for all
procedure calls. Missing these suffixes causes runtime errors.

Verified: TUI now launches successfully and all session operations work.
Two critical fixes:

1. app-store.ts: Fixed MessagePart content field name
   - Was creating: { type: 'text', text: content } ❌
   - Now creates: { type: 'text', content } ✅
   - Matches tRPC MessagePartSchema validation
   - Separated wire format (tRPC) from internal format (client state)
   - Wire format: no status on parts (validated by tRPC)
   - Internal format: with status on parts (SessionMessage type)

2. MarkdownText.tsx: Added undefined children guard
   - Component was calling children.split() without checking
   - Now returns null for undefined or non-string children
   - Prevents "undefined is not an object" error

Root Cause:
tRPC validation schema expects { type: 'text', content: string }
but code was sending { type: 'text', text: string }

Error fixed:
TRPCClientError: Invalid input for message.add
[{ "code": "invalid_union", "path": ["content", 0] }]
Use composite key (index + content hash) instead of array index alone
to avoid duplicate key warnings when rendering markdown lines.

Fixes React warning: "Encountered two children with the same key"
Include message timestamp in MessagePart keys to ensure uniqueness
across all messages. Previously used `part-${idx}` which caused
duplicates when multiple messages had the same number of parts.

Now uses `${msg.timestamp}-part-${idx}` for globally unique keys.
# Architecture Migration Summary

Migrated from HTTP daemon architecture to embedded server with in-process
tRPC by default. This provides 30x faster startup and ~0.1ms request
latency vs ~3ms for HTTP localhost.

## Changes

### 1. Code Server (@sylphx/code-server)
- Created `CodeServer` class for embedding (packages/code-server/src/server.ts)
- Exportable router and context for in-process use
- Optional HTTP server for Web GUI and remote connections
- Simplified CLI to use CodeServer class

### 2. Code Client (@sylphx/code-client)
- Created `inProcessLink` for zero-overhead tRPC communication
- Direct function calls without network stack
- Maintains HTTP link support for remote connections

### 3. TUI (@sylphx/code)
- Removed daemon management (server-manager.ts)
- Embedded CodeServer by default (in-process mode)
- Added --web flag to start HTTP server from TUI
- Added --server-url flag for remote connections
- Kept --server flag for standalone HTTP server

### 4. Architecture Documentation
- Complete rewrite of ARCHITECTURE.md
- Documented three modes: in-process, TUI+Web, remote
- Added performance comparison table
- Backed up old docs to ARCHIVE/2025-01-05-daemon-http-architecture/

## Benefits

✅ Faster startup: ~100ms vs ~2000ms (daemon spawn)
✅ Lower latency: ~0.1ms vs ~3ms (HTTP localhost)
✅ Simpler deployment: No daemon management
✅ Flexible: Can enable HTTP server when needed
✅ Backward compatible: Remote mode still works

## Usage

```bash
# Default: In-process (fastest)
code

# TUI + Web GUI
code --web

# Remote connection
code --server-url http://host:port

# Standalone server
code --server
```

See ARCHITECTURE.md for complete documentation.
- Fixed getProcedure to handle tRPC v11 merged router structure
- Merged routers flatten procedures with dot notation keys (e.g., 'session.getLast')
- Added fallback for nested navigation
- TUI now launches without 'Procedure not found' errors
- Removed unnecessary fallback logic for nested navigation
- tRPC v11 merged routers always flatten procedures with dot notation
- Simplified from 14 lines to 3 lines
- Maintains same functionality with cleaner code
- Changed from direct procedure calls to router.createCaller() API
- tRPC v11 requires server-side caller for procedure invocation
- Direct procedure calls throw 'client-only function' error
- Maintains zero-overhead communication with proper API usage

Fixes:
- 'This is a client-only function' error
- Proper context passing through caller
- Correct procedure invocation flow

Reference: https://trpc.io/docs/v11/server/server-side-calls
Replace global mutable state pattern with React Context for type-safe,
composable tRPC client access.

## Changes

### 1. New React Context API (`trpc-provider.tsx`)
- Created `TRPCProvider` component
- Added `useTRPCClient()` hook for React components
- Helper functions: `createInProcessClient()`, `createHTTPClient()`
- Full TypeScript support with `TypedTRPCClient` type

### 2. Internal API for Zustand Stores
- Kept `getTRPCClient()` as internal API for non-React code
- `TRPCProvider` automatically initializes global client for stores
- Marked with `@internal` JSDoc to discourage direct use

### 3. Updated TUI Entry Point
- Wrap App with `<TRPCProvider client={client}>`
- Removed manual `setTRPCClient()` call

## Benefits

✅ Type-safe client access in React components
✅ Explicit dependency via Context (easier to test/mock)
✅ Composable architecture (multiple providers in tests)
✅ Zustand stores still work via internal API
✅ Single source of truth (provider handles both)

## Migration

**React components:**
```typescript
// Before
const client = getTRPCClient();

// After
const client = useTRPCClient();
```

**Zustand stores:** No changes needed - internal API still works

**App entry:** Wrap with `<TRPCProvider client={client}>...</TRPCProvider>`
Replace global singleton state with composable functional providers.

## Architecture Changes

**Before (Anti-patterns):**
- Global mutable state with `any` types
- Singleton initialization via imperative calls
- setRuleAppStoreGetter() anti-pattern
- No composability, difficult to test

**After (Functional Provider Pattern):**
- Zero global state - all state in closures
- Functional factories returning service APIs
- Explicit dependency injection via Context
- Type-safe interfaces, fully composable

## Implementation

1. **AppContext (code-core/src/context.ts)**
   - Created DatabaseService functional provider
   - Created AgentManagerService functional provider
   - Created RuleManagerService functional provider
   - Lazy initialization via closures
   - createAppContext() composition root
   - initializeAppContext() initialization helper

2. **CodeServer Integration (code-server/src/server.ts)**
   - Refactored to use AppContext
   - Removed direct calls to global init functions
   - Binds AppContext to tRPC context via closure
   - Proper cleanup in close()

3. **tRPC Context (code-server/src/trpc/context.ts)**
   - Updated createContext to receive AppContext
   - Services accessed via context.appContext
   - Maintains sessionRepository for backward compat
   - Type-safe service access

## Benefits

✅ No global state
✅ No `any` types in provider APIs
✅ Composable via function composition
✅ Explicit dependencies (no hidden globals)
✅ Testable (can inject mock contexts)
✅ Type-safe service access
✅ Proper lifecycle (initialize/cleanup)

## Testing

Verified TUI initialization works correctly:
- Embedded server starts with AppContext
- All services initialize properly
- No runtime errors

## Pattern: Zustand (UI State) + Context (Services)

This follows the decision to use:
- **Zustand**: For UI state (navigation, loading)
- **Context**: For services (database, managers)
- **No classes**: Pure functional factories with closures

Eliminates all global mutable state while maintaining
clean separation of concerns.
Separate SDK layer from Application layer for proper responsibility

## Problem

code-core (SDK) should not manage application context - that's
application layer responsibility. SDK should only provide pure
functions and reusable building blocks.

## Solution

**Before:**
```
code-core (SDK)
├── AppContext ❌ Application concern
├── createAppContext() ❌
└── Pure functions ✓
```

**After:**
```
code-core (SDK - Library)
├── loadAllAgents() ✓ Pure function
├── loadAllRules() ✓ Pure function
├── initializeDatabase() ✓ Pure function
└── SessionRepository ✓ Reusable class

code-server (Application)
├── AppContext ✓ Composition root
├── createAppContext() ✓ Service assembly
└── CodeServer ✓ Lifecycle management
```

## Changes

1. **Moved context.ts**: code-core → code-server
   - AppContext now lives in application layer
   - code-server imports from code-core, not vice versa

2. **Updated code-core exports**: Export pure functions only
   - `initializeDatabase()` - database initialization
   - `loadAllAgents()` - load agents from filesystem
   - `loadAllRules()` - load rules from filesystem
   - Removed AppContext exports

3. **Updated code-server**: Import from code-core
   - Uses `loadAllAgents()`, `loadAllRules()`, `initializeDatabase()`
   - Assembles services into AppContext
   - Manages application lifecycle

## Benefits

✅ **Clear separation**: SDK vs Application layer
✅ **Reusability**: code-core can be used in other projects
✅ **Composability**: SDK provides building blocks, app assembles them
✅ **No circular deps**: code-server → code-core (one direction)

## Testing

✅ TUI compiles and runs correctly
✅ code-server builds successfully
✅ No runtime errors

## Next Steps (Future Work)

Still has global state in code-core that should be cleaned up:
- database.ts global singletons
- agent-manager.ts global state
- rule-manager.ts setRuleAppStoreGetter() anti-pattern

These will be addressed in follow-up refactoring.
This commit completes the refactoring to make code-core a pure SDK layer
with no global state, achieving proper separation between SDK and application layers.

## Changes

### 1. Removed Global State from code-core

**database/database.ts:**
- ❌ Removed: `getDatabase()`, `getSessionRepository()`, `resetDatabase()`
- ❌ Removed: Global variables `dbInstance`, `repositoryInstance`, `initPromise`
- ✅ Kept: `SessionRepository` class, `initializeDatabase()` function

**ai/agent-manager.ts:**
- ❌ Removed: `initializeAgentManager()`, `getAllAgents()`, `getAgentById()`, `reloadAgents()`
- ❌ Removed: Global state `let state: AgentManagerState | null = null`
- ✅ All functions moved to code-server AppContext (AgentManagerService)

**ai/rule-manager.ts:**
- ❌ Removed: All global state functions (initializeRuleManager, getAllRules, etc.)
- ❌ Removed: Horror anti-pattern `setRuleAppStoreGetter()` and `let getAppStore: (() => any) | null = null`
- ✅ All functions moved to code-server AppContext (RuleManagerService)

### 2. Updated Functions to Accept Dependencies

**ai/system-prompt-builder.ts:**
- Changed `buildSystemPrompt(agentId)` to accept explicit dependencies:
  `buildSystemPrompt(agentId, agents, enabledRules)`
- Pure function - no global state

**ai/session-service.ts:**
- Changed `getOrCreateSession(continueSession)` to accept repository:
  `getOrCreateSession(repository, continueSession)`
- Updated to use passed SessionRepository instead of global getter

**ai/ai-sdk.ts:**
- Removed import of `getEnabledRulesContent()` (no longer exists)

### 3. Updated code-core Exports

Updated `code-core/src/index.ts` to only export pure functions:
- ✅ Export: `loadAllAgents()`, `loadAllRules()`, `initializeDatabase()`
- ✅ Export: `SessionRepository` class
- ❌ Removed: All global state functions

Added comments explaining where the removed functions moved to.

### 4. Updated code-server to Use AppContext

**services/streaming.service.ts:**
- Added `appContext: AppContext` to `StreamAIResponseOptions`
- Updated `buildSystemPrompt()` call to use AppContext:
  ```typescript
  const agents = opts.appContext.agentManager.getAll();
  const enabledRules = opts.appContext.ruleManager.getEnabled(enabledRuleIds);
  const systemPrompt = buildSystemPrompt(agentId, agents, enabledRules);
  ```

**trpc/routers/message.router.ts:**
- Added `appContext: ctx.appContext` to `streamAIResponse()` call

## Architecture

Before (❌ Bad):
```
code-core (SDK + Global State)
└── Global singletons: database, agents, rules
```

After (✅ Good):
```
code-core (Pure SDK)
├── Pure functions: loadAllAgents(), loadAllRules(), initializeDatabase()
├── Reusable classes: SessionRepository
└── NO global state

code-server (Application)
└── AppContext (Functional Provider Pattern)
    ├── DatabaseService (manages database instance)
    ├── AgentManagerService (manages agents)
    └── RuleManagerService (manages rules)
```

## Benefits

1. **Clear separation**: SDK provides pure functions, Application manages state
2. **No global state**: All state managed through AppContext
3. **Composable**: Services created via factory functions with closures
4. **Type-safe**: No `any` types, explicit dependencies
5. **Testable**: Pure functions easy to test, no hidden global dependencies

## Migration Note

If you're using the old global functions:
- `getDatabase()` → Use `appContext.database.getDB()` in code-server
- `getSessionRepository()` → Use `appContext.database.getRepository()` in code-server
- `getAllAgents()` → Use `appContext.agentManager.getAll()` in code-server
- `getAgentById()` → Use `appContext.agentManager.getById()` in code-server
- `getAllRules()` → Use `appContext.ruleManager.getAll()` in code-server
- `getEnabledRules()` → Use `appContext.ruleManager.getEnabled()` in code-server
After removing global state from code-core, the TUI client (code package)
lost access to agents and rules data. This commit adds a temporary
compatibility layer to restore functionality.

## Changes

### 1. Exposed AppContext from CodeServer

**code-server/src/server.ts:**
- Added `getAppContext()` method to expose AppContext for embedded mode
- Allows in-process clients to access services directly

### 2. Created Embedded Context Bridge

**code/src/embedded-context.ts:** (NEW)
- Temporary compatibility layer for TUI
- Provides functions that access embedded server's AppContext:
  - `getAllAgents()`, `getAgentById()`
  - `getAllRules()`, `getRuleById()`
  - `getCurrentAgent()`, `switchAgent()`, `toggleRule()`
- TEMPORARY: Will be replaced with proper tRPC endpoints

### 3. Updated TUI Imports

**code/src/index.ts:**
- Register embedded server via `setEmbeddedServer()` during initialization

**code/src/components/StatusBar.tsx:**
- Changed import from `@sylphx/code-core` to `../embedded-context.js`

**code/src/screens/Dashboard.tsx:**
- Changed import from `@sylphx/code-core` to `../embedded-context.js`

**code/src/commands/definitions/agent.command.ts:**
- Changed imports from `@sylphx/code-core` to `../../embedded-context.js`

## Architecture

```
Before (❌ Broken):
TUI → @sylphx/code-core (global state removed) → ERROR

After (✅ Working):
TUI → embedded-context.ts → CodeServer.getAppContext() → AppContext
                                                           ├── AgentManager
                                                           ├── RuleManager
                                                           └── DatabaseService
```

## Why Temporary?

This is a compatibility bridge to restore TUI functionality quickly.
The proper long-term solution is to:
1. Create tRPC endpoints for agents and rules in code-server
2. Update TUI to use tRPC queries instead of direct function calls
3. Remove embedded-context.ts

For now, this allows the TUI to work while maintaining the clean
separation we achieved with the global state removal.

## Testing

✅ TUI starts without errors
✅ Agents and rules accessible
✅ StatusBar displays agent name
✅ Dashboard shows agents/rules list
TUI no longer creates or resumes sessions on startup. Sessions are now
created server-side only when the user sends their first message,
ensuring all sessions have at least one message.

- Remove auto-session initialization on TUI startup
- Client passes null sessionId with provider/model to trigger server creation
- Server creates session with first message and emits session-created event
- Client handles session-created event to update currentSessionId
- Skip optimistic UI updates when no session exists

Also removes unused .claude/commands files from previous refactoring.
TUI now starts with a fresh session screen (currentSessionId = null)
instead of automatically resuming the last session. Users can manually
resume sessions via /sessions command.

This completes the lazy session approach:
- TUI starts with no session loaded (fresh screen)
- No empty session created on startup
- Session only created when user sends first message
- Previous sessions accessible via /sessions command

Changes:
- Remove auto-load logic from useSessionPersistence hook
- TUI now starts in "new session" state
- Aligns with lazy session creation architecture
When TUI starts with no session (lazy session) and no AI provider
configured, the submit behavior was confusing - input cleared with
no feedback.

Changes:
- Simplified provider validation logic in subscriptionAdapter
- Removed duplicate checks
- Added TODO for better UX (error toast or prevent submit)
- Log when provider not configured for debugging

Current behavior:
- Welcome message shows 'No AI provider configured'
- User must run /provider to configure first
- Submit clears input but does nothing (known UX issue)

Note: messageHandler clears input before calling sendUserMessageToAI,
so by the time we detect no provider, input is already cleared.
Future fix should prevent submit or show error feedback.
StatusBar was hidden on TUI startup because it only displayed when
currentSession existed. With lazy sessions, currentSession is null
until first message is sent.

Changes:
- Show StatusBar with default provider/model when no session exists
- Show StatusBar with session's provider/model when session exists
- Hide StatusBar only if no session AND no default config

This ensures status bar is visible on startup showing current
default configuration, and updates to show session-specific
info once session is created.
REASON:
- Provider interface updated to use ProviderConfig (full config object)
- StatusBar was still using old API (single apiKey string)
- Some providers need more than apiKey (e.g., Google needs projectId, region)

CHANGES:
- StatusBar.tsx: Accept providerConfig instead of apiKey
- Chat.tsx: Pass full provider config instead of just apiKey
- Now compatible with multi-field provider configs

FIXES:
- Type mismatch between StatusBar and provider interface
- Support for providers requiring additional config fields
SECURITY FIX: Prevent API keys from being exposed to client

Problem:
- config.load() returned full config with API keys to client
- Dangerous for Web GUI and remote mode (keys visible in browser/network)
- StatusBar was passing providerConfig with API keys to client side

Solution:
1. config.load() now sanitizes config before returning
   - Uses provider ConfigField schema to identify secret fields
   - Masks secret fields: "sk-ant-123..." → "sk-ant-***"
   - Non-secret fields (provider/model) returned as-is

2. StatusBar no longer needs providerConfig
   - Uses hardcoded metadata from provider classes
   - No API keys needed on client side
   - Safe for all deployment modes

Implementation:
- sanitizeAIConfig() checks each provider's getConfigSchema()
- Only fields marked with secret=true are masked
- Dynamic approach (not hardcoded field names)
- Fallback handling if provider not found

Files changed:
- config.router.ts: Add sanitizeAIConfig() + sanitize in load()
- StatusBar.tsx: Remove providerConfig prop
- Chat.tsx: Remove providerConfig passing

Security impact:
✅ API keys no longer exposed to client
✅ Safe for Web GUI mode
✅ Safe for remote server mode
✅ Client can still SET config (user needs to configure)
❌ Client cannot GET full API keys
…d pagination

BREAKING CHANGES:
- session.getRecent now returns metadata only (no messages, no todos)
- session.search now returns metadata only
- message.getRecentUserMessages returns paginated results
- All endpoints use cursor-based pagination instead of offset

DATA ON DEMAND pattern:
- session.getRecent returns: id, title, provider, model, created, updated, messageCount
- Client fetches full session via session.getById when needed
- Massive performance improvement for large datasets

CURSOR-BASED PAGINATION:
- More efficient than offset pagination for large datasets
- Works correctly with concurrent updates
- Cursor = timestamp of last item in page
- Returns nextCursor for fetching next page

NEW ENDPOINTS:
- message.getBySession: Paginated message fetching for session
- Allows infinite scroll, lazy loading of chat history

Repository layer:
- Added getRecentSessionsMetadata() - metadata-only session list
- Added searchSessionsMetadata() - metadata-only search results
- Added getMessagesBySession() - paginated message fetching
- Updated getRecentUserMessages() - cursor-based pagination

Benefits:
- Avoid loading 1000s of messages when browsing sessions
- Efficient pagination for 100,000+ sessions
- Prevents memory bloat in Web GUI
- Faster initial load times
- Better UX for large datasets

FINE-GRAINED DATA TRANSFER:
- Old: getRecent returns full session with all messages
- New: getRecent returns metadata, client fetches full data on demand
SECURITY ENHANCEMENT: Add authentication middleware to protect API endpoints

Implementation:
- Dual-mode authentication (in-process trusted, HTTP API key)
- Protected procedures for all mutations and subscriptions
- Public procedures for read-only queries
- Context enrichment with auth status and source

Changes:
- Add auth context to tRPC context (isAuthenticated, userId, source)
- Create authentication middleware with UNAUTHORIZED error
- Create protectedProcedure using middleware
- Update all mutation endpoints to use protectedProcedure
- Fix bug: message.add now returns messageId (string) instead of message object

Security Features:
- In-process calls: Auto-authenticated (trusted local process)
- HTTP calls: Require API key via Authorization header (Bearer token)
- Environment variable: SYLPHX_API_KEY for HTTP authentication
- Clear error messages for unauthenticated requests

Testing:
- Add comprehensive authentication tests
- Verify in-process authentication works
- Verify all protected mutations work
- Test coverage: sessions, messages, todos, config

Documentation:
- Add SECURITY.md with authentication guide
- Document usage for both in-process and HTTP modes
- Document protected vs public procedures
- Include security best practices

OWASP API2: Broken Authentication ✅
SECURITY ENHANCEMENT: Add rate limiting to prevent resource exhaustion

Implementation:
- Token bucket algorithm with sliding window
- Automatic cleanup (prevents memory leaks)
- Dual-mode: in-process (no limits), HTTP (rate limited)
- Per-client isolation (IP or userId)
- HTTP headers (X-RateLimit-*)

Rate Limit Tiers:
- Strict (10 req/min): create, delete operations
- Moderate (30 req/min): update operations
- Lenient (100 req/min): queries (reserved)
- Streaming (5 streams/min): AI subscriptions

Changes:
- Add RateLimiter service with token bucket algorithm
- Create rate limit middleware factory
- Export rate-limited procedures (strict, moderate, lenient, streaming)
- Apply rate limiting to all routers
- Update SECURITY.md with comprehensive documentation

Security Features:
- DoS protection
- Fair usage enforcement
- Cost control (AI API calls)
- Graceful degradation under load
- Automatic memory cleanup

Rate Limit Response:
- Error code: TOO_MANY_REQUESTS (429)
- Message includes retry time
- Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Testing:
- All existing tests pass (rate limiting skipped for in-process)
- In-process clients unaffected (zero overhead)
- HTTP clients subject to rate limits

OWASP API4: Unrestricted Resource Consumption ✅
SECURITY ENHANCEMENT: Add role-based access control with hierarchical permissions

Implementation:
- Three-tier role hierarchy (admin, user, guest)
- Role assignment based on request source and authentication
- Authorization middleware with role checking
- Admin-only router for system management operations

Role Definitions:
- admin: Full access (in-process CLI, local user)
- user: Standard access (HTTP with API key)
- guest: Read-only public access (HTTP without API key)

Changes:
- Add UserRole type to context
- Update context creation to assign roles
- Create requireRole authorization middleware
- Export adminProcedure and userProcedure
- Create role + rate limit combined procedures
- Add admin router with system management endpoints
- Comprehensive authorization tests

Admin Operations:
- admin.deleteAllSessions - Delete all data (dangerous)
- admin.getSystemStats - System statistics
- admin.forceGC - Garbage collection
- admin.getHealth - Health check (public)

Security Features:
- Privilege separation (admin vs user vs guest)
- Least privilege principle
- Defense in depth (auth + authz + rate limiting)
- Clear error messages (FORBIDDEN vs UNAUTHORIZED)

Error Responses:
- FORBIDDEN: Wrong role for operation
- UNAUTHORIZED: Missing authentication

Testing:
- All admin operations work for in-process (admin) clients
- Comprehensive test coverage for authorization
- All existing tests still pass

Documentation:
- Updated SECURITY.md with API5 section
- Role hierarchy explanation
- Usage examples for all roles
- Error response documentation

OWASP API5: Broken Function Level Authorization ✅
SECURITY ENHANCEMENT: Add comprehensive API inventory management and documentation

Implementation:
- API inventory with all 44 endpoints documented
- Programmatic API discovery via endpoints
- Auto-generated API reference documentation
- Semantic versioning (1.0.0)
- Deprecation policy and change management guidelines

Features:
- Complete endpoint inventory (queries, mutations, subscriptions)
- Endpoint metadata (type, auth, rate limits, descriptions)
- Search and filter capabilities
- Markdown documentation generation
- Public API discovery endpoints

API Inventory:
- Total: 44 endpoints
- Queries: 14 endpoints
- Mutations: 19 endpoints
- Subscriptions: 5 endpoints
- Admin: 4 endpoints

New Endpoints:
- admin.getAPIInventory - Get complete API inventory
- admin.getAPIDocs - Get formatted API documentation

Documentation:
- API-REFERENCE.md - Complete API reference (auto-generated)
- SECURITY.md - Updated with API9 section
- Inline inventory in code with search/filter utilities

Versioning:
- Current version: 1.0.0
- Semantic versioning strategy
- Clear deprecation policy
- Change management guidelines

Security Benefits:
- Full API visibility (no shadow APIs)
- Clear ownership and purpose
- Version control and tracking
- Reduced attack surface
- Easy API discovery for developers

Testing:
- All existing tests pass
- API inventory accessible via endpoints
- Documentation auto-generates correctly

OWASP API9: Improper Inventory Management ✅

All 5 critical OWASP API Security items now implemented!
shtse8 and others added 27 commits November 13, 2025 15:29
…tion

Update all documentation to reflect the unified CLI:

## Command Changes
- Old: `sylphx-flow init` → New: `sylphx-flow setup`
- Old: `sylphx-flow run "task"` → New: `sylphx-flow "task"`
- Removed: Interactive mode references (not yet implemented)

## Key Updates
- README.md: All 6 occurrences updated
- docs/index.md: Installation section updated
- docs/guide/getting-started.md: Quick start guide updated

## Clarifications Added
- Setup is **optional** - auto-runs on first use
- Direct execution is the primary usage pattern
- Loop mode and file input examples added
- Removed confusing 'interactive mode' references

## Files Updated
- README.md (6 command updates + usage examples)
- docs/index.md (installation section)
- docs/guide/getting-started.md (quick start + note about auto-init)

All documentation now accurately reflects the actual CLI implementation.
OpenCode's `run` command has compatibility issues when combined with
certain flags in headless mode:
- `--format json` causes TypeError with `--agent` flag
- `-c` (continue) enters interactive mode, blocking loop execution

Changes:
- Removed `--format json` flag from print mode execution
- Removed `-c` (continue) flag usage in headless mode
- Added comments documenting the incompatibilities
- Updated both executeCommand and dry-run command generation

This fixes OpenCode loop mode hanging indefinitely when trying to
execute autonomous continuous workflows.
BREAKING CHANGE: --clean flag renamed to --sync for clarity

- Add --sync flag to replace --clean (better describes sync behavior)
- Create sync-utils.ts with manifest building and file deletion logic
- Add confirmation prompt before deleting template files
- Preserve configs (.sylphx-flow/, .secrets/, config files)
- Delete and re-install: agents, slash commands, rules
- Fix targetId undefined bug in dry-run mode
- Show preview of files to be deleted with confirmation

Implementation:
- buildSyncManifest(): Collect files to delete/preserve
- showSyncPreview(): Display files grouped by type
- executeSyncDelete(): Delete template files
- confirmSync(): User confirmation before deletion

Closes #sync-templates
- Add targetId checks before previewDryRun() and installComponents()
- Ensure selectedTarget is set in all code paths
- Fix both executeSetupPhase() and executeFlowOnce() functions
- Prevents ReferenceError during repair/init in loop mode
- Show executed command with chalk.dim() in print mode
- Helps debug OpenCode execution issues in loop mode
- Only prints when verbose or print mode enabled
- Emphasize auto-initialization on first use (no manual setup required)
- Mark loop mode as Claude Code only (OpenCode support coming soon)
- Document new --sync flag for template synchronization
- Update platform support clarity throughout docs
- Remove outdated setup command references
- Simplify getting started flow

Changes:
- README.md: Updated installation, usage examples, loop mode section
- docs/index.md: Updated quick examples and installation
- docs/guide/getting-started.md: Reorganized quick start, added sync docs

Context: OpenCode run command has TTY requirement preventing background
execution. Loop mode works perfectly with Claude Code headless mode.
Major documentation overhaul with detailed explanations of all features:

## New Documentation Pages

### Features
- **agents.md**: Complete guide to AI agents (Coder, Reviewer, Writer, Orchestrator)
  - Detailed descriptions of each agent's purpose and expertise
  - Usage examples and best practices
  - Agent selection guide and configuration

- **semantic-search.md**: Deep dive into semantic search capabilities
  - Codebase search (StarCoder2 tokenization, 70+ languages)
  - Knowledge base search (curated best practices)
  - Multilingual support and performance metrics
  - Integration with AI agents

### Guides
- **rules.md**: Complete rules system documentation
  - Built-in rules (Code Quality, Security, Testing, Performance)
  - OWASP compliance and best practices
  - Custom rules and enforcement levels
  - Rule syncing and management

- **mcp.md**: MCP (Model Context Protocol) integration guide
  - Pre-configured MCP servers (Web Search, Vision, Code Indexing)
  - Installation and configuration
  - Security and permissions
  - Developing custom MCP servers

## Updated Pages

### README.md
- Added npm package badge and link
- Added Twitter/X badge and link (@SylphxAI)
- New "Complete Feature Breakdown" section with:
  - AI Agents overview
  - Rules System explanation
  - MCP Server Integration details
  - Codebase Semantic Search features
  - Knowledge Semantic Search capabilities
  - Smart Configuration guide
  - System Hooks explanation
  - Template Synchronization

### docs/index.md
- Expanded feature grid with all major capabilities
- Added detailed "What Makes Flow Different?" section
- Comprehensive feature descriptions with links
- Added npm, GitHub, and Twitter links
- Better onboarding and navigation

### docs/.vitepress/config.ts
- Updated sidebar navigation for new pages
- Added npm link to navigation
- Added Twitter social link (@SylphxAI)
- Added twitter:site meta tag for better social sharing
- Reorganized sidebar structure

## Why These Changes

User feedback: "好多頁面入唔到,亦都無好詳細講下個project做啲咩,有啲咩agents,
rules, mcp, codebase semantic search, knowledge semantic search, etc.."

This update provides:
- Complete feature documentation
- Clear navigation structure
- Detailed explanations with examples
- Links to npm package and social media
- Better discoverability of all capabilities
Issue: v1.0.0 was published without dist/ directory, causing 'command not found' error
Fix: Add prepublishOnly script to automatically build before npm publish
Version: Bump to 1.0.1
- Remove build step and prepublishOnly
- Publish src/ directory instead of dist/
- Change bin to point to src/index.ts (already has shebang)
- Simplifies deployment and avoids Unicode/native binding issues
…ust exist

Issue: State detector marks components as installed if directory exists, even if empty
Fix: Only mark component as installed if it has files (count > 0)
Result: Eliminates false 'missing components' warnings on initialized projects
Issue: Claude Code includes rules/output-styles in agent files, but state
detector still checks for separate .claude/rules and .claude/output-styles
directories, causing false 'missing components' warnings.

Fix:
- Mark rules and output-styles as installed if agents are installed (Claude Code)
- Only check agents as required component
- Make hooks, MCP, and slash commands optional
- Remove unnecessary component checks from repair logic

Result: No more false warnings after initialization
Issue: Agents, rules, and other assets were not included in npm package,
causing 'Installed 0 agents' and 'Agent not found' errors.

Fix:
- Add prepublishOnly script to copy assets from monorepo root
- Add 'assets' to package.json files array
- Ensures agents, rules, output-styles, and slash-commands are included

Result: All template files will be available after npm install
PROBLEM:
User reported that Flow automatically selected z.ai provider without asking,
even though they didn't want automatic provider selection. The complaint
"點解有key就代表用?" (Why does having a key mean it should be used?) revealed
that the system was too aggressive about auto-selecting providers.

ROOT CAUSES:
1. Initial setup didn't ask if user wanted to set a default provider
2. The useDefaults option was ignored
3. Choices were saved even for one-time overrides (--select-provider)

FIXES:
1. Initial Setup - Explicit Confirmation:
   - Added prompt asking "Set {provider} as default provider for future runs?"
   - Separates "configuring API keys" from "setting default provider"
   - Only saves defaultProvider if user explicitly confirms

2. Respect useDefaults Option:
   - Now checks options.useDefaults === false
   - Forces prompt when --no-use-defaults is passed
   - Gives users explicit control over smart defaults behavior

3. Smart Save Logic:
   - Only saves choices as defaults when user was prompted due to missing defaults
   - Does NOT save when user passes --select-provider (one-time override)
   - Does NOT save when user passes --provider xyz (explicit choice)
   - Does NOT save when using existing defaults (unnecessary re-save)

BEHAVIOR:
Before: Initial setup → auto-select provider next run
After:  Initial setup → ask "set as default?" → respect user choice

Before: --select-provider → select → save as new default
After:  --select-provider → select → one-time override only

Before: --no-use-defaults → ignored
After:  --no-use-defaults → always prompt

Tests: All 51 tests pass
…e defaults

PROBLEM:
Previous attempt was too complex with conditional save logic, useDefaults flags,
and setupDefaultPreferences. User feedback: "唔想搞咁複雜" (don't want it so complex).

SOLUTION - Simple & Explicit (Always Ask):
1. API keys → saved (sensitive data, configure once)
2. Provider/Agent selection → always prompt each run
3. Don't want prompts → use --provider/--agent args
4. Never save runtime choices as defaults

CHANGES:
- selectRuntimeChoices: Removed all smart defaults logic, always prompts
- initialSetup: Only configures API keys, no default selection
- Removed setupDefaultPreferences function entirely
- Removed "last used" hints from prompts
- Removed all defaultProvider/defaultAgent save logic

BEHAVIOR:
Before: Smart defaults, conditional saves, complex logic
After:  Always ask unless explicit args provided

Example:
  sylphx-flow "prompt"                    → prompts for provider & agent
  sylphx-flow --provider default "prompt" → uses default, no prompt
  sylphx-flow --agent coder "prompt"      → uses coder, no prompt

Tests: All 51 tests pass
ASSUMPTION: Package must work standalone when installed globally

Added missing dependencies required for global installation:
- react, ink: UI components (fixes "Cannot find module 'react/jsx-dev-runtime'")
- drizzle-orm, @libsql/client: Database operations
- @modelcontextprotocol/sdk: MCP features
- @lancedb/lancedb: Vector storage
- @huggingface/transformers: Tokenization
- chokidar: File watching
- ignore: Gitignore parsing
- ai: AI SDK features

Without these, sylphx-flow fails when installed via `bun install -g @sylphx/flow`
because package only installs dependencies from its own package.json.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…tory creation

Changed the directory creation in saveHomeSettings to use USER_CONFIG_DIR instead of constructing the path from USER_SETTINGS_FILE. This ensures the correct directory is created for user settings.
@vercel
Copy link

vercel bot commented Nov 14, 2025

@shwuhk is attempting to deploy a commit to the Sylphx Team on Vercel.

A member of the Team first needs to authorize it.

@shtse8 shtse8 force-pushed the main branch 2 times, most recently from f53847e to c489f71 Compare December 2, 2025 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants