* Add wiki tiddler attachment support to agent chat Implements the ability to attach wiki tiddlers to agent chat messages. Updates the UI to allow selection of tiddlers from active wiki workspaces, fetches and renders tiddler content as plain text, and appends it to the user message sent to the AI. Includes e2e tests, updates to store actions, service interfaces, and prompt concatenation logic to support this feature. * fix: callback not useCallback cause autocomplete panel flash * Add wiki tiddler attachments to message bubbles Message bubbles now display attached wiki tiddlers as clickable chips, allowing users to navigate directly to the referenced tiddler in the appropriate workspace. Metadata handling and persistence for wiki tiddlers has been updated to include workspaceId, and tests have been added to verify the new UI behavior. The chat view also now closes the TiddlyWiki sidebar for better focus when navigating from a selection. * Support split view navigation for wiki tiddler attachments Adds isSplitView prop to ChatTabContent and related components to distinguish between split view and normal tab modes. Wiki tiddler attachment navigation now uses a different strategy in split view, opening tiddlers directly in the browser view. Updates types and tests to reflect the new behavior, and improves robustness of response handling in several places. * docs: move to .github/instructions/testing.instructions.md * test: view loading slow on mac * refactor(e2e): move wiki load steps to Background in talkWithAI.feature; remove all sidebar close delays and polling, only set state when TiddlyWiki is ready; clean up code and logs for sidebar auto-close in split view * docs: make test inst shorter * lint * refactor(view): slim ViewService, move menu to separate file, orchestrate view logic in WorkspaceViewService, update all callers, fix lint floating promise, all unit and e2e tests pass * fix: add data-testid to attachment listbox for E2E test - Add slotProps to MUI Autocomplete to ensure attachment-listbox is rendered with correct test-id - Fix E2E test timeout when waiting for attachment listbox element * lint * put 'Talk with AI' menu on top and attachment i18n Introduce a reusable createTalkWithAIMenuItems helper to build "Talk with AI" menu entries (default agent + other agents submenu) and integrate it into workspace menu generation. Add new i18n keys for Agent.Attachment and WikiEmbed across locales and update UI to use translation keys (remove hardcoded fallback strings). Improve chat input/attachment behavior: expose a test-id for the attachment listbox, use i18n for labels/placeholders, and tweak input component wiring. Fix Cucumber step handling by normalizing expected newline sequences and safely handling empty message content. Also adjust memo deps in SortableWorkspaceSelectorButton to include id. * feat: enhance AI interaction in workspace context menu with local trigger support * feat: add tool approval and timeout settings - Introduced ToolApprovalConfig and related types for managing tool execution approvals. - Implemented WebFetch and ZxScript tools for fetching web content and executing scripts, respectively. - Added token estimation utilities for context window management. - Enhanced ModelInfo interface with context window size and max output tokens. - Created API Retry Utility for handling transient failures with exponential backoff. - Updated AIAgent preferences section to include Tool Approval & Timeout Settings dialog. - Developed ToolApprovalSettingsDialog for configuring tool-specific approval rules and retry settings. - Modified vitest configuration to support aliasing for easier imports and stubbing. * Refactor agent instance tools and services for improved modularity and maintainability - Extracted type definitions and tool registry from defineTool.ts into separate files (defineToolTypes.ts, toolRegistry.ts) to reduce file size and enhance importability. - Implemented a retry mechanism in ExternalAPIService for stream creation to handle transient failures. - Updated ToolApprovalSettingsDialog to persist settings using localStorage instead of a preference service. - Created agentMessagePersistence.ts and agentRepository.ts to manage agent message and instance CRUD operations, reducing the size of AgentInstanceService. - Added a progress tracker document (AgentTODO.md) for the ongoing enhancement plan of the TidGi Agent. * feat: add AgentSwitcher component for agent definition switching - Implemented AgentSwitcher component with dropdown functionality for selecting agent definitions. - Integrated loading of agent definitions on dropdown open. - Added visual feedback for current selection and disabled state. feat: create ToolResultRenderer for generic tool result messages - Developed ToolResultRenderer to handle rendering of <functions_result> messages. - Included collapsible parameters and result display with error handling. - Added truncation for long results in collapsed view. test: add comprehensive tests for MessageRenderer components - Implemented tests for AskQuestionRenderer, ToolResultRenderer, ToolApprovalRenderer, and BaseMessageRenderer. - Ensured proper rendering and functionality for various message types and states. - Included pattern routing tests for MessageRenderer. feat: introduce TurnActionBar for action management in agent turns - Created TurnActionBar component for managing actions like rollback, retry, delete, and copy. - Integrated visual feedback for file changes and rollback status. - Added functionality for copying agent responses and full conversation to clipboard. feat: implement askQuestionPending for managing user responses - Developed infrastructure for handling pending ask-question requests. - Implemented promise-based blocking until user responds to agent questions. - Added timeout handling for ask-question requests. * feat: Implement background task management for agent instances - Added functionality to restore heartbeat timers and alarms for active agents upon service initialization. - Introduced methods to retrieve active background tasks and cancel them via the UI. - Enhanced alarm clock tool to persist alarm data in the database, ensuring alarms survive app restarts. - Updated agent instance schema to include scheduled alarm data. - Modified prompt concatenation logic to support context window size for message history. - Removed system prompt parameter from model parameters schema and related components. - Improved UI to display and manage background tasks, including heartbeat and alarm details. * feat: Implement Scheduled Tasks Management and Background Task Settings - Add feature for managing scheduled tasks for agents, including viewing, adding, and editing tasks. - Create tests for agent repository and background task settings APIs. - Introduce ScheduledTaskManager for unified scheduling of tasks with interval, at, and cron schedules. - Implement edit-agent-definition tool for modifying agent configurations, including heartbeat and prompt settings. - Ensure tasks persist across app restarts and respect active hours filtering. * Update wiki * fix(security): harden htmlToText against XSS via encoded tags - Use tolerant script/style regex that handles </script > with spaces - Stop decoding </> entities to prevent reintroducing HTML tags - Decode & last to avoid double-unescaping (&lt; < <) - Fixes all 4 CodeQL findings (bad filtering, incomplete sanitization, double escaping, incomplete multi-character sanitization) * fix: lint and format errors (eslint naming, dprint formatting, stub classes) * fix: exclude tidgi.config.json from template copy and fix log marker pattern - Skip tidgi.config.json when copying wiki template to prevent overriding workspace name - Change waitForLogMarker default pattern from 'wiki-' to '*' to match actual log filenames - Filter tidgi.config.json in e2e step too (fs.copy in wiki creation step) * perf(e2e): merge 5 scheduledTask scenarios into 2 to reduce app restarts * perf(e2e): merge crossWindowSync and streamingStatus scenarios to reduce CI time * ci: increase test timeout to 20min for larger e2e scenario count * perf(e2e): merge agent scenarios and enable parallel on CI - Merge agent.feature wiki-search + wiki-operation into one scenario - Merge agent.feature create-agent-from-newtab + create-agent-from-fallback into one - Enable cucumber parallel: 2 on CI (7GB RAM, dynamic ports for mock servers) - Total scenarios: 66 -> 61 * fix: cleanup MCP client processes when deleting or closing agent * ci: disable parallel (CPU contention), increase timeout to 30min for 61 scenarios * perf(e2e): merge 3 preference background-task scenarios into 1, increase timeout to 45min - Preference alarm/heartbeat CRUD merged into single scenario (saves 2 app restarts) - Total scenarios: 59 (was 66) - CI timeout 45min for the expanded e2e suite * fix: restructure e2e scenarios, fix subscription leak and debounce cleanup - Restructure agent/talkWithAI/streamingStatus feature files for reliability - Fix talkWithAI scenarios missing mock server startup and app launch - Remove duplicate subscription in handleSwitchAgent (useEffect already handles it) - Clean up debounced update functions when agent is deleted/closed - Use agentId:messageId key for debounced functions to enable per-agent cleanup * fix(e2e): remove :last-child selectors broken by TurnActionBar, fix tab/dialog selectors, increase CI timeout to 25min * fix: use const for non-reassigned variable (lint) * fix(e2e): fix close-all-tabs opacity issue, scheduledTask undefined steps, MUI multiline textarea targeting * fix(e2e): use tab-list-dropdown to close all tabs, fix selector for actual TabListDropdown component * fix(e2e): add timing waits for BrowserView repositioning and git log UI refresh * fix(e2e): make 'should not see' step wait for element to disappear instead of instant check * fix(e2e): increase executeInBrowserView default timeout from 500ms to 2000ms
12 KiB
Sync Architecture: IPC and Watch-FS Plugins
This document describes how the tidgi-ipc-syncadaptor (frontend) and watch-filesystem-adaptor (backend) plugins work together to provide real-time bidirectional synchronization between the TiddlyWiki in-memory store and the file system.
Architecture Overview
Frontend (Browser) Backend (Node.js Worker)
┌─────────────────────────────┐ ┌─────────────────────────────────────┐
│ TiddlyWiki │ │ TiddlyWiki (Server) │
│ In-Memory Store │ │ In-Memory Store │
└──────────┬──────────────────┘ └──────────┬──────────────────────────┘
│ │
│ TidGiIPCSyncAdaptor │ WatchFileSystemAdaptor
│ (syncadaptor) │ (syncadaptor)
│ │
│ ├── FileSystemWatcher
│ │ (monitors files via nsfw)
│ │
├─── Save via IPC ──────────────────►│
│ │
│◄────── IPC Observable ─────────────┤
│ (Change Events) │
│ │
│ ├── FileSystemAdaptor
│ │ (read/write files)
│ │
│ ▼
│ File System
└────────────────────────────────────┘
Key Design Principles
1. Single Source of Truth: File System
- Backend watch-fs monitors the file system using
nsfwlibrary - All file changes (external edits, saves from frontend) flow through file system
- Backend wiki state reflects file system state
2. Syncer-Driven Updates (Refactored Architecture)
Previous Approach (Problematic):
- FileSystemWatcher directly called
wiki.addTiddler()when files changed - Led to echo problems and complex edge case handling
Current Approach (Syncer-Driven):
- FileSystemWatcher only collects changes into
updatedTiddlerslist - Triggers
$tw.syncer.syncFromServer()to let TiddlyWiki's syncer handle updates - Syncer calls
getUpdatedTiddlers()to get change list - Syncer calls
loadTiddler()for each modified tiddler - Syncer uses
storeTiddler()which properly updates changeCount to prevent echo
Benefits:
- Leverages TiddlyWiki's built-in sync queue and throttling
- Proper handling of batch changes (git checkout)
- Eliminates echo loops via syncer's changeCount tracking
3. Two-Layer Echo Prevention
IPC Layer (First Defense):
ipcServerRoutes.tstracks recently saved tiddlers inrecentlySavedTiddlersSet- When
wiki.addTiddler()triggers change event, filter out tiddlers in the Set - Prevents frontend from receiving its own save operations as change notifications
Watch-FS Layer (Second Defense):
- When saving/deleting, watch-fs temporarily excludes file from monitoring
- Prevents watcher from detecting the file write operation
- Re-includes file after operation completes (with delay for nsfw debounce)
4. IPC Observable for Change Notification
- Backend sends change events to frontend via IPC Observable pattern (via
ipc-cat) - Frontend subscribes to
getWikiChangeObserver$observable fromwindow.observables.wiki - Change events trigger frontend's
syncFromServer()to pull updates
Module Responsibilities
FileSystemWatcher (Backend - New)
Purpose: Monitor file system changes without directly modifying wiki state
Key Features:
- Uses
nsfwlibrary for native file system watching - Maintains
updatedTiddlerslist for pending changes - Implements
getUpdatedTiddlers()for syncer integration - Implements
loadTiddler()for lazy loading from file system - Handles git revert/checkout via delayed deletion processing
- Manages file exclusion list for echo prevention
Key Methods:
getUpdatedTiddlers(syncer, callback): Returns collected changesloadTiddler(title, callback): Loads tiddler content from fileexcludeFile(path): Temporarily exclude file from watchingscheduleFileInclusion(path): Re-include file after delay
WatchFileSystemAdaptor (Backend)
Purpose: Coordinate between FileSystemWatcher and syncer, implement syncadaptor interface
Key Features:
- Extends FileSystemAdaptor for file save/delete operations
- Delegates file watching to FileSystemWatcher
- Implements full syncadaptor interface for Node.js syncer
- Coordinates file exclusion during save/delete operations
Key Methods:
getUpdatedTiddlers(): Delegates to FileSystemWatcherloadTiddler(): Delegates to FileSystemWatchersaveTiddler(): Saves to file with exclusion handlingdeleteTiddler(): Deletes file with exclusion handling
FileSystemAdaptor (Backend - Base Class)
Purpose: Handle tiddler file save/delete operations with sub-wiki routing
Key Features:
- Routes tiddlers to sub-wikis based on tags
- Generates file paths using TiddlyWiki's FileSystemPaths
- Handles external attachment file movement
- Provides retry logic for file lock errors
TidGiIPCSyncAdaptor (Frontend)
Purpose: Bridge between frontend TiddlyWiki and backend file system
Key Features:
- Communicates via IPC using
tidgi://custom protocol - Subscribes to change events via IPC Observable
- Maintains
updatedTiddlerslist from IPC events - Implements full syncadaptor interface for browser syncer
Data Flow Examples
Example 1: User Edits in Frontend
1. User clicks save in browser
├─► Frontend syncer calls saveTiddler()
│
2. TidGiIPCSyncAdaptor.saveTiddler()
├─► IPC call to putTiddler in ipcServerRoutes.ts
│
3. ipcServerRoutes.putTiddler()
├─► Marks tiddler in recentlySavedTiddlers (IPC echo prevention)
├─► Calls wiki.addTiddler() (triggers change event)
├─► Change event filtered by recentlySavedTiddlers
│
4. Backend syncer detects change
├─► Calls WatchFileSystemAdaptor.saveTiddler()
│
5. WatchFileSystemAdaptor.saveTiddler()
├─► Excludes file path from watching
├─► Calls FileSystemAdaptor.saveTiddler()
├─► Writes file to disk
├─► Schedules file re-inclusion after delay
│
6. nsfw might detect file change
├─► FileSystemWatcher checks exclusion list
├─► File is excluded, event ignored
Example 2: External Editor Modifies File
1. User edits file in VSCode/Vim
├─► File content changes on disk
│
2. nsfw detects file change
├─► File NOT in excludedFiles
│
3. FileSystemWatcher.handleFileAddOrChange()
├─► Adds title to updatedTiddlers.modifications
├─► Stores file info in pendingFileLoads
├─► Schedules syncer trigger (debounced 200ms)
│
4. $tw.syncer.syncFromServer() called
├─► Creates SyncFromServerTask
│
5. SyncFromServerTask.run()
├─► Calls getUpdatedTiddlers()
├─► Gets modifications/deletions list
├─► Adds titles to titlesToBeLoaded
│
6. For each title to load:
├─► LoadTiddlerTask.run()
├─► Calls loadTiddler(title)
├─► FileSystemWatcher loads from file
├─► syncer.storeTiddler() updates wiki
├─► Properly sets changeCount (prevents echo save)
│
7. wiki.addTiddler() triggers change event
├─► getWikiChangeObserver sends to frontend
│
8. Frontend receives change
├─► TidGiIPCSyncAdaptor adds to updatedTiddlers
├─► Frontend syncer.syncFromServer()
├─► Frontend loadTiddler() via IPC
├─► Frontend wiki updated
Example 3: Git Checkout (Batch Changes)
1. User runs git checkout
├─► Many files deleted/created/modified
│
2. nsfw debounces events (100ms)
├─► Multiple events batched together
│
3. FileSystemWatcher.handleNsfwEvents()
├─► For each DELETED file:
│ └─► Schedule deletion with 100ms delay
│ (handles git revert/checkout pattern)
├─► For each CREATED/MODIFIED file:
│ └─► Cancel any pending deletion for same path
│ └─► Add to updatedTiddlers.modifications
│
4. Syncer trigger debounced (200ms)
├─► All changes collected before sync starts
│
5. Single SyncFromServerTask processes all changes
├─► All modifications queued for loading
├─► All deletions processed (wiki.deleteTiddler)
│
6. LoadTiddlerTasks process sequentially
├─► Each tiddler loaded from file
├─► Frontend notified via IPC Observable
Key Configuration
Timing Constants
// FileSystemWatcher.ts
FILE_DELETION_DELAY_MS = 100; // Delay before processing DELETE events
FILE_INCLUSION_DELAY_MS = 150; // Delay before re-including file after save
GIT_NOTIFICATION_DELAY_MS = 1000; // Debounce for git status notification
SYNCER_TRIGGER_DELAY_MS = 200; // Debounce for syncer trigger
Syncer Configuration
- Frontend:
pollTimerInterval = 2_147_483_647(effectively disabled) - All updates come via IPC Observable (event-driven)
Troubleshooting
Changes Not Appearing in Frontend
- Check IPC Observable connection: Look for
[test-id-SSE_READY]in logs - Verify watch-fs is running: Look for
[test-id-WATCH_FS_STABILIZED] - Check file exclusion: Should see file being excluded then included
Echo/Duplicate Updates
- Check
recentlySavedTiddlersfiltering in ipcServerRoutes.ts - Verify file exclusion during save/delete operations
- Check syncer's changeCount tracking
Git Checkout Issues
- Ensure FILE_DELETION_DELAY_MS is working (files not prematurely deleted)
- Check that SYNCER_TRIGGER_DELAY_MS allows batch collection
- Verify syncer processes all changes in single SyncFromServerTask
Sub-Wiki Not Syncing
- Check sub-wiki watcher initialization: Look for
[WATCH_FS_SUBWIKI]logs - Verify tiddlywiki.info has correct configuration
- Check workspace
subWikiFolderssetting
Design Decisions
Why Syncer-Driven Instead of Direct Updates?
- Echo Prevention: Syncer's
storeTiddler()properly updates changeCount, preventing save loops - Batch Handling: Syncer queues all changes and processes them sequentially
- Throttling: Built-in throttle prevents rapid-fire saves
- Error Recovery: Syncer has built-in retry logic
Why Two-Layer Echo Prevention?
- IPC Layer: Prevents frontend from seeing its own saves via IPC
- Watch-FS Layer: Prevents file watcher from seeing our own file writes
- Both needed: IPC prevents wiki→wiki echo, Watch-FS prevents file→wiki echo
Why Delay DELETE Events?
Git operations often delete then recreate files quickly. The delay allows:
- CREATE event to arrive and cancel pending DELETE
- Treat as modification instead of delete+create
- Prevents "missing tiddler" errors during git operations