memory-lancedb-pro 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +81 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/README.md +59 -10
- package/README_CN.md +59 -10
- package/index.ts +7 -4
- package/openclaw.plugin.json +9 -5
- package/package.json +1 -1
- package/src/embedder.ts +79 -11
- package/src/tools.ts +47 -23
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
name: 🐛 Bug Report
|
|
2
|
+
description: Report a bug or unexpected behavior
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for reporting a bug! Please fill out the sections below.
|
|
9
|
+
|
|
10
|
+
- type: input
|
|
11
|
+
id: version
|
|
12
|
+
attributes:
|
|
13
|
+
label: Plugin Version
|
|
14
|
+
description: "Run `openclaw memory-pro version` to check"
|
|
15
|
+
placeholder: "e.g. 1.0.22"
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
18
|
+
|
|
19
|
+
- type: input
|
|
20
|
+
id: openclaw-version
|
|
21
|
+
attributes:
|
|
22
|
+
label: OpenClaw Version
|
|
23
|
+
description: "Run `openclaw --version` to check"
|
|
24
|
+
placeholder: "e.g. 2026.3.1"
|
|
25
|
+
validations:
|
|
26
|
+
required: true
|
|
27
|
+
|
|
28
|
+
- type: textarea
|
|
29
|
+
id: description
|
|
30
|
+
attributes:
|
|
31
|
+
label: Bug Description
|
|
32
|
+
description: A clear description of what happened
|
|
33
|
+
placeholder: "When I run `openclaw memory-pro search ...`, it throws..."
|
|
34
|
+
validations:
|
|
35
|
+
required: true
|
|
36
|
+
|
|
37
|
+
- type: textarea
|
|
38
|
+
id: expected
|
|
39
|
+
attributes:
|
|
40
|
+
label: Expected Behavior
|
|
41
|
+
description: What did you expect to happen?
|
|
42
|
+
validations:
|
|
43
|
+
required: true
|
|
44
|
+
|
|
45
|
+
- type: textarea
|
|
46
|
+
id: reproduce
|
|
47
|
+
attributes:
|
|
48
|
+
label: Steps to Reproduce
|
|
49
|
+
description: Minimal steps to reproduce the issue
|
|
50
|
+
placeholder: |
|
|
51
|
+
1. Set config ...
|
|
52
|
+
2. Run command ...
|
|
53
|
+
3. See error ...
|
|
54
|
+
validations:
|
|
55
|
+
required: true
|
|
56
|
+
|
|
57
|
+
- type: textarea
|
|
58
|
+
id: logs
|
|
59
|
+
attributes:
|
|
60
|
+
label: Error Logs / Screenshots
|
|
61
|
+
description: Paste relevant error output or screenshots
|
|
62
|
+
render: shell
|
|
63
|
+
|
|
64
|
+
- type: dropdown
|
|
65
|
+
id: embedding
|
|
66
|
+
attributes:
|
|
67
|
+
label: Embedding Provider
|
|
68
|
+
options:
|
|
69
|
+
- OpenAI
|
|
70
|
+
- Jina
|
|
71
|
+
- Gemini
|
|
72
|
+
- Ollama
|
|
73
|
+
- Other
|
|
74
|
+
validations:
|
|
75
|
+
required: false
|
|
76
|
+
|
|
77
|
+
- type: input
|
|
78
|
+
id: os
|
|
79
|
+
attributes:
|
|
80
|
+
label: OS / Platform
|
|
81
|
+
placeholder: "e.g. Ubuntu 24.04, macOS 15, Windows 11"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: ✨ Feature Request
|
|
2
|
+
description: Suggest a new feature or improvement
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for suggesting a feature! Please describe your idea below.
|
|
9
|
+
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: problem
|
|
12
|
+
attributes:
|
|
13
|
+
label: Problem / Motivation
|
|
14
|
+
description: What problem does this solve? Why do you need it?
|
|
15
|
+
placeholder: "I often need to ... but currently there's no way to ..."
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
18
|
+
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: solution
|
|
21
|
+
attributes:
|
|
22
|
+
label: Proposed Solution
|
|
23
|
+
description: How would you like this to work?
|
|
24
|
+
placeholder: "It would be great if `openclaw memory-pro` could ..."
|
|
25
|
+
validations:
|
|
26
|
+
required: true
|
|
27
|
+
|
|
28
|
+
- type: textarea
|
|
29
|
+
id: alternatives
|
|
30
|
+
attributes:
|
|
31
|
+
label: Alternatives Considered
|
|
32
|
+
description: Any workarounds or alternative approaches you've tried?
|
|
33
|
+
|
|
34
|
+
- type: dropdown
|
|
35
|
+
id: scope
|
|
36
|
+
attributes:
|
|
37
|
+
label: Area
|
|
38
|
+
description: Which part of the plugin does this affect?
|
|
39
|
+
options:
|
|
40
|
+
- Retrieval / Search
|
|
41
|
+
- Storage / Database
|
|
42
|
+
- Embedding
|
|
43
|
+
- CLI Commands
|
|
44
|
+
- Configuration
|
|
45
|
+
- Auto-capture / Auto-recall
|
|
46
|
+
- Scopes / Access Control
|
|
47
|
+
- Migration
|
|
48
|
+
- Documentation
|
|
49
|
+
- Other
|
|
50
|
+
validations:
|
|
51
|
+
required: false
|
|
52
|
+
|
|
53
|
+
- type: textarea
|
|
54
|
+
id: context
|
|
55
|
+
attributes:
|
|
56
|
+
label: Additional Context
|
|
57
|
+
description: Screenshots, examples, or links that help explain the feature
|
package/README.md
CHANGED
|
@@ -710,6 +710,51 @@ On LanceDB 0.26+ (via Apache Arrow), some numeric columns may be returned as `Bi
|
|
|
710
710
|
|
|
711
711
|
upgrade to **memory-lancedb-pro >= 1.0.14**. This plugin now coerces these values using `Number(...)` before doing arithmetic (for example, when computing scores or sorting by timestamp).
|
|
712
712
|
|
|
713
|
+
## Iron Rules for AI Agents (铁律)
|
|
714
|
+
|
|
715
|
+
> **For OpenClaw users**: copy the code block below into your `AGENTS.md` so your agent enforces these rules automatically.
|
|
716
|
+
|
|
717
|
+
```markdown
|
|
718
|
+
## Rule 1 — 双层记忆存储(铁律)
|
|
719
|
+
|
|
720
|
+
Every pitfall/lesson learned → IMMEDIATELY store TWO memories to LanceDB before moving on:
|
|
721
|
+
|
|
722
|
+
- **Technical layer**: Pitfall: [symptom]. Cause: [root cause]. Fix: [solution]. Prevention: [how to avoid]
|
|
723
|
+
(category: fact, importance ≥ 0.8)
|
|
724
|
+
- **Principle layer**: Decision principle ([tag]): [behavioral rule]. Trigger: [when it applies]. Action: [what to do]
|
|
725
|
+
(category: decision, importance ≥ 0.85)
|
|
726
|
+
- After each store, immediately `memory_recall` with anchor keywords to verify retrieval.
|
|
727
|
+
If not found, rewrite and re-store.
|
|
728
|
+
- Missing either layer = incomplete.
|
|
729
|
+
Do NOT proceed to next topic until both are stored and verified.
|
|
730
|
+
- Also update relevant SKILL.md files to prevent recurrence.
|
|
731
|
+
|
|
732
|
+
## Rule 2 — LanceDB 卫生
|
|
733
|
+
|
|
734
|
+
Entries must be short and atomic (< 500 chars). Never store raw conversation summaries, large blobs, or duplicates.
|
|
735
|
+
Prefer structured format with keywords for retrieval.
|
|
736
|
+
|
|
737
|
+
## Rule 3 — Recall before retry
|
|
738
|
+
|
|
739
|
+
On ANY tool failure, repeated error, or unexpected behavior, ALWAYS `memory_recall` with relevant keywords
|
|
740
|
+
(error message, tool name, symptom) BEFORE retrying. LanceDB likely already has the fix.
|
|
741
|
+
Blind retries waste time and repeat known mistakes.
|
|
742
|
+
|
|
743
|
+
## Rule 4 — 编辑前确认目标代码库
|
|
744
|
+
|
|
745
|
+
When working on memory plugins, confirm you are editing the intended package
|
|
746
|
+
(e.g., `memory-lancedb-pro` vs built-in `memory-lancedb`) before making changes;
|
|
747
|
+
use `memory_recall` + filesystem search to avoid patching the wrong repo.
|
|
748
|
+
|
|
749
|
+
## Rule 5 — 插件代码变更必须清 jiti 缓存(MANDATORY)
|
|
750
|
+
|
|
751
|
+
After modifying ANY `.ts` file under `plugins/`, MUST run `rm -rf /tmp/jiti/` BEFORE `openclaw gateway restart`.
|
|
752
|
+
jiti caches compiled TS; restart alone loads STALE code. This has caused silent bugs multiple times.
|
|
753
|
+
Config-only changes do NOT need cache clearing.
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
713
758
|
## Dependencies
|
|
714
759
|
|
|
715
760
|
| Package | Purpose |
|
|
@@ -720,16 +765,6 @@ upgrade to **memory-lancedb-pro >= 1.0.14**. This plugin now coerces these value
|
|
|
720
765
|
|
|
721
766
|
---
|
|
722
767
|
|
|
723
|
-
## ⭐ Star History
|
|
724
|
-
|
|
725
|
-
<a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
|
|
726
|
-
<picture>
|
|
727
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
|
|
728
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
729
|
-
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
730
|
-
</picture>
|
|
731
|
-
</a>
|
|
732
|
-
|
|
733
768
|
## Contributors
|
|
734
769
|
|
|
735
770
|
Top contributors (from GitHub’s contributors list, sorted by commit contributions; bots excluded):
|
|
@@ -742,11 +777,15 @@ Top contributors (from GitHub’s contributors list, sorted by commit contributi
|
|
|
742
777
|
<a href="https://github.com/Minidoracat"><img src="https://avatars.githubusercontent.com/u/11269639?v=4" width="48" height="48" alt="@Minidoracat" /></a>
|
|
743
778
|
<a href="https://github.com/furedericca-lab"><img src="https://avatars.githubusercontent.com/u/263020793?v=4" width="48" height="48" alt="@furedericca-lab" /></a>
|
|
744
779
|
<a href="https://github.com/joe2643"><img src="https://avatars.githubusercontent.com/u/19421931?v=4" width="48" height="48" alt="@joe2643" /></a>
|
|
780
|
+
<a href="https://github.com/AliceLJY"><img src="https://avatars.githubusercontent.com/u/136287420?v=4" width="48" height="48" alt="@AliceLJY" /></a>
|
|
781
|
+
<a href="https://github.com/chenjiyong"><img src="https://avatars.githubusercontent.com/u/8199522?v=4" width="48" height="48" alt="@chenjiyong" /></a>
|
|
745
782
|
</p>
|
|
746
783
|
|
|
747
784
|
- [@win4r](https://github.com/win4r) (3 commits)
|
|
748
785
|
- [@kctony](https://github.com/kctony) (2 commits)
|
|
749
786
|
- [@Akatsuki-Ryu](https://github.com/Akatsuki-Ryu) (1 commit)
|
|
787
|
+
- [@AliceLJY](https://github.com/AliceLJY) (1 commit)
|
|
788
|
+
- [@chenjiyong](https://github.com/chenjiyong) (1 commit)
|
|
750
789
|
- [@JasonSuz](https://github.com/JasonSuz) (1 commit)
|
|
751
790
|
- [@Minidoracat](https://github.com/Minidoracat) (1 commit)
|
|
752
791
|
- [@furedericca-lab](https://github.com/furedericca-lab) (1 commit)
|
|
@@ -754,6 +793,16 @@ Top contributors (from GitHub’s contributors list, sorted by commit contributi
|
|
|
754
793
|
|
|
755
794
|
Full list: https://github.com/win4r/memory-lancedb-pro/graphs/contributors
|
|
756
795
|
|
|
796
|
+
## ⭐ Star History
|
|
797
|
+
|
|
798
|
+
<a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
|
|
799
|
+
<picture>
|
|
800
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
|
|
801
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
802
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
803
|
+
</picture>
|
|
804
|
+
</a>
|
|
805
|
+
|
|
757
806
|
## License
|
|
758
807
|
|
|
759
808
|
MIT
|
package/README_CN.md
CHANGED
|
@@ -584,6 +584,51 @@ LanceDB 表 `memories`:
|
|
|
584
584
|
|
|
585
585
|
请升级到 **memory-lancedb-pro >= 1.0.14**。插件已对这些字段统一做 `Number(...)` 转换后再参与运算(例如:计算分数、按时间排序)。
|
|
586
586
|
|
|
587
|
+
## AI Agent 铁律(Iron Rules)
|
|
588
|
+
|
|
589
|
+
> **OpenClaw 用户**:将下方代码块复制到你的 `AGENTS.md` 中,让 Agent 自动遵守这些规则。
|
|
590
|
+
|
|
591
|
+
```markdown
|
|
592
|
+
## Rule 1 — 双层记忆存储(铁律)
|
|
593
|
+
|
|
594
|
+
Every pitfall/lesson learned → IMMEDIATELY store TWO memories to LanceDB before moving on:
|
|
595
|
+
|
|
596
|
+
- **Technical layer**: Pitfall: [symptom]. Cause: [root cause]. Fix: [solution]. Prevention: [how to avoid]
|
|
597
|
+
(category: fact, importance ≥ 0.8)
|
|
598
|
+
- **Principle layer**: Decision principle ([tag]): [behavioral rule]. Trigger: [when it applies]. Action: [what to do]
|
|
599
|
+
(category: decision, importance ≥ 0.85)
|
|
600
|
+
- After each store, immediately `memory_recall` with anchor keywords to verify retrieval.
|
|
601
|
+
If not found, rewrite and re-store.
|
|
602
|
+
- Missing either layer = incomplete.
|
|
603
|
+
Do NOT proceed to next topic until both are stored and verified.
|
|
604
|
+
- Also update relevant SKILL.md files to prevent recurrence.
|
|
605
|
+
|
|
606
|
+
## Rule 2 — LanceDB 卫生
|
|
607
|
+
|
|
608
|
+
Entries must be short and atomic (< 500 chars). Never store raw conversation summaries, large blobs, or duplicates.
|
|
609
|
+
Prefer structured format with keywords for retrieval.
|
|
610
|
+
|
|
611
|
+
## Rule 3 — Recall before retry
|
|
612
|
+
|
|
613
|
+
On ANY tool failure, repeated error, or unexpected behavior, ALWAYS `memory_recall` with relevant keywords
|
|
614
|
+
(error message, tool name, symptom) BEFORE retrying. LanceDB likely already has the fix.
|
|
615
|
+
Blind retries waste time and repeat known mistakes.
|
|
616
|
+
|
|
617
|
+
## Rule 4 — 编辑前确认目标代码库
|
|
618
|
+
|
|
619
|
+
When working on memory plugins, confirm you are editing the intended package
|
|
620
|
+
(e.g., `memory-lancedb-pro` vs built-in `memory-lancedb`) before making changes;
|
|
621
|
+
use `memory_recall` + filesystem search to avoid patching the wrong repo.
|
|
622
|
+
|
|
623
|
+
## Rule 5 — 插件代码变更必须清 jiti 缓存(MANDATORY)
|
|
624
|
+
|
|
625
|
+
After modifying ANY `.ts` file under `plugins/`, MUST run `rm -rf /tmp/jiti/` BEFORE `openclaw gateway restart`.
|
|
626
|
+
jiti caches compiled TS; restart alone loads STALE code. This has caused silent bugs multiple times.
|
|
627
|
+
Config-only changes do NOT need cache clearing.
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
587
632
|
## 依赖
|
|
588
633
|
|
|
589
634
|
| 包 | 用途 |
|
|
@@ -594,16 +639,6 @@ LanceDB 表 `memories`:
|
|
|
594
639
|
|
|
595
640
|
---
|
|
596
641
|
|
|
597
|
-
## ⭐ Star 趋势
|
|
598
|
-
|
|
599
|
-
<a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
|
|
600
|
-
<picture>
|
|
601
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
|
|
602
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
603
|
-
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
604
|
-
</picture>
|
|
605
|
-
</a>
|
|
606
|
-
|
|
607
642
|
## 主要贡献者
|
|
608
643
|
|
|
609
644
|
按 GitHub Contributors 列表自动生成(按 commit 贡献数排序,已排除 bot):
|
|
@@ -616,11 +651,15 @@ LanceDB 表 `memories`:
|
|
|
616
651
|
<a href="https://github.com/Minidoracat"><img src="https://avatars.githubusercontent.com/u/11269639?v=4" width="48" height="48" alt="@Minidoracat" /></a>
|
|
617
652
|
<a href="https://github.com/furedericca-lab"><img src="https://avatars.githubusercontent.com/u/263020793?v=4" width="48" height="48" alt="@furedericca-lab" /></a>
|
|
618
653
|
<a href="https://github.com/joe2643"><img src="https://avatars.githubusercontent.com/u/19421931?v=4" width="48" height="48" alt="@joe2643" /></a>
|
|
654
|
+
<a href="https://github.com/AliceLJY"><img src="https://avatars.githubusercontent.com/u/136287420?v=4" width="48" height="48" alt="@AliceLJY" /></a>
|
|
655
|
+
<a href="https://github.com/chenjiyong"><img src="https://avatars.githubusercontent.com/u/8199522?v=4" width="48" height="48" alt="@chenjiyong" /></a>
|
|
619
656
|
</p>
|
|
620
657
|
|
|
621
658
|
- [@win4r](https://github.com/win4r)(3 次提交)
|
|
622
659
|
- [@kctony](https://github.com/kctony)(2 次提交)
|
|
623
660
|
- [@Akatsuki-Ryu](https://github.com/Akatsuki-Ryu)(1 次提交)
|
|
661
|
+
- [@AliceLJY](https://github.com/AliceLJY)(1 次提交)
|
|
662
|
+
- [@chenjiyong](https://github.com/chenjiyong)(1 次提交)
|
|
624
663
|
- [@JasonSuz](https://github.com/JasonSuz)(1 次提交)
|
|
625
664
|
- [@Minidoracat](https://github.com/Minidoracat)(1 次提交)
|
|
626
665
|
- [@furedericca-lab](https://github.com/furedericca-lab)(1 次提交)
|
|
@@ -628,6 +667,16 @@ LanceDB 表 `memories`:
|
|
|
628
667
|
|
|
629
668
|
完整列表:https://github.com/win4r/memory-lancedb-pro/graphs/contributors
|
|
630
669
|
|
|
670
|
+
## ⭐ Star 趋势
|
|
671
|
+
|
|
672
|
+
<a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
|
|
673
|
+
<picture>
|
|
674
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
|
|
675
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
676
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
|
|
677
|
+
</picture>
|
|
678
|
+
</a>
|
|
679
|
+
|
|
631
680
|
## License
|
|
632
681
|
|
|
633
682
|
MIT
|
package/index.ts
CHANGED
|
@@ -336,7 +336,7 @@ const memoryLanceDBProPlugin = {
|
|
|
336
336
|
const store = new MemoryStore({ dbPath: resolvedDbPath, vectorDim });
|
|
337
337
|
const embedder = createEmbedder({
|
|
338
338
|
provider: "openai-compatible",
|
|
339
|
-
apiKey:
|
|
339
|
+
apiKey: config.embedding.apiKey,
|
|
340
340
|
model: config.embedding.model || "text-embedding-3-small",
|
|
341
341
|
baseURL: config.embedding.baseURL,
|
|
342
342
|
dimensions: config.embedding.dimensions,
|
|
@@ -742,11 +742,14 @@ function parsePluginConfig(value: unknown): PluginConfig {
|
|
|
742
742
|
throw new Error("embedding config is required");
|
|
743
743
|
}
|
|
744
744
|
|
|
745
|
-
|
|
745
|
+
// Accept single key (string) or array of keys for round-robin rotation
|
|
746
|
+
const apiKey: string | string[] = typeof embedding.apiKey === "string"
|
|
746
747
|
? embedding.apiKey
|
|
747
|
-
:
|
|
748
|
+
: Array.isArray(embedding.apiKey) && embedding.apiKey.length > 0 && embedding.apiKey.every((k: unknown) => typeof k === "string")
|
|
749
|
+
? (embedding.apiKey as string[])
|
|
750
|
+
: process.env.OPENAI_API_KEY || "";
|
|
748
751
|
|
|
749
|
-
if (!apiKey) {
|
|
752
|
+
if (!apiKey || (Array.isArray(apiKey) && apiKey.length === 0)) {
|
|
750
753
|
throw new Error("embedding.apiKey is required (set directly or via OPENAI_API_KEY env var)");
|
|
751
754
|
}
|
|
752
755
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "memory-lancedb-pro",
|
|
3
3
|
"name": "Memory (LanceDB Pro)",
|
|
4
4
|
"description": "Enhanced LanceDB-backed long-term memory with hybrid retrieval, multi-scope isolation, long-context chunking, and management CLI",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.23",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"const": "openai-compatible"
|
|
18
18
|
},
|
|
19
19
|
"apiKey": {
|
|
20
|
-
"
|
|
20
|
+
"oneOf": [
|
|
21
|
+
{ "type": "string" },
|
|
22
|
+
{ "type": "array", "items": { "type": "string" }, "minItems": 1 }
|
|
23
|
+
],
|
|
24
|
+
"description": "Single API key or array of keys for round-robin rotation"
|
|
21
25
|
},
|
|
22
26
|
"model": {
|
|
23
27
|
"type": "string"
|
|
@@ -243,10 +247,10 @@
|
|
|
243
247
|
},
|
|
244
248
|
"uiHints": {
|
|
245
249
|
"embedding.apiKey": {
|
|
246
|
-
"label": "API Key",
|
|
250
|
+
"label": "API Key(s)",
|
|
247
251
|
"sensitive": true,
|
|
248
|
-
"placeholder": "sk-proj-... or
|
|
249
|
-
"help": "API key for
|
|
252
|
+
"placeholder": "sk-proj-... or [\"key1\", \"key2\"] for rotation",
|
|
253
|
+
"help": "Single API key or array of keys for round-robin rotation with automatic failover on rate limits (or use ${OPENAI_API_KEY}; use a dummy value for keyless local endpoints)"
|
|
250
254
|
},
|
|
251
255
|
"embedding.model": {
|
|
252
256
|
"label": "Embedding Model",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-lancedb-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.24",
|
|
4
4
|
"description": "OpenClaw enhanced LanceDB memory plugin with hybrid retrieval (Vector + BM25), cross-encoder rerank, multi-scope isolation, long-context chunking, and management CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/src/embedder.ts
CHANGED
|
@@ -85,7 +85,8 @@ class EmbeddingCache {
|
|
|
85
85
|
|
|
86
86
|
export interface EmbeddingConfig {
|
|
87
87
|
provider: "openai-compatible";
|
|
88
|
-
|
|
88
|
+
/** Single API key or array of keys for round-robin rotation with failover. */
|
|
89
|
+
apiKey: string | string[];
|
|
89
90
|
model: string;
|
|
90
91
|
baseURL?: string;
|
|
91
92
|
dimensions?: number;
|
|
@@ -151,7 +152,11 @@ export function getVectorDimensions(model: string, overrideDims?: number): numbe
|
|
|
151
152
|
// ============================================================================
|
|
152
153
|
|
|
153
154
|
export class Embedder {
|
|
154
|
-
|
|
155
|
+
/** Pool of OpenAI clients — one per API key for round-robin rotation. */
|
|
156
|
+
private clients: OpenAI[];
|
|
157
|
+
/** Round-robin index for client rotation. */
|
|
158
|
+
private _clientIndex: number = 0;
|
|
159
|
+
|
|
155
160
|
public readonly dimensions: number;
|
|
156
161
|
private readonly _cache: EmbeddingCache;
|
|
157
162
|
|
|
@@ -166,8 +171,9 @@ export class Embedder {
|
|
|
166
171
|
private readonly _autoChunk: boolean;
|
|
167
172
|
|
|
168
173
|
constructor(config: EmbeddingConfig & { chunking?: boolean }) {
|
|
169
|
-
//
|
|
170
|
-
const
|
|
174
|
+
// Normalize apiKey to array and resolve environment variables
|
|
175
|
+
const apiKeys = Array.isArray(config.apiKey) ? config.apiKey : [config.apiKey];
|
|
176
|
+
const resolvedKeys = apiKeys.map(k => resolveEnvVars(k));
|
|
171
177
|
|
|
172
178
|
this._model = config.model;
|
|
173
179
|
this._taskQuery = config.taskQuery;
|
|
@@ -177,15 +183,74 @@ export class Embedder {
|
|
|
177
183
|
// Enable auto-chunking by default for better handling of long documents
|
|
178
184
|
this._autoChunk = config.chunking !== false;
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
// Create a client pool — one OpenAI client per key
|
|
187
|
+
this.clients = resolvedKeys.map(key => new OpenAI({
|
|
188
|
+
apiKey: key,
|
|
182
189
|
...(config.baseURL ? { baseURL: config.baseURL } : {}),
|
|
183
|
-
});
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
if (this.clients.length > 1) {
|
|
193
|
+
console.log(`[memory-lancedb-pro] Initialized ${this.clients.length} API keys for round-robin rotation`);
|
|
194
|
+
}
|
|
184
195
|
|
|
185
196
|
this.dimensions = getVectorDimensions(config.model, config.dimensions);
|
|
186
197
|
this._cache = new EmbeddingCache(256, 30); // 256 entries, 30 min TTL
|
|
187
198
|
}
|
|
188
199
|
|
|
200
|
+
// --------------------------------------------------------------------------
|
|
201
|
+
// Multi-key rotation helpers
|
|
202
|
+
// --------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
/** Return the next client in round-robin order. */
|
|
205
|
+
private nextClient(): OpenAI {
|
|
206
|
+
const client = this.clients[this._clientIndex % this.clients.length];
|
|
207
|
+
this._clientIndex = (this._clientIndex + 1) % this.clients.length;
|
|
208
|
+
return client;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Check whether an error is a rate-limit / quota-exceeded error. */
|
|
212
|
+
private isRateLimitError(error: unknown): boolean {
|
|
213
|
+
// OpenAI SDK typed error
|
|
214
|
+
if (error && typeof error === "object" && "status" in error && (error as any).status === 429) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
218
|
+
return /rate.limit|quota|too many requests|insufficient.*credit|429/i.test(msg);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Call embeddings.create with automatic key rotation on rate-limit errors.
|
|
223
|
+
* Tries each key in the pool at most once before giving up.
|
|
224
|
+
*/
|
|
225
|
+
private async embedWithRetry(payload: any): Promise<any> {
|
|
226
|
+
const maxAttempts = this.clients.length;
|
|
227
|
+
let lastError: Error | undefined;
|
|
228
|
+
|
|
229
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
230
|
+
const client = this.nextClient();
|
|
231
|
+
try {
|
|
232
|
+
return await client.embeddings.create(payload);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (this.isRateLimitError(error) && attempt < maxAttempts - 1) {
|
|
235
|
+
console.log(
|
|
236
|
+
`[memory-lancedb-pro] API key ${attempt + 1}/${maxAttempts} hit rate limit, rotating to next key...`
|
|
237
|
+
);
|
|
238
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
// Non-rate-limit error or last attempt — let caller handle
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
throw lastError || new Error("All API keys exhausted (rate limited)");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Number of API keys in the rotation pool. */
|
|
250
|
+
get keyCount(): number {
|
|
251
|
+
return this.clients.length;
|
|
252
|
+
}
|
|
253
|
+
|
|
189
254
|
// --------------------------------------------------------------------------
|
|
190
255
|
// Backward-compatible API
|
|
191
256
|
// --------------------------------------------------------------------------
|
|
@@ -271,7 +336,7 @@ export class Embedder {
|
|
|
271
336
|
if (cached) return cached;
|
|
272
337
|
|
|
273
338
|
try {
|
|
274
|
-
const response = await this.
|
|
339
|
+
const response = await this.embedWithRetry(this.buildPayload(text, task));
|
|
275
340
|
const embedding = response.data[0]?.embedding as number[] | undefined;
|
|
276
341
|
if (!embedding) {
|
|
277
342
|
throw new Error("No embedding returned from provider");
|
|
@@ -361,8 +426,8 @@ export class Embedder {
|
|
|
361
426
|
}
|
|
362
427
|
|
|
363
428
|
try {
|
|
364
|
-
const response = await this.
|
|
365
|
-
this.buildPayload(validTexts, task)
|
|
429
|
+
const response = await this.embedWithRetry(
|
|
430
|
+
this.buildPayload(validTexts, task)
|
|
366
431
|
);
|
|
367
432
|
|
|
368
433
|
// Create result array with proper length
|
|
@@ -479,7 +544,10 @@ export class Embedder {
|
|
|
479
544
|
}
|
|
480
545
|
|
|
481
546
|
get cacheStats() {
|
|
482
|
-
return
|
|
547
|
+
return {
|
|
548
|
+
...this._cache.stats,
|
|
549
|
+
keyCount: this.clients.length,
|
|
550
|
+
};
|
|
483
551
|
}
|
|
484
552
|
}
|
|
485
553
|
|
package/src/tools.ts
CHANGED
|
@@ -26,6 +26,12 @@ interface ToolContext {
|
|
|
26
26
|
agentId?: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function resolveAgentId(runtimeAgentId: unknown, fallback?: string): string | undefined {
|
|
30
|
+
if (typeof runtimeAgentId === "string" && runtimeAgentId.trim().length > 0) return runtimeAgentId;
|
|
31
|
+
if (typeof fallback === "string" && fallback.trim().length > 0) return fallback;
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
// ============================================================================
|
|
30
36
|
// Utility Functions
|
|
31
37
|
// ============================================================================
|
|
@@ -58,8 +64,10 @@ function sanitizeMemoryForSerialization(results: RetrievalResult[]) {
|
|
|
58
64
|
|
|
59
65
|
export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolContext) {
|
|
60
66
|
api.registerTool(
|
|
61
|
-
{
|
|
62
|
-
|
|
67
|
+
(toolCtx) => {
|
|
68
|
+
const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
|
|
69
|
+
return {
|
|
70
|
+
name: "memory_recall",
|
|
63
71
|
label: "Memory Recall",
|
|
64
72
|
description: "Search through long-term memories using hybrid retrieval (vector + keyword search). Use when you need context about user preferences, past decisions, or previously discussed topics.",
|
|
65
73
|
parameters: Type.Object({
|
|
@@ -80,9 +88,9 @@ export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
80
88
|
const safeLimit = clampInt(limit, 1, 20);
|
|
81
89
|
|
|
82
90
|
// Determine accessible scopes
|
|
83
|
-
let scopeFilter = context.scopeManager.getAccessibleScopes(
|
|
91
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
|
|
84
92
|
if (scope) {
|
|
85
|
-
if (context.scopeManager.isAccessible(scope,
|
|
93
|
+
if (context.scopeManager.isAccessible(scope, agentId)) {
|
|
86
94
|
scopeFilter = [scope];
|
|
87
95
|
} else {
|
|
88
96
|
return {
|
|
@@ -134,6 +142,7 @@ export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
134
142
|
};
|
|
135
143
|
}
|
|
136
144
|
},
|
|
145
|
+
};
|
|
137
146
|
},
|
|
138
147
|
{ name: "memory_recall" }
|
|
139
148
|
);
|
|
@@ -141,8 +150,10 @@ export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
141
150
|
|
|
142
151
|
export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolContext) {
|
|
143
152
|
api.registerTool(
|
|
144
|
-
{
|
|
145
|
-
|
|
153
|
+
(toolCtx) => {
|
|
154
|
+
const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
|
|
155
|
+
return {
|
|
156
|
+
name: "memory_store",
|
|
146
157
|
label: "Memory Store",
|
|
147
158
|
description: "Save important information in long-term memory. Use for preferences, facts, decisions, and other notable information.",
|
|
148
159
|
parameters: Type.Object({
|
|
@@ -166,10 +177,10 @@ export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolCon
|
|
|
166
177
|
|
|
167
178
|
try {
|
|
168
179
|
// Determine target scope
|
|
169
|
-
let targetScope = scope || context.scopeManager.getDefaultScope(
|
|
180
|
+
let targetScope = scope || context.scopeManager.getDefaultScope(agentId);
|
|
170
181
|
|
|
171
182
|
// Validate scope access
|
|
172
|
-
if (!context.scopeManager.isAccessible(targetScope,
|
|
183
|
+
if (!context.scopeManager.isAccessible(targetScope, agentId)) {
|
|
173
184
|
return {
|
|
174
185
|
content: [{ type: "text", text: `Access denied to scope: ${targetScope}` }],
|
|
175
186
|
details: { error: "scope_access_denied", requestedScope: targetScope },
|
|
@@ -233,6 +244,7 @@ export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolCon
|
|
|
233
244
|
};
|
|
234
245
|
}
|
|
235
246
|
},
|
|
247
|
+
};
|
|
236
248
|
},
|
|
237
249
|
{ name: "memory_store" }
|
|
238
250
|
);
|
|
@@ -240,8 +252,10 @@ export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolCon
|
|
|
240
252
|
|
|
241
253
|
export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolContext) {
|
|
242
254
|
api.registerTool(
|
|
243
|
-
{
|
|
244
|
-
|
|
255
|
+
(toolCtx) => {
|
|
256
|
+
const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
|
|
257
|
+
return {
|
|
258
|
+
name: "memory_forget",
|
|
245
259
|
label: "Memory Forget",
|
|
246
260
|
description: "Delete specific memories. Supports both search-based and direct ID-based deletion.",
|
|
247
261
|
parameters: Type.Object({
|
|
@@ -258,9 +272,9 @@ export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
258
272
|
|
|
259
273
|
try {
|
|
260
274
|
// Determine accessible scopes
|
|
261
|
-
let scopeFilter = context.scopeManager.getAccessibleScopes(
|
|
275
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
|
|
262
276
|
if (scope) {
|
|
263
|
-
if (context.scopeManager.isAccessible(scope,
|
|
277
|
+
if (context.scopeManager.isAccessible(scope, agentId)) {
|
|
264
278
|
scopeFilter = [scope];
|
|
265
279
|
} else {
|
|
266
280
|
return {
|
|
@@ -338,6 +352,7 @@ export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
338
352
|
};
|
|
339
353
|
}
|
|
340
354
|
},
|
|
355
|
+
};
|
|
341
356
|
},
|
|
342
357
|
{ name: "memory_forget" }
|
|
343
358
|
);
|
|
@@ -349,8 +364,10 @@ export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
349
364
|
|
|
350
365
|
export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolContext) {
|
|
351
366
|
api.registerTool(
|
|
352
|
-
{
|
|
353
|
-
|
|
367
|
+
(toolCtx) => {
|
|
368
|
+
const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
|
|
369
|
+
return {
|
|
370
|
+
name: "memory_update",
|
|
354
371
|
label: "Memory Update",
|
|
355
372
|
description: "Update an existing memory in-place. Preserves original timestamp. Use when correcting outdated info or adjusting importance/category without losing creation date.",
|
|
356
373
|
parameters: Type.Object({
|
|
@@ -376,7 +393,7 @@ export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
376
393
|
}
|
|
377
394
|
|
|
378
395
|
// Determine accessible scopes
|
|
379
|
-
const scopeFilter = context.scopeManager.getAccessibleScopes(
|
|
396
|
+
const scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
|
|
380
397
|
|
|
381
398
|
// Resolve memoryId: if it doesn't look like a UUID, try search
|
|
382
399
|
let resolvedId = memoryId;
|
|
@@ -452,6 +469,7 @@ export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
452
469
|
};
|
|
453
470
|
}
|
|
454
471
|
},
|
|
472
|
+
};
|
|
455
473
|
},
|
|
456
474
|
{ name: "memory_update" }
|
|
457
475
|
);
|
|
@@ -463,8 +481,10 @@ export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolCo
|
|
|
463
481
|
|
|
464
482
|
export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolContext) {
|
|
465
483
|
api.registerTool(
|
|
466
|
-
{
|
|
467
|
-
|
|
484
|
+
(toolCtx) => {
|
|
485
|
+
const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
|
|
486
|
+
return {
|
|
487
|
+
name: "memory_stats",
|
|
468
488
|
label: "Memory Statistics",
|
|
469
489
|
description: "Get statistics about memory usage, scopes, and categories.",
|
|
470
490
|
parameters: Type.Object({
|
|
@@ -475,9 +495,9 @@ export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolCon
|
|
|
475
495
|
|
|
476
496
|
try {
|
|
477
497
|
// Determine accessible scopes
|
|
478
|
-
let scopeFilter = context.scopeManager.getAccessibleScopes(
|
|
498
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
|
|
479
499
|
if (scope) {
|
|
480
|
-
if (context.scopeManager.isAccessible(scope,
|
|
500
|
+
if (context.scopeManager.isAccessible(scope, agentId)) {
|
|
481
501
|
scopeFilter = [scope];
|
|
482
502
|
} else {
|
|
483
503
|
return {
|
|
@@ -524,6 +544,7 @@ export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolCon
|
|
|
524
544
|
};
|
|
525
545
|
}
|
|
526
546
|
},
|
|
547
|
+
};
|
|
527
548
|
},
|
|
528
549
|
{ name: "memory_stats" }
|
|
529
550
|
);
|
|
@@ -531,8 +552,10 @@ export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolCon
|
|
|
531
552
|
|
|
532
553
|
export function registerMemoryListTool(api: OpenClawPluginApi, context: ToolContext) {
|
|
533
554
|
api.registerTool(
|
|
534
|
-
{
|
|
535
|
-
|
|
555
|
+
(toolCtx) => {
|
|
556
|
+
const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
|
|
557
|
+
return {
|
|
558
|
+
name: "memory_list",
|
|
536
559
|
label: "Memory List",
|
|
537
560
|
description: "List recent memories with optional filtering by scope and category.",
|
|
538
561
|
parameters: Type.Object({
|
|
@@ -559,9 +582,9 @@ export function registerMemoryListTool(api: OpenClawPluginApi, context: ToolCont
|
|
|
559
582
|
const safeOffset = clampInt(offset, 0, 1000);
|
|
560
583
|
|
|
561
584
|
// Determine accessible scopes
|
|
562
|
-
let scopeFilter = context.scopeManager.getAccessibleScopes(
|
|
585
|
+
let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
|
|
563
586
|
if (scope) {
|
|
564
|
-
if (context.scopeManager.isAccessible(scope,
|
|
587
|
+
if (context.scopeManager.isAccessible(scope, agentId)) {
|
|
565
588
|
scopeFilter = [scope];
|
|
566
589
|
} else {
|
|
567
590
|
return {
|
|
@@ -609,6 +632,7 @@ export function registerMemoryListTool(api: OpenClawPluginApi, context: ToolCont
|
|
|
609
632
|
};
|
|
610
633
|
}
|
|
611
634
|
},
|
|
635
|
+
};
|
|
612
636
|
},
|
|
613
637
|
{ name: "memory_list" }
|
|
614
638
|
);
|