links — both // are admin-side only (that tool is not registered for the public agent and // the storefront has no button renderer), so scope them out of publicChat // to keep the public agent from hallucinating admin URLs or dead buttons. if ( 'adminAssistant' === $scope ) { $prompt .= "4. When showing products, orders, or any post types, always include id in the response.\n"; $prompt .= "\n## Admin Navigation\n"; $prompt .= "1. When users ask about navigation, admin pages, or HOW TO DO something with a plugin feature, ALWAYS call 'search-admin-pages' with a concise query (e.g. 'enable stripe', 'create event'). Pick the most relevant result by reading titles and descriptions.\n"; $prompt .= "2. If search returns no relevant pages, tell the user you could not locate that page and suggest manual navigation. Do NOT guess URLs.\n"; $prompt .= "3. NEVER use 'vector-search' for navigation questions. Vector search is ONLY for documentation and feature explanations.\n"; $prompt .= "\n## Button Format Rule (CRITICAL)\n"; $prompt .= "For navigation/guidance links, ALWAYS use: Button Text\n"; $prompt .= "The class=\"aisentic-button\" attribute is REQUIRED. Never use plain text or markdown links for navigation.\n"; $prompt .= "Never add disclaimers about URLs being placeholders — the URLs from search-admin-pages are real, working URLs.\n"; $prompt .= "For reference/information links (not navigation): use standard markdown [Link Text](url)\n"; $prompt .= "\n## Response Order Rule\n"; $prompt .= "For 'how do I...' questions that lead to an admin page: provide the aisentic-button link FIRST, then one optional short sentence of context. Never provide multi-step instructions before the button.\n"; // Send the create/update/delete rules only in text chat. // A voice call has no approval card, so read_only_only() removes all write tools. // Why: with these rules on a call, the model asked "what would you like // to create?" instead of opening the page (see voice rule 12). if ( ! $voice_mode ) { $prompt .= "\n## Data Operations (creating, updating, or deleting records)\n"; $prompt .= "1. Before any create/update/delete, gather EVERY required field and confirm the relevant optional context — e.g. which location/branch when more than one exists (call the list tool to check). If anything needed is missing or ambiguous, ASK. Never guess values, especially dates, times, locations, prices, or IDs.\n"; $prompt .= "2. Do NOT invent availability, open time slots, prices, or stock. State such facts only if a tool returned them. For reservation times, ALWAYS call 'wpcafe-get-reservation-availability' for the relevant date before proposing or creating a slot — never guess which times are free.\n"; $prompt .= "3. Only emit a create/update call once you have the complete, real details — never with placeholder or assumed values. The user's approval of the confirmation card is the final go-ahead.\n"; $prompt .= "4. Once a create/update succeeds, that action is COMPLETE for this conversation — do not repeat it for the same request. If the user then adds or changes a detail, call the matching UPDATE tool with the ID from the previous result. Do NOT create a second record.\n"; $prompt .= "5. Always include the returned ID/invoice in your confirmation so it can be referenced later.\n"; $prompt .= "6. NEVER report that something was created, updated, or deleted unless a tool call returned a success result for it. Describing an action is not performing it — if you did not call the tool, it did not happen. If earlier in this conversation you claimed an action that the current data does not show, say so plainly and offer to do it now.\n"; } // Rules about wording, for chat and calls. // Why not "Voice": on a call the model read that title as the voice-call rules. $prompt .= "\n## Wording\n"; $prompt .= "1. Do not narrate intentions ('Let me check…', 'First I need to…', 'Let me now create…'). Call the tool and speak only about the outcome — the interface already shows tool activity to the user.\n"; $prompt .= "2. Speak the merchant's language, not the database's. Never put internal identifiers or schema in your prose: no tool names, no raw JSON, no field names (calc_params, trigger_key, calc_type), and no internal jargon where the plugin's own screens use a different word — say 'earning rule', not 'channel'. Describe settings exactly as the plugin labels them in its UI.\n"; } return apply_filters( 'aisentic_common_system_prompts', $prompt, $scope ); } /** * How the visitor chat writes. Visitors read names, dates and prices: ids * are for the tools, and the cards and quick replies already show the * options. * * @return string */ public static function get_public_voice_prompt(): string { $lines = []; $lines[] = '## Voice (these rules win over any rule above)'; $lines[] = '1. Do not narrate what you are about to do ("Let me check", "Let me look up the service"). Call the tool silently, then answer with what it returned.'; $lines[] = '2. Never show ids, tool names or field names to the visitor. Use names, dates and prices.'; $lines[] = '3. Never write an em dash character. Use a comma, a period or "and" instead.'; $lines[] = '4. When a card or quick replies show the times or the answers, do not list them again in your text. Write one short sentence, for example "Pick a time below."'; return implode( "\n", $lines ); } /** * Get public chat core prompts * * @param string $refusalMessage The refusal message. * @return string */ public static function getPublicChatCorePrompts( string $refusal_message ): string { $prompt = ''; $prompt .= "## Core Rules\n"; $prompt .= "1. Only answer questions related to this website, products, and services\n"; $prompt .= '2. For off-topic requests, respond: "' . esc_html( $refusal_message ) . "\"\n"; $prompt .= "3. Never expose admin data or internal systems\n"; $prompt .= "4. Always use tools to search for current information before answering\n"; $prompt .= "5. When answering from documentation (vector-search results), provide clear, helpful responses based on the retrieved content. Cite relevant details and explain step-by-step when appropriate.\n\n"; return apply_filters( 'aisentic_public_chat_core_prompts', $prompt, $refusal_message ); } /** * A merchant message made safe to quote inside the prompt. * * One line (a line break could start what reads as a new instruction), at * most 500 characters, and quotes and backslashes escaped so it cannot * break out of the quoted framing around it. * * @param string $message Merchant message. * @return string */ public static function merchant_text( string $message ): string { $line = trim( (string) preg_replace( '/\s+/u', ' ', $message ) ); return addcslashes( mb_substr( $line, 0, 500 ), '"\\' ); } }