Skip to content

Commit f046694

Browse files
committed
fix(stage-tamagotchi): bound memory-context lookup latency before composing 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
1 parent 8d8a147 commit f046694

3 files changed

Lines changed: 17 additions & 12 deletions

File tree

apps/stage-tamagotchi/src/main/services/airi/plugins/examples/openviking-memory/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export async function setupModules({ apis }: ContextInit): Promise<void> {
4444
},
4545
execute: async (input: unknown) => {
4646
const { query, limit } = input as { query: string, limit?: number }
47-
const results = await client!.searchMemories(query, limit ?? 5)
47+
const results = await client!.searchMemories(query, limit)
4848
return { results }
4949
},
5050
})

apps/stage-tamagotchi/src/main/services/airi/plugins/examples/openviking-memory/src/openviking.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface OpenVikingClientConfig {
2525
}
2626

2727
export interface OpenVikingClient {
28-
searchMemories: (query: string, limit: number) => Promise<Record<string, unknown>[]>
28+
searchMemories: (query: string, limit?: number) => Promise<Record<string, unknown>[]>
2929
readMemory: (uri: string) => Promise<{ uri: string, content: string }>
3030
saveConversation: (conversation: ConversationTurn) => Promise<ConversationSaveResult>
3131
saveMemory: (content: string, tags?: string[]) => Promise<{ id: string }>
@@ -99,7 +99,7 @@ export function createOpenVikingClient(config: OpenVikingClientConfig): OpenViki
9999
}
100100

101101
return {
102-
async searchMemories(query: string, limit: number): Promise<Record<string, unknown>[]> {
102+
async searchMemories(query: string, limit = 5): Promise<Record<string, unknown>[]> {
103103
const response = await apiFetch('/api/v1/search/find', {
104104
method: 'POST',
105105
body: JSON.stringify({ query, limit }),

apps/stage-tamagotchi/src/renderer/stores/plugin-tools.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { electronPluginInvokeTool, electronPluginListXsaiTools } from '../../sha
1717
const PLUGIN_CONTEXT_ID_PREFIX = 'system:plugin:'
1818
const MEMORY_SAVE_CONVERSATION_TOOL = 'memory_save_conversation'
1919
const MEMORY_SEARCH_TOOL = 'memory_search'
20+
const MEMORY_CONTEXT_TIMEOUT_MS = 3_000
2021

2122
function generateId(): string {
2223
return `plugin_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
@@ -138,27 +139,31 @@ export const useTamagotchiPluginToolsStore = defineStore('tamagotchi-plugin-tool
138139
return undefined
139140
}
140141

141-
const gathered: Array<{ text: string, pluginName: string }> = []
142-
for (const plugin of loadedPlugins) {
143-
try {
142+
const results = await Promise.allSettled(
143+
loadedPlugins.map(async (plugin) => {
144144
const result = await invokePluginTool({
145145
ownerPluginId: plugin.name,
146146
name: MEMORY_SEARCH_TOOL,
147147
input: { query: sendingMessage },
148-
})
148+
}, { signal: AbortSignal.timeout(MEMORY_CONTEXT_TIMEOUT_MS) })
149149

150150
const data = result as { results?: Array<Record<string, unknown>> }
151151

152152
const contextText = (data.results ?? [])
153153
.map(item => `[${item.uri}]\n${String(item.abstract ?? '')}`)
154154
.join('\n\n')
155155

156-
if (contextText) {
157-
gathered.push({ text: contextText, pluginName: plugin.name })
158-
}
156+
return contextText ? { text: contextText, pluginName: plugin.name } : null
157+
}),
158+
)
159+
160+
const gathered: Array<{ text: string, pluginName: string }> = []
161+
for (const result of results) {
162+
if (result.status === 'fulfilled' && result.value) {
163+
gathered.push(result.value)
159164
}
160-
catch (error) {
161-
console.warn(`[plugin-tools] failed to inject memory context from plugin "${plugin.name}":`, error)
165+
else if (result.status === 'rejected') {
166+
console.warn('[plugin-tools] memory context lookup timed out or failed:', result.reason)
162167
}
163168
}
164169

0 commit comments

Comments
 (0)