Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 16 additions & 67 deletions claw/services/ai/ai_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,103 +229,52 @@ typedef struct {
static ltm_entry_t s_ltm[LTM_MAX_ENTRIES];
static int s_ltm_count;

#ifdef CLAW_PLATFORM_ESP_IDF

#include "nvs_flash.h"
#include "nvs.h"
#include "osal/claw_kv.h"

#define LTM_NVS_NAMESPACE "claw_ltm"
#define LTM_NVS_KEY_DATA "data"
#define LTM_NVS_KEY_COUNT "cnt"
#define LTM_KV_NS "claw_ltm"
#define LTM_KV_DATA "data"
#define LTM_KV_CNT "cnt"

static int ltm_flush_nvs(void)
static int ltm_persist(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(LTM_NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK) {
CLAW_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
return CLAW_ERROR;
}

nvs_set_u8(h, LTM_NVS_KEY_COUNT, (uint8_t)s_ltm_count);
nvs_set_blob(h, LTM_NVS_KEY_DATA, s_ltm,
s_ltm_count * sizeof(ltm_entry_t));
err = nvs_commit(h);
nvs_close(h);

if (err != ESP_OK) {
CLAW_LOGE(TAG, "nvs_commit failed: %s", esp_err_to_name(err));
if (claw_kv_set_u8(LTM_KV_NS, LTM_KV_CNT,
(uint8_t)s_ltm_count) != CLAW_OK) {
return CLAW_ERROR;
}
return CLAW_OK;
return claw_kv_set_blob(LTM_KV_NS, LTM_KV_DATA, s_ltm,
s_ltm_count * sizeof(ltm_entry_t));
}

static int ltm_load_nvs(void)
static void ltm_load(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(LTM_NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND) {
s_ltm_count = 0;
return CLAW_OK;
}
if (err != ESP_OK) {
CLAW_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
return CLAW_ERROR;
}

uint8_t cnt = 0;
err = nvs_get_u8(h, LTM_NVS_KEY_COUNT, &cnt);
if (err != ESP_OK) {
nvs_close(h);
if (claw_kv_get_u8(LTM_KV_NS, LTM_KV_CNT, &cnt) != CLAW_OK) {
s_ltm_count = 0;
return CLAW_OK;
return;
}

if (cnt > LTM_MAX_ENTRIES) {
cnt = LTM_MAX_ENTRIES;
}

size_t blob_size = cnt * sizeof(ltm_entry_t);
err = nvs_get_blob(h, LTM_NVS_KEY_DATA, s_ltm, &blob_size);
nvs_close(h);

if (err == ESP_OK) {
if (claw_kv_get_blob(LTM_KV_NS, LTM_KV_DATA,
s_ltm, &blob_size) == CLAW_OK) {
s_ltm_count = cnt;
} else {
s_ltm_count = 0;
}

return CLAW_OK;
}

static int ltm_persist(void) { return ltm_flush_nvs(); }

int ai_ltm_init(void)
{
memset(s_ltm, 0, sizeof(s_ltm));
s_ltm_count = 0;

ltm_load_nvs();
CLAW_LOGI(TAG, "long-term initialized, %d/%d entries from flash",
ltm_load();
CLAW_LOGI(TAG, "long-term initialized, %d/%d entries",
s_ltm_count, LTM_MAX_ENTRIES);
return CLAW_OK;
}

#else /* non-ESP-IDF: RAM-only LTM */

static int ltm_persist(void) { return CLAW_OK; }

int ai_ltm_init(void)
{
memset(s_ltm, 0, sizeof(s_ltm));
s_ltm_count = 0;
CLAW_LOGI(TAG, "long-term initialized (RAM-only), max=%d",
LTM_MAX_ENTRIES);
return CLAW_OK;
}

#endif

int ai_ltm_save(const char *key, const char *value)
{
if (!key || !value || key[0] == '\0') {
Expand Down
72 changes: 20 additions & 52 deletions claw/services/ai/ai_skill.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,16 @@ typedef struct {
static ai_skill_t s_skills[SKILL_MAX];
static int s_count;

/* --- NVS persistence (ESP-IDF only) --- */
/* --- KV persistence (platform-independent via OSAL) --- */

#ifdef CLAW_PLATFORM_ESP_IDF
#include "nvs_flash.h"
#include "nvs.h"
#include "osal/claw_kv.h"

#define SKILL_NVS_NS "claw_skill"
#define SKILL_NVS_CNT "cnt"
#define SKILL_NVS_DATA "data"
#define SKILL_KV_NS "claw_skill"
#define SKILL_KV_CNT "cnt"
#define SKILL_KV_DATA "data"

static int skill_flush_nvs(void)
static int skill_persist(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(SKILL_NVS_NS, NVS_READWRITE, &h);
if (err != ESP_OK) {
CLAW_LOGE(TAG, "nvs_open: %s", esp_err_to_name(err));
return CLAW_ERROR;
}

/* Count user-created skills */
uint8_t cnt = 0;
for (int i = 0; i < s_count; i++) {
Expand All @@ -57,52 +48,36 @@ static int skill_flush_nvs(void)
}
}

nvs_set_u8(h, SKILL_NVS_CNT, cnt);
claw_kv_set_u8(SKILL_KV_NS, SKILL_KV_CNT, cnt);

if (cnt > 0) {
/*
* Pack user skills into a contiguous blob.
* Each entry: ai_skill_t (fixed size).
*/
ai_skill_t buf[SKILL_MAX];
int idx = 0;
for (int i = 0; i < s_count; i++) {
if (!s_skills[i].builtin) {
memcpy(&buf[idx++], &s_skills[i], sizeof(ai_skill_t));
}
}
nvs_set_blob(h, SKILL_NVS_DATA, buf,
idx * sizeof(ai_skill_t));
} else {
nvs_erase_key(h, SKILL_NVS_DATA);
return claw_kv_set_blob(SKILL_KV_NS, SKILL_KV_DATA, buf,
idx * sizeof(ai_skill_t));
}

err = nvs_commit(h);
nvs_close(h);
return (err == ESP_OK) ? CLAW_OK : CLAW_ERROR;
claw_kv_delete(SKILL_KV_NS, SKILL_KV_DATA);
return CLAW_OK;
}

static void skill_load_nvs(void)
static void skill_load(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(SKILL_NVS_NS, NVS_READONLY, &h);
if (err != ESP_OK) {
return;
}

uint8_t cnt = 0;
err = nvs_get_u8(h, SKILL_NVS_CNT, &cnt);
if (err != ESP_OK || cnt == 0) {
nvs_close(h);
if (claw_kv_get_u8(SKILL_KV_NS, SKILL_KV_CNT, &cnt) != CLAW_OK
|| cnt == 0) {
return;
}

ai_skill_t buf[SKILL_MAX];
size_t blob_sz = cnt * sizeof(ai_skill_t);
err = nvs_get_blob(h, SKILL_NVS_DATA, buf, &blob_sz);
nvs_close(h);

if (err != ESP_OK) {
if (claw_kv_get_blob(SKILL_KV_NS, SKILL_KV_DATA,
buf, &blob_sz) != CLAW_OK) {
return;
}

Expand All @@ -112,16 +87,9 @@ static void skill_load_nvs(void)
s_count++;
}

CLAW_LOGI(TAG, "restored %d user skill(s) from NVS", (int)cnt);
CLAW_LOGI(TAG, "restored %d user skill(s) from KV", (int)cnt);
}

#else /* non-ESP-IDF */

static int skill_flush_nvs(void) { return CLAW_OK; }
static void skill_load_nvs(void) {}

#endif

/* --- Built-in skill registration --- */

static void register_builtins(void)
Expand Down Expand Up @@ -161,7 +129,7 @@ int ai_skill_init(void)
s_count = 0;

register_builtins();
skill_load_nvs();
skill_load();

CLAW_LOGI(TAG, "initialized, %d skill(s)", s_count);
return CLAW_OK;
Expand Down Expand Up @@ -209,7 +177,7 @@ int ai_skill_remove(const char *name)
}
s_count--;
memset(&s_skills[s_count], 0, sizeof(ai_skill_t));
skill_flush_nvs();
skill_persist();
CLAW_LOGI(TAG, "removed skill: %s", name);
return CLAW_OK;
}
Expand Down Expand Up @@ -315,7 +283,7 @@ static int tool_create_skill(const cJSON *params, cJSON *result)
return CLAW_ERROR;
}

skill_flush_nvs();
skill_persist();

cJSON_AddStringToObject(result, "status", "ok");
char msg[64];
Expand Down
72 changes: 24 additions & 48 deletions claw/shell/shell_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,68 +26,44 @@
#include "claw/services/swarm/swarm.h"
#endif

#ifdef CLAW_PLATFORM_ESP_IDF
#include "nvs.h"
#endif
#include "osal/claw_kv.h"

/* ---- NVS persistence (ESP-IDF only) ---- */
/* ---- KV persistence (platform-independent via OSAL) ---- */

void shell_nvs_save_str(const char *ns, const char *key, const char *val)
{
#ifdef CLAW_PLATFORM_ESP_IDF
nvs_handle_t nvs;

if (nvs_open(ns, NVS_READWRITE, &nvs) != ESP_OK) {
printf("[error] NVS open failed\n");
return;
if (claw_kv_set_str(ns, key, val) != CLAW_OK) {
printf("[error] KV save failed\n");
}
nvs_set_str(nvs, key, val);
nvs_commit(nvs);
nvs_close(nvs);
#else
(void)ns;
(void)key;
(void)val;
#endif
}

void shell_nvs_config_load(void)
{
#ifdef CLAW_PLATFORM_ESP_IDF
nvs_handle_t nvs;
char buf[256];
size_t len;

/* Load AI config from NVS (overrides compile-time defaults) */
if (nvs_open(SHELL_NVS_NS_AI, NVS_READONLY, &nvs) == ESP_OK) {
len = sizeof(buf);
if (nvs_get_str(nvs, "api_key", buf, &len) == ESP_OK) {
ai_set_api_key(buf);
}
len = sizeof(buf);
if (nvs_get_str(nvs, "api_url", buf, &len) == ESP_OK) {
ai_set_api_url(buf);
}
len = sizeof(buf);
if (nvs_get_str(nvs, "model", buf, &len) == ESP_OK) {
ai_set_model(buf);
}
nvs_close(nvs);
/* Load AI config from KV (overrides compile-time defaults) */
if (claw_kv_get_str(SHELL_NVS_NS_AI, "api_key",
buf, sizeof(buf)) == CLAW_OK) {
ai_set_api_key(buf);
}
if (claw_kv_get_str(SHELL_NVS_NS_AI, "api_url",
buf, sizeof(buf)) == CLAW_OK) {
ai_set_api_url(buf);
}
if (claw_kv_get_str(SHELL_NVS_NS_AI, "model",
buf, sizeof(buf)) == CLAW_OK) {
ai_set_model(buf);
}

/* Load Feishu config from NVS */
if (nvs_open(SHELL_NVS_NS_FEISHU, NVS_READONLY, &nvs) == ESP_OK) {
len = sizeof(buf);
if (nvs_get_str(nvs, "app_id", buf, &len) == ESP_OK) {
feishu_set_app_id(buf);
}
len = sizeof(buf);
if (nvs_get_str(nvs, "app_secret", buf, &len) == ESP_OK) {
feishu_set_app_secret(buf);
}
nvs_close(nvs);
/* Load Feishu config from KV */
if (claw_kv_get_str(SHELL_NVS_NS_FEISHU, "app_id",
buf, sizeof(buf)) == CLAW_OK) {
feishu_set_app_id(buf);
}
if (claw_kv_get_str(SHELL_NVS_NS_FEISHU, "app_secret",
buf, sizeof(buf)) == CLAW_OK) {
feishu_set_app_secret(buf);
}
#endif
}

/* ---- Common command handlers ---- */
Expand Down
41 changes: 41 additions & 0 deletions include/osal/claw_kv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2026, Chao Liu <chao.liu.zevorn@gmail.com>
* SPDX-License-Identifier: MIT
*
* OSAL Key-Value storage abstraction.
* ESP-IDF: backed by NVS Flash.
* RT-Thread: RAM-only fallback (persistent backends TBD).
*/

#ifndef CLAW_KV_H
#define CLAW_KV_H

#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

int claw_kv_init(void);

int claw_kv_set_str(const char *ns, const char *key, const char *value);
int claw_kv_get_str(const char *ns, const char *key,
char *buf, size_t size);

int claw_kv_set_blob(const char *ns, const char *key,
const void *data, size_t len);
int claw_kv_get_blob(const char *ns, const char *key,
void *data, size_t *len);

int claw_kv_set_u8(const char *ns, const char *key, uint8_t val);
int claw_kv_get_u8(const char *ns, const char *key, uint8_t *val);

int claw_kv_delete(const char *ns, const char *key);
int claw_kv_erase_ns(const char *ns);

#ifdef __cplusplus
}
#endif

#endif /* CLAW_KV_H */
Loading
Loading