From 0db0f22565e35afaaf5c716889ba6eb5939477ac Mon Sep 17 00:00:00 2001 From: colorlink Date: Fri, 16 Oct 2026 01:23:56 +0000 Subject: [PATCH] =?UTF-8?q?feat(batch10):=20memory=20layer=20=E2=80=94=20o?= =?UTF-8?q?ption/transient=20KV=20store=20(set/get/list/delete/append)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/Extensions/Batch10.php | 108 ++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 includes/Extensions/Batch10.php diff --git a/includes/Extensions/Batch10.php b/includes/Extensions/Batch10.php new file mode 100644 index 0000000..c154124 --- /dev/null +++ b/includes/Extensions/Batch10.php @@ -0,0 +1,108 @@ + 'object', 'properties' => $props]; + if ($required) $schema['required'] = $required; + return ['name' => $name, 'description' => $desc, 'scope' => $scope, + 'handler' => 'AskTamMCP\\Extensions\\Batch10::' . $method, 'inputSchema' => $schema]; + }; + + $registry->register($def('memory_set', 'Store a string value under a key. Optional ttl_minutes makes it expire (otherwise it persists). For structured data, pass JSON text as value.', + ['key' => ['type' => 'string'], 'value' => ['type' => 'string'], 'ttl_minutes' => ['type' => 'integer', 'default' => 0]], + ['key', 'value'], 'write', 'setVal')); + + $registry->register($def('memory_get', 'Retrieve a stored value by key.', + ['key' => ['type' => 'string']], ['key'], 'read', 'getVal')); + + $registry->register($def('memory_list', 'List stored memory keys, optionally filtered by prefix.', + ['prefix' => ['type' => 'string']], [], 'read', 'listKeys')); + + $registry->register($def('memory_delete', 'Delete a stored value by key.', + ['key' => ['type' => 'string']], ['key'], 'write', 'deleteVal')); + + $registry->register($def('memory_append', 'Append an item to a newline-delimited list stored at key (created if absent).', + ['key' => ['type' => 'string'], 'item' => ['type' => 'string']], ['key', 'item'], 'write', 'appendVal')); + } + + private static function key(string $raw): string { + $k = preg_replace('/[^a-z0-9_\-]/i', '', $raw); + if ($k === '') throw new \RuntimeException('Invalid key.'); + return self::PREFIX . $k; + } + + public static function setVal(array $args): array { + $opt = self::key((string) ($args['key'] ?? '')); + $val = (string) ($args['value'] ?? ''); + $ttl = (int) ($args['ttl_minutes'] ?? 0); + if ($ttl > 0) { set_transient($opt, $val, $ttl * MINUTE_IN_SECONDS); delete_option($opt); } + else { update_option($opt, $val, false); delete_transient($opt); } + return ['key' => $args['key'], 'stored' => true, 'expires' => $ttl > 0 ? $ttl . 'm' : 'never']; + } + + public static function getVal(array $args): array { + $opt = self::key((string) ($args['key'] ?? '')); + $v = get_option($opt, null); + $source = 'option'; + if ($v === null) { + $t = get_transient($opt); + if ($t === false) { $v = null; $source = null; } else { $v = $t; $source = 'transient'; } + } + return ['key' => $args['key'], 'value' => $v, 'exists' => $v !== null, 'source' => $source]; + } + + public static function listKeys(array $args): array { + global $wpdb; + $prefix = isset($args['prefix']) ? (string) $args['prefix'] : ''; + $like1 = self::PREFIX . '%'; + $like2 = '_transient_' . self::PREFIX . '%'; + $names = $wpdb->get_col($wpdb->prepare( + "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", + $like1, $like2 + )); + $keys = []; + foreach ((array) $names as $n) { + $k = str_replace(['_transient_timeout_', '_transient_', self::PREFIX], '', $n); + if ($k === '' || strpos($n, '_transient_timeout_') === 0) continue; + if ($prefix !== '' && strpos($k, $prefix) !== 0) continue; + $keys[$k] = true; + } + $out = array_keys($keys); + return ['count' => count($out), 'keys' => $out]; + } + + public static function deleteVal(array $args): array { + $opt = self::key((string) ($args['key'] ?? '')); + $a = delete_option($opt); + $b = delete_transient($opt); + return ['key' => $args['key'], 'deleted' => ($a || $b)]; + } + + public static function appendVal(array $args): array { + $opt = self::key((string) ($args['key'] ?? '')); + $item = (string) ($args['item'] ?? ''); + $cur = get_option($opt, ''); + if (!is_string($cur)) $cur = ''; + $list = $cur === '' ? [] : explode("\n", $cur); + $list[] = $item; + update_option($opt, implode("\n", $list), false); + return ['key' => $args['key'], 'count' => count($list)]; + } +}