feat: MEMANTO integration — persistent memory for cross-session context (Phases 1-4)
Phase 1: Core Client
- New class-memanto-client.php: Singleton PHP client for MEMANTO API v2
- Health check with 5-min transient caching
- Agent CRUD (ensure, activate, deactivate sessions)
- Memory operations (remember, batch_remember, recall, recall_recent)
- Auto re-activation on expired session tokens (401 retry)
Phase 2: Write-Through Memory Hooks
- New class-memanto-context-enhancer.php: Orchestrates remember/recall
- Fires on: user message, plan generated/approved/rejected,
section written, block refined, config saved, session start/end
- All hooks via do_action() — zero coupling to MEMANTO when disabled
Phase 3: Context Enrichment
- Context builder injects recalled memories into AI prompts
via build_memanto_context() in build_working_context()
- 3-recall strategy: recent post memories, semantic search, user preferences
- Deduplication by content hash
Phase 4: Cross-Session Restore
- New REST endpoints: /memanto/restore, /memanto/preferences
- restore_session() recalls 15 recent memories + user preferences on editor load
- build_session_restore_message() creates AI-ready system message
- get_user_preferences_for_new_post() extracts tone/audience/length/language
- Frontend: 🧠 Restored badge in status bar with memory count tooltip
- Preference carry-over: auto-fills post config from stored user preferences
- deactivate_session() called on session end (triggers MEMANTO summary)
- Badge clears on new conversation start
Settings UI:
- MEMANTO Context Keeper section with enable toggle, URL, API key, test connection
- Settings registered via class-settings-v2.php + tab-memanto.php view
Graceful degradation: all MEMANTO calls guarded by is_active(),
frontend catches silently, plugin works identically when disabled.
This commit is contained in:
@@ -6,24 +6,24 @@
|
||||
* @var array $view_data Prepared view data from class-settings-v2.php
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
if (!defined("ABSPATH")) {
|
||||
exit();
|
||||
}
|
||||
|
||||
// Extract view data for easier access
|
||||
extract( $view_data );
|
||||
extract($view_data);
|
||||
?>
|
||||
<div class="wrap wpaw-settings-v2-wrap">
|
||||
<!-- Agentic IDE Split View Layout -->
|
||||
<div class="wpaw-ide-container d-flex">
|
||||
|
||||
|
||||
<!-- Left Sidebar: Settings Navigation -->
|
||||
<div class="wpaw-sidebar-nav flex-shrink-0">
|
||||
<!-- Header inside Sidebar -->
|
||||
<div class="wpaw-sidebar-header p-3 mb-2 border-bottom border-dark">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<img src="<?php echo esc_url( WP_AGENTIC_WRITER_URL . 'assets/img/icon.svg' ); ?>"
|
||||
alt="WP Agentic Writer"
|
||||
<img src="<?php echo esc_url(WP_AGENTIC_WRITER_URL . "assets/img/icon.svg"); ?>"
|
||||
alt="WP Agentic Writer"
|
||||
style="width: 24px; height: 24px; filter: invert(1)">
|
||||
<h1 class="h6 mb-0 text-white fw-bold">Agentic Writer</h1>
|
||||
</div>
|
||||
@@ -39,19 +39,25 @@ extract( $view_data );
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active w-100 text-start d-flex align-items-center gap-2" id="general-tab" data-bs-toggle="pill" data-bs-target="#general" type="button" role="tab" aria-controls="general" aria-selected="true">
|
||||
<i class="bi bi-sliders"></i>
|
||||
<?php esc_html_e( 'General', 'wp-agentic-writer' ); ?>
|
||||
<?php esc_html_e("General", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link w-100 text-start d-flex align-items-center gap-2" id="models-tab" data-bs-toggle="pill" data-bs-target="#models" type="button" role="tab" aria-controls="models" aria-selected="false">
|
||||
<i class="bi bi-stars"></i>
|
||||
<?php esc_html_e( 'AI Models', 'wp-agentic-writer' ); ?>
|
||||
<?php esc_html_e("AI Models", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link w-100 text-start d-flex align-items-center gap-2" id="local-backend-tab" data-bs-toggle="pill" data-bs-target="#local-backend" type="button" role="tab" aria-controls="local-backend" aria-selected="false">
|
||||
<i class="bi bi-house-fill"></i>
|
||||
<?php esc_html_e( 'Local Backend', 'wp-agentic-writer' ); ?>
|
||||
<?php esc_html_e("Local Backend", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link w-100 text-start d-flex align-items-center gap-2" id="memanto-tab" data-bs-toggle="pill" data-bs-target="#memanto" type="button" role="tab" aria-controls="memanto" aria-selected="false">
|
||||
<i class="bi bi-cpu"></i>
|
||||
<?php esc_html_e("MEMANTO", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -59,13 +65,13 @@ extract( $view_data );
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link w-100 text-start d-flex align-items-center gap-2" id="cost-log-tab" data-bs-toggle="pill" data-bs-target="#cost-log" type="button" role="tab" aria-controls="cost-log" aria-selected="false">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
<?php esc_html_e( 'OpenRouter Cost Log', 'wp-agentic-writer' ); ?>
|
||||
<?php esc_html_e("OpenRouter Cost Log", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link w-100 text-start d-flex align-items-center gap-2" id="guide-tab" data-bs-toggle="pill" data-bs-target="#guide" type="button" role="tab" aria-controls="guide" aria-selected="false">
|
||||
<i class="bi bi-book"></i>
|
||||
<?php esc_html_e( 'Model Guide', 'wp-agentic-writer' ); ?>
|
||||
<?php esc_html_e("Model Guide", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -75,7 +81,7 @@ extract( $view_data );
|
||||
<!-- Right Content Pane: Settings Forms -->
|
||||
<div class="wpaw-content-pane flex-grow-1 d-flex flex-column h-100">
|
||||
<form method="post" action="options.php" id="wpaw-settings-form" class="h-100 d-flex flex-column">
|
||||
<?php settings_fields( 'wp_agentic_writer_settings' ); ?>
|
||||
<?php settings_fields("wp_agentic_writer_settings"); ?>
|
||||
|
||||
<!-- Workflow Pipeline Progress -->
|
||||
<div class="wpaw-workflow-progress wpaw-workflow-compact mb-4" id="wpaw-workflow-display">
|
||||
@@ -140,7 +146,7 @@ extract( $view_data );
|
||||
<h2 class="h4 text-white m-0">General Settings</h2>
|
||||
<p class="text-secondary small mt-1">Configure global API keys, budget, and content parameters.</p>
|
||||
</div>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . 'views/settings/tab-general.php'; ?>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . "views/settings/tab-general.php"; ?>
|
||||
</div>
|
||||
|
||||
<!-- Models Tab -->
|
||||
@@ -149,7 +155,7 @@ extract( $view_data );
|
||||
<h2 class="h4 text-white m-0">AI Models</h2>
|
||||
<p class="text-secondary small mt-1">Select logic engines for different stages of the writing pipeline.</p>
|
||||
</div>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . 'views/settings/tab-models.php'; ?>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . "views/settings/tab-models.php"; ?>
|
||||
</div>
|
||||
|
||||
<!-- Local Backend Tab -->
|
||||
@@ -158,16 +164,25 @@ extract( $view_data );
|
||||
<h2 class="h4 text-white m-0">Local Backend</h2>
|
||||
<p class="text-secondary small mt-1">Configure connections to local LM Studio or Ollama instances.</p>
|
||||
</div>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . 'views/settings/tab-local-backend.php'; ?>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . "views/settings/tab-local-backend.php"; ?>
|
||||
</div>
|
||||
|
||||
<!-- Cost Log Tab -->
|
||||
<!-- MEMANTO Tab -->
|
||||
<div class="tab-pane fade" id="memanto" role="tabpanel" aria-labelledby="memanto-tab">
|
||||
<div class="mb-4 pb-3 border-bottom border-dark">
|
||||
<h2 class="h4 text-white m-0">MEMANTO Context Keeper</h2>
|
||||
<p class="text-secondary small mt-1">Optional persistent memory for your AI writing assistant. The plugin works perfectly without it.</p>
|
||||
</div>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . "views/settings/tab-memanto.php"; ?>
|
||||
</div>
|
||||
|
||||
<!-- Cost Log Tab -->
|
||||
<div class="tab-pane fade" id="cost-log" role="tabpanel" aria-labelledby="cost-log-tab">
|
||||
<div class="mb-4 pb-3 border-bottom border-dark">
|
||||
<h2 class="h4 text-white m-0">OpenRouter Cost Analytics</h2>
|
||||
<p class="text-secondary small mt-1">Track API token usage and expenses across all generations.</p>
|
||||
</div>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . 'views/settings/tab-cost-log.php'; ?>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . "views/settings/tab-cost-log.php"; ?>
|
||||
</div>
|
||||
|
||||
<!-- Guide Tab -->
|
||||
@@ -176,7 +191,7 @@ extract( $view_data );
|
||||
<h2 class="h4 text-white m-0">Provider Documentation</h2>
|
||||
<p class="text-secondary small mt-1">Reference materials for selecting the right model constraints.</p>
|
||||
</div>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . 'views/settings/tab-guide.php'; ?>
|
||||
<?php include WP_AGENTIC_WRITER_DIR . "views/settings/tab-guide.php"; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -185,18 +200,28 @@ extract( $view_data );
|
||||
<div class="wpaw-save-bar p-3 border-top border-dark d-flex justify-content-between align-items-center bg-transparent mt-auto sticky-bottom">
|
||||
<div class="text-secondary small d-flex align-items-center gap-2">
|
||||
<span class="dashicons dashicons-plugin text-primary"></span>
|
||||
<?php printf( esc_html__( 'v%s', 'wp-agentic-writer' ), esc_html( WP_AGENTIC_WRITER_VERSION ) ); ?>
|
||||
<?php printf(
|
||||
esc_html__("v%s", "wp-agentic-writer"),
|
||||
esc_html(WP_AGENTIC_WRITER_VERSION),
|
||||
); ?>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="wpaw-reset-settings">
|
||||
<?php esc_html_e( 'Reset Defaults', 'wp-agentic-writer' ); ?>
|
||||
<?php esc_html_e("Reset Defaults", "wp-agentic-writer"); ?>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary px-4 fw-semibold" id="wpaw-save-settings">
|
||||
<?php
|
||||
$is_mac = isset( $_SERVER['HTTP_USER_AGENT'] ) && strpos( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ), 'Mac OS' ) !== false;
|
||||
$cmd_key = $is_mac ? '⌘' : 'Ctrl';
|
||||
?>
|
||||
<?php esc_html_e( 'Save Settings', 'wp-agentic-writer' ); ?> <kbd class="ms-1 bg-dark text-white border-0 py-0"><?php echo esc_html( $cmd_key ); ?>+S</kbd>
|
||||
$is_mac =
|
||||
isset($_SERVER["HTTP_USER_AGENT"]) &&
|
||||
strpos(wp_unslash($_SERVER["HTTP_USER_AGENT"]), "Mac OS") !== false;
|
||||
$cmd_key = $is_mac ? "⌘" : "Ctrl";
|
||||
?>
|
||||
<?php esc_html_e(
|
||||
"Save Settings",
|
||||
"wp-agentic-writer",
|
||||
); ?> <kbd class="ms-1 bg-dark text-white border-0 py-0"><?php echo esc_html(
|
||||
$cmd_key,
|
||||
); ?>+S</kbd>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,7 +235,10 @@ extract( $view_data );
|
||||
<div id="wpaw-toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<span class="me-2">✨</span>
|
||||
<strong class="me-auto"><?php esc_html_e( 'WP Agentic Writer', 'wp-agentic-writer' ); ?></strong>
|
||||
<strong class="me-auto"><?php esc_html_e(
|
||||
"WP Agentic Writer",
|
||||
"wp-agentic-writer",
|
||||
); ?></strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body" id="wpaw-toast-message"></div>
|
||||
|
||||
195
views/settings/tab-memanto.php
Normal file
195
views/settings/tab-memanto.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Settings Tab: MEMANTO Context Keeper
|
||||
*
|
||||
* @package WP_Agentic_Writer
|
||||
* @since 0.3.0
|
||||
* @var array $view_data Prepared view data (extracted in layout.php)
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Variables from $view_data (extracted in layout.php).
|
||||
$memanto_enabled = $memanto_enabled ?? false;
|
||||
$memanto_url = $memanto_url ?? '';
|
||||
$memanto_moorcheh_key = $memanto_moorcheh_key ?? '';
|
||||
?>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Enable MEMANTO -->
|
||||
<div class="col-12">
|
||||
<div class="wpaw-card">
|
||||
<div class="wpaw-card-body">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="memanto_enabled"
|
||||
name="wp_agentic_writer_settings[memanto_enabled]"
|
||||
value="1"
|
||||
<?php checked( $memanto_enabled ); ?>
|
||||
>
|
||||
<label class="form-check-label text-white fw-medium" for="memanto_enabled">
|
||||
<?php esc_html_e( 'Enable MEMANTO Integration', 'wp-agentic-writer' ); ?>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-secondary small mt-2 mb-0">
|
||||
When enabled, the AI writing assistant will store and recall memories across sessions using MEMANTO.
|
||||
The plugin works perfectly without MEMANTO — this is an optional enhancement.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MEMANTO Instance URL -->
|
||||
<div class="col-md-6">
|
||||
<div class="wpaw-card h-100">
|
||||
<div class="wpaw-card-body">
|
||||
<label for="memanto_url" class="form-label text-white fw-medium">
|
||||
<i class="bi bi-link-45deg me-1"></i>
|
||||
<?php esc_html_e( 'MEMANTO Instance URL', 'wp-agentic-writer' ); ?>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
class="form-control"
|
||||
id="memanto_url"
|
||||
name="wp_agentic_writer_settings[memanto_url]"
|
||||
value="<?php echo esc_attr( $memanto_url ); ?>"
|
||||
placeholder="https://your-instance.context.wpagentic.dev"
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="form-text text-secondary">
|
||||
The URL of your MEMANTO instance. Provided when you subscribe to MEMANTO Context Keeper.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Moorcheh API Key -->
|
||||
<div class="col-md-6">
|
||||
<div class="wpaw-card h-100">
|
||||
<div class="wpaw-card-body">
|
||||
<label for="memanto_moorcheh_key" class="form-label text-white fw-medium">
|
||||
<i class="bi bi-key me-1"></i>
|
||||
<?php esc_html_e( 'Moorcheh API Key', 'wp-agentic-writer' ); ?>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="memanto_moorcheh_key"
|
||||
name="wp_agentic_writer_settings[memanto_moorcheh_key]"
|
||||
value="<?php echo esc_attr( $memanto_moorcheh_key ); ?>"
|
||||
placeholder="Enter your Moorcheh API key"
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<div class="form-text text-secondary">
|
||||
Get a free API key at <a href="https://moorcheh.ai" target="_blank" class="text-info">moorcheh.ai</a> (10K vectors/month included).
|
||||
Billed to your own Moorcheh account.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connection Status & Test -->
|
||||
<div class="col-12">
|
||||
<div class="wpaw-card">
|
||||
<div class="wpaw-card-body">
|
||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span id="memanto-status-indicator" class="badge rounded-pill bg-secondary">
|
||||
<?php if ( ! empty( $memanto_url ) && ! empty( $memanto_moorcheh_key ) ) : ?>
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"></span> Checking...
|
||||
<?php else : ?>
|
||||
Not configured
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<span id="memanto-status-detail" class="text-secondary small"></span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-info"
|
||||
id="wpaw-test-memanto"
|
||||
<?php echo ( empty( $memanto_url ) || empty( $memanto_moorcheh_key ) ) ? 'disabled' : ''; ?>
|
||||
>
|
||||
<i class="bi bi-wifi me-1"></i>
|
||||
<?php esc_html_e( 'Test Connection', 'wp-agentic-writer' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Box -->
|
||||
<div class="col-12">
|
||||
<div class="wpaw-card border-info" style="border-left: 3px solid var(--bs-info);">
|
||||
<div class="wpaw-card-body">
|
||||
<div class="d-flex gap-3">
|
||||
<span class="fs-4">🧠</span>
|
||||
<div>
|
||||
<h6 class="text-white mb-2">What MEMANTO does</h6>
|
||||
<ul class="text-secondary small mb-2 ps-3">
|
||||
<li>Your AI assistant <strong class="text-white">remembers context</strong> across sessions and browser refreshes</li>
|
||||
<li>Writing preferences <strong class="text-white">carry over</strong> between posts</li>
|
||||
<li>Eliminates <strong class="text-white">context loss</strong> when switching between Chat, Planning, and Writing modes</li>
|
||||
<li>Reduces AI token usage by up to <strong class="text-white">63%</strong> through smart memory recall</li>
|
||||
</ul>
|
||||
<p class="text-secondary small mb-0">
|
||||
Don't have MEMANTO? <a href="https://wpagentic.dev/memanto" target="_blank" class="text-info">Get MEMANTO Context Keeper</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
// Test MEMANTO connection.
|
||||
$('#wpaw-test-memanto').on('click', function() {
|
||||
var btn = $(this);
|
||||
var statusEl = $('#memanto-status-indicator');
|
||||
var detailEl = $('#memanto-status-detail');
|
||||
|
||||
btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span> Testing...');
|
||||
statusEl.removeClass('bg-success bg-danger bg-secondary bg-warning').addClass('bg-warning').html('Testing...');
|
||||
detailEl.text('');
|
||||
|
||||
$.post(wpawSettingsV2.ajaxUrl, {
|
||||
action: 'wpaw_test_memanto',
|
||||
nonce: wpawSettingsV2.nonce,
|
||||
url: $('#memanto_url').val(),
|
||||
key: $('#memanto_moorcheh_key').val()
|
||||
}, function(response) {
|
||||
btn.prop('disabled', false).html('<i class="bi bi-wifi me-1"></i> Test Connection');
|
||||
|
||||
if (response.success && response.data.healthy) {
|
||||
statusEl.removeClass('bg-warning bg-danger bg-secondary').addClass('bg-success').html('🟢 Connected');
|
||||
var detail = response.data.details || {};
|
||||
var info = detail.service ? detail.service + ' v' + (detail.version || '?') : 'Healthy';
|
||||
if (detail.moorcheh_connected) {
|
||||
info += ' · Moorcheh connected';
|
||||
}
|
||||
detailEl.text(info);
|
||||
} else {
|
||||
statusEl.removeClass('bg-warning bg-success bg-secondary').addClass('bg-danger').html('🔴 Error');
|
||||
detailEl.text(response.data?.message || 'Connection failed');
|
||||
}
|
||||
}).fail(function() {
|
||||
btn.prop('disabled', false).html('<i class="bi bi-wifi me-1"></i> Test Connection');
|
||||
statusEl.removeClass('bg-warning bg-success bg-secondary').addClass('bg-danger').html('🔴 Error');
|
||||
detailEl.text('Request failed');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-test on load if both fields are filled.
|
||||
if ($('#memanto_url').val() && $('#memanto_moorcheh_key').val()) {
|
||||
$('#wpaw-test-memanto').trigger('click');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user