TidGi-Desktop/docs/features/AgentInstanceWorkflow.md
lin onetwo 64cc965a4a
Feat/discuss note with agent (#685)
* 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 &lt;/&gt; entities to prevent reintroducing HTML tags
- Decode &amp; last to avoid double-unescaping (&amp;lt;  &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
2026-03-09 04:07:39 +08:00

13 KiB
Raw Blame History

AgentInstance and the plugin-based workflow

This document explains how an agentInstance invokes a handler and how logic is composed via plugins to enable strategy-like processing. It covers message persistence, streaming updates, tool calling, and second-round handoff.

Overview

  • Entry: IAgentInstanceService.sendMsgToAgent receives user input.
  • Orchestrator: basicPromptConcatHandler drives prompt concatenation, AI calls, and plugin hooks.
  • Plugins: createHooksWithPlugins attaches plugins to unified hooks with shared context, enabling decoupled, replaceable strategies.
  • Data: message model AgentInstanceMessage, status model AgentInstanceLatestStatus.

Handler selection and registration

  • Source of handlerID: prefer the instances handlerID, fallback to the agent definitions handlerID (see src/pages/Agent/store/agentChatStore/actions/agentActions.ts#getHandlerId and the preferences hook useHandlerConfigManagement.ts).
  • Backend registration: in AgentInstanceService.initialize(), registerBuiltinHandlers() registers basicPromptConcatHandler under the ID basicPromptConcatHandler; initializePluginSystem() registers built-in plugins.
  • Runtime selection: inside sendMsgToAgent(), the handler is fetched from this.agentHandlers by agentDef.handlerID and started as an async generator const generator = handler(handlerContext), then iterated with for await (const result of generator).

Related code:

  • index.ts: initialize(), registerBuiltinHandlers(), sendMsgToAgent()
  • taskAgent.ts: main handler (iterative while-loop)

Sequence

sequenceDiagram
  autonumber
  participant User as User
  participant AISvc as IAgentInstanceService
  participant Handler as basicPromptConcatHandler
  participant Hooks as Plugins(Hooks)
  participant API as External API

  User->>AISvc: sendMsgToAgent(text,file)
  AISvc-->>Handler: append to agent.messages
  Handler->>Hooks: userMessageReceived
  Hooks-->>AISvc: saveUserMessage / debounceUpdateMessage
  Handler->>Hooks: agentStatusChanged(working)
  loop generation and streaming updates
    Handler->>AISvc: concatPrompt(handlerConfig, messages)
    AISvc-->>Handler: flatPrompts
    Handler->>API: generateFromAI(flatPrompts)
    API-->>Handler: update(content)
    Handler->>Hooks: responseUpdate(update)
    Hooks-->>AISvc: debounceUpdateMessage
  end
  API-->>Handler: done(final content)
  Handler->>Hooks: responseComplete(done)
  alt plugin requests next round
    Hooks-->>Handler: actions.yieldNextRoundTo = self
    Handler->>Handler: append messages and continue flow
  else return to user
    Handler-->>AISvc: completed(final)
  end

Key design points

1. Event-driven strategy composition

createHooksWithPlugins exposes unified hooks: processPrompts, userMessageReceived, agentStatusChanged, responseUpdate, responseComplete, toolExecuted. Plugins subscribe as needed and compose different strategies without changing the main flow.

Plugin registration and wiring:

  • At app init, initializePluginSystem() registers built-in plugins to a global registry.
  • For each round, createHooksWithPlugins(handlerConfig) creates a fresh hooks instance and attaches plugins per config.
  • responseConcat() and promptConcat also look up builtInPlugins and run plugin logic (e.g., postProcess) with a dedicated context.

Stateless plugins requirement:

  • Plugins must be stateless. Do not persist cross-round or cross-session state inside closures.
  • All state must travel through context (e.g., handlerContext.agent.messages, metadata).
  • Plugins may be registered to multiple hooks across conversations and then discarded; internal mutable state risks races and contamination.

2. Messages as the source of truth

User, assistant, and tool result messages are all AgentInstanceMessage. duration limits how many subsequent rounds include a message in context. UI and persistence coordinate via saveUserMessage and debounceUpdateMessage.

Persistence and UI updates:

User messages: messageManagementPlugin.userMessageReceived persists via IAgentInstanceService.saveUserMessage, pushes into handlerContext.agent.messages, and calls debounceUpdateMessage to notify UI. Streaming updates: responseUpdate maintains an in-progress assistant message (metadata.isComplete=false) with debounced UI updates. Finalization: responseComplete persists the final assistant message and updates UI once more. Tool results: toolExecuted persists messages with metadata.isToolResult and sets metadata.isPersisted to avoid duplicates.

3. Second-round handoff and control

Plugins may set actions.yieldNextRoundTo = 'self' in responseComplete to trigger another LLM round immediately. The handler stops after reaching retry limits and returns the final result.

concatPrompt and prompt delivery:

AgentInstanceService.concatPrompt exposes an observable stream for prompt assembly. The handler uses getFinalPromptResult to obtain final prompts before calling the external API.

Example plugins

messageManagementPlugin

Responsibilities:

Persist user messages in userMessageReceived and sync UI. Manage streaming assistant message in responseUpdate; persist final content in responseComplete. Update status in agentStatusChanged. Persist tool results in toolExecuted and mark as persisted.

Notes:

Update handlerContext.agent.messages in place for immediate UI rendering. Use debounced updates to reduce re-renders. Mark streaming messages with metadata.isComplete.

wikiSearchPlugin

Responsibilities:

Inject available wiki workspaces and tool list in processPrompts. On responseComplete, detect tool calls, execute, produce isToolResult message with duration=1. Set actions.yieldNextRoundTo = 'self' to continue immediately with tool outputs.

Notes:

Validate parameters with zod. Use messages as the carrier for tool I/O. Set duration=1 for tool-call assistant messages to economize context.

Tool calling details:

Parse: detect tool-call patterns via matchToolCalling in responseComplete. Validate & execute: validate with zod, then executeWikiSearchTool uses workspace and wiki services to fetch results. History: create an isToolResult message (role: 'user', duration=1) for the next round; report via hooks.toolExecuted.promise(...) so messageManagementPlugin persists and notifies UI. Loop: set actions.yieldNextRoundTo='self' to continue another round using tool outputs.

Flow

flowchart TD
  A[User input] --> B[sendMsgToAgent]
  B --> C[Message enqueued to agent.messages]
  C --> D[userMessageReceived persist + UI]
  D --> E[agentStatusChanged = working]
  E --> F[concatPrompt generate prompts]
  F --> G[generateFromAI streaming]
  G --> H[responseUpdate update UI]
  H --> I{responseComplete}
  I -->|tool call| J[Execute tool and write tool result message]
  J --> K[actions.yieldNextRoundTo=self]
  K --> F
  I -->|plain reply| L[Complete and return to UI]

New architecture additions (2025-02)

Iterative while-loop (replacing recursion)

The handler uses a while loop instead of recursive generator calls. This prevents stack overflow for long agentic loops and makes the control flow easier to follow.

Parallel tool execution

When the LLM wraps multiple <tool_use> calls inside <parallel_tool_calls>, the framework executes them concurrently using a custom executeToolCallsParallel() utility:

  • Does NOT use Promise.all (which would reject on first failure).
  • Each tool gets its own timeout (configurable per-tool or using the global default).
  • Results are collected for all tools (success, failure, and timeout), similar to Promise.allSettled.

Related code:

Tool approval mechanism

Tools can be configured with approval rules:

  • auto: execute immediately without user confirmation
  • confirm: pause and show an inline approval UI; the user must allow or deny
  • Regex patterns: allowPatterns auto-approve matching calls, denyPatterns auto-deny
  • Evaluation order: denyPatterns → allowPatterns → mode

Settings are configurable via the "Tool Approval & Timeout Settings" modal in Preferences → AI Agent.

Related code:

Sub-agent support

The spawn-agent tool creates child AgentInstance instances:

  • Marked with isSubAgent: true and parentAgentId in the database
  • Hidden from the default user-facing agent list
  • Run independently with their own conversation and tools
  • Return their final result to the parent agent as a tool result

Token estimation and context window

  • Approximate token counting via character heuristics (4 chars/token for Latin, 1 char/token for CJK)
  • TokenBreakdown splits context into: system, tools, user, assistant, tool results
  • Pie chart UI component shows usage ratio with warning/danger thresholds
  • Future: API-based precise token counting

API retry with exponential backoff

Uses the exponential-backoff npm package with:

  • Configurable max attempts, initial delay, max delay, backoff multiplier
  • Full jitter to prevent thundering herd
  • Retryable error detection (429, 5xx, network errors)
  • Retry-After header support

MCP integration

Each agent instance creates its own MCP client connection(s):

  • Supports both stdio and SSE transports
  • Client connections are managed per-instance and cleaned up on agent close
  • MCP tools are dynamically discovered and injected into the prompt

New tools

Tool ID Description
summary Terminates agent loop with a final answer
alarm-clock Schedules a future self-wake
ask-question Pauses to ask user a clarifying question with options
wiki-backlinks Find tiddlers linking to a given tiddler
wiki-toc Get tag tree hierarchy
wiki-recent Recently modified tiddlers
wiki-list-tiddlers Paginated tiddler list (skinny data)
wiki-get-errors Render tiddler and check for errors
zx-script Execute zx scripts in wiki context
web-fetch Fetch external web content
spawn-agent Delegate sub-task to a new agent instance

Frontend improvements

  • Virtualization: MessagesContainer uses react-window VariableSizeList for conversations with 50+ messages
  • Lazy loading: Messages load by ID; content fetched from store only when rendered
  • React.memo: MessageBubble wrapped with memo to reduce re-renders during streaming
  • WikitextMessageRenderer: Renders wikitext via TiddlyWiki server with streaming opacity
  • AskQuestionRenderer: Interactive inline UI for agent questions with clickable options
  • ToolApprovalRenderer: Inline allow/deny buttons for tool approval requests

Benefits

Loose coupling: the main flow stays unchanged while capabilities are pluggable. Testability: plugins can be unit-tested and integration-tested with the handler. Evolvability: new capabilities land as new plugins and hook subscriptions.

Notes

Avoid double persistence; use metadata flags for dedup. Ensure idempotency and robust error handling; prefer UI updates over persistence when degrading. Control retry limits and exit conditions to avoid infinite loops.