feat: modular .agents config + unified Agents (no user profiles)#1108
feat: modular .agents config + unified Agents (no user profiles)#1108aj47 wants to merge 45 commits into
Conversation
…persistence Introduce a canonical .agents/ directory structure for storing agent configuration as discrete, workspace-shareable files with simple key:value frontmatter. ## New infrastructure (apps/desktop/src/main/agents-files/) - frontmatter.ts: Simple key:value frontmatter parser/serializer (no YAML dep) - safe-file.ts: Atomic writes (temp+rename), timestamped backups with rotation, auto-recovery from backup on JSON parse failure - modular-config.ts: AgentsLayerPaths type, global+workspace overlay semantics, config splitting/merging utilities - memories.ts: Memory .md file read/write with frontmatter metadata - skills.ts: Skill .md file read/write, directory scanning, backup support - Full test coverage for all modules (5 test files) ## Skills migration to .agents canonical storage - SkillsService now loads/writes skills from .agents/skills/**/skill.md - Global + workspace overlay (workspace overrides global by ID) - Legacy skills.json auto-migrated on first load (non-destructive) - All CRUD operations use atomic writes with backup+rollback - initializeBundledSkills() copies to .agents/skills/ instead of legacy folder - File watcher watches .agents/skills dirs (global + workspace) - openSkillsFolder IPC opens .agents/skills directory ## Memory service migration - MemoryService now uses .agents/memories/*.md as canonical storage - Global + workspace overlay semantics ## Config updates - config.ts exports globalAgentsFolder and resolveWorkspaceAgentsFolder() - System prompts refactored to support modular config All existing method signatures preserved. TypeScript compilation and all 111 unit tests pass.
Previously, getConfig() skipped loading config.json entirely when .agents files existed. This caused settings loss (API keys, preferences) because the .agents files were seeded with defaults on first run, then treated as the exclusive source on subsequent runs. Now the merge order is: defaults ← config.json ← .agents (if present). This ensures existing config.json settings are always preserved while .agents files can selectively override specific values.
Phase 3: Migrate all main process callers from profileService → agentProfileService - tipc.ts: All ~56 profileService references migrated - Session snapshots (2 locations) - Profile CRUD, setCurrentProfile, export/import - MCP/model config handlers - Skill auto-enable + management handlers - Memory handlers - builtin-tools.ts: All 7 tool handlers migrated - remote-server.ts: All REST API endpoints migrated - llm.ts, mcp-service.ts, loop-service.ts: Already migrated Phase 4: IPC handler consolidation - Legacy handlers (getProfiles, etc.) now call agentProfileService - Unified handlers (getAgentProfiles, etc.) remain canonical - No duplicate logic - legacy handlers use backward-compat wrappers Phase 5: Merge UI - New settings-agents.tsx with tabbed view (User Profiles, Personas, External Agents) - Updated router.tsx with redirects from old paths - Updated app-layout.tsx sidebar (2 entries → 1 'Agents' entry) - Updated sidebar-profile-selector.tsx to use unified types REST API: Added ?role= filter to /v1/agent-profiles endpoint Legacy /v1/profiles endpoints kept as aliases for backward compat
…stem - Remove user profiles concept entirely; settings are now global - Rebrand 'Persona' to 'Agent' in all user-facing text and comments - Remove 7 profile built-in tools (list/switch/get/create/update/delete/duplicate) - Remove profile management UI from settings-tools, settings-loops, settings-skills - Rewrite settings-agents.tsx as flat Agents list (no more 3-tab view) - Remove sidebar profile selector and dead files (profile-manager, settings-agent-personas, settings-external-agents) - Fix memory system: remove profile requirement from delete operations - Fix memories page: remove profile badge and profile-scoped queries - Fix LLM memory loading: use getAllMemories() instead of profile-filtered - Fix getCurrentProfile() fallback to find any default agent - Rename personaProperties → agentProperties across types, llm, system-prompts - Rename getAgentPersonasPromptAddition → getAgentsPromptAddition - Update prompt text: 'AVAILABLE AGENT PERSONAS' → 'AVAILABLE AGENTS'
…ies tabs - Add tabbed edit form: General, Behavior, Model, Tools, Skills, Properties - Model tab: LLM provider selector (openai/groq/gemini) with model picker - Tools tab: per-agent MCP server enable/disable with opt-in/opt-out mode - Skills tab: per-agent skill toggles from available skills - Properties tab: key-value editor for dynamic system prompt variables - Enhanced agent list cards with server/skill/property count badges - All config saved via updateAgentProfile IPC handler
… concept - Rename built-in 'General Assistant' to 'Main Agent' as the primary default agent - Remove legacy 'Default' user-profile entry from DEFAULT_PROFILES - Main Agent gets isDefault: true and owns .agents/agents.md guidelines - Update setCurrentProfile to accept any agent (not just user-profiles) - Update importProfile and createUserProfile to create delegation-targets - Use Main Agent for skills config in non-agent mode (llm.ts) - Remove isUserProfile filter from settings-agents.tsx - Remove legacy profile cleanup: profile-badge.tsx deleted, ~200 lines of per-profile UI logic removed from mcp-config-manager, settings-skills, predefined-prompts-menu, settings-mcp-tools, settings-providers - Update DEBUGGING.md with two-protocol documentation
Adds an AGENT RESOURCES section to AGENT_MODE_ADDITIONS that teaches every agent how to discover, manage, and use their resources: - Tools: list/explore/inspect/toggle MCP servers and tools - Skills: load instructions, create new skills, run in skill dirs - Memories: save/list/delete persistent cross-session knowledge - Loops: understand scheduled recurring agent tasks
…llback - internal-agent.ts: Remove duplicate createProfileSnapshotFromAgentProfile(), use canonical createSessionSnapshotFromProfile() from agent-profile-service.ts. Fixes: allServersDisabledByDefault defaulting to true (broke Main Agent delegation), profileId prefix with 'agent_', and missing profileName fallback. - agent-profile-service.ts: getCurrentProfile() now falls back to isBuiltIn profile when no isDefault or currentProfileId is set. Handles migrated data where isDefault wasn't propagated from DEFAULT_PROFILES.
…ools call The previous architecture fired two parallel LLM API calls on every agent iteration: - makeLLMCallWithStreaming (streamText, no tools) — display only - makeLLMCallWithFetch (generateText, with tools) — actual tool execution Because these were independent API calls to the same model, they could produce different outputs due to non-determinism. The user would see one plan streaming in (e.g. 'I'll call load_skill_instructions') while a completely different tool actually executed (e.g. iterm:list_sessions). Fix: replace both calls with a single makeLLMCallWithStreamingAndTools function that uses streamText() with tools enabled. Iterating fullStream gives real-time text-delta events for display and tool-call events for execution from the same model response — divergence is structurally impossible. The old makeLLMCallWithStreaming export is preserved for compatibility but is no longer used in the agent loop.
- Install facehash package for deterministic client-side avatar generation - Redesign agents list as a card grid with facehash avatars - Add avatarDataUrl field to AgentProfile type for custom photo storage - Generate unique per-agent color palettes via djb2 hash over curated palette - Add avatar upload UI in agent edit form (General tab) with remove option - Fall back to colored facehash when no custom photo is set - Store custom avatars as base64 data URLs (CSP-safe, no external requests)
Two fixes in getAvailableToolsForProfile() in mcp-service.ts: 1. Treat empty enabledBuiltinTools array same as undefined (no whitelist). Legacy profiles had enabledBuiltinTools: [] which JS treated as truthy, causing the filter to reject all non-essential builtins. 2. Apply disabledTools filtering only to external MCP tools, not builtins. Builtin availability is controlled by enabledBuiltinTools whitelist. Legacy profiles had all builtin names in disabledTools, which incorrectly filtered them out. Also includes: streaming+tools unification, system prompt improvements, skills service cleanup, and defensive tool-call filtering.
Aligned system prompts with main branch's proactive tool usage guidance: - DEFAULT_SYSTEM_PROMPT: 'Work iteratively until goals are fully achieved' instead of 'do not call tools unnecessarily' - AGENT_MODE_ADDITIONS: 'Continue calling tools until the task is completely resolved' instead of 'Decide first: respond directly (no tools)' - Added 'Prefer tools over asking users' and 'use them proactively' - Tier-3 fallback prompt also updated to match proactive behavior - Restored detailed file/command execution guidance from main
…stroying On macOS, closing the main window (Cmd+W / red button) now hides it instead of destroying it. This keeps the app visible in the Cmd+Tab switcher and dock. The window is re-shown via: - Clicking the dock icon (app 'activate' event) - Keyboard shortcut (Ctrl+Shift+S) The panel window has skipTaskbar:true and focusable:false, so when the main window was destroyed, the app disappeared from Cmd+Tab entirely. Implementation: - Added isAppQuitting flag + setAppQuitting() export in window.ts - Main window 'close' event on macOS: preventDefault + hide (unless quitting) - before-quit handler calls setAppQuitting() to allow actual close - activate handler shows existing hidden window instead of creating new one
1. Delegation priority: rules now appear BEFORE agent list with stronger wording — 'ALWAYS delegate BEFORE responding or asking for clarification' 2. Completion signal: numbered steps enforce respond_to_user BEFORE mark_work_complete; never put final answer in plain assistant text 3. Duplicate skills fix: skip loading profileSkillsInstructions when agentSkillsInstructions already present in profile snapshot 4. Self-delegation fix: added excludeAgentId param to constructSystemPrompt and getAgentsPromptAddition so sub-sessions don't list themselves
|
This pull request is too large for Augment to review. The PR exceeds the maximum size limit of 100000 tokens (approximately 400000 characters) for automated code review. Please consider breaking this PR into smaller, more focused changes. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f3f88241a3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Canonical: write modular `.agents` config (global layer) | ||
| try { | ||
| const globalLayer = getAgentsLayerPaths(globalAgentsFolder) | ||
| writeAgentsLayerFromConfig(globalLayer, this.config, DEFAULT_SYSTEM_PROMPT, { |
There was a problem hiding this comment.
Avoid persisting workspace overrides into global config
Writing this.config directly to the global .agents layer leaks workspace-only overrides into global state, because this.config is loaded from defaults <- config.json <- merged(global, workspace) in getConfig(). In a workspace session, changing any unrelated setting and calling save() will serialize workspace prompt/model/MCP overrides into ~/.agents, so those values unexpectedly appear outside that workspace and break the documented overlay behavior.
Useful? React with 👍 / 👎.
| try { | ||
| const profiles = profileService.getProfiles() | ||
| const currentProfile = profileService.getCurrentProfile() | ||
| const profiles = agentProfileService.getUserProfiles() |
There was a problem hiding this comment.
Return all selectable profiles from /v1/profiles
This endpoint now filters with getUserProfiles(), but new/imported profiles are created as delegation targets (role: "delegation-target", isUserProfile: false), so /v1/profiles can become empty even though /v1/profiles/current still reports an active profile. That makes profile discovery/switching via the mobile-facing profile API inconsistent and can hide profiles that were just imported or created through compatibility flows.
Useful? React with 👍 / 👎.
- Mock electron app.getPath and config exports used by agent-profile-service\n- Normalize indentation in Memories page (remove tabs)
- Auto-derive internal name (slug) from displayName via toAgentSlug() - Remove the separate 'Name (slug)' input from agent settings UI - Users now see and edit a single 'Name' field (the display name) - Replace all displayName||name fallbacks with just displayName - Built-in agents (like main-agent) keep their hardcoded slug
… profileId - Remove ProfileMemoryConfig type and memoryConfig from AgentProfile - Remove Memory tab from agent settings (tool access controls memory ability) - Remove profileId from AgentMemory type (memories are a shared global pool) - Remove isMemoryEnabledForContext gating from builtin tools - Always inject memories into system prompt for all agents - Always auto-save summaries as global memories - Clean up getMemoriesByProfile, profileId filtering from memory service - Clean up profileId params from tipc handlers and remote server API - Remove agent name badge from Memories UI page
… with collapsible sections - Replace separate Tools and Skills tabs with single Capabilities tab - Three collapsible sections: MCP Servers, Built-in Tools, Skills - Each section header shows 'X of Y enabled' summary badge - MCP Servers: per-server expand to show individual tool toggles (uses disabledTools) - Individual tool toggles disabled when parent server is off - Remove confusing 'Disable all servers by default' toggle from UI - Tab count: General, Model, Capabilities, Properties
- New settings-capabilities.tsx with tab switcher (MCP Servers / Skills) - Sidebar: replace separate MCP Tools and Skills links with single Capabilities entry - Old routes (/settings/mcp-tools, /settings/skills) redirect to /settings/capabilities - Route aliases ensure sidebar highlighting works from redirected routes
Previously truncated to 5 tools per server with '+N more' suffix, meaning the LLM couldn't discover or use tools it didn't know existed. Now lists all tool names per server so the agent has full visibility.
- Sidebar: 'Loops' → 'Repeat Tasks' - Route: /settings/repeat-tasks (with redirect from /settings/loops) - Page title: 'Agent Loops' → 'Repeat Tasks' - All button labels, toasts, confirmations, error messages - Conversation title prefix: [Loop] → [Repeat] - Comments and log messages in loop-service, remote-server, index - Internal code names (LoopConfig, loopService, IPC handlers) unchanged - API endpoint paths (/v1/loops) unchanged for mobile app compat
remote-server.ts was passing cfg.mcpMaxIterations ?? 10 directly, ignoring cfg.mcpUnlimitedIterations. This caused the agent to hit the max iteration limit even when unlimited was enabled, when triggered via mobile app or remote API. Now uses the same pattern as tipc.ts: cfg.mcpUnlimitedIterations ? Infinity : (cfg.mcpMaxIterations ?? 10)
743ea1f to
0e30f1d
Compare
…lls-first ordering, macOS dock icon - Sub-sessions now respect mcpUnlimitedIterations config instead of hardcoded 10 - Hide Max Iterations field when Unlimited Iterations is enabled - Add collapsible/defaultCollapsed props to ControlGroup component - Make settings groups collapsible (shortcuts, whatsapp, langfuse collapsed by default) - Reorder capabilities: Skills appears above MCP Servers and Built-in Tools - Fix macOS dock icon disappearing from Cmd+Tab by always restoring on window show
…ce into General, move Agent Settings to top - All ControlGroup sections in settings-general now default collapsed - Merged 'App' (dock, launch, streamer) and 'Appearance' (theme) into single 'General' group - Agent Settings moved to top of the settings page - Order: Agent Settings > General > Modular config > Shortcuts > STT > TTS > Panel > WhatsApp > Langfuse > About
…emories from reappearing The migrateLegacyMemoriesJson() method re-imported memories from the legacy memories.json file on every app restart. If a user deleted a memory (removing its .md file from .agents/memories/), the migration would re-create it from the legacy JSON because the ID was no longer in the existing set. Now the legacy file is removed after a successful migration, preventing deleted memories from being resurrected on restart.
- Make Capabilities tab read-only in settings-agents.tsx (Check/Circle icons) - Remove toggle functions and toolConfig/skillsConfig from handleSave - Add new AgentCapabilitiesSidebar component with full capability controls (skills, MCP servers, built-in tools per agent with Switch toggles) - Place AgentCapabilitiesSidebar in app-layout between Sessions and Settings - Sidebar changes persist immediately via updateAgentProfile IPC
- Add onPlayStateChange callback to AudioPlayer component - Show pulsing Volume2 icon on message header during TTS playback - Show spinning Loader2 icon during audio generation - Click icon to stop TTS via ttsManager.stopAll() - Agents sidebar title click navigates to /settings/agents (matches Sessions pattern) - Chevron still toggles expand/collapse independently - Remove Agents h1 header from settings page, keep Add Agent button
…fresh - Restore toggle functions (toggleServer, toggleTool, toggleBuiltinTool, toggleSkill) in settings-agents - Include toolConfig and skillsConfig in handleSave payload again - Replace read-only Check/Circle icons with interactive Switch controls - Invalidate agentProfilesSidebar query on save/delete so sidebar updates immediately - Add useQueryClient to settings-agents for cross-component cache invalidation
- Sidebar agent name click navigates to /settings/agents?edit=<id> - Chevron still toggles capability expand/collapse independently - settings-agents reads ?edit= search param and auto-opens the edit form - Param is cleared after opening to avoid re-triggering on refresh
- Rename legacy skills.json to skills.json.migrated after migration so it doesn't re-import deleted skills on every app startup - Clean up empty parent directory after deleting a skill file
- Agent profiles stored as .agents/agents/<id>/agent.md + config.json - Repeat tasks stored as .agents/tasks/<id>/task.md - Two-layer merge: global (~/.agents) + workspace (./.agents) - Shadow-sync to legacy JSON files for backward compatibility - Dedicated IPC handlers for loop CRUD (saveLoop, deleteLoop, getLoops) - Migration script to transfer existing profiles.json and config.json loops - Updated agents.md documentation with new directory structure
Supersedes #1107 — this branch grew substantially after the original PR was opened, so this PR is a fresh review surface with an updated summary.
What this PR does
1) Canonical modular
.agents/config (global + workspace overlay).agents/directory structure for agent-related config stored as discrete markdown files with simplekey: valuefrontmatter.2) Skills + memories migrated to
.agents/.agents/skills/<skill-id>/skill.md..agents/memories/<memory-id>.md.3) Agents: unify internal agent + personas + external agents →
AgentProfile4) MCP tool gating cleanup (Option B for built-ins)
Built-in tools (e.g.
speakmcp-settings:*,speakmcp-builtin:*) are now controlled only via an allowlist:enabledBuiltinToolsis an allowlist.enabledBuiltinTools: []is treated as unconfigured → allow all built-ins.disabledTools/mcpDisabledTools.speakmcp-settings:mark_work_complete.This is enforced consistently across:
5) Additional fixes / improvements included on this branch
Testing
pnpm -C apps/desktop run typecheck:nodepnpm -C apps/desktop run test -- run src/main/mcp-service.option-b.test.tsReview notes
This PR is large. Suggested review order:
apps/desktop/src/main/agents-files/*agent-profile-service,settings-agents, router)mcp-service,llm-tool-gating,builtin-tools)Follow-ups (not included)
Pull Request opened by Augment Code with guidance from the PR author