@clawmem-ai/clawmem 0.1.8 → 0.1.10
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/README.md +25 -23
- package/openclaw.plugin.json +17 -85
- package/package.json +1 -1
- package/src/config.test.ts +82 -0
- package/src/config.ts +26 -8
- package/src/conversation.test.ts +14 -20
- package/src/conversation.ts +180 -21
- package/src/github-client.ts +55 -1
- package/src/memory.test.ts +371 -0
- package/src/memory.ts +368 -47
- package/src/service.ts +517 -34
- package/src/state.ts +16 -0
- package/src/types.ts +22 -5
- package/src/utils.ts +13 -0
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**What it does:**
|
|
6
6
|
- Creates one `type:conversation` issue per session, mirrors the full transcript as comments.
|
|
7
|
-
-
|
|
7
|
+
- During request-scoped hooks: best-effort extracts durable memories and stores each as a `type:memory` issue.
|
|
8
8
|
- On session start: searches active memories by relevance and injects them into context.
|
|
9
|
+
- Lets agents inspect memory indexes and schema, fetch exact memories, update canonical facts in place, and write structured memories with `kind:*` and `topic:*` labels through plugin tools.
|
|
9
10
|
|
|
10
11
|
---
|
|
11
12
|
|
|
@@ -33,10 +34,13 @@ Finally: your `IDENTITY.md` is not something you fill out on day one. It fills i
|
|
|
33
34
|
|
|
34
35
|
```bash
|
|
35
36
|
openclaw plugins install @clawmem-ai/clawmem
|
|
37
|
+
openclaw plugins enable clawmem
|
|
38
|
+
openclaw config set plugins.slots.memory clawmem
|
|
39
|
+
openclaw config validate
|
|
36
40
|
openclaw gateway restart
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
After restart,
|
|
43
|
+
After restart, confirm OpenClaw shows ClawMem as the active memory plugin. On first use, clawmem bootstraps each agent identity by calling `POST /api/v3/agents` on `git.clawmem.ai`, then writes the returned `token` plus `repo_full_name` back into your config under `plugins.entries.clawmem.config.agents.<agentId>` as that agent's `defaultRepo`. Automatic flows use that `defaultRepo`, while explicit memory tool calls may target other repos. When talking to an older backend that does not expose `POST /api/v3/agents`, the plugin falls back to the deprecated anonymous bootstrap path.
|
|
40
44
|
|
|
41
45
|
---
|
|
42
46
|
|
|
@@ -75,7 +79,7 @@ Without it, every session starts from zero. With it, what you learn persists. On
|
|
|
75
79
|
After installing clawmem, you have two memory systems running in parallel:
|
|
76
80
|
|
|
77
81
|
1. **Workspace files** (`MEMORY.md`, `SOUL.md`, etc.) — always injected into every session via system prompt
|
|
78
|
-
2. **clawmem** — auto-extracts memories
|
|
82
|
+
2. **clawmem** — auto-extracts memories during request-scoped maintenance; selectively injects by relevance at session start
|
|
79
83
|
|
|
80
84
|
These two systems do not sync. Without explicit rules, the agent defaults to the familiar path (workspace files), and the two systems start to contradict each other.
|
|
81
85
|
|
|
@@ -113,7 +117,7 @@ clawmem is my brain. One system per job — no exceptions:
|
|
|
113
117
|
| What | Where |
|
|
114
118
|
|---|---|
|
|
115
119
|
| Credentials / API keys | `knowledge/MEMORY.md` (always injected) |
|
|
116
|
-
| Everything I learn, conclude, or figure out | clawmem only — auto-captured
|
|
120
|
+
| Everything I learn, conclude, or figure out | clawmem only — auto-captured during request-scoped maintenance |
|
|
117
121
|
| Files | Only when a tool or another human needs to read them |
|
|
118
122
|
| Workspace versioning | clawmem git service (not github.com) |
|
|
119
123
|
|
|
@@ -144,7 +148,7 @@ Strip everything except credentials. Add a header comment:
|
|
|
144
148
|
|
|
145
149
|
## Locking Key Insights Manually
|
|
146
150
|
|
|
147
|
-
clawmem auto-extracts memories
|
|
151
|
+
clawmem auto-extracts memories during later request-scoped maintenance — but important insights deserve to be locked immediately, not left to chance extraction.
|
|
148
152
|
|
|
149
153
|
After any significant realization, create a memory issue directly:
|
|
150
154
|
|
|
@@ -158,7 +162,7 @@ curl -X POST "https://git.clawmem.ai/api/v3/repos/$CLAWMEM_REPO/issues" \
|
|
|
158
162
|
-d '{
|
|
159
163
|
"title": "Memory: <concise title>",
|
|
160
164
|
"body": "<the insight, in plain language>",
|
|
161
|
-
"labels": ["type:memory"
|
|
165
|
+
"labels": ["type:memory"]
|
|
162
166
|
}'
|
|
163
167
|
```
|
|
164
168
|
|
|
@@ -189,7 +193,7 @@ GH_HOST=git.clawmem.ai GH_TOKEN=$CLAWMEM_TOKEN \
|
|
|
189
193
|
gh issue create --repo <owner/team-memory> \
|
|
190
194
|
--title "Memory: ..." \
|
|
191
195
|
--body "..." \
|
|
192
|
-
--label "type:memory,
|
|
196
|
+
--label "type:memory,source:team"
|
|
193
197
|
```
|
|
194
198
|
|
|
195
199
|
**Read team memories:**
|
|
@@ -197,7 +201,8 @@ GH_HOST=git.clawmem.ai GH_TOKEN=$CLAWMEM_TOKEN \
|
|
|
197
201
|
```bash
|
|
198
202
|
GH_HOST=git.clawmem.ai GH_TOKEN=$CLAWMEM_TOKEN \
|
|
199
203
|
gh issue list --repo <owner/team-memory> \
|
|
200
|
-
--
|
|
204
|
+
--state open \
|
|
205
|
+
--label "type:memory" \
|
|
201
206
|
--json number,title,body
|
|
202
207
|
```
|
|
203
208
|
|
|
@@ -269,25 +274,15 @@ Full config with all options:
|
|
|
269
274
|
agents: {
|
|
270
275
|
main: {
|
|
271
276
|
baseUrl: "https://git.clawmem.ai/api/v3",
|
|
272
|
-
|
|
277
|
+
defaultRepo: "owner/main-memory",
|
|
273
278
|
token: "<token>",
|
|
274
279
|
authScheme: "token"
|
|
275
280
|
},
|
|
276
281
|
coder: {
|
|
277
|
-
|
|
282
|
+
defaultRepo: "owner/coder-memory",
|
|
278
283
|
token: "<token>"
|
|
279
284
|
}
|
|
280
285
|
},
|
|
281
|
-
issueTitlePrefix: "Session: ",
|
|
282
|
-
memoryTitlePrefix: "Memory: ",
|
|
283
|
-
defaultLabels: ["source:openclaw"],
|
|
284
|
-
agentLabelPrefix: "agent:",
|
|
285
|
-
activeStatusLabel: "status:active",
|
|
286
|
-
closedStatusLabel: "status:closed",
|
|
287
|
-
memoryActiveStatusLabel: "memory-status:active",
|
|
288
|
-
memoryStaleStatusLabel: "memory-status:stale",
|
|
289
|
-
autoCreateLabels: true,
|
|
290
|
-
closeIssueOnReset: true,
|
|
291
286
|
turnCommentDelayMs: 1000,
|
|
292
287
|
summaryWaitTimeoutMs: 120000,
|
|
293
288
|
memoryRecallLimit: 5
|
|
@@ -304,6 +299,13 @@ Full config with all options:
|
|
|
304
299
|
|
|
305
300
|
- Conversation comments exclude tool calls, tool results, system messages, and heartbeat noise.
|
|
306
301
|
- Summary failures do not block finalization; the `summary` field is written as `failed: ...`.
|
|
307
|
-
- Memory search and auto-injection only return `memory
|
|
308
|
-
-
|
|
309
|
-
-
|
|
302
|
+
- Memory search and auto-injection only return open `type:memory` issues. Closed memory issues are treated as stale.
|
|
303
|
+
- `memory_recall` now prefers the backend `/api/v3/search/issues` endpoint scoped to the current repo plus `label:"type:memory"`; if backend search fails, clawmem falls back to local lexical ranking.
|
|
304
|
+
- Durable memories are extracted best-effort during later request-scoped maintenance, not by background subagent work after a request has already ended.
|
|
305
|
+
- The plugin exposes `memory_repos`, `memory_repo_create`, `memory_list`, `memory_get`, `memory_labels`, `memory_recall`, `memory_store`, `memory_update`, and `memory_forget` for mid-session use.
|
|
306
|
+
- Route resolution is now: agent identity supplies credentials, `defaultRepo` is the fallback memory space, and explicit tool calls may override repo per operation.
|
|
307
|
+
- `memory_store` accepts optional schema hints such as kind and topics; the plugin normalizes them into managed `kind:*` and `topic:*` labels.
|
|
308
|
+
- Memory issues no longer use `session:*` labels. Session linkage remains a conversation concern, not part of the durable memory schema.
|
|
309
|
+
- `memory_update` updates one existing memory issue in place; use it for evolving canonical facts or active tasks instead of creating a duplicate node.
|
|
310
|
+
- Conversation lifecycle is stored in native issue state (`open` while live, `closed` after finalize); memory lifecycle uses native issue state too (`open` active, `closed` stale).
|
|
311
|
+
- Memory issue bodies store the durable detail plus flat metadata such as `memory_hash` and logical `date`; labels are reserved for schema and routing.
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "clawmem",
|
|
3
3
|
"name": "ClawMem",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.10",
|
|
5
5
|
"description": "Mirror OpenClaw sessions into GitHub-compatible issues and comments.",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
9
|
-
"additionalProperties":
|
|
9
|
+
"additionalProperties": true,
|
|
10
10
|
"properties": {
|
|
11
11
|
"baseUrl": {
|
|
12
12
|
"type": "string",
|
|
13
13
|
"minLength": 1,
|
|
14
14
|
"default": "https://git.clawmem.ai"
|
|
15
15
|
},
|
|
16
|
+
"defaultRepo": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"pattern": "^[^/]+/[^/]+$"
|
|
19
|
+
},
|
|
16
20
|
"repo": {
|
|
17
21
|
"type": "string",
|
|
18
22
|
"pattern": "^[^/]+/[^/]+$"
|
|
@@ -36,6 +40,10 @@
|
|
|
36
40
|
"type": "string",
|
|
37
41
|
"minLength": 1
|
|
38
42
|
},
|
|
43
|
+
"defaultRepo": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"pattern": "^[^/]+/[^/]+$"
|
|
46
|
+
},
|
|
39
47
|
"repo": {
|
|
40
48
|
"type": "string",
|
|
41
49
|
"pattern": "^[^/]+/[^/]+$"
|
|
@@ -51,40 +59,6 @@
|
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
},
|
|
54
|
-
"issueTitlePrefix": {
|
|
55
|
-
"type": "string"
|
|
56
|
-
},
|
|
57
|
-
"memoryTitlePrefix": {
|
|
58
|
-
"type": "string"
|
|
59
|
-
},
|
|
60
|
-
"defaultLabels": {
|
|
61
|
-
"type": "array",
|
|
62
|
-
"items": {
|
|
63
|
-
"type": "string"
|
|
64
|
-
},
|
|
65
|
-
"default": ["source:openclaw"]
|
|
66
|
-
},
|
|
67
|
-
"agentLabelPrefix": {
|
|
68
|
-
"type": "string"
|
|
69
|
-
},
|
|
70
|
-
"activeStatusLabel": {
|
|
71
|
-
"type": "string"
|
|
72
|
-
},
|
|
73
|
-
"closedStatusLabel": {
|
|
74
|
-
"type": "string"
|
|
75
|
-
},
|
|
76
|
-
"memoryActiveStatusLabel": {
|
|
77
|
-
"type": "string"
|
|
78
|
-
},
|
|
79
|
-
"memoryStaleStatusLabel": {
|
|
80
|
-
"type": "string"
|
|
81
|
-
},
|
|
82
|
-
"autoCreateLabels": {
|
|
83
|
-
"type": "boolean"
|
|
84
|
-
},
|
|
85
|
-
"closeIssueOnReset": {
|
|
86
|
-
"type": "boolean"
|
|
87
|
-
},
|
|
88
62
|
"turnCommentDelayMs": {
|
|
89
63
|
"type": "integer",
|
|
90
64
|
"minimum": 0,
|
|
@@ -99,15 +73,6 @@
|
|
|
99
73
|
"type": "integer",
|
|
100
74
|
"minimum": 1,
|
|
101
75
|
"maximum": 20
|
|
102
|
-
},
|
|
103
|
-
"labelColor": {
|
|
104
|
-
"type": "string",
|
|
105
|
-
"pattern": "^#?[0-9a-fA-F]{6}$"
|
|
106
|
-
},
|
|
107
|
-
"maxExcerptChars": {
|
|
108
|
-
"type": "integer",
|
|
109
|
-
"minimum": 120,
|
|
110
|
-
"maximum": 4000
|
|
111
76
|
}
|
|
112
77
|
}
|
|
113
78
|
},
|
|
@@ -117,10 +82,15 @@
|
|
|
117
82
|
"placeholder": "https://git.clawmem.ai",
|
|
118
83
|
"help": "GitHub-compatible API base URL. Root URLs are normalized to /api/v3 automatically."
|
|
119
84
|
},
|
|
85
|
+
"defaultRepo": {
|
|
86
|
+
"label": "Default Repo",
|
|
87
|
+
"placeholder": "owner/repo",
|
|
88
|
+
"help": "Default memory repo for automatic flows and for tool calls that do not specify repo explicitly."
|
|
89
|
+
},
|
|
120
90
|
"repo": {
|
|
121
91
|
"label": "Repository",
|
|
122
92
|
"placeholder": "owner/repo",
|
|
123
|
-
"help": "Legacy
|
|
93
|
+
"help": "Legacy alias for defaultRepo. New installs should use defaultRepo or per-agent defaultRepo under agents.<agentId>."
|
|
124
94
|
},
|
|
125
95
|
"token": {
|
|
126
96
|
"label": "API Token",
|
|
@@ -133,37 +103,7 @@
|
|
|
133
103
|
},
|
|
134
104
|
"agents": {
|
|
135
105
|
"label": "Agent Routes",
|
|
136
|
-
"help": "Per-agent ClawMem
|
|
137
|
-
},
|
|
138
|
-
"defaultLabels": {
|
|
139
|
-
"label": "Default Labels",
|
|
140
|
-
"help": "Labels added to every new session issue. Good place for topic:* tags."
|
|
141
|
-
},
|
|
142
|
-
"memoryTitlePrefix": {
|
|
143
|
-
"label": "Memory Title Prefix",
|
|
144
|
-
"help": "Prefix used when creating memory issue titles."
|
|
145
|
-
},
|
|
146
|
-
"agentLabelPrefix": {
|
|
147
|
-
"label": "Agent Label Prefix",
|
|
148
|
-
"help": "Dynamic agent label prefix, for example agent:coder."
|
|
149
|
-
},
|
|
150
|
-
"activeStatusLabel": {
|
|
151
|
-
"label": "Active Status Label"
|
|
152
|
-
},
|
|
153
|
-
"closedStatusLabel": {
|
|
154
|
-
"label": "Closed Status Label"
|
|
155
|
-
},
|
|
156
|
-
"memoryActiveStatusLabel": {
|
|
157
|
-
"label": "Memory Active Status Label"
|
|
158
|
-
},
|
|
159
|
-
"memoryStaleStatusLabel": {
|
|
160
|
-
"label": "Memory Stale Status Label"
|
|
161
|
-
},
|
|
162
|
-
"autoCreateLabels": {
|
|
163
|
-
"label": "Auto Create Labels"
|
|
164
|
-
},
|
|
165
|
-
"closeIssueOnReset": {
|
|
166
|
-
"label": "Close On Reset"
|
|
106
|
+
"help": "Per-agent ClawMem identities keyed by agent id. Each identity has credentials plus an optional defaultRepo."
|
|
167
107
|
},
|
|
168
108
|
"turnCommentDelayMs": {
|
|
169
109
|
"label": "Turn Sync Delay (ms)",
|
|
@@ -176,14 +116,6 @@
|
|
|
176
116
|
"memoryRecallLimit": {
|
|
177
117
|
"label": "Memory Recall Limit",
|
|
178
118
|
"help": "Maximum number of active memories injected into context before an agent starts."
|
|
179
|
-
},
|
|
180
|
-
"labelColor": {
|
|
181
|
-
"label": "Fallback Label Color",
|
|
182
|
-
"placeholder": "0e8a16"
|
|
183
|
-
},
|
|
184
|
-
"maxExcerptChars": {
|
|
185
|
-
"label": "Max Excerpt Chars",
|
|
186
|
-
"help": "Soft cap used when rendering summaries and comment sections."
|
|
187
119
|
}
|
|
188
120
|
}
|
|
189
121
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { hasDefaultRepo, isAgentConfigured, resolveAgentRoute } from "./config.js";
|
|
2
|
+
import type { ClawMemPluginConfig } from "./types.js";
|
|
3
|
+
import { buildAgentBootstrapRegistration, DEFAULT_BOOTSTRAP_REPO_NAME } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
function assert(condition: unknown, message: string): void {
|
|
6
|
+
if (!condition) throw new Error(message);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function baseConfig(): ClawMemPluginConfig {
|
|
10
|
+
return {
|
|
11
|
+
baseUrl: "https://git.clawmem.ai/api/v3",
|
|
12
|
+
authScheme: "token",
|
|
13
|
+
token: "top-token",
|
|
14
|
+
defaultRepo: "global/default-memory",
|
|
15
|
+
repo: "global/legacy-memory",
|
|
16
|
+
agents: {
|
|
17
|
+
main: {
|
|
18
|
+
token: "agent-token",
|
|
19
|
+
defaultRepo: "main/private-memory",
|
|
20
|
+
},
|
|
21
|
+
legacy: {
|
|
22
|
+
token: "legacy-token",
|
|
23
|
+
repo: "legacy/old-memory",
|
|
24
|
+
},
|
|
25
|
+
identityOnly: {
|
|
26
|
+
token: "identity-token",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
memoryRecallLimit: 5,
|
|
30
|
+
turnCommentDelayMs: 1000,
|
|
31
|
+
summaryWaitTimeoutMs: 120000,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function testDefaultRepoResolution(): void {
|
|
36
|
+
const route = resolveAgentRoute(baseConfig(), "main");
|
|
37
|
+
assert(route.defaultRepo === "main/private-memory", "expected per-agent defaultRepo to be preferred");
|
|
38
|
+
assert(route.repo === "main/private-memory", "expected selected repo to default to defaultRepo");
|
|
39
|
+
assert(route.token === "agent-token", "expected per-agent token to be preferred");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function testRepoOverride(): void {
|
|
43
|
+
const route = resolveAgentRoute(baseConfig(), "main", "org/shared-memory");
|
|
44
|
+
assert(route.defaultRepo === "main/private-memory", "expected defaultRepo to remain unchanged");
|
|
45
|
+
assert(route.repo === "org/shared-memory", "expected explicit repo override to win");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function testLegacyRepoFallback(): void {
|
|
49
|
+
const route = resolveAgentRoute(baseConfig(), "legacy");
|
|
50
|
+
assert(route.defaultRepo === "legacy/old-memory", "expected legacy repo to act as defaultRepo fallback");
|
|
51
|
+
assert(route.repo === "legacy/old-memory", "expected selected repo to use the legacy repo fallback");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function testIdentityOnlyStillConfigured(): void {
|
|
55
|
+
const config = baseConfig();
|
|
56
|
+
delete config.defaultRepo;
|
|
57
|
+
delete config.repo;
|
|
58
|
+
const route = resolveAgentRoute(config, "identityOnly");
|
|
59
|
+
assert(isAgentConfigured(route) === true, "expected an identity with baseUrl and token to count as configured");
|
|
60
|
+
assert(hasDefaultRepo(route) === false, "expected no default repo when only credentials are present");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function testBootstrapRegistrationUsesStableDefaults(): void {
|
|
64
|
+
const registration = buildAgentBootstrapRegistration("Main_Coder");
|
|
65
|
+
assert(registration.prefixLogin === "main-coder", "expected agent bootstrap login prefix to match backend format");
|
|
66
|
+
assert(registration.defaultRepoName === DEFAULT_BOOTSTRAP_REPO_NAME, "expected bootstrap repo name to use the stable default");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function testBootstrapRegistrationTrimsLongPrefixes(): void {
|
|
70
|
+
const registration = buildAgentBootstrapRegistration("___THIS_IS_A_SUPER_LONG_AGENT_ID_THAT_SHOULD_BE_TRIMMED___");
|
|
71
|
+
assert(/^[a-z0-9][a-z0-9-]*$/.test(registration.prefixLogin), "expected bootstrap login prefix to satisfy backend validation");
|
|
72
|
+
assert(registration.prefixLogin.length <= 32, "expected bootstrap login prefix to fit backend max length");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
testDefaultRepoResolution();
|
|
76
|
+
testRepoOverride();
|
|
77
|
+
testLegacyRepoFallback();
|
|
78
|
+
testIdentityOnlyStillConfigured();
|
|
79
|
+
testBootstrapRegistrationUsesStableDefaults();
|
|
80
|
+
testBootstrapRegistrationTrimsLongPrefixes();
|
|
81
|
+
|
|
82
|
+
console.log("config tests passed");
|
package/src/config.ts
CHANGED
|
@@ -5,14 +5,14 @@ import { normalizeAgentId } from "./utils.js";
|
|
|
5
5
|
|
|
6
6
|
export const SESSION_TITLE_PREFIX = "Session: ";
|
|
7
7
|
export const MEMORY_TITLE_PREFIX = "Memory: ";
|
|
8
|
-
export const DEFAULT_LABELS: readonly string[] = [
|
|
8
|
+
export const DEFAULT_LABELS: readonly string[] = [];
|
|
9
9
|
export const AGENT_LABEL_PREFIX = "agent:";
|
|
10
10
|
export const LABEL_ACTIVE = "status:active";
|
|
11
11
|
export const LABEL_CLOSED = "status:closed";
|
|
12
12
|
export const LABEL_MEMORY_ACTIVE = "memory-status:active";
|
|
13
13
|
export const LABEL_MEMORY_STALE = "memory-status:stale";
|
|
14
14
|
|
|
15
|
-
const MANAGED_PREFIXES = ["type:", "session:", "date:", "topic:", "agent:", "source:"];
|
|
15
|
+
const MANAGED_PREFIXES = ["type:", "kind:", "session:", "date:", "topic:", "agent:", "source:"];
|
|
16
16
|
const MANAGED_EXACT = new Set([LABEL_ACTIVE, LABEL_CLOSED, LABEL_MEMORY_ACTIVE, LABEL_MEMORY_STALE]);
|
|
17
17
|
|
|
18
18
|
export function resolvePluginConfig(api: OpenClawPluginApi): ClawMemPluginConfig {
|
|
@@ -31,6 +31,7 @@ export function resolvePluginConfig(api: OpenClawPluginApi): ClawMemPluginConfig
|
|
|
31
31
|
const agent = rawAgentConfig as Record<string, unknown>;
|
|
32
32
|
agents[agentId] = {
|
|
33
33
|
baseUrl: str(agent.baseUrl)?.replace(/\/+$/, ""),
|
|
34
|
+
defaultRepo: normalizeRepoName(str(agent.defaultRepo) ?? str(agent.repo)),
|
|
34
35
|
repo: str(agent.repo),
|
|
35
36
|
token: str(agent.token),
|
|
36
37
|
authScheme: agent.authScheme === "bearer" ? "bearer" : agent.authScheme === "token" ? "token" : undefined,
|
|
@@ -38,6 +39,9 @@ export function resolvePluginConfig(api: OpenClawPluginApi): ClawMemPluginConfig
|
|
|
38
39
|
}
|
|
39
40
|
return {
|
|
40
41
|
baseUrl: baseUrl.endsWith("/api/v3") ? baseUrl : `${baseUrl}/api/v3`,
|
|
42
|
+
defaultRepo: normalizeRepoName(str(raw.defaultRepo) ?? str(raw.repo)),
|
|
43
|
+
repo: normalizeRepoName(str(raw.repo)),
|
|
44
|
+
token: str(raw.token),
|
|
41
45
|
authScheme: raw.authScheme === "bearer" ? "bearer" : "token",
|
|
42
46
|
agents,
|
|
43
47
|
memoryRecallLimit: clamp(num(raw.memoryRecallLimit, 5), 1, 20),
|
|
@@ -46,27 +50,35 @@ export function resolvePluginConfig(api: OpenClawPluginApi): ClawMemPluginConfig
|
|
|
46
50
|
};
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
export function resolveAgentRoute(config: ClawMemPluginConfig, agentId?: string): ClawMemResolvedRoute {
|
|
53
|
+
export function resolveAgentRoute(config: ClawMemPluginConfig, agentId?: string, repoOverride?: string): ClawMemResolvedRoute {
|
|
50
54
|
const id = normalizeAgentId(agentId);
|
|
51
55
|
const agent = config.agents[id] ?? {};
|
|
52
56
|
const baseUrl = (agent.baseUrl ?? config.baseUrl).replace(/\/+$/, "");
|
|
57
|
+
const defaultRepo = normalizeRepoName(agent.defaultRepo ?? agent.repo) ?? config.defaultRepo ?? normalizeRepoName(config.repo);
|
|
58
|
+
const repo = normalizeRepoName(repoOverride) ?? defaultRepo;
|
|
53
59
|
return {
|
|
54
60
|
agentId: id,
|
|
55
61
|
baseUrl: baseUrl.endsWith("/api/v3") ? baseUrl : `${baseUrl}/api/v3`,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
...(defaultRepo ? { defaultRepo } : {}),
|
|
63
|
+
...(repo ? { repo } : {}),
|
|
64
|
+
token: agent.token?.trim() || config.token?.trim() || undefined,
|
|
65
|
+
authScheme: agent.authScheme === "bearer" ? "bearer" : agent.authScheme === "token" ? "token" : config.authScheme,
|
|
59
66
|
};
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
export function isAgentConfigured(route: ClawMemResolvedRoute): boolean {
|
|
63
|
-
return Boolean(route.baseUrl && route.
|
|
70
|
+
return Boolean(route.baseUrl && route.token);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function hasDefaultRepo(route: ClawMemResolvedRoute): boolean {
|
|
74
|
+
return Boolean(route.defaultRepo);
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
export function resolveLabelColor(label: string): string {
|
|
67
78
|
if (label.startsWith("status:")) return "b60205";
|
|
68
79
|
if (label.startsWith("memory-status:")) return label.endsWith(":stale") ? "d93f0b" : "0e8a16";
|
|
69
80
|
if (label.startsWith("type:")) return label === "type:memory" ? "5319e7" : "1d76db";
|
|
81
|
+
if (label.startsWith("kind:")) return "5319e7";
|
|
70
82
|
if (label.startsWith("date:")) return "c5def5";
|
|
71
83
|
if (label.startsWith("topic:")) return "fbca04";
|
|
72
84
|
if (label.startsWith("session:")) return "bfdadc";
|
|
@@ -76,7 +88,7 @@ export function resolveLabelColor(label: string): string {
|
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
export function labelDescription(label: string): string {
|
|
79
|
-
for (const [pfx, d] of [["type:", "Issue type"], ["memory-status:", "Memory lifecycle status"],
|
|
91
|
+
for (const [pfx, d] of [["type:", "Issue type"], ["kind:", "Memory kind"], ["memory-status:", "Memory lifecycle status"],
|
|
80
92
|
["status:", "Conversation lifecycle status"], ["session:", "Session association"],
|
|
81
93
|
["date:", "Date"], ["topic:", "Topic"], ["agent:", "Agent"], ["source:", "Source"]] as const)
|
|
82
94
|
if (label.startsWith(pfx)) return `${d} label managed by clawmem.`;
|
|
@@ -96,3 +108,9 @@ export function labelVal(labels: string[], prefix: string): string | undefined {
|
|
|
96
108
|
const m = labels.find((l) => l.startsWith(prefix));
|
|
97
109
|
return m ? m.slice(prefix.length).trim() || undefined : undefined;
|
|
98
110
|
}
|
|
111
|
+
|
|
112
|
+
function normalizeRepoName(value: string | undefined): string | undefined {
|
|
113
|
+
if (!value) return undefined;
|
|
114
|
+
const trimmed = value.trim().replace(/^\/+|\/+$/g, "");
|
|
115
|
+
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? trimmed : undefined;
|
|
116
|
+
}
|
package/src/conversation.test.ts
CHANGED
|
@@ -6,54 +6,48 @@ function msg(role: string, text: string): NormalizedMessage {
|
|
|
6
6
|
return { role, text };
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const tests: Array<{ name: string; messages: NormalizedMessage[]; sessionId: string; expected: string
|
|
9
|
+
const tests: Array<{ name: string; messages: NormalizedMessage[]; sessionId: string; expected: string }> = [
|
|
10
10
|
{
|
|
11
|
-
name: "
|
|
11
|
+
name: "returns placeholder regardless of user message content",
|
|
12
12
|
messages: [msg("user", "How do I configure Redis rate limiting?")],
|
|
13
13
|
sessionId: "abc123",
|
|
14
|
-
expected: "
|
|
14
|
+
expected: "Session: abc123",
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
-
name: "
|
|
17
|
+
name: "returns placeholder for long messages",
|
|
18
18
|
messages: [msg("user", "I need help with configuring the distributed rate limiting system for our production Redis cluster")],
|
|
19
19
|
sessionId: "abc123",
|
|
20
|
-
expected:
|
|
20
|
+
expected: "Session: abc123",
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
name: "
|
|
23
|
+
name: "returns placeholder for messages with markdown",
|
|
24
24
|
messages: [msg("user", "## How do I **configure** `Redis` rate limiting?")],
|
|
25
25
|
sessionId: "abc123",
|
|
26
|
-
expected: "
|
|
26
|
+
expected: "Session: abc123",
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
name: "
|
|
29
|
+
name: "returns placeholder for short messages",
|
|
30
30
|
messages: [msg("user", "hi")],
|
|
31
31
|
sessionId: "abc-def-123",
|
|
32
32
|
expected: "Session: abc-def-123",
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
name: "
|
|
35
|
+
name: "returns placeholder when no user messages",
|
|
36
36
|
messages: [msg("assistant", "Hello!")],
|
|
37
37
|
sessionId: "xyz-789",
|
|
38
38
|
expected: "Session: xyz-789",
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
name: "
|
|
41
|
+
name: "returns placeholder for empty messages",
|
|
42
42
|
messages: [],
|
|
43
43
|
sessionId: "empty-sess",
|
|
44
44
|
expected: "Session: empty-sess",
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
name: "
|
|
48
|
-
messages: [msg("user", "How do I configure\n\nRedis?")],
|
|
49
|
-
sessionId: "abc",
|
|
50
|
-
expected: "How do I configure Redis?",
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: "skips assistant messages, uses first user message",
|
|
47
|
+
name: "returns placeholder with session ID for any input",
|
|
54
48
|
messages: [msg("assistant", "Welcome!"), msg("user", "Fix the login bug please")],
|
|
55
49
|
sessionId: "abc",
|
|
56
|
-
expected: "
|
|
50
|
+
expected: "Session: abc",
|
|
57
51
|
},
|
|
58
52
|
];
|
|
59
53
|
|
|
@@ -62,9 +56,9 @@ let failed = 0;
|
|
|
62
56
|
|
|
63
57
|
for (const t of tests) {
|
|
64
58
|
const got = deriveInitialTitle(t.messages, t.sessionId);
|
|
65
|
-
const ok =
|
|
59
|
+
const ok = got === t.expected;
|
|
66
60
|
if (!ok) {
|
|
67
|
-
console.error(`FAIL: ${t.name}\n got: ${JSON.stringify(got)}\n expected: ${
|
|
61
|
+
console.error(`FAIL: ${t.name}\n got: ${JSON.stringify(got)}\n expected: ${JSON.stringify(t.expected)}`);
|
|
68
62
|
failed++;
|
|
69
63
|
} else {
|
|
70
64
|
console.log(`PASS: ${t.name}`);
|