feat: implement EDA long-term memory plugin and upgrade plugin system infrastructure#1870
feat: implement EDA long-term memory plugin and upgrade plugin system infrastructure#1870xiaobin83 wants to merge 17 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1215d52ef2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f554c1bdbe
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
update: I noticed there is runtimeContextProviders added in chat-orchestrator-runtime.ts 5 days ago, which can be used for my injectMemoryContext, I'll change the implementation. I was using onBeforeCompose (added just in the place of runtimeContextProviders). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d67784f9fb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c59f1d806
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
2c59f1d to
85dfcbf
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85dfcbf5a0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce5e8a78cd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13a96c1d8b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
e367d49 to
f046694
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f046694358
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…onfig system, context hooks, and example plugin - stage-tamagotchi: add PluginHost config system with schema validation, Eventa IPC, settings UI, and i18n - plugin-sdk: extend plugin-tools with injectMemoryContext and post-processing hooks - stage-ui: add plugin context provider registration for chat orchestrator - stage-tamagotchi: redesign plugin settings config UX with dirty tracking - stage-tamagotchi: fix plugin tool timing and config apply workaround - openviking-memory: add example plugin with memory CRUD via PluginHost SDK, reorganize source, and add build pipeline - docs: update plugin host design documents
…ptions Add config?: ModuleConfigEnvelope to PluginStartOptions for passing updated config to init Fix PluginHost.init() to use options.config when calling applyConfiguration, instead of always passing an empty envelope Update loadPluginByName in stage-tamagotchi host to accept and forward config to host.start Let loadEnabledPlugins build a config envelope from the already-fetched config and pass it directly, avoiding a redundant getConfig() call Let setPluginConfig pass the updated config envelope via host.reload() options so reloaded sessions receive the latest config
Reset conversationSessionId via useChatMaintenanceStore $onAction when cleanupMessages fires, so plugin memory saves start a fresh session. - Fix save conversation not working across cleanup boundaries - Reimplement injectMemoryContext as runtime context provider
…rom chat session - Wrap listPlugins() calls in injectMemoryContext and onChatTurnComplete with try-catch to soft-fail on transient errors - Derive conversationSessionId from active chat session instead of a global ref; remove cleanupMessages workaround - Remove noisy console.info logs from plugin-tools and openviking
…te on plugin config reload Revoke prior asset sessions after host.reload triggered by setPluginConfig, preventing stale 30-day TTL asset URLs/cookies from accumulating. Clear loaded state (loaded set + loadedSessionIds) and revoke asset sessions when host.reload throws, preventing inconsistent host state where a plugin appears loaded but holds a stale session id. - clearModuleAssetSessionCacheByOwnerSessionId + revokeByOwnerSessionId called after successful host.reload for the old session - On reload failure, loaded/loadedSessionIds are cleaned up and old asset sessions revoked before re-throwing the error
…rt it Filter `memory_save_conversation` invocation to only plugins that actually register the tool, eliminating warning noise and unnecessary IPC work on every chat turn. - Extract 'memory_save_conversation' string into MEMORY_SAVE_CONVERSATION_TOOL constant - Cache plugin IDs that expose this tool during refresh() from xsai tool definitions - Filter loaded+enabled plugins in registerPostProcessingHook against the cache - Clear the cache in dispose()
…ect memory_search tool invocation Remove the intermediate queryContext Eventa IPC pathway and the associated contract definition file Replace indirect queryContext calls with direct invokePluginTool calls to the memory_search tool in the renderer store Add limit parameter support to searchMemories API with default value Split pluginsWithMemoryTool into separate pluginsWithMemorySaveTool and pluginsWithMemorySearchTool sets for clearer responsibility Clean up stale mocks and imports in tests
…posing sends The chat send path blocked on plugin context lookup because performSend awaits runtime context providers, and injectMemoryContext invoked invokePluginTool sequentially for each loaded plugin. When the OpenViking backend is down, memory_search could take ~30s+ per plugin due to retry+timeout, freezing user sends before any token streams. Changes: - Replace sequential for-loop with concurrent Promise.allSettled - Add AbortSignal.timeout(3_000) per plugin invoke to fail fast - Each slow/timed-out plugin is silently skipped with a warning - Maximum total wait is bounded to 3s regardless of plugin count
- Add required field validation: reject missing or empty required fields - Add value type validation: enforce string/secret/number/boolean per schema - Preserve existing unknown key rejection with stricter type assertion - Add comprehensive unit test suite (13 test cases covering all validation paths)
…d cleanup in setPluginConfig - Split single try/catch into two: one for host.reload() failures, another for post-reload asset cookie revocation - Log cleanup errors instead of clearing host bookkeeping when reload succeeded but asset revocation fails - Prevents host bookkeeping inconsistency where a running session is marked as unloaded due to non-fatal cleanup errors
…atTurnComplete to prevent misattribution - Change ensureConversationSessionId signature to require (sessionId, generation) parameters so callers must provide captured metadata - Move sessionId/generation capture before the await listPlugins() call in onChatTurnComplete callback to lock the originating session synchronously - This ensures that a concurrent session switch during streaming does not persist the completed turn under the wrong session ID in long-term memory
85ed762 to
d2b515e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2b515e307
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ation Replace generic Error with TypeError in host plugin config validation for more precise error semantics when type mismatches occur. - Config field string type validation - Config field number type validation - Config field boolean type validation
… in plugin config validation
The required-field guard in setPluginConfig only rejected empty strings for type === 'string', but skipped the same check for type === 'secret'. This allowed a schema field marked { required: true, type: 'secret' } to be saved as '', persisted, and reloaded as an unusable credential.
- Extended the empty-value check to cover both 'string' and 'secret' types
- Made the error message dynamic to reflect the actual field type
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93d7f73985
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Eliminate a race condition in plugin-tools where a concurrent session switch during streaming could misattribute a completed turn to the wrong session. The sessionId and generation are now carried inside ChatStreamEventContext instead of read from the session store at async handler time. - Add sessionId and generation fields to ChatStreamEventContext so downstream consumers identify the originating session without external store state - Persist generation counter in ChatSessionMeta so it survives reloads; ensureGeneration and loadIndexForUser restore from persisted value - Remove useChatSessionStore dependency from plugin-tools store; read sessionId and generation from the event context instead
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 34c9e9ae56
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…er inputs in plugin settings Preserve unsaved config edits when plugin list refreshes instead of resetting all editors; merge existing editor states and only add/remove entries for new/stale plugins Reject invalid number input in saveConfig with an inline error message instead of silently omitting the value from the saved config Reload only the saved plugin's config after savePluginConfig instead of reloading all configs, preventing the refresh from discarding unsaved edits in other plugin forms Remove now-unused loadAllConfigs helper
applyRemoteSnapshot previously kept the local generation when already initialized, causing follower windows to retain stale generation values after another window resets a chat. Since memory session IDs include the generation, subsequent turns in the follower would be persisted under the pre-reset namespace, mixing old and new memory history. - Remove fallback to sessionGenerations.value[sessionId] when applying remote snapshot - Always trust snapshot.sessionMetas[sessionId]?.generation for each session in the incoming snapshot
The refactor-remote-plugins-to-plugin-host planning document has served its purpose — M1-M3 are completed and the core migration is done. Removing it to keep the .trae/documents directory clean.
Description
This PR adds the openviking-memory EDA (Event-Driven Architecture) long-term memory plugin, and supplements & optimizes the project-wide plugin system infrastructure.
Core changes are as follows:
stage-tamagotchi: Implemented PluginHost config system with schema validation, Eventa IPC, settings UI and i18n; redesigned plugin config UX with dirty tracking; fixed plugin tool timing and config application bugs.
plugin-sdk: Extended plugin-tools with memory context injection and post-processing hooks.
stage-ui: Registered plugin context provider for chat orchestrator to support plugin capability docking.
openviking-memory: Implemented complete memory CRUD capabilities based on PluginHost SDK, sorted out plugin code structure, and added supporting build pipeline.
Additional Context
Please focus on the main project code modifications (stage-tamagotchi / plugin-sdk / stage-ui) during review, including plugin host configuration logic, SDK hook extension, chat orchestrator context registration and related bug fixes, to verify main process stability and capability compatibility.
Known Issues
Coming soon
Screenshots
Notice, the first two screenshots were captured from development mode. The last one was captured from app.
Open a new chat,

Reset the chat,

Restart the app,
