Compare commits

...

2 commits

Author SHA1 Message Date
lin onetwo
02124bad1a further rename 2025-11-27 01:29:21 +08:00
lin onetwo
d0bcc684ef refactor: more rename 2025-11-26 15:27:25 +08:00
48 changed files with 731 additions and 723 deletions

View file

@ -46,7 +46,7 @@ Object.defineProperty(window, 'observables', {
userInfo$: new BehaviorSubject(undefined).asObservable(),
},
agentInstance: {
concatPrompt: vi.fn((promptDescription: Pick<AgentPromptDescription, 'handlerConfig'>, messages: AgentInstanceMessage[]) => {
concatPrompt: vi.fn((promptDescription: Pick<AgentPromptDescription, 'agentFrameworkConfig'>, messages: AgentInstanceMessage[]) => {
const agentInstanceService = container.get<AgentInstanceService>(serviceIdentifier.AgentInstance);
// Initialize handlers (plugins and built-in handlers) before calling concatPrompt
// We need to wrap this in an Observable since concatPrompt returns an Observable

View file

@ -4,7 +4,7 @@ import { Box, Button, Container, Step, StepLabel, Stepper, TextField, Typography
import { styled } from '@mui/material/styles';
import type { RJSFSchema } from '@rjsf/utils';
import type { AgentDefinition } from '@services/agentDefinition/interface';
import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import { nanoid } from 'nanoid';
import React, { useEffect, useState } from 'react';
@ -99,19 +99,19 @@ export const CreateNewAgentContent: React.FC<CreateNewAgentContentProps> = ({ ta
// Load schema when temporaryAgentDefinition is available
useEffect(() => {
const loadSchema = async () => {
if (temporaryAgentDefinition?.handlerID) {
if (temporaryAgentDefinition?.agentFrameworkID) {
try {
const schema = await window.service.agentInstance.getFrameworkConfigSchema(temporaryAgentDefinition.handlerID);
const schema = await window.service.agentInstance.getFrameworkConfigSchema(temporaryAgentDefinition.agentFrameworkID);
setPromptSchema(schema as RJSFSchema);
} catch (error) {
console.error('Failed to load handler config schema:', error);
console.error('Failed to load framework config schema:', error);
setPromptSchema(null);
}
}
};
void loadSchema();
}, [temporaryAgentDefinition?.handlerID]);
}, [temporaryAgentDefinition?.agentFrameworkID]);
// Create preview agent when entering step 3
useEffect(() => {
@ -374,11 +374,11 @@ export const CreateNewAgentContent: React.FC<CreateNewAgentContentProps> = ({ ta
<Box sx={{ mt: 2, height: 400, overflow: 'auto' }}>
<PromptConfigForm
schema={promptSchema}
formData={(temporaryAgentDefinition.handlerConfig || {}) as HandlerConfig}
formData={(temporaryAgentDefinition.agentFrameworkConfig || {}) as AgentFrameworkConfig}
onChange={(updatedConfig) => {
void handleAgentDefinitionChange({
...temporaryAgentDefinition,
handlerConfig: updatedConfig as Record<string, unknown>,
agentFrameworkConfig: updatedConfig as Record<string, unknown>,
});
}}
loading={false}

View file

@ -2,7 +2,7 @@ import { Box, Button, CircularProgress, Container, Divider, TextField, Typograph
import { styled } from '@mui/material/styles';
import type { RJSFSchema } from '@rjsf/utils';
import type { AgentDefinition } from '@services/agentDefinition/interface';
import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { AgentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -94,30 +94,30 @@ export const EditAgentDefinitionContent: React.FC<EditAgentDefinitionContentProp
void loadAgentDefinition();
}, [tab.agentDefId]);
// Load handler config schema
// Load framework config schema
useEffect(() => {
const loadSchema = async () => {
if (!agentDefinition?.handlerID) {
// No handlerID found
if (!agentDefinition?.agentFrameworkID) {
// No agentFrameworkID found
return;
}
try {
// Loading framework config schema
const schema = await window.service.agentInstance.getFrameworkConfigSchema(agentDefinition.handlerID);
const schema = await window.service.agentInstance.getFrameworkConfigSchema(agentDefinition.agentFrameworkID);
// Schema loaded successfully
setPromptSchema(schema);
} catch (error) {
void window.service.native.log('error', 'EditAgentDefinitionContent: Failed to load handler config schema', {
void window.service.native.log('error', 'EditAgentDefinitionContent: Failed to load framework config schema', {
error,
handlerID: agentDefinition.handlerID,
agentFrameworkID: agentDefinition.agentFrameworkID,
});
console.error('Failed to load handler config schema:', error);
console.error('Failed to load framework config schema:', error);
}
};
void loadSchema();
}, [agentDefinition?.handlerID]);
}, [agentDefinition?.agentFrameworkID]);
// Auto-save to backend whenever agentDefinition changes (debounced)
const saveToBackendDebounced = useDebouncedCallback(
@ -235,7 +235,7 @@ export const EditAgentDefinitionContent: React.FC<EditAgentDefinitionContentProp
return {
...previous,
handlerConfig: formData as Record<string, unknown>,
agentFrameworkConfig: formData as Record<string, unknown>,
};
},
);
@ -356,7 +356,7 @@ export const EditAgentDefinitionContent: React.FC<EditAgentDefinitionContentProp
<Box sx={{ mt: 2 }} data-testid='edit-agent-prompt-form'>
<PromptConfigForm
schema={promptSchema}
formData={agentDefinition.handlerConfig as HandlerConfig}
formData={agentDefinition.agentFrameworkConfig as AgentFrameworkConfig}
onChange={handlePromptConfigChange}
/>
</Box>

View file

@ -157,7 +157,7 @@ describe('CreateNewAgentContent', () => {
id: 'template-1',
name: 'Test Template',
description: 'Test Description',
handlerConfig: { systemPrompt: 'Test prompt' },
agentFrameworkConfig: { systemPrompt: 'Test prompt' },
};
mockCreateAgentDef.mockResolvedValue({
@ -258,8 +258,8 @@ describe('CreateNewAgentContent', () => {
const mockAgentDefinition = {
id: 'temp-123',
name: 'Test Agent',
handlerID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Original prompt', role: 'system' }] },
agentFrameworkID: 'test-handler',
agentFrameworkConfig: { prompts: [{ text: 'Original prompt', role: 'system' }] },
};
mockGetAgentDef.mockResolvedValue(mockAgentDefinition);
@ -285,13 +285,13 @@ describe('CreateNewAgentContent', () => {
expect(mockUpdateAgentDef).not.toHaveBeenCalled();
});
it('should trigger schema loading when temporaryAgentDefinition has handlerID', async () => {
// Mock agent definition with handlerID that will be restored
it('should trigger schema loading when temporaryAgentDefinition has agentFrameworkID', async () => {
// Mock agent definition with agentFrameworkID that will be restored
const mockAgentDefinition = {
id: 'temp-123',
name: 'Test Agent',
handlerID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] },
agentFrameworkID: 'test-handler',
agentFrameworkConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] },
};
mockGetAgentDef.mockResolvedValue(mockAgentDefinition);
@ -313,7 +313,7 @@ describe('CreateNewAgentContent', () => {
expect(mockGetAgentDef).toHaveBeenCalledWith('temp-123');
}, { timeout: 1000 });
// After restoration, the component should have the handlerID and trigger schema loading
// After restoration, the component should have the agentFrameworkID and trigger schema loading
await waitFor(() => {
expect(mockGetFrameworkConfigSchema).toHaveBeenCalledWith('test-handler');
}, { timeout: 2000 });
@ -341,8 +341,8 @@ describe('CreateNewAgentContent', () => {
const mockTemplate = {
id: 'template-1',
name: 'Test Template',
handlerID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] },
agentFrameworkID: 'test-handler',
agentFrameworkConfig: { prompts: [{ text: 'Test prompt', role: 'system' }] },
};
const mockCreatedDefinition = {
@ -378,8 +378,8 @@ describe('CreateNewAgentContent', () => {
const mockTemplate = {
id: 'template-1',
name: 'Test Template',
handlerID: 'test-handler',
handlerConfig: { prompts: [{ text: 'Original prompt' }] },
agentFrameworkID: 'test-handler',
agentFrameworkConfig: { prompts: [{ text: 'Original prompt' }] },
};
const mockCreatedDefinition = {
@ -443,7 +443,7 @@ describe('CreateNewAgentContent', () => {
expect(mockCreateAgentDef).toHaveBeenCalledWith(
expect.objectContaining({
name: 'My Agent',
handlerID: 'test-handler',
agentFrameworkID: 'test-handler',
}),
);
});
@ -459,7 +459,7 @@ describe('CreateNewAgentContent', () => {
expect(mockUpdateAgentDef).toHaveBeenCalledWith(
expect.objectContaining({
id: expect.stringContaining('temp-'),
handlerID: 'test-handler',
agentFrameworkID: 'test-handler',
}),
);
}, { timeout: 500 });
@ -470,8 +470,8 @@ describe('CreateNewAgentContent', () => {
const mockTemplate = {
id: 'task-agent',
name: 'Example Agent',
handlerID: 'basicPromptConcatHandler',
handlerConfig: {
agentFrameworkID: 'basicPromptConcatHandler',
agentFrameworkConfig: {
prompts: [
{
id: 'system',
@ -503,7 +503,7 @@ describe('CreateNewAgentContent', () => {
// Step 1: Create agent definition (simulates template selection)
const createdDef = await window.service.agentDefinition.createAgentDef(mockCreatedDefinition);
expect(createdDef).toBeDefined();
const prompts = (createdDef.handlerConfig).prompts as Array<{
const prompts = (createdDef.agentFrameworkConfig).prompts as Array<{
children?: Array<{ text?: string }>;
}>;
expect((prompts as Array<{ children?: Array<{ text?: string }> }>)[0]?.children?.[0]?.text).toBe('You are a helpful assistant for Tiddlywiki user.');
@ -511,14 +511,14 @@ describe('CreateNewAgentContent', () => {
// Step 2: Update system prompt in nested structure
const updatedDefinition = {
...mockCreatedDefinition,
handlerConfig: {
...mockCreatedDefinition.handlerConfig,
agentFrameworkConfig: {
...mockCreatedDefinition.agentFrameworkConfig,
prompts: [
{
...mockCreatedDefinition.handlerConfig.prompts[0],
...mockCreatedDefinition.agentFrameworkConfig.prompts[0],
children: [
{
...mockCreatedDefinition.handlerConfig.prompts[0].children[0],
...mockCreatedDefinition.agentFrameworkConfig.prompts[0].children[0],
text: '你是一个专业的代码助手,请用中文回答编程问题。',
},
],
@ -532,7 +532,7 @@ describe('CreateNewAgentContent', () => {
// Verify the correct nested structure is updated
expect(mockUpdateAgentDef).toHaveBeenCalledWith(
expect.objectContaining({
handlerConfig: expect.objectContaining({
agentFrameworkConfig: expect.objectContaining({
prompts: expect.arrayContaining([
expect.objectContaining({
role: 'system',

View file

@ -49,7 +49,7 @@ const mockAgentDefinition = {
id: 'test-agent-def-id',
name: 'Test Agent',
description: 'A test agent for editing',
handlerID: 'testHandler',
agentFrameworkID: 'testHandler',
config: {},
};

View file

@ -302,35 +302,35 @@ export const agentActions = (
}
},
getHandlerId: async () => {
getAgentFrameworkId: async () => {
try {
const { agent, agentDef } = get();
if (agentDef?.handlerID) {
return agentDef.handlerID;
if (agentDef?.agentFrameworkID) {
return agentDef.agentFrameworkID;
}
if (agent?.agentDefId) {
const fetchedAgentDefinition = await window.service.agentDefinition.getAgentDef(agent.agentDefId);
if (fetchedAgentDefinition?.handlerID) {
return fetchedAgentDefinition.handlerID;
if (fetchedAgentDefinition?.agentFrameworkID) {
return fetchedAgentDefinition.agentFrameworkID;
}
}
throw new Error('No active agent in store or handler ID not found');
throw new Error('No active agent in store or agent framework ID not found');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const finalError = new Error(`Failed to get handler ID: ${errorMessage}`);
const finalError = new Error(`Failed to get agent framework ID: ${errorMessage}`);
set({ error: finalError });
throw finalError;
}
},
/**
* Get handler configuration schema for current handler
* Get framework configuration schema for current framework
*/
getFrameworkConfigSchema: async () => {
try {
const handlerId = await get().getHandlerId();
return await window.service.agentInstance.getFrameworkConfigSchema(handlerId);
const agentFrameworkId = await get().getAgentFrameworkId();
return await window.service.agentInstance.getFrameworkConfigSchema(agentFrameworkId);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const finalError = new Error(`Failed to get handler schema: ${errorMessage}`);

View file

@ -70,14 +70,14 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
getPreviewPromptResult: async (
inputText: string,
handlerConfig: AgentPromptDescription['handlerConfig'],
agentFrameworkConfig: AgentPromptDescription['agentFrameworkConfig'],
) => {
try {
set({ previewLoading: true });
const messages = Array.from(get().messages.values());
// Safety check - if handlerConfig is empty, fail early
if (Object.keys(handlerConfig).length === 0) {
// Safety check - if agentFrameworkConfig is empty, fail early
if (!agentFrameworkConfig || Object.keys(agentFrameworkConfig).length === 0) {
set({ previewLoading: false, previewResult: null });
return null;
}
@ -93,7 +93,7 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
}
// Use the streaming API with progress updates
const concatStream = window.observables.agentInstance.concatPrompt({ handlerConfig }, messages);
const concatStream = window.observables.agentInstance.concatPrompt({ agentFrameworkConfig }, messages);
// Initialize progress
set({
@ -113,7 +113,7 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
next: (state) => {
// Update progress and current step
const stepDescription = state.step === 'plugin'
? `Processing plugin: ${state.currentPlugin?.pluginId || 'unknown'}`
? `Processing tool: ${state.currentPlugin?.toolId || 'unknown'}`
: state.step === 'finalize'
? 'Finalizing prompts...'
: state.step === 'flatten'
@ -123,7 +123,7 @@ export const previewActionsMiddleware: StateCreator<AgentChatStoreType, [], [],
set({
previewProgress: state.progress,
previewCurrentStep: stepDescription,
previewCurrentPlugin: state.currentPlugin?.pluginId || null,
previewCurrentPlugin: state.currentPlugin?.toolId || null,
// Update intermediate results
previewResult: {
flatPrompts: state.flatPrompts,

View file

@ -90,7 +90,7 @@ export interface BasicActions {
cancelAgent: () => Promise<void>;
/** Get the handler ID for the current agent */
getHandlerId: () => Promise<string>;
getAgentFrameworkId: () => Promise<string>;
/** Get the configuration schema for the current handler */
getFrameworkConfigSchema: () => Promise<Record<string, unknown>>;
@ -188,12 +188,12 @@ export interface PreviewActions {
/**
* Generates a preview of prompts for the current agent state
* @param inputText Input text to include in the preview
* @param handlerConfig Prompt configuration to use for preview
* @param agentFrameworkConfig Framework configuration to use for preview
* @returns Promise that resolves when preview is generated and state is updated
*/
getPreviewPromptResult: (
inputText: string,
handlerConfig: AgentPromptDescription['handlerConfig'],
agentFrameworkConfig: AgentPromptDescription['agentFrameworkConfig'],
) => Promise<
{
flatPrompts: ModelMessage[];

View file

@ -1,4 +1,4 @@
import { useHandlerConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement';
import { useAgentFrameworkConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement';
import MonacoEditor from '@monaco-editor/react';
import { Box, styled } from '@mui/material';
import Tab from '@mui/material/Tab';
@ -9,7 +9,7 @@ import React, { FC, SyntheticEvent, useCallback, useEffect, useState } from 'rea
import { useTranslation } from 'react-i18next';
import { useShallow } from 'zustand/react/shallow';
import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { agentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { useAgentChatStore } from '../../../Agent/store/agentChatStore/index';
import { PromptConfigForm } from './PromptConfigForm';
@ -40,11 +40,11 @@ export const EditView: FC<EditViewProps> = ({
);
const {
loading: handlerConfigLoading,
config: handlerConfig,
loading: agentFrameworkConfigLoading,
config: agentFrameworkConfig,
schema: handlerSchema,
handleConfigChange,
} = useHandlerConfigManagement({
} = useAgentFrameworkConfigManagement({
agentDefId: agent?.agentDefId,
agentId: agent?.id,
});
@ -98,7 +98,7 @@ export const EditView: FC<EditViewProps> = ({
);
const handleFormChange = useDebouncedCallback(
async (updatedConfig: HandlerConfig) => {
async (updatedConfig: agentFrameworkConfig) => {
try {
// Ensure the config change is fully persisted before proceeding
await handleConfigChange(updatedConfig);
@ -121,7 +121,7 @@ export const EditView: FC<EditViewProps> = ({
const handleEditorChange = useCallback((value: string | undefined) => {
if (!value) return;
try {
const parsedConfig = JSON.parse(value) as HandlerConfig;
const parsedConfig = JSON.parse(value) as agentFrameworkConfig;
void handleFormChange(parsedConfig);
} catch (error) {
void window.service.native.log('error', 'EditView: Invalid JSON in code editor:', { error });
@ -163,16 +163,16 @@ export const EditView: FC<EditViewProps> = ({
{editorMode === 'form' && (
<PromptConfigForm
schema={handlerSchema ?? {}}
formData={handlerConfig}
formData={agentFrameworkConfig}
onChange={handleFormChange}
loading={handlerConfigLoading}
loading={agentFrameworkConfigLoading}
/>
)}
{editorMode === 'code' && (
<MonacoEditor
height='100%'
defaultLanguage='json'
value={handlerConfig ? JSON.stringify(handlerConfig, null, 2) : '{}'}
value={agentFrameworkConfig ? JSON.stringify(agentFrameworkConfig, null, 2) : '{}'}
onChange={handleEditorChange}
options={{
minimap: { enabled: true },

View file

@ -3,7 +3,7 @@ import { IChangeEvent } from '@rjsf/core';
import Form from '@rjsf/mui';
import { ObjectFieldTemplateProps, RJSFSchema, RJSFValidationError } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { agentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ErrorDisplay } from './components/ErrorDisplay';
@ -37,9 +37,9 @@ interface PromptConfigFormProps {
/** UI schema for layout customization */
uiSchema?: Record<string, unknown>;
/** Initial form data */
formData?: HandlerConfig;
formData?: agentFrameworkConfig;
/** Change handler for form data */
onChange?: (formData: HandlerConfig) => void;
onChange?: (formData: agentFrameworkConfig) => void;
/** Error handler for form validation errors */
onError?: (errors: RJSFValidationError[]) => void;
/** Whether the form is disabled */
@ -87,7 +87,7 @@ export const PromptConfigForm: React.FC<PromptConfigFormProps> = ({
onError?.(errors);
}, [onError]);
const handleChange = useCallback((changeEvent: IChangeEvent<HandlerConfig>) => {
const handleChange = useCallback((changeEvent: IChangeEvent<agentFrameworkConfig>) => {
const formData = changeEvent.formData;
if (formData) {
onChange?.(formData);

View file

@ -15,10 +15,10 @@ import { ModelMessage } from 'ai';
import { PromptPreviewDialog } from '../index';
// Mock handler config management hook
vi.mock('@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement', () => ({
useHandlerConfigManagement: vi.fn(() => ({
vi.mock('@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement', () => ({
useAgentFrameworkConfigManagement: vi.fn(() => ({
loading: false,
config: defaultAgents[0].handlerConfig,
config: defaultAgents[0].agentFrameworkConfig,
handleConfigChange: vi.fn(),
})),
}));
@ -64,7 +64,7 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
expect(globalThis.window?.observables?.agentInstance?.concatPrompt).toBeDefined();
// Create test data matching taskAgents.json - cast to avoid type issues in test
const handlerConfig = defaultAgents[0].handlerConfig as never;
const agentFrameworkConfig = defaultAgents[0].agentFrameworkConfig as never;
const messages = [{
id: 'test-message-1',
agentId: 'test-agent',
@ -74,7 +74,7 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
// Call the real concatPrompt implementation
const observable = globalThis.window.observables.agentInstance.concatPrompt(
{ handlerConfig },
{ agentFrameworkConfig },
messages,
);
@ -118,11 +118,11 @@ describe('PromptPreviewDialog - Tool Information Rendering', () => {
it('should render workspaces and tools info from real concatPrompt execution', async () => {
// First execute real concatPrompt to get the structured data
const handlerConfig = defaultAgents[0].handlerConfig;
const agentFrameworkConfig = defaultAgents[0].agentFrameworkConfig;
const messages = [{ id: 'test', role: 'user' as const, content: 'Hello world', created: new Date(), modified: new Date(), agentId: 'test' }];
// Pass handlerConfig wrapped (same shape used elsewhere)
const observable = window.observables.agentInstance.concatPrompt({ handlerConfig } as never, messages);
// Pass agentFrameworkConfig wrapped (same shape used elsewhere)
const observable = window.observables.agentInstance.concatPrompt({ agentFrameworkConfig } as never, messages);
const results: unknown[] = [];
let finalResult: { flatPrompts: ModelMessage[]; processedPrompts: IPrompt[] } | undefined;

View file

@ -13,10 +13,10 @@ import defaultAgents from '@services/agentInstance/agentFrameworks/taskAgents.js
import { PromptPreviewDialog } from '../index';
// Mock handler config management hook
vi.mock('@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement', () => ({
useHandlerConfigManagement: vi.fn(() => ({
vi.mock('@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement', () => ({
useAgentFrameworkConfigManagement: vi.fn(() => ({
loading: false,
config: defaultAgents[0].handlerConfig,
config: defaultAgents[0].agentFrameworkConfig,
handleConfigChange: vi.fn(),
})),
}));

View file

@ -1,4 +1,4 @@
import { useHandlerConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useHandlerConfigManagement';
import { useAgentFrameworkConfigManagement } from '@/windows/Preferences/sections/ExternalAPI/useAgentFrameworkConfigManagement';
import CloseIcon from '@mui/icons-material/Close';
import EditIcon from '@mui/icons-material/Edit';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
@ -36,9 +36,9 @@ export const PromptPreviewDialog: React.FC<PromptPreviewDialogProps> = ({
const [isEditMode, setIsEditMode] = useState(false);
const {
loading: handlerConfigLoading,
config: handlerConfig,
} = useHandlerConfigManagement({
loading: agentFrameworkConfigLoading,
config: agentFrameworkConfig,
} = useAgentFrameworkConfigManagement({
agentDefId: agent?.agentDefId,
agentId: agent?.id,
});
@ -54,17 +54,17 @@ export const PromptPreviewDialog: React.FC<PromptPreviewDialogProps> = ({
);
useEffect(() => {
const fetchInitialPreview = async () => {
if (!agent?.agentDefId || handlerConfigLoading || !handlerConfig || !open) {
if (!agent?.agentDefId || agentFrameworkConfigLoading || !agentFrameworkConfig || !open) {
return;
}
try {
await getPreviewPromptResult(inputText, handlerConfig);
await getPreviewPromptResult(inputText, agentFrameworkConfig);
} catch (error) {
console.error('PromptPreviewDialog: Error fetching initial preview:', error);
}
};
void fetchInitialPreview();
}, [agent?.agentDefId, handlerConfig, handlerConfigLoading, inputText, open]); // 移除 getPreviewPromptResult
}, [agent?.agentDefId, agentFrameworkConfig, agentFrameworkConfigLoading, inputText, open]); // 移除 getPreviewPromptResult
const handleToggleFullScreen = useCallback((): void => {
setIsFullScreen(previous => !previous);

View file

@ -90,12 +90,13 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const defs = await freshService.getAgentDefs();
expect(defs.length).toBeGreaterThan(0);
const exampleAgent = defs.find(d => d.id === (defaultAgents as AgentDefinition[])[0].id);
// Fixed
const exampleAgent = defs.find(d => d.id === (defaultAgents as unknown as AgentDefinition[])[0].id);
expect(exampleAgent).toBeDefined();
expect(exampleAgent!.name).toBeDefined();
expect(exampleAgent!.handlerID).toBeDefined();
expect(exampleAgent!.handlerConfig).toBeDefined();
expect(typeof exampleAgent!.handlerConfig).toBe('object');
expect(exampleAgent!.agentFrameworkID).toBeDefined();
expect(exampleAgent!.agentFrameworkConfig).toBeDefined();
expect(typeof exampleAgent!.agentFrameworkConfig).toBe('object');
});
it('should return only database data without fallback to defaultAgents', async () => {
@ -105,7 +106,7 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity);
// Save only minimal record (id only) to test new behavior
const example = (defaultAgents as AgentDefinition[])[0];
const example = (defaultAgents as unknown as AgentDefinition[])[0];
await agentDefRepo.save({
id: example.id,
});
@ -116,11 +117,11 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
expect(found).toBeDefined();
// With new behavior, only id should be present, other fields should be undefined or empty
expect(found!.id).toBe(example.id);
expect(found!.handlerID).toBeUndefined();
expect(found!.agentFrameworkID).toBeUndefined();
expect(found!.name).toBeUndefined();
expect(found!.description).toBeUndefined();
expect(found!.avatarUrl).toBeUndefined();
expect(found!.handlerConfig).toEqual({});
expect(found!.agentFrameworkConfig).toEqual({});
expect(found!.aiApiConfig).toBeUndefined();
expect(found!.agentTools).toBeUndefined();
});
@ -132,7 +133,7 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const agentDefRepo = realDataSource.getRepository(AgentDefinitionEntity);
// Save only minimal record (id only) as per new behavior
const example = (defaultAgents as AgentDefinition[])[0];
const example = (defaultAgents as unknown as AgentDefinition[])[0];
await agentDefRepo.save({
id: example.id,
});
@ -148,8 +149,8 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
expect(entity!.name).toBeNull();
expect(entity!.description).toBeNull();
expect(entity!.avatarUrl).toBeNull();
expect(entity!.handlerID).toBeNull();
expect(entity!.handlerConfig).toBeNull();
expect(entity!.agentFrameworkID).toBeNull();
expect(entity!.agentFrameworkConfig).toBeNull();
expect(entity!.aiApiConfig).toBeNull();
expect(entity!.agentTools).toBeNull();
});
@ -158,15 +159,15 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
const templates = await agentDefinitionService.getAgentTemplates();
// Should include all default agents
expect(templates.length).toBe((defaultAgents as AgentDefinition[]).length);
expect(templates.length).toBe((defaultAgents as unknown as AgentDefinition[]).length);
// Check that template has complete data from taskAgents.json
const exampleTemplate = templates.find(t => t.id === (defaultAgents as AgentDefinition[])[0].id);
const exampleTemplate = templates.find(t => t.id === (defaultAgents as unknown as AgentDefinition[])[0].id);
expect(exampleTemplate).toBeDefined();
expect(exampleTemplate!.name).toBeDefined();
expect(exampleTemplate!.handlerID).toBeDefined();
expect(exampleTemplate!.handlerConfig).toBeDefined();
expect(typeof exampleTemplate!.handlerConfig).toBe('object');
expect(exampleTemplate!.agentFrameworkID).toBeDefined();
expect(exampleTemplate!.agentFrameworkConfig).toBeDefined();
expect(typeof exampleTemplate!.agentFrameworkConfig).toBe('object');
});
it('should not throw when searchName filtering is requested (client-side filtering expected)', async () => {
@ -185,6 +186,6 @@ describe('AgentDefinitionService getAgentDefs integration', () => {
// Should still return default agents and not throw
const templates = await agentDefinitionService.getAgentTemplates();
expect(templates.length).toBe((defaultAgents as AgentDefinition[]).length);
expect(templates.length).toBe((defaultAgents as unknown as AgentDefinition[]).length);
});
});

View file

@ -116,21 +116,21 @@ export function validateAndConvertWikiTiddlerToAgentTemplate(
};
// Try to parse the tiddler text as JSON for agent configuration
let handlerConfig: Record<string, unknown>;
let agentFrameworkConfig: Record<string, unknown>;
try {
const textContent = typeof tiddler.text === 'string' ? tiddler.text : JSON.stringify(tiddler.text || '{}');
const parsed = JSON.parse(textContent) as unknown;
// Ensure handlerConfig is a valid object
// Ensure agentFrameworkConfig is a valid object
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
logger.warn('Invalid handlerConfig in tiddler', {
logger.warn('Invalid agentFrameworkConfig in tiddler', {
function: 'validateAndConvertWikiTiddlerToAgentTemplate',
title: getStringField(tiddler.title),
reason: 'not an object',
});
return null;
}
handlerConfig = parsed as Record<string, unknown>;
agentFrameworkConfig = parsed as Record<string, unknown>;
} catch (parseError) {
logger.warn('Failed to parse agent template from tiddler', {
function: 'validateAndConvertWikiTiddlerToAgentTemplate',
@ -146,8 +146,8 @@ export function validateAndConvertWikiTiddlerToAgentTemplate(
name: getStringField(tiddler.caption) || getStringField(tiddler.title),
description: getStringField(tiddler.description) || `Agent template from ${workspaceName || 'wiki'}`,
avatarUrl: getStringField(tiddler.avatar_url) || undefined,
handlerID: getStringField(tiddler.handler_id) || 'basicPromptConcatHandler',
handlerConfig,
agentFrameworkID: getStringField(tiddler.agentFrameworkID) || 'basicPromptConcatHandler',
agentFrameworkConfig,
aiApiConfig: parseAiApiConfig(tiddler.ai_api_config),
agentTools: parseAgentTools(tiddler.agent_tools),
};

View file

@ -78,8 +78,8 @@ export class AgentDefinitionService implements IAgentDefinitionService {
name: defaultAgent.name,
description: defaultAgent.description,
avatarUrl: defaultAgent.avatarUrl,
handlerID: defaultAgent.handlerID,
handlerConfig: defaultAgent.handlerConfig,
agentFrameworkID: defaultAgent.agentFrameworkID,
agentFrameworkConfig: defaultAgent.agentFrameworkConfig,
aiApiConfig: defaultAgent.aiApiConfig,
agentTools: defaultAgent.agentTools,
})
@ -143,7 +143,7 @@ export class AgentDefinitionService implements IAgentDefinitionService {
throw new Error(`Agent definition not found: ${agent.id}`);
}
const pickedProperties = pick(agent, ['name', 'description', 'avatarUrl', 'handlerID', 'handlerConfig', 'aiApiConfig']);
const pickedProperties = pick(agent, ['name', 'description', 'avatarUrl', 'agentFrameworkID', 'agentFrameworkConfig', 'aiApiConfig']);
Object.assign(existingAgent, pickedProperties);
await this.agentDefRepository!.save(existingAgent);
@ -171,8 +171,8 @@ export class AgentDefinitionService implements IAgentDefinitionService {
name: entity.name || undefined,
description: entity.description || undefined,
avatarUrl: entity.avatarUrl || undefined,
handlerID: entity.handlerID || undefined,
handlerConfig: entity.handlerConfig || {},
agentFrameworkID: entity.agentFrameworkID || undefined,
agentFrameworkConfig: entity.agentFrameworkConfig || {},
aiApiConfig: entity.aiApiConfig || undefined,
agentTools: entity.agentTools || undefined,
}));
@ -212,8 +212,8 @@ export class AgentDefinitionService implements IAgentDefinitionService {
name: entity.name || undefined,
description: entity.description || undefined,
avatarUrl: entity.avatarUrl || undefined,
handlerID: entity.handlerID || undefined,
handlerConfig: entity.handlerConfig || {},
agentFrameworkID: entity.agentFrameworkID || undefined,
agentFrameworkConfig: entity.agentFrameworkConfig || {},
aiApiConfig: entity.aiApiConfig || undefined,
agentTools: entity.agentTools || undefined,
};

View file

@ -42,10 +42,10 @@ export interface AgentDefinition {
description?: string;
/** Agent icon or avatar URL */
avatarUrl?: string;
/** Agent handler function's id, we will find function by this id */
handlerID?: string;
/** Agent handler's config, specific to the handler. This is required to ensure agent has valid configuration. */
handlerConfig: Record<string, unknown>;
/** Agent framework function's id, we will find function by this id */
agentFrameworkID?: string;
/** Agent framework's config, specific to the framework. This is required to ensure agent has valid configuration. */
agentFrameworkConfig: Record<string, unknown>;
/**
* Overwrite the default AI configuration for this agent.
* Priority is higher than the global default agent config.

View file

@ -78,7 +78,7 @@ describe('AgentInstance failure path - external API logs on error', () => {
agentDef: {
id: 'def-1',
name: 'Def 1',
handlerConfig: {},
agentFrameworkConfig: {},
aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } },
},
isCancelled: () => false,

View file

@ -73,7 +73,7 @@ describe('AgentInstanceService Streaming Behavior', () => {
// Mock agent definition service to return our test agent definition
mockAgentDefinitionService.getAgentDef = vi.fn().mockResolvedValue({
...exampleAgent,
handlerID: 'basicPromptConcatHandler',
agentFrameworkID: 'basicPromptConcatHandler',
});
// Mock the getAgent method to return our test instance
vi.spyOn(agentInstanceService, 'getAgent').mockResolvedValue(testAgentInstance);

View file

@ -2,11 +2,11 @@ import { describe, expect, it } from 'vitest';
import { createAgentInstanceData } from '../utilities';
describe('createAgentInstanceData', () => {
it('should create agent instance with undefined handlerConfig (fallback to definition)', () => {
it('should create agent instance with undefined agentFrameworkConfig (fallback to definition)', () => {
const agentDefinition = {
id: 'test-agent-def',
name: 'Test Agent',
handlerConfig: {
agentFrameworkConfig: {
prompts: [
{
text: 'You are a helpful assistant.',
@ -14,29 +14,29 @@ describe('createAgentInstanceData', () => {
},
],
},
handlerID: 'basicPromptConcatHandler',
agentFrameworkID: 'basicPromptConcatHandler',
};
const { instanceData } = createAgentInstanceData(agentDefinition);
expect(instanceData.handlerConfig).toBeUndefined();
expect(instanceData.agentFrameworkConfig).toBeUndefined();
expect(instanceData.agentDefId).toBe('test-agent-def');
expect(instanceData.handlerID).toBe('basicPromptConcatHandler');
expect(instanceData.agentFrameworkID).toBe('basicPromptConcatHandler');
expect(instanceData.name).toContain('Test Agent');
});
it('should create agent instance with undefined handlerConfig even when definition has required handlerConfig', () => {
it('should create agent instance with undefined agentFrameworkConfig even when definition has required agentFrameworkConfig', () => {
const agentDefinition = {
id: 'test-agent-def-no-config',
name: 'Test Agent No Config',
handlerID: 'basicPromptConcatHandler',
handlerConfig: {}, // Required by AgentDefinition interface
agentFrameworkID: 'basicPromptConcatHandler',
agentFrameworkConfig: {}, // Required by AgentDefinition interface
};
const { instanceData } = createAgentInstanceData(agentDefinition);
expect(instanceData.handlerConfig).toBeUndefined();
expect(instanceData.agentFrameworkConfig).toBeUndefined();
expect(instanceData.agentDefId).toBe('test-agent-def-no-config');
expect(instanceData.handlerID).toBe('basicPromptConcatHandler');
expect(instanceData.agentFrameworkID).toBe('basicPromptConcatHandler');
});
});

View file

@ -33,7 +33,7 @@ function makeContext(agentId: string, agentDefId: string, messages: AgentInstanc
agentDef: {
id: agentDefId,
name: 'Test Agent',
handlerConfig: {},
agentFrameworkConfig: {},
aiApiConfig: { api: { provider: 'test-provider', model: 'test-model' }, modelParameters: { temperature: 0.7, systemPrompt: '', topP: 0.95 } } as AiAPIConfig,
},
isCancelled: () => false,
@ -94,10 +94,10 @@ describe('basicPromptConcatHandler - failure path persists error message and log
vi.spyOn(agentDefSvc, 'getAgentDef').mockResolvedValue({
id: 'def-1',
name: 'Def 1',
handlerID: 'basicPromptConcatHandler',
handlerConfig: {
agentFrameworkID: 'basicPromptConcatHandler',
agentFrameworkConfig: {
plugins: [
{ pluginId: 'wikiOperation', wikiOperationParam: {} },
{ toolId: 'wikiOperation', wikiOperationParam: {} },
],
},
});

View file

@ -30,7 +30,7 @@ let testStreamResponses: Array<{ status: string; content: string; requestId: str
import type { IPromptConcatTool } from '@services/agentInstance/promptConcat/promptConcatSchema';
import type { IDatabaseService } from '@services/database/interface';
import { createAgentFrameworkHooks, createHooksWithTools, initializeToolSystem, PromptConcatHookContext } from '../../tools/index';
import { wikiSearchPlugin } from '../../tools/wikiSearch';
import { wikiSearchTool } from '../../tools/wikiSearch';
import { basicPromptConcatHandler } from '../taskAgent';
import type { AgentFrameworkContext } from '../utilities/type';
@ -90,30 +90,30 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
it('should complete full wiki search workflow: tool list -> tool execution -> response', async () => {
// Use real agent config from taskAgents.json
const exampleAgent = defaultAgents[0];
const handlerConfig = exampleAgent.handlerConfig;
const agentFrameworkConfig = exampleAgent.agentFrameworkConfig;
// Get the wiki search plugin configuration
const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch');
// Get the wiki search tool configuration
const wikiPlugin = agentFrameworkConfig.plugins.find(p => p.toolId === 'wikiSearch');
expect(wikiPlugin).toBeDefined();
if (!wikiPlugin) throw new Error('wikiPlugin not found');
const prompts = JSON.parse(JSON.stringify(handlerConfig.prompts));
const prompts = JSON.parse(JSON.stringify(agentFrameworkConfig.prompts));
// Phase 1: Tool List Injection
const promptConcatHookContext: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
messages: [],
agentDefId: exampleAgent.id,
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
handlerConfig: {},
agentFrameworkConfig: {},
},
agentDef: { id: exampleAgent.id, name: exampleAgent.name, handlerConfig: exampleAgent.handlerConfig },
agentDef: { id: exampleAgent.id, name: exampleAgent.name, agentFrameworkConfig: exampleAgent.agentFrameworkConfig },
isCancelled: () => false,
},
pluginConfig: wikiPlugin as IPromptConcatTool,
toolConfig: wikiPlugin as IPromptConcatTool,
prompts,
messages: [
{
@ -127,12 +127,12 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
],
};
// Create hooks and register plugins as defined in handlerConfig
const { hooks: promptHooks } = await createHooksWithTools(handlerConfig);
// First run workspacesList plugin to inject available workspaces (if present)
const workspacesPlugin = handlerConfig.plugins?.find(p => p.pluginId === 'workspacesList');
// Create hooks and register tools as defined in agentFrameworkConfig
const { hooks: promptHooks } = await createHooksWithTools(agentFrameworkConfig);
// First run workspacesList tool to inject available workspaces (if present)
const workspacesPlugin = agentFrameworkConfig.plugins?.find(p => p.toolId === 'workspacesList');
if (workspacesPlugin) {
const workspacesContext = { ...promptConcatHookContext, pluginConfig: workspacesPlugin } as unknown as PromptConcatHookContext;
const workspacesContext = { ...promptConcatHookContext, toolConfig: workspacesPlugin } as unknown as PromptConcatHookContext;
await promptHooks.processPrompts.promise(workspacesContext);
}
// Then run wikiSearch plugin to inject the tool list
@ -169,7 +169,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
};
const responseContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -179,9 +179,9 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
},
created: new Date(),
messages: [],
handlerConfig: {},
agentFrameworkConfig: {},
},
agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as AgentDefinition,
agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as AgentDefinition,
isCancelled: () => false,
},
response: {
@ -191,7 +191,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
},
requestId: 'test-request',
isFinal: true,
pluginConfig: wikiPlugin as IPromptConcatTool,
toolConfig: wikiPlugin as IPromptConcatTool,
prompts: [],
messages: [],
llmResponse: 'I will search for important content using wiki-search tool.',
@ -199,8 +199,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
actions: {} as unknown as Record<string, unknown>,
};
// Use hooks registered with all plugins from handlerConfig
const { hooks: responseHooks } = await createHooksWithTools(handlerConfig);
// Use hooks registered with all plugins from agentFrameworkConfig
const { hooks: responseHooks } = await createHooksWithTools(agentFrameworkConfig);
// Execute the response complete hook
await responseHooks.responseComplete.promise(responseContext);
// reuse containerForAssert from above assertions
@ -213,8 +213,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
expect(responseContext.actions.yieldNextRoundTo).toBe('self');
// Verify tool result message was added to agent history
expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0);
const toolResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage;
expect(responseContext.agentFrameworkContext.agent.messages.length).toBeGreaterThan(0);
const toolResultMessage = responseContext.agentFrameworkContext.agent.messages[responseContext.agentFrameworkContext.agent.messages.length - 1] as AgentInstanceMessage;
expect(toolResultMessage.role).toBe('tool'); // Tool result message
expect(toolResultMessage.content).toContain('<functions_result>');
expect(toolResultMessage.content).toContain('Tool: wiki-search');
@ -224,16 +224,16 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
it('should handle errors in wiki search gracefully', async () => {
// Use real agent config from taskAgents.json
const exampleAgent = defaultAgents[0];
const handlerConfig = exampleAgent.handlerConfig;
const agentFrameworkConfig = exampleAgent.agentFrameworkConfig;
// Get the wiki search plugin configuration
const wikiPlugin = handlerConfig.plugins.find(p => p.pluginId === 'wikiSearch');
// Get the wiki search tool configuration
const wikiPlugin = agentFrameworkConfig.plugins.find(p => p.toolId === 'wikiSearch');
expect(wikiPlugin).toBeDefined();
// Mock tool calling with invalid workspace
const responseContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -243,9 +243,9 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
},
created: new Date(),
messages: [],
handlerConfig: {},
agentFrameworkConfig: {},
},
agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as AgentDefinition,
agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as AgentDefinition,
isCancelled: () => false,
},
response: {
@ -255,7 +255,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
},
requestId: 'test-request',
isFinal: true,
pluginConfig: wikiPlugin as IPromptConcatTool,
toolConfig: wikiPlugin as IPromptConcatTool,
prompts: [],
messages: [],
llmResponse: 'Search in nonexistent wiki',
@ -267,7 +267,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
const responseHooks = createAgentFrameworkHooks();
// Register the plugin
wikiSearchPlugin(responseHooks);
wikiSearchTool(responseHooks);
// Execute the response complete hook
await responseHooks.responseComplete.promise(responseContext);
@ -276,8 +276,8 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
expect(responseContext.actions.yieldNextRoundTo).toBe('self');
// Verify error message was added to agent history
expect(responseContext.handlerContext.agent.messages.length).toBeGreaterThan(0);
const errorResultMessage = responseContext.handlerContext.agent.messages[responseContext.handlerContext.agent.messages.length - 1] as AgentInstanceMessage;
expect(responseContext.agentFrameworkContext.agent.messages.length).toBeGreaterThan(0);
const errorResultMessage = responseContext.agentFrameworkContext.agent.messages[responseContext.agentFrameworkContext.agent.messages.length - 1] as AgentInstanceMessage;
expect(errorResultMessage.role).toBe('tool'); // Tool error message
// The error should be indicated in the message content
@ -315,7 +315,7 @@ describe('WikiSearch Plugin Integration & YieldNextRound Mechanism', () => {
agentDef: {
id: exampleAgent.id,
name: exampleAgent.name,
handlerConfig: exampleAgent.handlerConfig,
agentFrameworkConfig: exampleAgent.agentFrameworkConfig,
},
isCancelled: () => false,
};

View file

@ -4,7 +4,7 @@ import { logger } from '@services/libs/log';
import serviceIdentifier from '@services/serviceIdentifier';
import { merge } from 'lodash';
import type { AgentInstanceLatestStatus, AgentInstanceMessage, IAgentInstanceService } from '../interface';
import { AgentPromptDescription, AiAPIConfig, HandlerConfig } from '../promptConcat/promptConcatSchema';
import { AgentFrameworkConfig, AgentPromptDescription, AiAPIConfig } from '../promptConcat/promptConcatSchema';
import type { IPromptConcatTool } from '../promptConcat/promptConcatSchema/plugin';
import { responseConcat } from '../promptConcat/responseConcat';
import { getFinalPromptResult } from '../promptConcat/utilities';
@ -31,15 +31,15 @@ export async function* basicPromptConcatHandler(context: AgentFrameworkContext)
// Initialize variables for request tracking
let currentRequestId: string | undefined;
const lastUserMessage: AgentInstanceMessage | undefined = context.agent.messages[context.agent.messages.length - 1];
// Create and register handler hooks based on handler config
const { hooks: handlerHooks, pluginConfigs } = await createHooksWithTools(context.agentDef.handlerConfig || {});
// Create and register handler hooks based on framework config
const { hooks: agentFrameworkHooks, toolConfigs } = await createHooksWithTools(context.agentDef.agentFrameworkConfig || {});
// Log the start of handler execution with context information
logger.debug('Starting prompt handler execution', {
method: 'basicPromptConcatHandler',
agentId: context.agent.id,
defId: context.agentDef.id,
handlerId: context.agentDef.handlerID,
agentFrameworkId: context.agentDef.agentFrameworkID,
messageCount: context.agent.messages.length,
});
// Check if there's a new user message to process - trigger user message received hook
@ -48,8 +48,8 @@ export async function* basicPromptConcatHandler(context: AgentFrameworkContext)
if (isNewUserMessage) {
// Trigger user message received hook
await handlerHooks.userMessageReceived.promise({
handlerContext: context,
await agentFrameworkHooks.userMessageReceived.promise({
agentFrameworkContext: context,
content: {
text: lastUserMessage.content,
file: lastUserMessage.metadata?.file as File | undefined,
@ -62,8 +62,8 @@ export async function* basicPromptConcatHandler(context: AgentFrameworkContext)
lastUserMessage.metadata = { ...lastUserMessage.metadata, processed: true };
// Trigger agent status change to working
await handlerHooks.agentStatusChanged.promise({
handlerContext: context,
await agentFrameworkHooks.agentStatusChanged.promise({
agentFrameworkContext: context,
status: {
state: 'working',
modified: new Date(),
@ -94,12 +94,12 @@ export async function* basicPromptConcatHandler(context: AgentFrameworkContext)
// Process prompts using common handler function
try {
const handlerConfig: HandlerConfig = context.agentDef.handlerConfig as HandlerConfig;
const agentFrameworkConfig = context.agentDef.agentFrameworkConfig as AgentFrameworkConfig;
const agentPromptDescription: AgentPromptDescription = {
id: context.agentDef.id,
api: aiApiConfig.api,
modelParameters: aiApiConfig.modelParameters,
handlerConfig,
agentFrameworkConfig,
};
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
@ -146,12 +146,12 @@ export async function* basicPromptConcatHandler(context: AgentFrameworkContext)
if (response.status === 'update') {
// For responseUpdate, we'll skip plugin-specific config for now
// since it's called frequently during streaming
await handlerHooks.responseUpdate.promise({
handlerContext: context,
await agentFrameworkHooks.responseUpdate.promise({
agentFrameworkContext: context,
response,
requestId: currentRequestId,
isFinal: false,
pluginConfig: {} as IPromptConcatTool, // Empty config for streaming updates
toolConfig: {} as IPromptConcatTool, // Empty config for streaming updates
});
}
@ -164,16 +164,16 @@ export async function* basicPromptConcatHandler(context: AgentFrameworkContext)
// Delegate final response processing to handler hooks
const responseCompleteContext = {
handlerContext: context,
agentFrameworkContext: context,
response,
requestId: currentRequestId,
isFinal: true,
pluginConfig: (pluginConfigs.length > 0 ? pluginConfigs[0] : {}) as IPromptConcatTool, // First config for compatibility
handlerConfig: context.agentDef.handlerConfig, // Pass complete config for plugin access
toolConfig: (toolConfigs.length > 0 ? toolConfigs[0] : {}) as IPromptConcatTool, // First config for compatibility
agentFrameworkConfig: context.agentDef.agentFrameworkConfig, // Pass complete config for tool access
actions: undefined as { yieldNextRoundTo?: 'self' | 'human'; newUserMessage?: string } | undefined,
};
await handlerHooks.responseComplete.promise(responseCompleteContext);
await agentFrameworkHooks.responseComplete.promise(responseCompleteContext);
// Check if responseComplete hooks set yieldNextRoundTo
let yieldNextRoundFromHooks: YieldNextRoundTarget | undefined;

View file

@ -4,8 +4,8 @@
"name": "Task Agent",
"description": "Example agent with prompt processing",
"avatarUrl": "https://example.com/task-agent.png",
"handlerID": "basicPromptConcatHandler",
"handlerConfig": {
"agentFrameworkID": "basicPromptConcatHandler",
"agentFrameworkConfig": {
"prompts": [
{
"id": "system",
@ -72,7 +72,7 @@
"plugins": [
{
"id": "efe5be74-540d-487d-8a05-7377e486953d",
"pluginId": "fullReplacement",
"toolId": "fullReplacement",
"fullReplacementParam": {
"targetId": "default-history",
"sourceType": "historyOfSession"
@ -84,7 +84,7 @@
"id": "f0e1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5",
"caption": "Wiki工作空间列表",
"description": "自动在提示词中注入可用的Wiki工作空间列表",
"pluginId": "workspacesList",
"toolId": "workspacesList",
"workspacesListParam": {
"targetId": "default-before-tool",
"position": "after"
@ -94,7 +94,7 @@
"id": "d0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5",
"caption": "Wiki搜索和向量索引工具",
"description": "提供Wiki搜索filter和vector以及向量嵌入索引管理功能",
"pluginId": "wikiSearch",
"toolId": "wikiSearch",
"wikiSearchParam": {
"sourceType": "wiki",
"toolListPosition": {
@ -107,7 +107,7 @@
"id": "e1f2b3c4-5d6e-7f8g-9h0i-k1l2m3n4o5p6",
"caption": "Wiki操作工具",
"description": "允许AI在Wiki工作空间中创建、更新和删除笔记",
"pluginId": "wikiOperation",
"toolId": "wikiOperation",
"wikiOperationParam": {
"toolListPosition": {
"position": "after",
@ -117,7 +117,7 @@
},
{
"id": "a0f1b2c3-4d5e-6f7g-8h9i-j0k1l2m3n4o5",
"pluginId": "fullReplacement",
"toolId": "fullReplacement",
"fullReplacementParam": {
"targetId": "default-response",
"sourceType": "llmResponse"

View file

@ -9,7 +9,7 @@ import { basicPromptConcatHandler } from '@services/agentInstance/agentFramework
import type { AgentFramework, AgentFrameworkContext } from '@services/agentInstance/agentFrameworks/utilities/type';
import { promptConcatStream, PromptConcatStreamState } from '@services/agentInstance/promptConcat/promptConcat';
import type { AgentPromptDescription } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { getPromptConcatHandlerConfigJsonSchema } from '@services/agentInstance/promptConcat/promptConcatSchema/jsonSchema';
import { getPromptConcatAgentFrameworkConfigJsonSchema } from '@services/agentInstance/promptConcat/promptConcatSchema/jsonSchema';
import { createHooksWithTools, initializeToolSystem } from '@services/agentInstance/tools';
import type { IDatabaseService } from '@services/database/interface';
import { AgentInstanceEntity, AgentInstanceMessageEntity } from '@services/database/schema/agent';
@ -80,7 +80,7 @@ export class AgentInstanceService implements IAgentInstanceService {
public registerBuiltinFrameworks(): void {
// Tools are already registered in initialize(), so we only register frameworks here
// Register basic prompt concatenation framework with its schema
this.registerFramework('basicPromptConcatHandler', basicPromptConcatHandler, getPromptConcatHandlerConfigJsonSchema());
this.registerFramework('basicPromptConcatHandler', basicPromptConcatHandler, getPromptConcatAgentFrameworkConfigJsonSchema());
}
/**
@ -216,7 +216,7 @@ export class AgentInstanceService implements IAgentInstanceService {
}
// Update fields using pick + Object.assign for consistency with updateAgentDef
const pickedProperties = pick(data, ['name', 'status', 'avatarUrl', 'aiApiConfig', 'closed', 'handlerConfig']);
const pickedProperties = pick(data, ['name', 'status', 'avatarUrl', 'aiApiConfig', 'closed', 'agentFrameworkConfig']);
Object.assign(instanceEntity, pickedProperties);
// Save instance updates
@ -354,13 +354,13 @@ export class AgentInstanceService implements IAgentInstanceService {
}
// Get appropriate framework
const handlerId = agentDefinition.handlerID;
if (!handlerId) {
throw new Error(`Handler ID not found in agent definition: ${agentDefinition.id}`);
const agentFrameworkId = agentDefinition.agentFrameworkID;
if (!agentFrameworkId) {
throw new Error(`Agent framework ID not found in agent definition: ${agentDefinition.id}`);
}
const framework = this.agentFrameworks.get(handlerId);
const framework = this.agentFrameworks.get(agentFrameworkId);
if (!framework) {
throw new Error(`Framework not found: ${handlerId}`);
throw new Error(`Framework not found: ${agentFrameworkId}`);
}
// Create framework context with temporary message added for processing
@ -380,11 +380,11 @@ export class AgentInstanceService implements IAgentInstanceService {
};
// Create fresh hooks for this framework execution and register tools based on frameworkConfig
const { hooks: frameworkHooks } = await createHooksWithTools(agentDefinition.handlerConfig || {});
const { hooks: frameworkHooks } = await createHooksWithTools(agentDefinition.agentFrameworkConfig || {});
// Trigger userMessageReceived hook with the configured tools
await frameworkHooks.userMessageReceived.promise({
handlerContext: frameworkContext,
agentFrameworkContext: frameworkContext,
content,
messageId,
timestamp: now,
@ -443,7 +443,7 @@ export class AgentInstanceService implements IAgentInstanceService {
// Trigger agentStatusChanged hook for completion
await frameworkHooks.agentStatusChanged.promise({
handlerContext: frameworkContext,
agentFrameworkContext: frameworkContext,
status: {
state: 'completed',
modified: new Date(),
@ -459,7 +459,7 @@ export class AgentInstanceService implements IAgentInstanceService {
// Trigger agentStatusChanged hook for failure
await frameworkHooks.agentStatusChanged.promise({
handlerContext: frameworkContext,
agentFrameworkContext: frameworkContext,
status: {
state: 'failed',
modified: new Date(),
@ -848,10 +848,10 @@ export class AgentInstanceService implements IAgentInstanceService {
}
}
public concatPrompt(promptDescription: Pick<AgentPromptDescription, 'handlerConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState> {
public concatPrompt(promptDescription: Pick<AgentPromptDescription, 'agentFrameworkConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState> {
logger.debug('AgentInstanceService.concatPrompt called', {
hasPromptConfig: !!promptDescription.handlerConfig,
promptConfigKeys: Object.keys(promptDescription.handlerConfig),
hasPromptConfig: !!promptDescription.agentFrameworkConfig,
promptConfigKeys: Object.keys(promptDescription.agentFrameworkConfig || {}),
messagesCount: messages.length,
});
@ -866,9 +866,9 @@ export class AgentInstanceService implements IAgentInstanceService {
agentDefId: 'temp',
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
handlerConfig: {},
agentFrameworkConfig: {},
},
agentDef: { id: 'temp', name: 'temp', handlerConfig: promptDescription.handlerConfig },
agentDef: { id: 'temp', name: 'temp', agentFrameworkConfig: promptDescription.agentFrameworkConfig || {} },
isCancelled: () => false,
};

View file

@ -8,16 +8,16 @@ import { AgentPromptDescription } from '@services/agentInstance/promptConcat/pro
/**
* Content of a session instance that user chat with an agent.
* Inherits from AgentDefinition but makes handlerConfig optional to allow fallback.
* Inherits from AgentDefinition but makes agentFrameworkConfig optional to allow fallback.
* The instance can override the definition's configuration, or fall back to using it.
*/
export interface AgentInstance extends Omit<AgentDefinition, 'name' | 'handlerConfig'> {
export interface AgentInstance extends Omit<AgentDefinition, 'name' | 'agentFrameworkConfig'> {
/** Agent description ID that generates this instance */
agentDefId: string;
/** Session name, optional in instance unlike definition */
name?: string;
/** Agent handler's config - optional, falls back to AgentDefinition.handlerConfig if not set */
handlerConfig?: Record<string, unknown>;
/** Agent framework's config - optional, falls back to AgentDefinition.agentFrameworkConfig if not set */
agentFrameworkConfig?: Record<string, unknown>;
/**
* Message history.
* latest on top, so it's easy to get first one as user's latest input, and rest as history.
@ -196,12 +196,12 @@ export interface IAgentInstanceService {
* @param messages Messages to be included in prompt generation
* @returns Observable stream of processing states, with final state containing complete results
*/
concatPrompt(promptDescription: Pick<AgentPromptDescription, 'handlerConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState>;
concatPrompt(promptDescription: Pick<AgentPromptDescription, 'agentFrameworkConfig'>, messages: AgentInstanceMessage[]): Observable<PromptConcatStreamState>;
/**
* Get JSON Schema for handler configuration
* This allows frontend to generate a form based on the schema for a specific handler
* @param handlerId Handler ID to get schema for
* @param agentFrameworkID Handler ID to get schema for
* @returns JSON Schema for handler configuration
*/
getFrameworkConfigSchema(frameworkId: string): Record<string, unknown>;

View file

@ -2,11 +2,11 @@
Prompt engineering and message processing with a plugin-based architecture.
If final prompt is a food, then `handlerConfig.prompts` is the recipe. Chat history and user input are raw materials.
If final prompt is a food, then `agentFrameworkConfig.prompts` is the recipe. Chat history and user input are raw materials.
## Implementation
The `promptConcat` function uses a tapable hooks-based plugin system. Built-in plugins are registered by `pluginId` and loaded based on configuration in `taskAgents.json`.
The `promptConcat` function uses a tapable hooks-based plugin system. Built-in tools are registered by `toolId` and loaded based on configuration in `taskAgents.json`.
### Plugin System Architecture
@ -23,13 +23,13 @@ The `promptConcat` function uses a tapable hooks-based plugin system. Built-in p
- `toolCalling`: Processes function calls in responses
3. **Plugin Registration**:
- Plugins are registered by `pluginId` field in the `plugins` array
- Plugins are registered by `toolId` field in the `plugins` array
- Each plugin instance has its own configuration parameters
- Built-in plugins are auto-registered on system initialization
### Plugin Lifecycle
2. **Configuration**: Plugins are loaded based on `handlerConfig.plugins` array
2. **Configuration**: Plugins are loaded based on `agentFrameworkConfig.plugins` array
3. **Execution**: Hooks execute plugins in registration order
4. **Error Handling**: Individual plugin failures don't stop the pipeline
@ -37,7 +37,7 @@ The `promptConcat` function uses a tapable hooks-based plugin system. Built-in p
1. Create plugin function in `plugins/` directory
2. Register in `plugins/index.ts`
3. Add `pluginId` to schema enum
3. Add `toolId` to schema enum
4. Add parameter schema if needed
Each plugin receives a hooks object and registers handlers for specific hook points. Plugins can modify prompt trees, inject content, process responses, and trigger additional LLM calls.

View file

@ -10,7 +10,7 @@
* Main Concepts:
* - Prompts are tree-structured, can have roles (system/user/assistant) and children.
* - Plugins use hooks to modify the prompt tree at runtime.
* - Built-in plugins are registered by pluginId and executed when matching plugins are found.
* - Built-in tools are registered by toolId and executed when matching tools are found.
*/
import { logger } from '@services/libs/log';
@ -203,40 +203,41 @@ export interface PromptConcatStreamState {
* Yields intermediate results for real-time UI updates
*/
export async function* promptConcatStream(
agentConfig: Pick<AgentPromptDescription, 'handlerConfig'>,
agentConfig: Pick<AgentPromptDescription, 'agentFrameworkConfig'>,
messages: AgentInstanceMessage[],
handlerContext: AgentFrameworkContext,
agentFrameworkContext: AgentFrameworkContext,
): AsyncGenerator<PromptConcatStreamState, PromptConcatStreamState, unknown> {
const promptConfigs = Array.isArray(agentConfig.handlerConfig.prompts) ? agentConfig.handlerConfig.prompts : [];
const pluginConfigs = (Array.isArray(agentConfig.handlerConfig.plugins) ? agentConfig.handlerConfig.plugins : []) as IPromptConcatTool[];
const agentFrameworkConfig = agentConfig.agentFrameworkConfig;
const promptConfigs = Array.isArray(agentFrameworkConfig?.prompts) ? agentFrameworkConfig.prompts : [];
const toolConfigs = (Array.isArray(agentFrameworkConfig?.plugins) ? agentFrameworkConfig.plugins : []) as IPromptConcatTool[];
const promptsCopy = cloneDeep(promptConfigs);
const sourcePaths = generateSourcePaths(promptsCopy, pluginConfigs);
const sourcePaths = generateSourcePaths(promptsCopy, toolConfigs);
const hooks = createAgentFrameworkHooks();
// Register plugins that match the configuration
for (const plugin of pluginConfigs) {
const builtInPlugin = builtInTools.get(plugin.pluginId);
if (builtInPlugin) {
builtInPlugin(hooks);
logger.debug('Registered plugin', {
pluginId: plugin.pluginId,
pluginInstanceId: plugin.id,
// Register tools that match the configuration
for (const tool of toolConfigs) {
const builtInTool = builtInTools.get(tool.toolId);
if (builtInTool) {
builtInTool(hooks);
logger.debug('Registered tool', {
toolId: tool.toolId,
toolInstanceId: tool.id,
});
} else {
logger.info(`No built-in plugin found for pluginId: ${plugin.pluginId}`);
logger.info(`No built-in tool found for toolId: ${tool.toolId}`);
}
}
// Process each plugin through hooks with streaming
let modifiedPrompts = promptsCopy;
const totalSteps = pluginConfigs.length + 2; // plugins + finalize + flatten
const totalSteps = toolConfigs.length + 2; // plugins + finalize + flatten
for (let index = 0; index < pluginConfigs.length; index++) {
for (let index = 0; index < toolConfigs.length; index++) {
const context: PromptConcatHookContext = {
handlerContext,
agentFrameworkContext: agentFrameworkContext,
messages,
prompts: modifiedPrompts,
pluginConfig: pluginConfigs[index],
toolConfig: toolConfigs[index],
metadata: { sourcePaths },
};
try {
@ -255,13 +256,13 @@ export async function* promptConcatStream(
processedPrompts: modifiedPrompts,
flatPrompts: intermediateFlat,
step: 'plugin',
currentPlugin: pluginConfigs[index],
currentPlugin: toolConfigs[index],
progress: (index + 1) / totalSteps,
isComplete: false,
};
} catch (error) {
logger.error('Plugin processing error', {
pluginConfig: pluginConfigs[index],
toolConfig: toolConfigs[index],
error,
});
// Continue processing other plugins even if one fails
@ -273,15 +274,15 @@ export async function* promptConcatStream(
processedPrompts: modifiedPrompts,
flatPrompts: flattenPrompts(modifiedPrompts),
step: 'finalize',
progress: (pluginConfigs.length + 1) / totalSteps,
progress: (toolConfigs.length + 1) / totalSteps,
isComplete: false,
};
const finalContext: PromptConcatHookContext = {
handlerContext,
agentFrameworkContext: agentFrameworkContext,
messages,
prompts: modifiedPrompts,
pluginConfig: {} as IPromptConcatTool, // Empty plugin for finalization
toolConfig: {} as IPromptConcatTool, // Empty plugin for finalization
metadata: { sourcePaths },
};
@ -297,7 +298,7 @@ export async function* promptConcatStream(
processedPrompts: modifiedPrompts,
flatPrompts: flattenPrompts(modifiedPrompts),
step: 'flatten',
progress: (pluginConfigs.length + 2) / totalSteps,
progress: (toolConfigs.length + 2) / totalSteps,
isComplete: false,
};
@ -341,15 +342,15 @@ export async function* promptConcatStream(
* @returns Processed prompt array and original prompt tree
*/
export async function promptConcat(
agentConfig: Pick<AgentPromptDescription, 'handlerConfig'>,
agentConfig: Pick<AgentPromptDescription, 'agentFrameworkConfig'>,
messages: AgentInstanceMessage[],
handlerContext: AgentFrameworkContext,
agentFrameworkContext: AgentFrameworkContext,
): Promise<{
flatPrompts: ModelMessage[];
processedPrompts: IPrompt[];
}> {
// Use the streaming version and just return the final result
const stream = promptConcatStream(agentConfig, messages, handlerContext);
const stream = promptConcatStream(agentConfig, messages, agentFrameworkContext);
let finalResult: PromptConcatStreamState;
// Consume all intermediate states to get the final result

View file

@ -72,7 +72,7 @@ export function getFrameworkConfigSchema() {
* "model": "Qwen/Qwen2.5-7B-Instruct"
* },
* "modelParameters": { ... },
* "handlerConfig": {
* "agentFrameworkConfig": {
* "prompts": [ ... ],
* "response": [ ... ],
* "plugins": [ ... ],
@ -88,7 +88,7 @@ export function getAgentConfigSchema() {
title: t('Schema.AgentConfig.IdTitle'),
description: t('Schema.AgentConfig.Id'),
}),
handlerConfig: dynamicFrameworkConfigSchema,
agentFrameworkConfig: dynamicFrameworkConfigSchema,
}).meta({
title: t('Schema.AgentConfig.Title'),
description: t('Schema.AgentConfig.Description'),
@ -110,7 +110,9 @@ export function getDefaultAgentsSchema() {
export type DefaultAgents = z.infer<ReturnType<typeof getDefaultAgentsSchema>>;
export type AgentPromptDescription = z.infer<ReturnType<typeof getAgentConfigSchema>>;
export type AiAPIConfig = z.infer<typeof AIConfigSchema>;
export type HandlerConfig = z.infer<ReturnType<typeof getFrameworkConfigSchema>>;
export type AgentFrameworkConfig = z.infer<ReturnType<typeof getFrameworkConfigSchema>>;
// Backward compat alias
export type agentFrameworkConfig = AgentFrameworkConfig;
// Re-export all schemas and types
export * from './modelParameters';

View file

@ -11,7 +11,7 @@ import { getFrameworkConfigSchema } from './index';
*
* Description field is i18n key, use i18nAlly extension to see it on VSCode. And use react-i18next to translate it on frontend.
*/
export function getPromptConcatHandlerConfigJsonSchema() {
export function getPromptConcatAgentFrameworkConfigJsonSchema() {
const dynamicFrameworkConfigSchema = getFrameworkConfigSchema();
return z.toJSONSchema(dynamicFrameworkConfigSchema, { target: 'draft-7' });
}

View file

@ -14,7 +14,7 @@ export type IPromptConcatTool = {
caption?: string;
content?: string;
forbidOverrides?: boolean;
pluginId: string;
toolId: string;
// Tool-specific parameters
fullReplacementParam?: FullReplacementParameter;

View file

@ -11,7 +11,7 @@ import { AgentInstanceMessage } from '../interface';
import { builtInTools, createAgentFrameworkHooks } from '../tools';
import { AgentResponse, PostProcessContext, YieldNextRoundTarget } from '../tools/types';
import type { IPromptConcatTool } from './promptConcatSchema';
import { AgentPromptDescription, HandlerConfig } from './promptConcatSchema';
import { AgentFrameworkConfig, AgentPromptDescription } from './promptConcatSchema';
/**
* Process response configuration, apply plugins, and return final response
@ -38,33 +38,33 @@ export async function responseConcat(
responseLength: llmResponse.length,
});
const { handlerConfig } = agentConfig;
const responses: HandlerConfig['response'] = Array.isArray(handlerConfig.response) ? handlerConfig.response : [];
const plugins = (Array.isArray(handlerConfig.plugins) ? handlerConfig.plugins : []) as IPromptConcatTool[];
const { agentFrameworkConfig } = agentConfig;
const responses: AgentFrameworkConfig['response'] = Array.isArray(agentFrameworkConfig?.response) ? (agentFrameworkConfig?.response || []) : [];
const toolConfigs = (Array.isArray(agentFrameworkConfig.plugins) ? agentFrameworkConfig.plugins : []) as IPromptConcatTool[];
let modifiedResponses = cloneDeep(responses) as AgentResponse[];
// Create hooks instance
const hooks = createAgentFrameworkHooks();
// Register all plugins from configuration
for (const plugin of plugins) {
const builtInPlugin = builtInTools.get(plugin.pluginId);
if (builtInPlugin) {
builtInPlugin(hooks);
// Register all tools from configuration
for (const tool of toolConfigs) {
const builtInTool = builtInTools.get(tool.toolId);
if (builtInTool) {
builtInTool(hooks);
} else {
logger.warn(`No built-in plugin found for response pluginId: ${plugin.pluginId}`);
logger.warn(`No built-in tool found for response toolId: ${tool.toolId}`);
}
}
// Process each plugin through hooks
// Process each tool through hooks
let yieldNextRoundTo: YieldNextRoundTarget | undefined;
let toolCallInfo: ToolCallingMatch | undefined;
for (const plugin of plugins) {
for (const tool of toolConfigs) {
const responseContext: PostProcessContext = {
handlerContext: context,
agentFrameworkContext: context,
messages,
prompts: [], // Not used in response processing
pluginConfig: plugin,
toolConfig: tool,
llmResponse,
responses: modifiedResponses,
metadata: {},
@ -78,31 +78,31 @@ export async function responseConcat(
modifiedResponses = result.responses;
}
// Check if plugin indicated need for new LLM call via actions
// Check if tool indicated need for new LLM call via actions
if (result.actions?.yieldNextRoundTo) {
yieldNextRoundTo = result.actions.yieldNextRoundTo;
if (result.actions.toolCalling) {
toolCallInfo = result.actions.toolCalling;
}
logger.debug('Plugin requested yield next round', {
pluginId: plugin.pluginId,
pluginInstanceId: plugin.id,
logger.debug('Tool requested yield next round', {
toolId: tool.toolId,
toolInstanceId: tool.id,
yieldNextRoundTo,
hasToolCall: !!result.actions.toolCalling,
});
}
logger.debug('Response plugin processed successfully', {
pluginId: plugin.pluginId,
pluginInstanceId: plugin.id,
logger.debug('Response tool processed successfully', {
toolId: tool.toolId,
toolInstanceId: tool.id,
});
} catch (error) {
logger.error('Response plugin processing error', {
pluginId: plugin.pluginId,
pluginInstanceId: plugin.id,
logger.error('Response tool processing error', {
toolId: tool.toolId,
toolInstanceId: tool.id,
error,
});
// Continue processing other plugins even if one fails
// Continue processing other tools even if one fails
}
}

View file

@ -11,11 +11,11 @@ import type { IPrompt } from '../../promptConcat/promptConcatSchema/prompts';
import { cloneDeep } from 'lodash';
import defaultAgents from '../../agentFrameworks/taskAgents.json';
import { createAgentFrameworkHooks, PromptConcatHookContext } from '../index';
import { fullReplacementPlugin } from '../prompt';
import { fullReplacementTool } from '../prompt';
// Use the real agent config
const exampleAgent = defaultAgents[0];
const realHandlerConfig = exampleAgent.handlerConfig;
const realagentFrameworkConfig = exampleAgent.agentFrameworkConfig;
describe('Full Replacement Plugin - Duration Mechanism', () => {
beforeEach(() => {
@ -25,14 +25,14 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
describe('History Source Type with Duration Filtering', () => {
it('should filter out expired messages (duration=1) from historyOfSession', async () => {
// Find the real fullReplacement plugin for history from taskAgents.json
const historyPlugin = realHandlerConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
const historyPlugin = realagentFrameworkConfig.plugins.find(
p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
);
expect(historyPlugin).toBeDefined();
expect(historyPlugin!.fullReplacementParam!.targetId).toBe('default-history'); // Real target ID
// Use real prompts structure from taskAgents.json
const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[];
const testPrompts = cloneDeep(realagentFrameworkConfig.prompts) as IPrompt[];
const messages: AgentInstanceMessage[] = [
// Message 0: User message, no duration - should be included
@ -96,7 +96,7 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
];
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
messages,
@ -104,16 +104,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
},
pluginConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast due to JSON import limitations
toolConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast due to JSON import limitations
prompts: testPrompts,
messages,
};
const hooks = createAgentFrameworkHooks();
fullReplacementPlugin(hooks);
fullReplacementTool(hooks);
// Execute the processPrompts hook
await hooks.processPrompts.promise(context);
@ -126,8 +126,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
const targetPrompt = historyPrompt!.children?.find(child => child.id === targetId);
expect(targetPrompt).toBeDefined();
// The fullReplacementPlugin puts filtered messages in children array
// Note: fullReplacementPlugin removes the last message (current user message)
// The fullReplacementTool puts filtered messages in children array
// Note: fullReplacementTool removes the last message (current user message)
const children = (targetPrompt as unknown as { children?: IPrompt[] }).children || [];
expect(children.length).toBe(2); // Only non-expired messages (user1, ai-response), excluding last user message
@ -147,8 +147,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
});
it('should include messages with duration=0 (visible in current round)', async () => {
const historyPlugin = realHandlerConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
const historyPlugin = realagentFrameworkConfig.plugins.find(
p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
);
const messages: AgentInstanceMessage[] = [
@ -181,10 +181,10 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
},
];
const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[];
const testPrompts = cloneDeep(realagentFrameworkConfig.prompts) as IPrompt[];
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
messages,
@ -192,16 +192,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
},
pluginConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast for JSON import
toolConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast for JSON import
prompts: testPrompts,
messages,
};
const hooks = createAgentFrameworkHooks();
fullReplacementPlugin(hooks);
fullReplacementTool(hooks);
await hooks.processPrompts.promise(context);
@ -220,8 +220,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
});
it('should handle mixed duration values correctly', async () => {
const historyPlugin = realHandlerConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
const historyPlugin = realagentFrameworkConfig.plugins.find(
p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'historyOfSession',
);
const messages: AgentInstanceMessage[] = [
@ -263,10 +263,10 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
},
];
const testPrompts = cloneDeep(realHandlerConfig.prompts) as IPrompt[];
const testPrompts = cloneDeep(realagentFrameworkConfig.prompts) as IPrompt[];
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
messages,
@ -274,16 +274,16 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
},
pluginConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast for JSON import
toolConfig: historyPlugin! as unknown as IPromptConcatTool, // Type cast for JSON import
prompts: testPrompts,
messages,
};
const hooks = createAgentFrameworkHooks();
fullReplacementPlugin(hooks);
fullReplacementTool(hooks);
await hooks.processPrompts.promise(context);
@ -308,8 +308,8 @@ describe('Full Replacement Plugin - Duration Mechanism', () => {
describe('LLM Response Source Type', () => {
it('should verify LLM response replacement config exists', () => {
// Verify the real config has LLM response replacement
const llmResponsePlugin = realHandlerConfig.plugins.find(
p => p.pluginId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'llmResponse',
const llmResponsePlugin = realagentFrameworkConfig.plugins.find(
p => p.toolId === 'fullReplacement' && p.fullReplacementParam?.sourceType === 'llmResponse',
);
expect(llmResponsePlugin).toBeDefined();
expect(llmResponsePlugin!.fullReplacementParam!.targetId).toBe('default-response');

View file

@ -1,5 +1,5 @@
/**
* Deep integration tests for messageManagementPlugin with real SQLite database
* Deep integration tests for messageManagementTool with real SQLite database
* Tests actual message persistence scenarios using taskAgents.json configuration
*/
import { container } from '@services/container';
@ -11,7 +11,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import defaultAgents from '../../agentFrameworks/taskAgents.json';
import type { AgentInstanceMessage, IAgentInstanceService } from '../../interface';
import { createAgentFrameworkHooks } from '../index';
import { messageManagementPlugin } from '../messageManagement';
import { messageManagementTool } from '../messageManagement';
import type { ToolExecutionContext, UserMessageContext } from '../types';
// Use the real agent config from taskAgents.json
@ -70,14 +70,14 @@ describe('Message Management Plugin - Real Database Integration', () => {
// Initialize plugin
hooks = createAgentFrameworkHooks();
messageManagementPlugin(hooks);
messageManagementTool(hooks);
});
afterEach(async () => {
// Clean up is handled automatically by beforeEach for each test
});
const createHandlerContext = (messages: AgentInstanceMessage[] = []) => ({
const createagentFrameworkContext = (messages: AgentInstanceMessage[] = []) => ({
agent: {
id: testAgentId,
agentDefId: exampleAgent.id,
@ -90,19 +90,19 @@ describe('Message Management Plugin - Real Database Integration', () => {
name: exampleAgent.name,
version: '1.0.0',
capabilities: [],
handlerConfig: exampleAgent.handlerConfig,
agentFrameworkConfig: exampleAgent.agentFrameworkConfig,
},
isCancelled: () => false,
});
describe('Real Wiki Search Scenario - The Missing Tool Result Bug', () => {
it('should persist all messages in wiki search flow: user query → AI tool call → tool result → AI final response', async () => {
const handlerContext = createHandlerContext();
const agentFrameworkContext = createagentFrameworkContext();
// Step 1: User asks to search wiki
const userMessageId = `user-msg-${Date.now()}`;
const userContext: UserMessageContext = {
handlerContext,
agentFrameworkContext,
content: { text: '搜索 wiki 中的 Index 条目并解释' },
messageId: userMessageId,
timestamp: new Date(),
@ -133,10 +133,10 @@ describe('Message Management Plugin - Real Database Integration', () => {
};
await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage);
handlerContext.agent.messages.push(aiToolCallMessage);
agentFrameworkContext.agent.messages.push(aiToolCallMessage);
// Step 3: Tool result message (THIS IS THE MISSING PIECE!)
// This simulates what wikiSearchPlugin does when tool execution completes
// This simulates what wikiSearchTool does when tool execution completes
const toolResultMessage: AgentInstanceMessage = {
id: `tool-result-${Date.now()}`,
agentId: testAgentId,
@ -164,11 +164,11 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
duration: 10, // Tool results might have expiration
};
// Add tool result to agent messages (simulating what wikiSearchPlugin does)
handlerContext.agent.messages.push(toolResultMessage);
// Add tool result to agent messages (simulating what wikiSearchTool does)
agentFrameworkContext.agent.messages.push(toolResultMessage);
const toolContext: ToolExecutionContext = {
handlerContext,
agentFrameworkContext,
toolResult: {
success: true,
data: 'Wiki search completed successfully',
@ -202,7 +202,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
expect(savedToolResult?.duration).toBe(10);
// Verify isPersisted flag was updated
const toolMessageInMemory = handlerContext.agent.messages.find(
const toolMessageInMemory = agentFrameworkContext.agent.messages.find(
(m) => m.metadata?.isToolResult,
);
expect(toolMessageInMemory?.metadata?.isPersisted).toBe(true);
@ -249,7 +249,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
});
it('should handle multiple tool results in one execution', async () => {
const handlerContext = createHandlerContext();
const agentFrameworkContext = createagentFrameworkContext();
// Add multiple tool result messages
const toolResult1: AgentInstanceMessage = {
@ -282,10 +282,10 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
duration: 3,
};
handlerContext.agent.messages.push(toolResult1, toolResult2);
agentFrameworkContext.agent.messages.push(toolResult1, toolResult2);
const toolContext: ToolExecutionContext = {
handlerContext,
agentFrameworkContext,
toolResult: {
success: true,
data: 'Multiple tool search completed',
@ -316,7 +316,7 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
it('should maintain message integrity when reloading from database (simulating page refresh)', async () => {
// This test simulates the issue where tool results are missing after page refresh
const handlerContext = createHandlerContext();
const agentFrameworkContext = createagentFrameworkContext();
// Step 1: Complete chat flow with user message → AI tool call → tool result → AI response
const userMessage: AgentInstanceMessage = {
@ -372,9 +372,9 @@ Result: 在wiki中找到了名为"Index"的条目。这个条目包含以下内
await agentInstanceServiceImpl.saveUserMessage(aiToolCallMessage);
// Add tool result to context and trigger persistence via toolExecuted hook
handlerContext.agent.messages.push(toolResultMessage);
agentFrameworkContext.agent.messages.push(toolResultMessage);
const toolContext: ToolExecutionContext = {
handlerContext,
agentFrameworkContext,
toolResult: { success: true, data: 'Search completed' },
toolInfo: { toolId: 'wiki-search', parameters: {} },
};

View file

@ -1,5 +1,5 @@
/**
* Tests for wikiOperationPlugin
* Tests for wikiOperationTool
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@ -19,8 +19,8 @@ import type { AgentFrameworkContext } from '../../agentFrameworks/utilities/type
import type { AgentInstance } from '../../interface';
import { createAgentFrameworkHooks } from '../index';
import type { AIResponseContext, PromptConcatHookContext, ToolActions } from '../types';
import { wikiOperationPlugin } from '../wikiOperation';
import { workspacesListPlugin } from '../workspacesList';
import { wikiOperationTool } from '../wikiOperation';
import { workspacesListTool } from '../workspacesList';
// Mock i18n
vi.mock('@services/libs/i18n', () => ({
@ -50,8 +50,8 @@ vi.mock('@services/libs/i18n', () => ({
},
}));
// Helper to construct a complete AgentHandlerContext for tests
const makeHandlerContext = (agentId = 'test-agent'): AgentFrameworkContext => ({
// Helper to construct a complete AgentagentFrameworkContext for tests
const makeagentFrameworkContext = (agentId = 'test-agent'): AgentFrameworkContext => ({
agent: {
id: agentId,
agentDefId: 'test-agent-def',
@ -59,11 +59,11 @@ const makeHandlerContext = (agentId = 'test-agent'): AgentFrameworkContext => ({
status: { state: 'working', modified: new Date() },
created: new Date(),
} as unknown as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test-agent-def', handlerConfig: {} } as unknown as { id: string; name: string; handlerConfig: Record<string, unknown> },
agentDef: { id: 'test-agent-def', name: 'test-agent-def', agentFrameworkConfig: {} } as unknown as { id: string; name: string; agentFrameworkConfig: Record<string, unknown> },
isCancelled: () => false,
});
describe('wikiOperationPlugin', () => {
describe('wikiOperationTool', () => {
beforeEach(async () => {
vi.clearAllMocks();
});
@ -74,11 +74,11 @@ describe('wikiOperationPlugin', () => {
it('should inject wiki operation tool content when plugin is configured', async () => {
const hooks = createAgentFrameworkHooks();
// First register workspacesListPlugin to inject available workspaces from the global mock
workspacesListPlugin(hooks);
wikiOperationPlugin(hooks);
// First register workspacesListTool to inject available workspaces from the global mock
workspacesListTool(hooks);
wikiOperationTool(hooks);
// Start with prompts and run workspacesList injection first (pluginConfig for workspacesList)
// Start with prompts and run workspacesList injection first (toolConfig for workspacesList)
const prompts: IPrompt[] = [
{
id: 'target-prompt',
@ -88,18 +88,18 @@ describe('wikiOperationPlugin', () => {
];
const workspacesContext: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: { id: 'test-agent', messages: [], agentDefId: 'test', status: { state: 'working' as const, modified: new Date() }, created: new Date() },
agentDef: { id: 'test', name: 'test', handlerConfig: {} },
agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
},
messages: [],
prompts,
pluginConfig: {
toolConfig: {
id: 'workspaces-plugin',
caption: 'Workspaces Plugin',
forbidOverrides: false,
pluginId: 'workspacesList',
toolId: 'workspacesList',
workspacesListParam: {
targetId: 'target-prompt',
position: 'after' as const,
@ -111,12 +111,12 @@ describe('wikiOperationPlugin', () => {
// Then run wikiOperation injection which will append its tool content to the same prompt
const wikiOpContext: PromptConcatHookContext = {
handlerContext: workspacesContext.handlerContext,
agentFrameworkContext: workspacesContext.agentFrameworkContext,
messages: [],
prompts,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiOperation',
toolId: 'wikiOperation',
wikiOperationParam: {
toolListPosition: {
targetId: 'target-prompt',
@ -129,7 +129,7 @@ describe('wikiOperationPlugin', () => {
await hooks.processPrompts.promise(wikiOpContext);
const targetPrompt = prompts[0];
// workspacesListPlugin and wikiOperationPlugin may both add children; assert the combined children text contains expected snippets
// workspacesListTool and wikiOperationTool may both add children; assert the combined children text contains expected snippets
const childrenText = JSON.stringify(targetPrompt.children);
expect(childrenText).toContain('wiki-operation');
// Ensure the injected tool content documents the supported operations (enum values)
@ -148,16 +148,16 @@ describe('wikiOperationPlugin', () => {
describe('tool execution', () => {
it('should execute create operation successfully', async () => {
const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks);
wikiOperationTool(hooks);
const handlerContext = makeHandlerContext();
const agentFrameworkContext = makeagentFrameworkContext();
const context = {
handlerContext,
handlerConfig: {
agentFrameworkContext,
agentFrameworkConfig: {
plugins: [
{
pluginId: 'wikiOperation',
toolId: 'wikiOperation',
wikiOperationParam: {
toolResultDuration: 1,
},
@ -183,9 +183,9 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(createParams)}</tool_use>`;
// Add an assistant message containing the tool_use so the plugin can find it
handlerContext.agent.messages.push({
agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'assistant',
content: context.response.content,
modified: new Date(),
@ -209,9 +209,9 @@ describe('wikiOperationPlugin', () => {
expect(typeof wikiSvc.wikiOperationInServer).toBe('function');
const responseCtx: AIResponseContext = {
handlerContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
agentFrameworkContext,
toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-create', content: context.response.content, status: 'done' } as AIStreamResponse,
requestId: 'r-create',
isFinal: true,
@ -227,7 +227,7 @@ describe('wikiOperationPlugin', () => {
);
// Verify a tool result message was added to agent history
const toolResultMessage = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
const toolResultMessage = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult);
expect(toolResultMessage).toBeTruthy();
expect(toolResultMessage?.content).toContain('<functions_result>');
// Check for general success wording and tiddler title
@ -239,14 +239,14 @@ describe('wikiOperationPlugin', () => {
it('should execute update operation successfully', async () => {
const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks);
wikiOperationTool(hooks);
const handlerContext = makeHandlerContext();
const agentFrameworkContext = makeagentFrameworkContext();
const context = {
handlerContext,
handlerConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }],
agentFrameworkContext,
agentFrameworkConfig: {
plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
},
response: {
status: 'done' as const,
@ -256,9 +256,9 @@ describe('wikiOperationPlugin', () => {
};
// Add assistant message so plugin can detect the tool call
handlerContext.agent.messages.push({
agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'assistant',
content: context.response.content,
modified: new Date(),
@ -274,9 +274,9 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(updateParams)}</tool_use>`;
const respCtx2: AIResponseContext = {
handlerContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
agentFrameworkContext,
toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-update', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as ToolActions,
requestId: 'r-update',
@ -291,7 +291,7 @@ describe('wikiOperationPlugin', () => {
);
// Check general update success wording and tiddler title
const updateResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
const updateResult = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult);
expect(updateResult).toBeTruthy();
expect(updateResult?.content).toContain('成功在Wiki工作空间');
expect(updateResult?.content).toContain('Existing Note');
@ -299,14 +299,14 @@ describe('wikiOperationPlugin', () => {
it('should execute delete operation successfully', async () => {
const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks);
wikiOperationTool(hooks);
const handlerContext = makeHandlerContext();
const agentFrameworkContext = makeagentFrameworkContext();
const context = {
handlerContext,
handlerConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }],
agentFrameworkContext,
agentFrameworkConfig: {
plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
},
response: {
status: 'done' as const,
@ -316,9 +316,9 @@ describe('wikiOperationPlugin', () => {
};
// Add assistant message so plugin can detect the tool call
handlerContext.agent.messages.push({
agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'assistant',
content: context.response.content,
modified: new Date(),
@ -333,9 +333,9 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(deleteParams)}</tool_use>`;
const respCtx3: AIResponseContext = {
handlerContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
agentFrameworkContext,
toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-delete', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as ToolActions,
requestId: 'r-delete',
@ -349,22 +349,22 @@ describe('wikiOperationPlugin', () => {
['Note to Delete'],
);
const deleteResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
const deleteResult = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult);
expect(deleteResult).toBeTruthy();
expect(deleteResult?.content).toContain('成功从Wiki工作空间');
});
it('should handle workspace not found error', async () => {
const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks);
wikiOperationTool(hooks);
// Use an actual tool_use payload with a nonexistent workspace
const handlerContext = makeHandlerContext();
const agentFrameworkContext = makeagentFrameworkContext();
const context = {
handlerContext,
handlerConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }],
agentFrameworkContext,
agentFrameworkConfig: {
plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
},
response: {
status: 'done',
@ -374,9 +374,9 @@ describe('wikiOperationPlugin', () => {
};
// Add assistant message so plugin can detect the tool call
handlerContext.agent.messages.push({
agentFrameworkContext.agent.messages.push({
id: `m-${Date.now()}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'assistant',
content: context.response.content,
modified: new Date(),
@ -390,9 +390,9 @@ describe('wikiOperationPlugin', () => {
context.response.content = `<tool_use name="wiki-operation">${JSON.stringify(badParams)}</tool_use>`;
const respCtx4: AIResponseContext = {
handlerContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
agentFrameworkContext,
toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-error', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as ToolActions,
requestId: 'r-error',
@ -400,7 +400,7 @@ describe('wikiOperationPlugin', () => {
};
await hooks.responseComplete.promise(respCtx4);
const errResult = handlerContext.agent.messages.find(m => m.metadata?.isToolResult);
const errResult = agentFrameworkContext.agent.messages.find(m => m.metadata?.isToolResult);
expect(errResult).toBeTruthy();
expect(errResult?.content).toContain('工作空间名称或ID');
// Ensure control is yielded to self on error so AI gets the next round
@ -409,16 +409,16 @@ describe('wikiOperationPlugin', () => {
it('should not execute when tool call is not found', async () => {
const hooks = createAgentFrameworkHooks();
wikiOperationPlugin(hooks);
wikiOperationTool(hooks);
// No tool_use in response
const handlerContext = makeHandlerContext();
const agentFrameworkContext = makeagentFrameworkContext();
const context = {
handlerContext,
handlerConfig: {
plugins: [{ pluginId: 'wikiOperation', wikiOperationParam: {} }],
agentFrameworkContext,
agentFrameworkConfig: {
plugins: [{ toolId: 'wikiOperation', wikiOperationParam: {} }],
},
response: {
status: 'done' as const,
@ -428,9 +428,9 @@ describe('wikiOperationPlugin', () => {
};
await hooks.responseComplete.promise({
handlerContext,
pluginConfig: context.handlerConfig?.plugins?.[0] as unknown as IPromptConcatTool,
handlerConfig: context.handlerConfig as { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
agentFrameworkContext,
toolConfig: context.agentFrameworkConfig?.plugins?.[0] as unknown as IPromptConcatTool,
agentFrameworkConfig: context.agentFrameworkConfig as { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
response: { requestId: 'r-none', content: context.response.content, status: 'done' } as AIStreamResponse,
actions: {} as ToolActions,
requestId: 'r-none',
@ -439,7 +439,7 @@ describe('wikiOperationPlugin', () => {
const wikiLocalAssert = container.get<Partial<IWikiService>>(serviceIdentifier.Wiki);
expect(wikiLocalAssert.wikiOperationInServer).not.toHaveBeenCalled();
expect(handlerContext.agent.messages).toHaveLength(0);
expect(agentFrameworkContext.agent.messages).toHaveLength(0);
});
});
});

View file

@ -20,8 +20,8 @@ import type { IPromptConcatTool } from '@services/agentInstance/promptConcat/pro
import { cloneDeep } from 'lodash';
import defaultAgents from '../../agentFrameworks/taskAgents.json';
import { createAgentFrameworkHooks, PromptConcatHookContext } from '../index';
import { messageManagementPlugin } from '../messageManagement';
import { wikiSearchPlugin } from '../wikiSearch';
import { messageManagementTool } from '../messageManagement';
import { wikiSearchTool } from '../wikiSearch';
// Mock i18n
vi.mock('@services/libs/i18n', () => ({
@ -53,7 +53,7 @@ vi.mock('@services/libs/i18n', () => ({
// Use the real agent config
const exampleAgent = defaultAgents[0];
const handlerConfig = exampleAgent.handlerConfig as AgentPromptDescription['handlerConfig'];
const agentFrameworkConfig = (exampleAgent.agentFrameworkConfig || {}) as AgentPromptDescription['agentFrameworkConfig'];
// Services will be retrieved from container on demand inside each test/describe
@ -77,7 +77,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
it('should inject wiki tools into prompts when configured', async () => {
// Find the wiki search plugin config, make sure our default config
const wikiPlugin = handlerConfig.plugins.find((p: unknown): p is IPromptConcatTool => (p as IPromptConcatTool).pluginId === 'wikiSearch');
const wikiPlugin = agentFrameworkConfig.plugins.find((p: unknown): p is IPromptConcatTool => (p as IPromptConcatTool).toolId === 'wikiSearch');
expect(wikiPlugin).toBeDefined();
if (!wikiPlugin) {
// throw error to keep ts believe the plugin exists
@ -89,7 +89,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
expect(wikiPlugin.wikiSearchParam?.toolListPosition).toBeDefined();
// Create a copy of prompts to test modification
const prompts = cloneDeep(handlerConfig.prompts);
const prompts = cloneDeep(agentFrameworkConfig.prompts);
const messages = [
{
id: 'user-1',
@ -102,19 +102,19 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
];
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: { id: 'test', messages: [], agentDefId: 'test', status: { state: 'working' as const, modified: new Date() }, created: new Date() },
agentDef: { id: 'test', name: 'test', handlerConfig: {} },
agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
},
pluginConfig: wikiPlugin,
toolConfig: wikiPlugin,
prompts: prompts,
messages,
};
// Use real hooks from the plugin system
const promptHooks = createAgentFrameworkHooks();
wikiSearchPlugin(promptHooks);
wikiSearchTool(promptHooks);
// Execute the processPrompts hook
await promptHooks.processPrompts.promise(context);
@ -130,7 +130,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
// Create a plugin config with trigger that won't match
const wikiPlugin = {
id: 'test-wiki-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
retrievalAugmentedGenerationParam: {
sourceType: 'wiki' as const,
@ -144,11 +144,11 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
};
const prompts = cloneDeep(defaultAgents[0].handlerConfig.prompts);
const prompts = cloneDeep(defaultAgents[0].agentFrameworkConfig.prompts);
const originalPromptsText = JSON.stringify(prompts);
const context = {
pluginConfig: wikiPlugin,
toolConfig: wikiPlugin,
prompts,
messages: [
{
@ -163,7 +163,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
// build a minimal PromptConcatHookContext to run the plugin's processPrompts
const handlerCtx: AgentFrameworkContext = {
agent: {
@ -173,12 +173,12 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
} as AgentInstance,
agentDef: { id: 'test', name: 'test', handlerConfig: {} } as AgentDefinition,
agentDef: { id: 'test', name: 'test', agentFrameworkConfig: {} } as AgentDefinition,
isCancelled: () => false,
};
const hookContext: PromptConcatHookContext = {
handlerContext: handlerCtx,
pluginConfig: wikiPlugin as IPromptConcatTool,
agentFrameworkContext: handlerCtx,
toolConfig: wikiPlugin as IPromptConcatTool,
prompts: prompts as IPrompt[],
messages: context.messages as AgentInstanceMessage[],
};
@ -222,11 +222,11 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
it('should execute wiki search with correct duration=1 and trigger next round', async () => {
// Find the real wikiSearch plugin config from taskAgents.json
const wikiPlugin = handlerConfig.plugins.find((p: unknown): p is IPromptConcatTool => (p as IPromptConcatTool).pluginId === 'wikiSearch');
const wikiPlugin = agentFrameworkConfig.plugins.find((p: unknown): p is IPromptConcatTool => (p as IPromptConcatTool).toolId === 'wikiSearch');
expect(wikiPlugin).toBeDefined();
expect(wikiPlugin!.wikiSearchParam).toBeDefined();
const handlerContext = {
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -258,7 +258,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -270,11 +270,11 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-123',
isFinal: true,
pluginConfig: wikiPlugin!,
toolConfig: wikiPlugin!,
prompts: [],
messages: [],
llmResponse: response.content,
@ -286,7 +286,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
const hooks = createAgentFrameworkHooks();
// Register the plugin
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
// Execute the response complete hook
await hooks.responseComplete.promise(context);
@ -307,15 +307,15 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
);
// Check that AI tool call message now has duration=1 (should gray out immediately)
const aiToolCallMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
const aiToolCallMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(aiToolCallMessage.id).toBe('ai-tool-call-msg');
expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately
expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true);
expect(aiToolCallMessage.metadata?.toolId).toBe('wiki-search');
// Verify tool result message was added to agent history with correct settings
expect(handlerContext.agent.messages.length).toBe(3); // user + ai + tool_result
const toolResultMessage = handlerContext.agent.messages[2] as AgentInstanceMessage;
expect(agentFrameworkContext.agent.messages.length).toBe(3); // user + ai + tool_result
const toolResultMessage = agentFrameworkContext.agent.messages[2] as AgentInstanceMessage;
expect(toolResultMessage.role).toBe('tool'); // Tool result message
expect(toolResultMessage.content).toContain('<functions_result>');
expect(toolResultMessage.content).toContain('Tool: wiki-search');
@ -325,13 +325,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
expect(toolResultMessage.duration).toBe(1); // Tool result uses configurable toolResultDuration (default 1)
// Check that previous user message is unchanged
const userMessage = handlerContext.agent.messages[0] as AgentInstanceMessage;
const userMessage = agentFrameworkContext.agent.messages[0] as AgentInstanceMessage;
expect(userMessage.id).toBe('user-msg-1');
expect(userMessage.duration).toBeUndefined(); // Should stay visible
});
it('should handle wiki search errors gracefully and set duration=1 for both messages', async () => {
const handlerContext = {
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -352,7 +352,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -364,13 +364,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-error',
isFinal: true,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
},
prompts: [],
@ -384,7 +384,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
await hooks.responseComplete.promise(context);
@ -392,14 +392,14 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
expect(context.actions.yieldNextRoundTo).toBe('self');
// Check that AI tool call message has duration=1 even after error (should gray out immediately)
const aiToolCallMessage = handlerContext.agent.messages[0] as AgentInstanceMessage;
const aiToolCallMessage = agentFrameworkContext.agent.messages[0] as AgentInstanceMessage;
expect(aiToolCallMessage.id).toBe('ai-error-tool-call');
expect(aiToolCallMessage.duration).toBe(1); // Should be 1 to gray out immediately
expect(aiToolCallMessage.metadata?.containsToolCall).toBe(true);
// Verify error message was added to agent history
expect(handlerContext.agent.messages.length).toBe(2); // tool_call + error_result
const errorResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
expect(agentFrameworkContext.agent.messages.length).toBe(2); // tool_call + error_result
const errorResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(errorResultMessage.role).toBe('tool'); // Tool error message
expect(errorResultMessage.content).toContain('<functions_result>');
expect(errorResultMessage.content).toContain('Error:');
@ -411,7 +411,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
});
it('should not modify duration of unrelated messages', async () => {
const handlerContext = {
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -450,7 +450,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -461,13 +461,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-selective',
isFinal: true,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
},
prompts: [],
@ -481,19 +481,19 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
await hooks.responseComplete.promise(context);
// Check that unrelated messages were not modified
const unrelatedUserMsg = handlerContext.agent.messages[0] as AgentInstanceMessage;
const unrelatedUserMsg = agentFrameworkContext.agent.messages[0] as AgentInstanceMessage;
expect(unrelatedUserMsg.duration).toBe(5); // Should remain unchanged
const unrelatedAiMsg = handlerContext.agent.messages[1] as AgentInstanceMessage;
const unrelatedAiMsg = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(unrelatedAiMsg.duration).toBeUndefined(); // Should remain unchanged
// Check that only the tool call message was modified
const toolCallMsg = handlerContext.agent.messages[2] as AgentInstanceMessage;
const toolCallMsg = agentFrameworkContext.agent.messages[2] as AgentInstanceMessage;
expect(toolCallMsg.duration).toBe(1); // Should be set to 1
expect(toolCallMsg.metadata?.containsToolCall).toBe(true);
});
@ -507,20 +507,20 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
status: { state: 'working' as const, modified: new Date() },
created: new Date(),
} as AgentInstance,
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} } as AgentDefinition,
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} } as AgentDefinition,
isCancelled: () => false,
};
const context: AIResponseContext = {
handlerContext: handlerCtx,
pluginConfig: { id: 'test-plugin', pluginId: 'wikiSearch' } as IPromptConcatTool,
agentFrameworkContext: handlerCtx,
toolConfig: { id: 'test-plugin', toolId: 'wikiSearch' } as IPromptConcatTool,
response: { requestId: 'test-request-345', content: 'Just a regular response without any tool calls', status: 'done' },
requestId: 'test-request',
isFinal: true,
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
await hooks.responseComplete.promise(context);
@ -597,7 +597,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
}) as unknown as IWikiService['wikiOperationInServer'],
);
const handlerContext = {
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -627,7 +627,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -638,13 +638,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-vector',
requestId: 'test-request-vector-error',
isFinal: true,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
},
prompts: [],
@ -655,7 +655,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
await hooks.responseComplete.promise(context);
@ -676,9 +676,9 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
// Verify results were processed
expect(context.actions.yieldNextRoundTo).toBe('self');
expect(handlerContext.agent.messages.length).toBe(2);
expect(agentFrameworkContext.agent.messages.length).toBe(2);
const toolResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
const toolResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(toolResultMessage.content).toContain('<functions_result>');
expect(toolResultMessage.content).toContain('Vector Result 1');
expect(toolResultMessage.content).toContain('Vector Result 2');
@ -696,7 +696,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
new Error('Vector database not initialized'),
);
const handlerContext = {
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -724,7 +724,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -735,13 +735,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-vector-error',
isFinal: true,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
},
prompts: [],
@ -752,14 +752,14 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
await hooks.responseComplete.promise(context);
// Should still set up next round with error message
expect(context.actions.yieldNextRoundTo).toBe('self');
const errorResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
const errorResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(errorResultMessage.content).toContain('Error:');
// Error message contains i18n key or actual error
expect(errorResultMessage.content).toMatch(/Vector database not initialized|Tool\.WikiSearch\.Error\.VectorSearchFailed/);
@ -767,7 +767,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
});
it('should require query parameter for vector search', async () => {
const handlerContext = {
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -795,7 +795,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -806,13 +806,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-no-query',
isFinal: true,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
},
prompts: [],
@ -823,12 +823,12 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
wikiSearchTool(hooks);
await hooks.responseComplete.promise(context);
// Should return error about missing query
const errorMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
const errorMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(errorMessage.content).toContain('Error:');
// Error message contains i18n key or translated text
expect(errorMessage.content).toMatch(/query|Tool\.WikiSearch\.Error\.VectorSearchRequiresQuery/);
@ -836,9 +836,9 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
});
describe('Message Persistence Integration', () => {
it('should work with messageManagementPlugin for complete persistence flow', async () => {
// This test ensures wikiSearchPlugin works well with messageManagementPlugin
const handlerContext = {
it('should work with messageManagementTool for complete persistence flow', async () => {
// This test ensures wikiSearchTool works well with messageManagementTool
const agentFrameworkContext = {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -859,7 +859,7 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
},
],
},
agentDef: { id: 'test-agent-def', name: 'test', handlerConfig: {} },
agentDef: { id: 'test-agent-def', name: 'test', agentFrameworkConfig: {} },
isCancelled: () => false,
};
@ -870,13 +870,13 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const context = {
handlerContext,
agentFrameworkContext,
response,
requestId: 'test-request-integration',
isFinal: true,
pluginConfig: {
toolConfig: {
id: 'test-plugin',
pluginId: 'wikiSearch' as const,
toolId: 'wikiSearch' as const,
forbidOverrides: false,
},
prompts: [],
@ -890,18 +890,18 @@ describe('Wiki Search Plugin - Comprehensive Tests', () => {
};
const hooks = createAgentFrameworkHooks();
wikiSearchPlugin(hooks);
messageManagementPlugin(hooks);
wikiSearchTool(hooks);
messageManagementTool(hooks);
await hooks.responseComplete.promise(context);
// Verify integration works
expect(context.actions.yieldNextRoundTo).toBe('self');
expect(handlerContext.agent.messages.length).toBe(2); // original + tool result
expect(agentFrameworkContext.agent.messages.length).toBe(2); // original + tool result
const toolResultMessage = handlerContext.agent.messages[1] as AgentInstanceMessage;
const toolResultMessage = agentFrameworkContext.agent.messages[1] as AgentInstanceMessage;
expect(toolResultMessage.metadata?.isToolResult).toBe(true);
expect(toolResultMessage.metadata?.isPersisted).toBe(true); // Should be true after messageManagementPlugin processing
expect(toolResultMessage.metadata?.isPersisted).toBe(true); // Should be true after messageManagementTool processing
});
it('should prevent regression: tool result not filtered in second round', async () => {

View file

@ -1,5 +1,5 @@
/**
* Tests for workspacesListPlugin
* Tests for workspacesListTool
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@ -14,9 +14,9 @@ import type { PromptConcatHookContext } from '../types';
import type { IWorkspaceService } from '@services/workspaces/interface';
import { createAgentFrameworkHooks } from '../index';
import { workspacesListPlugin } from '../workspacesList';
import { workspacesListTool } from '../workspacesList';
describe('workspacesListPlugin', () => {
describe('workspacesListTool', () => {
beforeEach(async () => {
vi.clearAllMocks();
});
@ -28,10 +28,10 @@ describe('workspacesListPlugin', () => {
describe('workspaces list injection', () => {
it('should inject workspaces list when plugin is configured', async () => {
const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks);
workspacesListTool(hooks);
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -50,11 +50,11 @@ describe('workspacesListPlugin', () => {
children: [],
},
],
pluginConfig: {
toolConfig: {
id: 'test-plugin',
caption: 'Test Plugin',
forbidOverrides: false,
pluginId: 'workspacesList',
toolId: 'workspacesList',
workspacesListParam: {
targetId: 'target-prompt',
position: 'after' as const,
@ -75,10 +75,10 @@ describe('workspacesListPlugin', () => {
it('should inject workspaces list when position is before', async () => {
const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks);
workspacesListTool(hooks);
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -97,11 +97,11 @@ describe('workspacesListPlugin', () => {
children: [],
},
],
pluginConfig: {
toolConfig: {
id: 'test-plugin',
caption: 'Test Plugin',
forbidOverrides: false,
pluginId: 'workspacesList',
toolId: 'workspacesList',
workspacesListParam: {
targetId: 'target-prompt',
position: 'before' as const,
@ -119,10 +119,10 @@ describe('workspacesListPlugin', () => {
it('should not inject content when plugin is not configured', async () => {
const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks);
workspacesListTool(hooks);
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -141,7 +141,7 @@ describe('workspacesListPlugin', () => {
children: [],
},
],
pluginConfig: { id: 'test-plugin', pluginId: 'otherPlugin', forbidOverrides: false } as unknown as IPromptConcatTool,
toolConfig: { id: 'test-plugin', pluginId: 'otherPlugin', forbidOverrides: false } as unknown as IPromptConcatTool,
};
await hooks.processPrompts.promise(context);
@ -156,10 +156,10 @@ describe('workspacesListPlugin', () => {
workspaceService.getWorkspacesAsList = vi.fn().mockResolvedValue([]) as unknown as IWorkspaceService['getWorkspacesAsList'];
const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks);
workspacesListTool(hooks);
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -178,11 +178,11 @@ describe('workspacesListPlugin', () => {
children: [],
},
],
pluginConfig: {
toolConfig: {
id: 'test-plugin',
caption: 'Test Plugin',
forbidOverrides: false,
pluginId: 'workspacesList',
toolId: 'workspacesList',
workspacesListParam: {
targetId: 'target-prompt',
position: 'after' as const,
@ -195,16 +195,16 @@ describe('workspacesListPlugin', () => {
const targetPrompt = context.prompts[0];
expect(targetPrompt.children).toHaveLength(0);
expect(logger.debug).toHaveBeenCalledWith('No wiki workspaces found to inject', {
pluginId: 'test-plugin',
toolId: 'test-plugin',
});
});
it('should warn when target prompt is not found', async () => {
const hooks = createAgentFrameworkHooks();
workspacesListPlugin(hooks);
workspacesListTool(hooks);
const context: PromptConcatHookContext = {
handlerContext: {
agentFrameworkContext: {
agent: {
id: 'test-agent',
agentDefId: 'test-agent-def',
@ -223,11 +223,11 @@ describe('workspacesListPlugin', () => {
children: [],
},
],
pluginConfig: {
toolConfig: {
id: 'test-plugin',
caption: 'Test Plugin',
forbidOverrides: false,
pluginId: 'workspacesList',
toolId: 'workspacesList',
workspacesListParam: {
targetId: 'non-existent-prompt',
position: 'after' as const,
@ -239,7 +239,7 @@ describe('workspacesListPlugin', () => {
expect(logger.warn).toHaveBeenCalledWith('Workspaces list target prompt not found', {
targetId: 'non-existent-prompt',
pluginId: 'test-plugin',
toolId: 'test-plugin',
});
});
});

View file

@ -9,7 +9,7 @@ export type { AgentResponse, PromptConcatHookContext, PromptConcatHooks, PromptC
export type { PromptConcatTool as PromptConcatPlugin };
/**
* Registry for built-in tools
* Registry for built-in framework tools
*/
export const builtInTools = new Map<string, PromptConcatTool>();
@ -36,7 +36,7 @@ export function createAgentFrameworkHooks(): PromptConcatHooks {
*/
async function getAllTools() {
const [
promptPluginsModule,
promptToolsModule,
wikiSearchModule,
wikiOperationModule,
workspacesListModule,
@ -50,40 +50,40 @@ async function getAllTools() {
]);
return {
messageManagementPlugin: messageManagementModule.messageManagementPlugin,
fullReplacementPlugin: promptPluginsModule.fullReplacementPlugin,
wikiSearchPlugin: wikiSearchModule.wikiSearchPlugin,
wikiOperationPlugin: wikiOperationModule.wikiOperationPlugin,
workspacesListPlugin: workspacesListModule.workspacesListPlugin,
messageManagement: messageManagementModule.messageManagementTool,
fullReplacement: promptToolsModule.fullReplacementTool,
wikiSearch: wikiSearchModule.wikiSearchTool,
wikiOperation: wikiOperationModule.wikiOperationTool,
workspacesList: workspacesListModule.workspacesListTool,
};
}
/**
* Register tools to hooks based on framework configuration
* @param hooks - The hooks instance to register tools to
* @param frameworkConfig - The framework configuration containing tool settings
* @param agentFrameworkConfig - The framework configuration containing tool settings
*/
export async function registerToolsToHooksFromConfig(
hooks: PromptConcatHooks,
frameworkConfig: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
agentFrameworkConfig: { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
): Promise<void> {
// Always register core tools that are needed for basic functionality
const messageManagementModule = await import('./messageManagement');
messageManagementModule.messageManagementPlugin(hooks);
logger.debug('Registered messageManagementPlugin to hooks');
messageManagementModule.messageManagementTool(hooks);
logger.debug('Registered messageManagementTool to hooks');
// Register tools based on framework configuration
if (frameworkConfig.plugins) {
for (const pluginConfig of frameworkConfig.plugins) {
const { pluginId } = pluginConfig;
if (agentFrameworkConfig.plugins) {
for (const toolConfig of agentFrameworkConfig.plugins) {
const { toolId } = toolConfig;
// Get tool from global registry (supports both built-in and dynamic tools)
const plugin = builtInTools.get(pluginId);
if (plugin) {
plugin(hooks);
logger.debug(`Registered tool ${pluginId} to hooks`);
const tool = builtInTools.get(toolId);
if (tool) {
tool(hooks);
logger.debug(`Registered tool ${toolId} to hooks`);
} else {
logger.warn(`Tool not found in registry: ${pluginId}`);
logger.warn(`Tool not found in registry: ${toolId}`);
}
}
}
@ -96,7 +96,7 @@ export async function registerToolsToHooksFromConfig(
export async function initializeToolSystem(): Promise<void> {
// Import tool schemas and register them
const [
promptPluginsModule,
promptToolsModule,
wikiSearchModule,
wikiOperationModule,
workspacesListModule,
@ -112,7 +112,7 @@ export async function initializeToolSystem(): Promise<void> {
// Register tool parameter schemas
registerToolParameterSchema(
'fullReplacement',
promptPluginsModule.getFullReplacementParameterSchema(),
promptToolsModule.getFullReplacementParameterSchema(),
{
displayName: 'Full Replacement',
description: 'Replace target content with content from specified source',
@ -121,7 +121,7 @@ export async function initializeToolSystem(): Promise<void> {
registerToolParameterSchema(
'dynamicPosition',
promptPluginsModule.getDynamicPositionParameterSchema(),
promptToolsModule.getDynamicPositionParameterSchema(),
{
displayName: 'Dynamic Position',
description: 'Insert content at a specific position relative to a target element',
@ -164,13 +164,13 @@ export async function initializeToolSystem(): Promise<void> {
},
);
const plugins = await getAllTools();
const tools = await getAllTools();
// Register all built-in tools to global registry for discovery
builtInTools.set('messageManagement', plugins.messageManagementPlugin);
builtInTools.set('fullReplacement', plugins.fullReplacementPlugin);
builtInTools.set('wikiSearch', plugins.wikiSearchPlugin);
builtInTools.set('wikiOperation', plugins.wikiOperationPlugin);
builtInTools.set('workspacesList', plugins.workspacesListPlugin);
builtInTools.set('messageManagement', tools.messageManagement);
builtInTools.set('fullReplacement', tools.fullReplacement);
builtInTools.set('wikiSearch', tools.wikiSearch);
builtInTools.set('wikiOperation', tools.wikiOperation);
builtInTools.set('workspacesList', tools.workspacesList);
logger.debug('All built-in tools and schemas registered successfully');
}
@ -179,12 +179,12 @@ export async function initializeToolSystem(): Promise<void> {
* This creates a new hooks instance and registers tools for that specific context
*/
export async function createHooksWithTools(
frameworkConfig: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> },
): Promise<{ hooks: PromptConcatHooks; pluginConfigs: Array<{ pluginId: string; [key: string]: unknown }> }> {
agentFrameworkConfig: { plugins?: Array<{ toolId: string; [key: string]: unknown }> },
): Promise<{ hooks: PromptConcatHooks; toolConfigs: Array<{ toolId: string; [key: string]: unknown }> }> {
const hooks = createAgentFrameworkHooks();
await registerToolsToHooksFromConfig(hooks, frameworkConfig);
await registerToolsToHooksFromConfig(hooks, agentFrameworkConfig);
return {
hooks,
pluginConfigs: frameworkConfig.plugins || [],
toolConfigs: agentFrameworkConfig.plugins || [],
};
}

View file

@ -14,14 +14,14 @@ import type { AgentStatusContext, AIResponseContext, PromptConcatTool, ToolExecu
* Message management plugin
* Handles all message-related operations: persistence, streaming, UI updates, and duration-based filtering
*/
export const messageManagementPlugin: PromptConcatTool = (hooks) => {
export const messageManagementTool: PromptConcatTool = (hooks) => {
// Handle user message persistence
hooks.userMessageReceived.tapAsync('messageManagementPlugin', async (context: UserMessageContext, callback) => {
hooks.userMessageReceived.tapAsync('messageManagementTool', async (context: UserMessageContext, callback) => {
try {
const { handlerContext, content, messageId } = context;
const { agentFrameworkContext, content, messageId } = context;
// Create user message using the helper function
const userMessage = createAgentMessage(messageId, handlerContext.agent.id, {
const userMessage = createAgentMessage(messageId, agentFrameworkContext.agent.id, {
role: 'user',
content: content.text,
contentType: 'text/plain',
@ -30,7 +30,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
});
// Add message to the agent's message array for immediate use (do this before persistence so plugins see it)
handlerContext.agent.messages.push(userMessage);
agentFrameworkContext.agent.messages.push(userMessage);
// Get the agent instance service to access repositories
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
@ -40,7 +40,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
logger.debug('User message persisted to database', {
messageId,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
contentLength: content.text.length,
});
@ -49,30 +49,30 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
logger.error('Message management plugin error in userMessageReceived', {
error,
messageId: context.messageId,
agentId: context.handlerContext.agent.id,
agentId: context.agentFrameworkContext.agent.id,
});
callback();
}
});
// Handle agent status persistence
hooks.agentStatusChanged.tapAsync('messageManagementPlugin', async (context: AgentStatusContext, callback) => {
hooks.agentStatusChanged.tapAsync('messageManagementTool', async (context: AgentStatusContext, callback) => {
try {
const { handlerContext, status } = context;
const { agentFrameworkContext, status } = context;
// Get the agent instance service to update status
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
// Update agent status in database
await agentInstanceService.updateAgent(handlerContext.agent.id, {
await agentInstanceService.updateAgent(agentFrameworkContext.agent.id, {
status,
});
// Update the agent object for immediate use
handlerContext.agent.status = status;
agentFrameworkContext.agent.status = status;
logger.debug('Agent status updated in database', {
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
state: status.state,
});
@ -80,7 +80,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
} catch (error) {
logger.error('Message management plugin error in agentStatusChanged', {
error,
agentId: context.handlerContext.agent.id,
agentId: context.agentFrameworkContext.agent.id,
status: context.status,
});
callback();
@ -88,13 +88,13 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
});
// Handle AI response updates during streaming
hooks.responseUpdate.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => {
hooks.responseUpdate.tapAsync('messageManagementTool', async (context: AIResponseContext, callback) => {
try {
const { handlerContext, response } = context;
const { agentFrameworkContext, response } = context;
if (response.status === 'update' && response.content) {
// Find or create AI response message in agent's message array
let aiMessage = handlerContext.agent.messages.find(
let aiMessage = agentFrameworkContext.agent.messages.find(
(message) => message.role === 'assistant' && !message.metadata?.isComplete,
);
@ -103,7 +103,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
const now = new Date();
aiMessage = {
id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'assistant',
content: response.content,
created: now,
@ -111,7 +111,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
metadata: { isComplete: false },
duration: undefined, // AI responses persist indefinitely by default
};
handlerContext.agent.messages.push(aiMessage);
agentFrameworkContext.agent.messages.push(aiMessage);
// Persist immediately so DB timestamp reflects conversation order
try {
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
@ -132,7 +132,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
// Update UI using the agent instance service
try {
const agentInstanceService = container.get<IAgentInstanceService>(serviceIdentifier.AgentInstance);
agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id);
agentInstanceService.debounceUpdateMessage(aiMessage, agentFrameworkContext.agent.id);
} catch (serviceError) {
logger.warn('Failed to update UI for streaming message', {
error: serviceError,
@ -150,13 +150,13 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
});
// Handle AI response completion
hooks.responseComplete.tapAsync('messageManagementPlugin', async (context: AIResponseContext, callback) => {
hooks.responseComplete.tapAsync('messageManagementTool', async (context: AIResponseContext, callback) => {
try {
const { handlerContext, response } = context;
const { agentFrameworkContext, response } = context;
if (response.status === 'done' && response.content) {
// Find and finalize AI response message
let aiMessage = handlerContext.agent.messages.find(
let aiMessage = agentFrameworkContext.agent.messages.find(
(message) => message.role === 'assistant' && !message.metadata?.isComplete && !message.metadata?.isToolResult,
);
@ -170,7 +170,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
const nowFinal = new Date();
aiMessage = {
id: `ai-response-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'assistant',
content: response.content,
created: nowFinal,
@ -180,7 +180,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
},
duration: undefined, // Default duration for AI responses
};
handlerContext.agent.messages.push(aiMessage);
agentFrameworkContext.agent.messages.push(aiMessage);
}
// Get the agent instance service for persistence and UI updates
@ -191,7 +191,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
// Final UI update
try {
agentInstanceService.debounceUpdateMessage(aiMessage, handlerContext.agent.id);
agentInstanceService.debounceUpdateMessage(aiMessage, agentFrameworkContext.agent.id);
} catch (serviceError) {
logger.warn('Failed to update UI for completed message', {
error: serviceError,
@ -215,12 +215,12 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
});
// Handle tool result messages persistence and UI updates
hooks.toolExecuted.tapAsync('messageManagementPlugin', async (context: ToolExecutionContext, callback) => {
hooks.toolExecuted.tapAsync('messageManagementTool', async (context: ToolExecutionContext, callback) => {
try {
const { handlerContext } = context;
const { agentFrameworkContext } = context;
// Find newly added tool result messages that need to be persisted
const newToolResultMessages = handlerContext.agent.messages.filter(
const newToolResultMessages = agentFrameworkContext.agent.messages.filter(
(message) => message.metadata?.isToolResult && !message.metadata.isPersisted,
);
@ -233,7 +233,7 @@ export const messageManagementPlugin: PromptConcatTool = (hooks) => {
await agentInstanceService.saveUserMessage(message);
// Update UI
agentInstanceService.debounceUpdateMessage(message, handlerContext.agent.id);
agentInstanceService.debounceUpdateMessage(message, agentFrameworkContext.agent.id);
// Mark as persisted to avoid duplicate saves
message.metadata = { ...message.metadata, isPersisted: true, uiUpdated: true };

View file

@ -76,17 +76,17 @@ export function getDynamicPositionParameterSchema() {
* Full replacement plugin
* Replaces target content with content from specified source
*/
export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
export const fullReplacementTool: PromptConcatTool = (hooks) => {
// Normalize an AgentInstanceMessage role to Prompt role
hooks.processPrompts.tapAsync('fullReplacementPlugin', async (context, callback) => {
const { pluginConfig, prompts, messages } = context;
hooks.processPrompts.tapAsync('fullReplacementTool', async (context, callback) => {
const { toolConfig, prompts, messages } = context;
if (pluginConfig.pluginId !== 'fullReplacement' || !pluginConfig.fullReplacementParam) {
if (toolConfig.toolId !== 'fullReplacement' || !toolConfig.fullReplacementParam) {
callback();
return;
}
const fullReplacementConfig = pluginConfig.fullReplacementParam;
const fullReplacementConfig = toolConfig.fullReplacementParam;
if (!fullReplacementConfig) {
callback();
return;
@ -98,7 +98,7 @@ export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
if (!found) {
logger.warn('Target prompt not found for fullReplacement', {
targetId,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
return;
@ -173,16 +173,16 @@ export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
});
// Handle response phase for llmResponse source type
hooks.postProcess.tapAsync('fullReplacementPlugin', async (context, callback) => {
hooks.postProcess.tapAsync('fullReplacementTool', async (context, callback) => {
const responseContext = context as ResponseHookContext;
const { pluginConfig, llmResponse, responses } = responseContext;
const { toolConfig, llmResponse, responses } = responseContext;
if (pluginConfig.pluginId !== 'fullReplacement' || !pluginConfig.fullReplacementParam) {
if (toolConfig.toolId !== 'fullReplacement' || !toolConfig.fullReplacementParam) {
callback();
return;
}
const fullReplacementParameter = pluginConfig.fullReplacementParam;
const fullReplacementParameter = toolConfig.fullReplacementParam;
if (!fullReplacementParameter) {
callback();
return;
@ -202,7 +202,7 @@ export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
if (!found) {
logger.warn('Full replacement target not found in responses', {
targetId,
pluginId: pluginConfig.id,
pluginId: toolConfig.id,
});
callback();
return;
@ -212,7 +212,7 @@ export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
logger.debug('Replacing target with LLM response', {
targetId,
responseLength: llmResponse.length,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
found.text = llmResponse;
@ -220,6 +220,7 @@ export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
logger.debug('Full replacement completed in response phase', {
targetId,
sourceType,
toolId: toolConfig.id,
});
callback();
@ -230,16 +231,16 @@ export const fullReplacementPlugin: PromptConcatTool = (hooks) => {
* Dynamic position plugin
* Inserts content at a specific position relative to a target element
*/
export const dynamicPositionPlugin: PromptConcatTool = (hooks) => {
hooks.processPrompts.tapAsync('dynamicPositionPlugin', async (context, callback) => {
const { pluginConfig, prompts } = context;
export const dynamicPositionTool: PromptConcatTool = (hooks) => {
hooks.processPrompts.tapAsync('dynamicPositionTool', async (context, callback) => {
const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'dynamicPosition' || !pluginConfig.dynamicPositionParam || !pluginConfig.content) {
if (toolConfig.toolId !== 'dynamicPosition' || !toolConfig.dynamicPositionParam || !toolConfig.content) {
callback();
return;
}
const dynamicPositionConfig = pluginConfig.dynamicPositionParam;
const dynamicPositionConfig = toolConfig.dynamicPositionParam;
if (!dynamicPositionConfig) {
callback();
return;
@ -251,7 +252,7 @@ export const dynamicPositionPlugin: PromptConcatTool = (hooks) => {
if (!found) {
logger.warn('Target prompt not found for dynamicPosition', {
targetId,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
return;
@ -259,9 +260,9 @@ export const dynamicPositionPlugin: PromptConcatTool = (hooks) => {
// Create new prompt part
const newPart: IPrompt = {
id: `dynamic-${pluginConfig.id}-${Date.now()}`,
caption: pluginConfig.caption || 'Dynamic Content',
text: pluginConfig.content,
id: `dynamic-${toolConfig.id}-${Date.now()}`,
caption: toolConfig.caption || 'Dynamic Content',
text: toolConfig.content,
};
// Insert based on position
@ -288,7 +289,8 @@ export const dynamicPositionPlugin: PromptConcatTool = (hooks) => {
logger.debug('Dynamic position insertion completed', {
targetId,
position,
contentLength: pluginConfig.content.length,
contentLength: toolConfig.content.length,
toolId: toolConfig.id,
});
callback();

View file

@ -24,7 +24,7 @@ const toolMetadata = new Map<string, {
/**
* Register a tool parameter schema
* @param toolId The tool ID (should match pluginId enum values)
* @param toolId The tool ID (should match toolId enum values)
* @param schema The Zod schema for this tool's parameters
* @param metadata Optional metadata for display purposes
*/
@ -76,16 +76,16 @@ export function createDynamicPromptConcatToolSchema(): z.ZodType {
// Base tool configuration without parameter-specific fields
const baseToolSchema = z.object({
id: z.string().meta({
title: t('Schema.Plugin.IdTitle'),
description: t('Schema.Plugin.Id'),
title: t('Schema.Tool.IdTitle'),
description: t('Schema.Tool.Id'),
}),
caption: z.string().optional().meta({
title: t('Schema.Plugin.CaptionTitle'),
description: t('Schema.Plugin.Caption'),
title: t('Schema.Tool.CaptionTitle'),
description: t('Schema.Tool.Caption'),
}),
content: z.string().optional().meta({
title: t('Schema.Plugin.ContentTitle'),
description: t('Schema.Plugin.Content'),
title: t('Schema.Tool.ContentTitle'),
description: t('Schema.Tool.Content'),
}),
forbidOverrides: z.boolean().optional().default(false).meta({
title: t('Schema.Plugin.ForbidOverridesTitle'),
@ -99,17 +99,17 @@ export function createDynamicPromptConcatToolSchema(): z.ZodType {
if (registeredToolIds.length === 0) {
// Fallback to a basic schema if no tools are registered yet
return baseToolSchema.extend({
pluginId: z.string().meta({
title: t('Schema.Plugin.PluginIdTitle'),
description: t('Schema.Plugin.PluginId'),
toolId: z.string().meta({
title: t('Schema.Tool.ToolIdTitle'),
description: t('Schema.Tool.ToolId'),
}),
});
}
// Create enum from registered tool IDs
const pluginIdEnum = z.enum(registeredToolIds as [string, ...string[]]).meta({
title: t('Schema.Plugin.PluginIdTitle'),
description: t('Schema.Plugin.PluginId'),
const toolIdEnum = z.enum(registeredToolIds as [string, ...string[]]).meta({
title: t('Schema.Tool.ToolIdTitle'),
description: t('Schema.Tool.ToolId'),
enumOptions: registeredToolIds.map(toolId => {
const metadata = getToolMetadata(toolId);
return {
@ -135,7 +135,7 @@ export function createDynamicPromptConcatToolSchema(): z.ZodType {
// Combine base schema with tool ID and parameters
return baseToolSchema.extend({
pluginId: pluginIdEnum,
toolId: toolIdEnum,
...parameterSchema,
});
}

View file

@ -27,7 +27,7 @@ export interface ToolActions {
*/
export interface BaseToolContext {
/** Framework context */
handlerContext: AgentFrameworkContext;
agentFrameworkContext: AgentFrameworkContext;
/** Additional context data */
metadata?: Record<string, unknown>;
/** Actions set by tools during processing */
@ -43,7 +43,7 @@ export interface PromptConcatHookContext extends BaseToolContext {
/** Current prompt tree */
prompts: IPrompt[];
/** Tool configuration */
pluginConfig: IPromptConcatTool;
toolConfig: IPromptConcatTool;
}
/**
@ -61,9 +61,9 @@ export interface PostProcessContext extends PromptConcatHookContext {
*/
export interface AIResponseContext extends BaseToolContext {
/** Tool configuration - for backward compatibility */
pluginConfig: IPromptConcatTool;
toolConfig: IPromptConcatTool;
/** Complete framework configuration - allows tools to access all configs */
handlerConfig?: { plugins?: Array<{ pluginId: string; [key: string]: unknown }> };
agentFrameworkConfig?: { plugins?: Array<{ toolId: string; [key: string]: unknown }> };
/** AI streaming response */
response: AIStreamResponse;
/** Current request ID */

View file

@ -101,17 +101,17 @@ const WikiOperationToolParameterSchema = z.object({
* Wiki Operation plugin - Prompt processing
* Handles tool list injection for wiki operation functionality
*/
export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
export const wikiOperationTool: PromptConcatTool = (hooks) => {
// First tapAsync: Tool list injection
hooks.processPrompts.tapAsync('wikiOperationPlugin-toolList', async (context, callback) => {
const { pluginConfig, prompts } = context;
hooks.processPrompts.tapAsync('wikiOperationTool-toolList', async (context, callback) => {
const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'wikiOperation' || !pluginConfig.wikiOperationParam) {
if (toolConfig.toolId !== 'wikiOperation' || !toolConfig.wikiOperationParam) {
callback();
return;
}
const wikiOperationParameter = pluginConfig.wikiOperationParam;
const wikiOperationParameter = toolConfig.wikiOperationParam;
try {
// Handle tool list injection if toolListPosition is configured
@ -121,7 +121,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
if (!toolListTarget) {
logger.warn('Tool list target prompt not found', {
targetId: toolListPosition.targetId,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
return;
@ -140,7 +140,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
}
const insertIndex = toolListTarget.prompt.children.length;
toolListTarget.prompt.children.splice(insertIndex, 0, {
id: `wiki-operation-tool-${pluginConfig.id}`,
id: `wiki-operation-tool-${toolConfig.id}`,
caption: 'Wiki Operation Tool',
text: wikiOperationToolContent,
});
@ -149,7 +149,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
toolListTarget.prompt.children = [];
}
toolListTarget.prompt.children.unshift({
id: `wiki-operation-tool-${pluginConfig.id}`,
id: `wiki-operation-tool-${toolConfig.id}`,
caption: 'Wiki Operation Tool',
text: wikiOperationToolContent,
});
@ -161,7 +161,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
logger.debug('Wiki operation tool list injected', {
targetId: toolListPosition.targetId,
position: toolListPosition.position,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
}
@ -169,20 +169,20 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
} catch (error) {
logger.error('Error in wiki operation tool list injection', {
error,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
}
});
// 2. Tool execution when AI response is complete
hooks.responseComplete.tapAsync('wikiOperationPlugin-handler', async (context, callback) => {
hooks.responseComplete.tapAsync('wikiOperationTool-handler', async (context, callback) => {
try {
const { handlerContext, response, handlerConfig } = context;
const { agentFrameworkContext, response, agentFrameworkConfig } = context;
// Find this plugin's configuration from handlerConfig
const wikiOperationPluginConfig = handlerConfig?.plugins?.find(p => p.pluginId === 'wikiOperation');
const wikiOperationParameter = wikiOperationPluginConfig?.wikiOperationParam as { toolResultDuration?: number } | undefined;
// Find this plugin's configuration from agentFrameworkConfig
const wikiOperationToolConfig = agentFrameworkConfig?.plugins?.find((p: { toolId: string; [key: string]: unknown }) => p.toolId === 'wikiOperation');
const wikiOperationParameter = wikiOperationToolConfig?.wikiOperationParam as { toolResultDuration?: number } | undefined;
const toolResultDuration = wikiOperationParameter?.toolResultDuration || 1; // Default to 1 round
if (response.status !== 'done' || !response.content) {
@ -200,19 +200,19 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
logger.debug('Wiki operation tool call detected', {
toolId: toolMatch.toolId,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
// Set duration=1 for the AI message containing the tool call
// Find the most recent AI message (should be the one containing the tool call)
const aiMessages = handlerContext.agent.messages.filter(message => message.role === 'assistant');
const aiMessages = agentFrameworkContext.agent.messages.filter((message: AgentInstanceMessage) => message.role === 'assistant');
if (aiMessages.length > 0) {
const latestAiMessage = aiMessages[aiMessages.length - 1];
latestAiMessage.duration = toolResultDuration;
logger.debug('Set AI message duration for tool call', {
messageId: latestAiMessage.id,
duration: toolResultDuration,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
}
@ -220,7 +220,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
try {
logger.debug('Parsing wiki operation tool parameters', {
toolMatch: toolMatch.parameters,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
// Use parameters returned by matchToolCalling directly. Let zod schema validate.
@ -253,7 +253,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
workspaceName,
operation,
title,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
let result: string;
@ -293,7 +293,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
workspaceID,
operation,
title,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
// Format the tool result for display
@ -307,8 +307,8 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
logger.debug('Wiki operation setting yieldNextRoundTo=self', {
toolId: 'wiki-operation',
agentId: handlerContext.agent.id,
messageCount: handlerContext.agent.messages.length,
agentId: agentFrameworkContext.agent.id,
messageCount: agentFrameworkContext.agent.messages.length,
toolResultPreview: toolResultText.slice(0, 200),
});
@ -316,7 +316,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
const toolResultTime = new Date();
const toolResultMessage: AgentInstanceMessage = {
id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'tool', // Tool result message
content: toolResultText,
modified: toolResultTime,
@ -331,7 +331,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
artificialOrder: Date.now() + 10, // Additional ordering hint
},
};
handlerContext.agent.messages.push(toolResultMessage);
agentFrameworkContext.agent.messages.push(toolResultMessage);
// Persist tool result immediately so DB ordering matches in-memory order
try {
@ -347,7 +347,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
// Signal that tool was executed AFTER adding and persisting the message
await hooks.toolExecuted.promise({
handlerContext,
agentFrameworkContext,
toolResult: {
success: true,
data: result,
@ -370,7 +370,7 @@ export const wikiOperationPlugin: PromptConcatTool = (hooks) => {
} catch (error) {
logger.error('Wiki operation tool execution failed', {
error,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
toolParameters: toolMatch.parameters,
});
@ -389,7 +389,7 @@ Error: ${error instanceof Error ? error.message : String(error)}
const errorResultTime = new Date();
const errorResultMessage: AgentInstanceMessage = {
id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'tool', // Tool error message
content: errorMessage,
modified: errorResultTime,
@ -402,11 +402,11 @@ Error: ${error instanceof Error ? error.message : String(error)}
isComplete: true, // Mark as complete to prevent messageManagementPlugin from overwriting content
},
};
handlerContext.agent.messages.push(errorResultMessage);
agentFrameworkContext.agent.messages.push(errorResultMessage);
// Signal that tool was executed (with error) AFTER adding the message
await hooks.toolExecuted.promise({
handlerContext,
agentFrameworkContext,
toolResult: {
success: false,
error: error instanceof Error ? error.message : String(error),

View file

@ -488,17 +488,17 @@ async function executeWikiUpdateEmbeddingsTool(
* Wiki Search plugin - Prompt processing
* Handles tool list injection for wiki search and update embeddings functionality
*/
export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
export const wikiSearchTool: PromptConcatTool = (hooks) => {
// First tapAsync: Tool list injection
hooks.processPrompts.tapAsync('wikiSearchPlugin-toolList', async (context, callback) => {
const { pluginConfig, prompts } = context;
hooks.processPrompts.tapAsync('wikiSearchTool-toolList', async (context, callback) => {
const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'wikiSearch' || !pluginConfig.wikiSearchParam) {
if (toolConfig.toolId !== 'wikiSearch' || !toolConfig.wikiSearchParam) {
callback();
return;
}
const wikiSearchParameter = pluginConfig.wikiSearchParam;
const wikiSearchParameter = toolConfig.wikiSearchParam;
try {
// Handle tool list injection if toolListPosition is configured
@ -508,7 +508,7 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
if (!toolListTarget) {
logger.warn('Tool list target prompt not found', {
targetId: toolListPosition.targetId,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
return;
@ -544,7 +544,7 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
targetId: toolListPosition.targetId,
position: toolListPosition.position,
toolCount: 2, // wiki-search and wiki-update-embeddings
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
}
@ -552,20 +552,20 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
} catch (error) {
logger.error('Error in wiki search tool list injection', {
error,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
}
});
// 2. Tool execution when AI response is complete
hooks.responseComplete.tapAsync('wikiSearchPlugin-handler', async (context, callback) => {
hooks.responseComplete.tapAsync('wikiSearchTool-handler', async (context, callback) => {
try {
const { handlerContext, response, handlerConfig } = context;
const { agentFrameworkContext, response, agentFrameworkConfig } = context;
// Find this plugin's configuration from handlerConfig
const wikiSearchPluginConfig = handlerConfig?.plugins?.find(p => p.pluginId === 'wikiSearch');
const wikiSearchParameter = wikiSearchPluginConfig?.wikiSearchParam as { toolResultDuration?: number } | undefined;
// Find this plugin's configuration from agentFrameworkConfig
const wikiSearchToolConfig = agentFrameworkConfig?.plugins?.find((p: { toolId: string; [key: string]: unknown }) => p.toolId === 'wikiSearch');
const wikiSearchParameter = wikiSearchToolConfig?.wikiSearchParam as { toolResultDuration?: number } | undefined;
const toolResultDuration = wikiSearchParameter?.toolResultDuration || 1; // Default to 1 round
if (response.status !== 'done' || !response.content) {
@ -583,12 +583,12 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
logger.debug('Wiki tool call detected', {
toolId: toolMatch.toolId,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
// Set duration=1 for the AI message containing the tool call
// Find the most recent AI message (should be the one containing the tool call)
const aiMessages = handlerContext.agent.messages.filter(message => message.role === 'assistant');
const aiMessages = agentFrameworkContext.agent.messages.filter((message: AgentInstanceMessage) => message.role === 'assistant');
if (aiMessages.length > 0) {
const latestAiMessage = aiMessages[aiMessages.length - 1];
if (latestAiMessage.content === response.content) {
@ -614,7 +614,7 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
}
// Also update UI immediately
agentInstanceService.debounceUpdateMessage(latestAiMessage, handlerContext.agent.id, 0); // No delay
agentInstanceService.debounceUpdateMessage(latestAiMessage, agentFrameworkContext.agent.id, 0); // No delay
logger.debug('Set duration=1 for AI tool call message', {
messageId: latestAiMessage.id,
@ -626,10 +626,10 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
// Execute the appropriate tool
try {
// Check if cancelled before starting tool execution
if (handlerContext.isCancelled()) {
if (agentFrameworkContext.isCancelled()) {
logger.debug('Wiki tool cancelled before execution', {
toolId: toolMatch.toolId,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
callback();
return;
@ -644,9 +644,9 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
result = await executeWikiSearchTool(
validatedParameters,
{
agentId: handlerContext.agent.id,
messageId: handlerContext.agent.messages[handlerContext.agent.messages.length - 1]?.id,
config: handlerContext.agent.aiApiConfig as AiAPIConfig | undefined,
agentId: agentFrameworkContext.agent.id,
messageId: agentFrameworkContext.agent.messages[agentFrameworkContext.agent.messages.length - 1]?.id,
config: agentFrameworkContext.agent.aiApiConfig as AiAPIConfig | undefined,
},
);
} else {
@ -655,18 +655,18 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
result = await executeWikiUpdateEmbeddingsTool(
validatedParameters,
{
agentId: handlerContext.agent.id,
messageId: handlerContext.agent.messages[handlerContext.agent.messages.length - 1]?.id,
aiConfig: handlerContext.agent.aiApiConfig,
agentId: agentFrameworkContext.agent.id,
messageId: agentFrameworkContext.agent.messages[agentFrameworkContext.agent.messages.length - 1]?.id,
aiConfig: agentFrameworkContext.agent.aiApiConfig,
},
);
}
// Check if cancelled after tool execution
if (handlerContext.isCancelled()) {
if (agentFrameworkContext.isCancelled()) {
logger.debug('Wiki tool cancelled after execution', {
toolId: toolMatch.toolId,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
});
callback();
return;
@ -692,8 +692,8 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
logger.debug('Wiki search setting yieldNextRoundTo=self', {
toolId: 'wiki-search',
agentId: handlerContext.agent.id,
messageCount: handlerContext.agent.messages.length,
agentId: agentFrameworkContext.agent.id,
messageCount: agentFrameworkContext.agent.messages.length,
toolResultPreview: toolResultText.slice(0, 200),
});
@ -701,7 +701,7 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
const nowTool = new Date();
const toolResultMessage: AgentInstanceMessage = {
id: `tool-result-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'tool', // Tool result message
content: toolResultText,
created: nowTool,
@ -717,13 +717,13 @@ export const wikiSearchPlugin: PromptConcatTool = (hooks) => {
artificialOrder: Date.now() + 10, // Additional ordering hint
},
};
handlerContext.agent.messages.push(toolResultMessage);
agentFrameworkContext.agent.messages.push(toolResultMessage);
// Do not persist immediately here. Let messageManagementPlugin handle persistence
// Signal that tool was executed AFTER adding and persisting the message
await hooks.toolExecuted.promise({
handlerContext,
agentFrameworkContext,
toolResult: {
success: true,
data: result.success ? result.data : result.error,
@ -765,7 +765,7 @@ Error: ${error instanceof Error ? error.message : String(error)}
const nowError = new Date();
const errorResultMessage: AgentInstanceMessage = {
id: `tool-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
agentId: handlerContext.agent.id,
agentId: agentFrameworkContext.agent.id,
role: 'tool', // Tool error message
content: errorMessage,
created: nowError,
@ -779,11 +779,11 @@ Error: ${error instanceof Error ? error.message : String(error)}
isComplete: true, // Mark as complete to prevent messageManagementPlugin from overwriting content
},
};
handlerContext.agent.messages.push(errorResultMessage);
agentFrameworkContext.agent.messages.push(errorResultMessage);
// Do not persist immediately; let messageManagementPlugin handle it during toolExecuted
await hooks.toolExecuted.promise({
handlerContext,
agentFrameworkContext,
toolResult: {
success: false,
error: error instanceof Error ? error.message : String(error),

View file

@ -51,17 +51,17 @@ import type { PromptConcatTool } from './types';
* Workspaces List plugin - Prompt processing
* Handles injection of available wiki workspaces list
*/
export const workspacesListPlugin: PromptConcatTool = (hooks) => {
export const workspacesListTool: PromptConcatTool = (hooks) => {
// Tool list injection
hooks.processPrompts.tapAsync('workspacesListPlugin-injection', async (context, callback) => {
const { pluginConfig, prompts } = context;
hooks.processPrompts.tapAsync('workspacesListTool-injection', async (context, callback) => {
const { toolConfig, prompts } = context;
if (pluginConfig.pluginId !== 'workspacesList' || !pluginConfig.workspacesListParam) {
if (toolConfig.toolId !== 'workspacesList' || !toolConfig.workspacesListParam) {
callback();
return;
}
const workspacesListParameter = pluginConfig.workspacesListParam;
const workspacesListParameter = toolConfig.workspacesListParam;
try {
// Handle workspaces list injection if targetId is configured
@ -70,7 +70,7 @@ export const workspacesListPlugin: PromptConcatTool = (hooks) => {
if (!target) {
logger.warn('Workspaces list target prompt not found', {
targetId: workspacesListParameter.targetId,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
return;
@ -96,7 +96,7 @@ export const workspacesListPlugin: PromptConcatTool = (hooks) => {
}
const insertIndex = target.prompt.children.length;
target.prompt.children.splice(insertIndex, 0, {
id: `workspaces-list-${pluginConfig.id}`,
id: `workspaces-list-${toolConfig.id}`,
caption: 'Available Workspaces',
text: workspacesListContent,
});
@ -105,7 +105,7 @@ export const workspacesListPlugin: PromptConcatTool = (hooks) => {
target.prompt.children = [];
}
target.prompt.children.unshift({
id: `workspaces-list-${pluginConfig.id}`,
id: `workspaces-list-${toolConfig.id}`,
caption: 'Available Workspaces',
text: workspacesListContent,
});
@ -117,12 +117,12 @@ export const workspacesListPlugin: PromptConcatTool = (hooks) => {
logger.debug('Workspaces list injected successfully', {
targetId: workspacesListParameter.targetId,
position: workspacesListParameter.position,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
workspaceCount: wikiWorkspaces.length,
});
} else {
logger.debug('No wiki workspaces found to inject', {
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
}
}
@ -131,7 +131,7 @@ export const workspacesListPlugin: PromptConcatTool = (hooks) => {
} catch (error) {
logger.error('Error in workspaces list injection', {
error,
pluginId: pluginConfig.id,
toolId: toolConfig.id,
});
callback();
}

View file

@ -14,8 +14,8 @@ export function createAgentInstanceData(agentDefinition: {
name: string;
avatarUrl?: string;
aiApiConfig?: Record<string, unknown>;
handlerConfig?: Record<string, unknown>;
handlerID?: string;
agentFrameworkConfig?: Record<string, unknown>;
agentFrameworkID?: string;
}): {
instanceData: Omit<AgentInstance, 'created' | 'modified'>;
instanceId: string;
@ -31,7 +31,7 @@ export function createAgentInstanceData(agentDefinition: {
};
// Extract necessary fields from agent definition
const { avatarUrl, aiApiConfig, handlerID } = agentDefinition;
const { avatarUrl, aiApiConfig, agentFrameworkID } = agentDefinition;
const instanceData = {
id: instanceId,
@ -40,9 +40,9 @@ export function createAgentInstanceData(agentDefinition: {
status: initialStatus,
avatarUrl,
aiApiConfig,
// Don't copy handlerConfig to instance - it should fallback to definition
handlerConfig: undefined,
handlerID,
// Don't copy agentFrameworkConfig to instance - it should fallback to definition
agentFrameworkConfig: undefined,
agentFrameworkID,
messages: [],
closed: false,
};
@ -119,6 +119,6 @@ export const AGENT_INSTANCE_FIELDS = [
'modified',
'avatarUrl',
'aiApiConfig',
'handlerConfig',
'agentFrameworkConfig',
'closed',
] as const;

View file

@ -29,11 +29,11 @@ export class AgentDefinitionEntity implements Partial<AgentDefinition> {
/** Agent handler function ID, nullable indicates using default handler */
@Column({ nullable: true })
handlerID?: string;
agentFrameworkID?: string;
/** Agent handler configuration parameters, stored as JSON */
@Column({ type: 'simple-json', nullable: true })
handlerConfig?: Record<string, unknown>;
agentFrameworkConfig?: Record<string, unknown>;
/** Agent's AI API configuration, can override global default config */
@Column({ type: 'simple-json', nullable: true })
@ -88,7 +88,7 @@ export class AgentInstanceEntity implements Partial<AgentInstance> {
/** Agent handler configuration parameters, inherited from AgentDefinition */
@Column({ type: 'simple-json', nullable: true })
handlerConfig?: Record<string, unknown>;
agentFrameworkConfig?: Record<string, unknown>;
@Column({ default: false })
closed: boolean = false;

View file

@ -1,29 +1,29 @@
import { HandlerConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { agentFrameworkConfig } from '@services/agentInstance/promptConcat/promptConcatSchema';
import { useCallback, useEffect, useState } from 'react';
interface UseHandlerConfigManagementProps {
interface useAgentFrameworkConfigManagementProps {
agentDefId?: string;
agentId?: string;
}
interface UseHandlerConfigManagementResult {
interface useAgentFrameworkConfigManagementResult {
loading: boolean;
config: HandlerConfig | undefined;
config: agentFrameworkConfig | undefined;
schema?: Record<string, unknown>;
handleConfigChange: (newConfig: HandlerConfig) => Promise<void>;
handleConfigChange: (newConfig: agentFrameworkConfig) => Promise<void>;
}
export const useHandlerConfigManagement = ({ agentDefId, agentId }: UseHandlerConfigManagementProps = {}): UseHandlerConfigManagementResult => {
export const useAgentFrameworkConfigManagement = ({ agentDefId, agentId }: useAgentFrameworkConfigManagementProps = {}): useAgentFrameworkConfigManagementResult => {
const [loading, setLoading] = useState(true);
const [config, setConfig] = useState<HandlerConfig | undefined>(undefined);
const [config, setConfig] = useState<agentFrameworkConfig | undefined>(undefined);
const [schema, setSchema] = useState<Record<string, unknown> | undefined>(undefined);
useEffect(() => {
const fetchConfig = async () => {
try {
setLoading(true);
let finalConfig: HandlerConfig | undefined;
let handlerID: string | undefined;
let finalConfig: agentFrameworkConfig | undefined;
let agentFrameworkID: string | undefined;
if (agentId) {
const agentInstance = await window.service.agentInstance.getAgent(agentId);
@ -32,34 +32,34 @@ export const useHandlerConfigManagement = ({ agentDefId, agentId }: UseHandlerCo
agentDefinition = await window.service.agentDefinition.getAgentDef(agentInstance.agentDefId);
}
// Use instance config if available, otherwise fallback to definition config
if (agentInstance?.handlerConfig && Object.keys(agentInstance.handlerConfig).length > 0) {
finalConfig = agentInstance.handlerConfig as HandlerConfig;
} else if (agentDefinition?.handlerConfig) {
finalConfig = agentDefinition.handlerConfig as HandlerConfig;
if (agentInstance?.agentFrameworkConfig && Object.keys(agentInstance.agentFrameworkConfig).length > 0) {
finalConfig = agentInstance.agentFrameworkConfig as agentFrameworkConfig;
} else if (agentDefinition?.agentFrameworkConfig) {
finalConfig = agentDefinition.agentFrameworkConfig as agentFrameworkConfig;
}
// Use handlerID from instance, fallback to definition
handlerID = agentInstance?.handlerID || agentDefinition?.handlerID;
// Use agentFrameworkID from instance, fallback to definition
agentFrameworkID = agentInstance?.agentFrameworkID || agentDefinition?.agentFrameworkID;
} else if (agentDefId) {
const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId);
if (agentDefinition?.handlerConfig) {
finalConfig = agentDefinition.handlerConfig as HandlerConfig;
if (agentDefinition?.agentFrameworkConfig) {
finalConfig = agentDefinition.agentFrameworkConfig as agentFrameworkConfig;
}
handlerID = agentDefinition?.handlerID;
agentFrameworkID = agentDefinition?.agentFrameworkID;
}
if (handlerID) {
if (agentFrameworkID) {
try {
const frameworkSchema = await window.service.agentInstance.getFrameworkConfigSchema(handlerID);
const frameworkSchema = await window.service.agentInstance.getFrameworkConfigSchema(agentFrameworkID);
setSchema(frameworkSchema);
} catch (error) {
void window.service.native.log('error', 'Failed to load handler schema', { function: 'useHandlerConfigManagement.fetchConfig', error });
void window.service.native.log('error', 'Failed to load handler schema', { function: 'useAgentFrameworkConfigManagement.fetchConfig', error });
}
}
setConfig(finalConfig);
setLoading(false);
} catch (error) {
void window.service.native.log('error', 'Failed to load handler configuration', { function: 'useHandlerConfigManagement.fetchConfig', error });
void window.service.native.log('error', 'Failed to load handler configuration', { function: 'useAgentFrameworkConfigManagement.fetchConfig', error });
setLoading(false);
}
};
@ -67,27 +67,29 @@ export const useHandlerConfigManagement = ({ agentDefId, agentId }: UseHandlerCo
void fetchConfig();
}, [agentDefId, agentId]);
const handleConfigChange = useCallback(async (newConfig: HandlerConfig) => {
const handleConfigChange = useCallback(async (newConfig: agentFrameworkConfig) => {
try {
setConfig(newConfig);
if (agentId) {
await window.service.agentInstance.updateAgent(agentId, {
handlerConfig: newConfig,
agentFrameworkConfig: newConfig,
});
} else if (agentDefId) {
const agentDefinition = await window.service.agentDefinition.getAgentDef(agentDefId);
if (agentDefinition) {
await window.service.agentDefinition.updateAgentDef({
...agentDefinition,
handlerConfig: newConfig,
agentFrameworkConfig: newConfig,
});
}
} else {
void window.service.native.log('error', 'No agent ID or definition ID provided for updating handler config', { function: 'useHandlerConfigManagement.handleConfigChange' });
void window.service.native.log('error', 'No agent ID or definition ID provided for updating handler config', {
function: 'useAgentFrameworkConfigManagement.handleConfigChange',
});
}
} catch (error) {
void window.service.native.log('error', 'Failed to update handler configuration', { function: 'useHandlerConfigManagement.handleConfigChange', error });
void window.service.native.log('error', 'Failed to update handler configuration', { function: 'useAgentFrameworkConfigManagement.handleConfigChange', error });
}
}, [agentId, agentDefId]);