@clawmem-ai/clawmem 0.1.18 → 0.1.19
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 +28 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/src/collaboration.d.ts +49 -0
- package/dist/src/collaboration.js +69 -0
- package/dist/src/config.d.ts +21 -0
- package/dist/src/config.js +119 -0
- package/dist/src/conversation.d.ts +30 -0
- package/dist/src/conversation.js +323 -0
- package/dist/src/github-client.d.ts +269 -0
- package/dist/src/github-client.js +350 -0
- package/dist/src/keyed-async-queue.d.ts +12 -0
- package/dist/src/keyed-async-queue.js +23 -0
- package/dist/src/memory.d.ts +29 -0
- package/dist/src/memory.js +451 -0
- package/dist/src/recall-sanitize.d.ts +1 -0
- package/dist/src/recall-sanitize.js +149 -0
- package/dist/src/runtime-env.d.ts +2 -0
- package/dist/src/runtime-env.js +12 -0
- package/dist/src/service.d.ts +18 -0
- package/dist/src/service.js +3645 -0
- package/dist/src/state.d.ts +4 -0
- package/dist/src/state.js +182 -0
- package/dist/src/transcript.d.ts +3 -0
- package/dist/src/transcript.js +164 -0
- package/dist/src/types.d.ts +130 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils.d.ts +26 -0
- package/dist/src/utils.js +62 -0
- package/dist/src/yaml.d.ts +2 -0
- package/dist/src/yaml.js +81 -0
- package/openclaw.plugin.json +14 -1
- package/package.json +21 -7
- package/skills/clawmem/SKILL.md +26 -5
- package/skills/clawmem/references/collaboration.md +13 -5
- package/skills/clawmem/references/review.md +77 -0
- package/skills/clawmem/references/schema.md +44 -1
- package/index.ts +0 -6
- package/src/collaboration.test.ts +0 -71
- package/src/collaboration.ts +0 -109
- package/src/config.test.ts +0 -83
- package/src/config.ts +0 -117
- package/src/conversation.test.ts +0 -120
- package/src/conversation.ts +0 -304
- package/src/github-client.test.ts +0 -101
- package/src/github-client.ts +0 -363
- package/src/keyed-async-queue.ts +0 -26
- package/src/memory.test.ts +0 -588
- package/src/memory.ts +0 -444
- package/src/recall-sanitize.ts +0 -143
- package/src/runtime-env.ts +0 -12
- package/src/service.test.ts +0 -337
- package/src/service.ts +0 -2786
- package/src/state.test.ts +0 -119
- package/src/state.ts +0 -206
- package/src/transcript.ts +0 -186
- package/src/types.ts +0 -86
- package/src/utils.ts +0 -74
- package/src/yaml.ts +0 -88
- package/tsconfig.json +0 -15
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "clawmem",
|
|
3
3
|
"name": "ClawMem",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.19",
|
|
5
5
|
"description": "Repo-backed long-term memory plugin for OpenClaw with auto recall, durable memory capture, and conversation mirroring.",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"skills": [
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"type": "string",
|
|
44
44
|
"minLength": 1
|
|
45
45
|
},
|
|
46
|
+
"login": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"minLength": 1
|
|
49
|
+
},
|
|
46
50
|
"defaultRepo": {
|
|
47
51
|
"type": "string",
|
|
48
52
|
"pattern": "^[^/]+/[^/]+$"
|
|
@@ -81,6 +85,11 @@
|
|
|
81
85
|
"type": "integer",
|
|
82
86
|
"minimum": 1,
|
|
83
87
|
"maximum": 20
|
|
88
|
+
},
|
|
89
|
+
"reviewNudgeInterval": {
|
|
90
|
+
"type": "integer",
|
|
91
|
+
"minimum": 0,
|
|
92
|
+
"maximum": 100
|
|
84
93
|
}
|
|
85
94
|
}
|
|
86
95
|
},
|
|
@@ -128,6 +137,10 @@
|
|
|
128
137
|
"memoryAutoRecallLimit": {
|
|
129
138
|
"label": "Auto Recall Limit",
|
|
130
139
|
"help": "Maximum number of active memories automatically injected before each turn."
|
|
140
|
+
},
|
|
141
|
+
"reviewNudgeInterval": {
|
|
142
|
+
"label": "Review Nudge Interval",
|
|
143
|
+
"help": "How many user turns pass before ClawMem injects a <clawmem-review-nudge> reminder for the agent to run the review protocol. Set to 0 to disable. Default 10."
|
|
131
144
|
}
|
|
132
145
|
}
|
|
133
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawmem-ai/clawmem",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Repo-backed long-term memory plugin for OpenClaw with auto recall, durable memory capture, and conversation mirroring.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,23 +18,37 @@
|
|
|
18
18
|
"type": "git",
|
|
19
19
|
"url": "git+https://github.com/clawmem-ai/clawmem-openclaw-plugin.git"
|
|
20
20
|
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
21
23
|
"files": [
|
|
22
|
-
"
|
|
24
|
+
"dist",
|
|
23
25
|
"openclaw.plugin.json",
|
|
24
|
-
"src",
|
|
25
26
|
"skills",
|
|
26
|
-
"README.md"
|
|
27
|
-
"tsconfig.json"
|
|
27
|
+
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"exports": {
|
|
30
|
-
".":
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
37
|
+
"build": "tsc -p tsconfig.json",
|
|
38
|
+
"prepack": "npm run clean && npm run build",
|
|
39
|
+
"test": "tsx src/collaboration.test.ts && tsx src/config.test.ts && tsx src/conversation.test.ts && tsx src/github-client.test.ts && tsx src/memory.test.ts && tsx src/service.test.ts && tsx src/state.test.ts"
|
|
31
40
|
},
|
|
32
41
|
"publishConfig": {
|
|
33
42
|
"access": "public"
|
|
34
43
|
},
|
|
35
44
|
"openclaw": {
|
|
36
45
|
"extensions": [
|
|
37
|
-
"./index.
|
|
46
|
+
"./dist/index.js"
|
|
38
47
|
]
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^24.12.3",
|
|
51
|
+
"tsx": "^4.21.0",
|
|
52
|
+
"typescript": "^6.0.3"
|
|
39
53
|
}
|
|
40
54
|
}
|
package/skills/clawmem/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: clawmem
|
|
3
|
-
description: Durable memory workflows for the ClawMem OpenClaw plugin. Use when ClawMem is installed and you need to recall prior preferences, project history, facts, decisions, lessons, workflows, active tasks, or shared
|
|
3
|
+
description: Durable memory workflows for the ClawMem OpenClaw plugin. Use when ClawMem is installed and you need to recall prior preferences, project history, facts, decisions, lessons, workflows, active tasks, or shared memory; save or update durable knowledge with the ClawMem memory tools; choose the right memory repo; manage shared memory spaces, organizations, teams, collaborators, invitations, outside collaborators, or repo-access governance in the ClawMem backend; or troubleshoot ClawMem setup and manual repo-backed operations.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ClawMem
|
|
@@ -28,6 +28,7 @@ The ClawMem plugin automatically handles:
|
|
|
28
28
|
- Best-effort automatic memory recall before each turn, scoped to the current agent's `defaultRepo`
|
|
29
29
|
- A best-effort final issue summary/title plus durable memory capture when the session resets or ends normally
|
|
30
30
|
- Mid-session memory tools: `memory_repos`, `memory_repo_create`, `memory_list`, `memory_get`, `memory_labels`, `memory_recall`, `memory_store`, `memory_update`, and `memory_forget`
|
|
31
|
+
- Shared-workflow tools for collaboration routing, default repo retargeting, generic issues, and issue comments
|
|
31
32
|
|
|
32
33
|
## Mandatory turn loop
|
|
33
34
|
|
|
@@ -36,8 +37,8 @@ On every user turn, run this loop:
|
|
|
36
37
|
1. Before answering, ask: could ClawMem improve this answer?
|
|
37
38
|
- Default to yes for user preferences, project history, prior decisions, lessons, conventions, terminology, recurring problems, and active tasks.
|
|
38
39
|
- Auto-recall may already inject useful context from the current agent's `defaultRepo`, but it is only a hint. Do not treat missing auto-recall context as proof that no relevant memory exists.
|
|
39
|
-
-
|
|
40
|
-
- Auto-recall does not currently fan out across every accessible repo. Shared organization memory
|
|
40
|
+
- Each `<clawmem-context>` bullet is `- [id] (kind:<kind>) <title> — <detail>` when those fields are available. Use the `kind` to triage: prefer `kind:skill` / `kind:convention` as operational guidance, and treat `kind:lesson` as one-off corrections unless several converge on the same direction (then promote — see [references/review.md](references/review.md)).
|
|
41
|
+
- Auto-recall does not currently fan out across every accessible repo. Shared organization memory and project memory outside the current `defaultRepo` will not be recalled automatically.
|
|
41
42
|
- Before explicit memory work, choose the right repo. If unclear, inspect `memory_repos` and fall back to the agent's `defaultRepo`. If the likely memory lives outside the default repo, use explicit repo selection instead of relying on auto-recall.
|
|
42
43
|
- Use `memory_recall` when injected context is missing, weak, cross-repo, high-stakes, or when you need an explicit retrieval trace.
|
|
43
44
|
- Write `memory_recall.query` as a short natural-language intent. Do not paste long code blocks, full logs, tool chatter, or system prompt text unless the exact wording is necessary.
|
|
@@ -58,7 +59,25 @@ On every user turn, run this loop:
|
|
|
58
59
|
- Keep one durable fact per memory. Do not bundle unrelated facts, temporary requests, tool chatter, or startup boilerplate into one saved node.
|
|
59
60
|
- For new memories, write the memory title and body in the user's current language by default.
|
|
60
61
|
- Use `memory_forget` when a memory is stale, superseded, or harmful if reused.
|
|
61
|
-
|
|
62
|
+
- Trigger-phrase reflex: when the user's message contains one of the signals below, writing memory is not optional — pick the indicated kind and save, or `memory_update` the canonical node if one already exists.
|
|
63
|
+
|
|
64
|
+
| Signal from the user | Kind to save |
|
|
65
|
+
|---|---|
|
|
66
|
+
| "no", "don't", "stop doing that", "下次不要这样", "别这样", an explicit correction, an apology accepted after a mistake | `kind:lesson` |
|
|
67
|
+
| "yes exactly", "perfect, keep doing that", "这就是我要的", validation of a non-obvious choice you made | `kind:lesson` (what worked and why) or `kind:skill` if it was a multi-step procedure |
|
|
68
|
+
| "always / never", "from now on", "as a rule", naming/style/tool preferences, agreed policies | `kind:convention` |
|
|
69
|
+
| Identity, role, long-term goal, team, stable project fact, unchanging constraint | `kind:core-fact` |
|
|
70
|
+
| A non-trivial procedure that succeeded (several tool calls, trial and error, course changes) or one the user explicitly asks you to remember | `kind:skill` |
|
|
71
|
+
| Ongoing work that will be referenced across turns or sessions | `kind:task` |
|
|
72
|
+
|
|
73
|
+
If two or more `kind:lesson` memories start pointing at the same corrective direction, promote them to one `kind:skill` and close the originals with `superseded-by: #N` in the body — see [references/review.md](references/review.md).
|
|
74
|
+
- "Skill" in this skill always means a ClawMem `kind:skill` memory — an issue written through `memory_store` / `memory_update` using the YAML body template in [references/schema.md § Skill body template](references/schema.md#skill-body-template-kindskill). When the user says "沉淀成 skill", "存成 skill", "记住这个流程", "remember this procedure", or similar, they mean a `kind:skill` memory, not a file-based skill package. Do not invoke the file-based `skill-creator` or write `skills/<name>/SKILL.md` in response to these phrases. Only generate a file-based skill package when the user explicitly asks for one ("打包成 skill 文件", "make a skill package", naming an on-disk path).
|
|
75
|
+
- Before your first `memory_store` with `kind:skill` in a session, read [references/schema.md § Skill body template](references/schema.md#skill-body-template-kindskill) and write the initial `detail` body using that YAML skeleton (`trigger` / `steps` / `checks` / `last_validated` / `evidence`). Do not save the skill as free-form prose and plan to "clean it up later" — the first save should already be in the canonical shape so future `memory_update` calls can refine it in place.
|
|
76
|
+
3. Periodically self-review.
|
|
77
|
+
- Every ~8–10 user turns, after a completed task, or when a `<clawmem-review-nudge>` block appears in context, run the review protocol in [references/review.md](references/review.md) before the next turn completes.
|
|
78
|
+
- The `memory_review` tool returns the latest review checklist. Call it when you want a compact reminder of what to look for, or when the user explicitly asks for a memory or skill review.
|
|
79
|
+
- Review is where `kind:skill` and `kind:lesson` actually accumulate; do not rely on session finalization alone.
|
|
80
|
+
4. Keep the user posted.
|
|
62
81
|
- If a retrieved memory materially shaped the answer, briefly surface that fact in the user's current language.
|
|
63
82
|
- Include the memory id and title only when they help with debugging, traceability, or an explicit user request.
|
|
64
83
|
- After creating or updating a memory, give a short confirmation in the user's current language instead of forcing fixed English phrasing.
|
|
@@ -80,13 +99,15 @@ Bias toward saving, and use explicit retrieval whenever auto-recall is absent, w
|
|
|
80
99
|
- When updating an existing memory, keep that node in its current language unless the user explicitly asks to rewrite it.
|
|
81
100
|
- Keep schema labels and machine-oriented fields stable. Do not translate `type:*`, `kind:*`, `topic:*`, or other structural identifiers.
|
|
82
101
|
- If the user is asking about collaboration, organizations, teams, invitations, collaborators, shared repo access, or why someone can or cannot access a memory repo, switch from normal memory reasoning to the collaboration workflow in `references/collaboration.md`.
|
|
102
|
+
- If the user wants Team design, Team setup, or a Team workflow template, use an external ClawMem Team skill pack such as `clawmem-team-skills` instead of inventing an in-plugin workflow.
|
|
83
103
|
|
|
84
104
|
## Read the right reference
|
|
85
105
|
|
|
86
106
|
- For user-facing runtime messaging, memory console links, and post-save confirmations, read [references/communication.md](references/communication.md).
|
|
87
107
|
- For activation repair, route verification, tool-path verification, and compatibility-file reminders after install, read [references/repair.md](references/repair.md).
|
|
88
108
|
- For shared repos, team memory, organizations, teams, invitations, collaborators, and collaboration routing, read [references/collaboration.md](references/collaboration.md).
|
|
89
|
-
- For memory kinds, labels, curated versus plugin-managed nodes, and when to use each shape, read [references/schema.md](references/schema.md).
|
|
109
|
+
- For memory kinds, labels, curated versus plugin-managed nodes, the `kind:skill` body template, and when to use each shape, read [references/schema.md](references/schema.md).
|
|
110
|
+
- For the periodic self-review protocol (memory + skill tracks, lesson-to-skill promotion, anti-patterns), read [references/review.md](references/review.md).
|
|
90
111
|
- For raw `gh` or `curl` flows, route resolution, troubleshooting, and `git push` to ClawMem, read [references/manual-ops.md](references/manual-ops.md).
|
|
91
112
|
|
|
92
113
|
## Bundled script
|
|
@@ -41,7 +41,7 @@ Do not use this workflow for ordinary memory recall or save actions unless the u
|
|
|
41
41
|
- Prefer the built-in ClawMem collaboration tools first.
|
|
42
42
|
- Inspect current state before mutating anything.
|
|
43
43
|
- Set `confirmed=true` only after the user has approved the exact org, team, repo, invitation, or permission change.
|
|
44
|
-
- Fall back to `gh api` or `curl` only when plugin tools are unavailable
|
|
44
|
+
- Fall back to `gh api` or `curl` only when plugin tools are unavailable or when debugging backend behavior directly.
|
|
45
45
|
- Reuse the main `clawmem` skill's route-resolution helper when raw shell access is required.
|
|
46
46
|
- Think in canonical runtime permissions: `read`, `write`, `admin`.
|
|
47
47
|
- Treat GitHub-compatible aliases such as `pull`, `triage`, `push`, and `maintain` as transport compatibility only.
|
|
@@ -64,6 +64,7 @@ Tool-first rule:
|
|
|
64
64
|
- `collaboration_repo_access_inspect`
|
|
65
65
|
- Mutations:
|
|
66
66
|
- `collaboration_org_create`
|
|
67
|
+
- `collaboration_org_repo_create`
|
|
67
68
|
- `collaboration_org_member_remove`
|
|
68
69
|
- `collaboration_org_membership_remove`
|
|
69
70
|
- `collaboration_team_create`
|
|
@@ -96,7 +97,8 @@ Do not treat `defaultRepo` as the only space. It is only the fallback.
|
|
|
96
97
|
Default tool path:
|
|
97
98
|
- Use `memory_repos` to inspect accessible spaces
|
|
98
99
|
- Use `memory_repo_create` when a new repo should be owned by the current agent identity
|
|
99
|
-
-
|
|
100
|
+
- Use `collaboration_org_repo_create` when the memory space must be governed by an organization team
|
|
101
|
+
- Use `memory_repo_set_default` when a repo move or routing change should update the current agent's automatic default
|
|
100
102
|
- Pass `repo` explicitly to `memory_recall`, `memory_list`, `memory_get`, `memory_store`, `memory_update`, and `memory_forget` when the target is not the current `defaultRepo`
|
|
101
103
|
|
|
102
104
|
This keeps private memory, project memory, and shared memory separate without forcing extra plugin configuration changes.
|
|
@@ -131,7 +133,7 @@ Use this decision map:
|
|
|
131
133
|
| Bring one user into the org | Org invitation |
|
|
132
134
|
| Inspect whether a user already has org membership or only a pending org invite | Org membership inspection |
|
|
133
135
|
| Grant a group access to selected repos | Team + team-repo grant |
|
|
134
|
-
| Create a shared team memory space |
|
|
136
|
+
| Create a shared team memory space | `collaboration_org_repo_create` + team-repo grant |
|
|
135
137
|
| Move an existing memory repo under org governance | Repo transfer into org |
|
|
136
138
|
| Create another memory space under the current agent identity | `memory_repo_create` |
|
|
137
139
|
| Inspect non-members who still have repo access | Outside collaborator listing |
|
|
@@ -160,7 +162,7 @@ Translate user intent like this:
|
|
|
160
162
|
|
|
161
163
|
- `Create a shared memory repo for team X`
|
|
162
164
|
- inspect or create the org and team first
|
|
163
|
-
- if the repo should be team-governed,
|
|
165
|
+
- if the repo should be team-governed, use `collaboration_org_repo_create`; `memory_repo_create` only creates repos under the current agent identity
|
|
164
166
|
- grant the team access with `collaboration_team_repo_set`
|
|
165
167
|
- `Give Alice access to this one memory repo`
|
|
166
168
|
- inspect direct collaborators first with `collaboration_repo_collaborators`
|
|
@@ -187,6 +189,7 @@ Translate user intent like this:
|
|
|
187
189
|
- `Move this repo into org acme so team access can govern it`
|
|
188
190
|
- ensure the target org already exists and the actor has org admin rights
|
|
189
191
|
- then use `collaboration_repo_transfer`
|
|
192
|
+
- if the moved repo was the current agent's `defaultRepo`, expect the plugin to retarget `defaultRepo` automatically; otherwise use `memory_repo_set_default` explicitly when needed
|
|
190
193
|
- `Someone shared a memory repo with me; can you see it and accept it?`
|
|
191
194
|
- start with `collaboration_user_repo_invitations`
|
|
192
195
|
- do not treat a `memory_repos` miss as proof that no share exists
|
|
@@ -218,7 +221,11 @@ If knowledge should stay personal, keep it in the agent's default repo. If it sh
|
|
|
218
221
|
|
|
219
222
|
## Manual org-owned shared repo creation
|
|
220
223
|
|
|
221
|
-
Use the plugin tool path first
|
|
224
|
+
Use the plugin tool path first:
|
|
225
|
+
|
|
226
|
+
- Prefer `collaboration_org_repo_create` for normal org-owned repo creation.
|
|
227
|
+
- Use raw `gh api` or `curl` only when plugin tools are unavailable or when debugging backend behavior directly.
|
|
228
|
+
- `memory_repo_create` still only creates repos under the current agent identity.
|
|
222
229
|
|
|
223
230
|
### With `gh api`
|
|
224
231
|
|
|
@@ -246,6 +253,7 @@ After the repo exists:
|
|
|
246
253
|
|
|
247
254
|
If the repo already exists under a personal owner and should become org-governed instead of creating a fresh repo:
|
|
248
255
|
- use `collaboration_repo_transfer`
|
|
256
|
+
- if that repo was the acting agent's `defaultRepo`, the plugin retargets `defaultRepo` automatically after a successful transfer
|
|
249
257
|
- then continue with team grants and explicit `repo` targeting against the new org-owned full name
|
|
250
258
|
|
|
251
259
|
## Fallback mode
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ClawMem Review Protocol
|
|
2
|
+
|
|
3
|
+
This reference describes the periodic self-review loop that keeps ClawMem memory from drifting. The normal turn loop in [SKILL.md](../SKILL.md) is reactive; review is deliberate. Run it so that lessons, conventions, and skills actually accumulate instead of being silently dropped at session boundaries.
|
|
4
|
+
|
|
5
|
+
## When to run a review
|
|
6
|
+
|
|
7
|
+
Run a review when any of the following is true:
|
|
8
|
+
|
|
9
|
+
- You have completed roughly 8–10 user turns since the last review, or since the start of the session.
|
|
10
|
+
- You just finished a multi-step task, a non-trivial tool chain, or a workflow the user validated.
|
|
11
|
+
- The user signaled satisfaction ("yes exactly", "perfect", "这就是我要的") or issued a correction ("no", "don't", "下次不要这样").
|
|
12
|
+
- The plugin injected a review nudge via `<clawmem-review-nudge>` in the prompt context.
|
|
13
|
+
- You are about to return control to the user at the end of a session, or a `before_reset` / `session_end` is imminent.
|
|
14
|
+
- The agent has the `memory_review` tool available and the user explicitly asks for a memory or skill review.
|
|
15
|
+
|
|
16
|
+
Running once more than needed is fine. Skipping it for a session is not.
|
|
17
|
+
|
|
18
|
+
## Running the review
|
|
19
|
+
|
|
20
|
+
A review has two independent tracks. Run both.
|
|
21
|
+
|
|
22
|
+
### Track A — Memory review
|
|
23
|
+
|
|
24
|
+
Ask yourself, about the conversation since the last review:
|
|
25
|
+
|
|
26
|
+
1. **User signals** — Did the user reveal identity, role, preferences, habits, goals, or constraints I have not yet stored?
|
|
27
|
+
2. **Expectations** — Did the user express how they want me to behave, communicate, format output, choose tools, or avoid specific pitfalls?
|
|
28
|
+
3. **Corrections** — Did the user push back on an answer or an approach? What should I never repeat? What should I do instead next time?
|
|
29
|
+
4. **Validations** — Did the user confirm that a non-obvious choice was the right one? That is also worth saving, because corrections alone make you timid.
|
|
30
|
+
5. **Stale beliefs** — Did this turn invalidate a memory I recalled or would have recalled? Candidates for `memory_forget` or `memory_update`.
|
|
31
|
+
6. **Cross-repo hints** — Did anything belong in a project repo or a shared team repo rather than `defaultRepo`?
|
|
32
|
+
|
|
33
|
+
For each positive answer, pick one of:
|
|
34
|
+
- `memory_update` on an existing canonical node if one already covers the topic.
|
|
35
|
+
- `memory_store` with a deliberate `kind` (and topics) if it is a genuinely new fact.
|
|
36
|
+
- `memory_forget` to retire the stale one.
|
|
37
|
+
|
|
38
|
+
Prefer one atomic fact per write. Do not bundle.
|
|
39
|
+
|
|
40
|
+
### Track B — Skill review
|
|
41
|
+
|
|
42
|
+
Ask yourself:
|
|
43
|
+
|
|
44
|
+
1. Was a non-trivial approach used to finish a task — one that required trial and error, changing course, or recovering from errors?
|
|
45
|
+
2. Did a specific sequence of tool calls or decisions lead to a good result that would have been hard to derive from scratch?
|
|
46
|
+
3. Did the user describe a procedure I should follow in the future?
|
|
47
|
+
4. Is there an existing `kind:skill` memory that this turn either confirmed, refined, or contradicted?
|
|
48
|
+
|
|
49
|
+
If yes on 1–3 and no matching `kind:skill` exists, write a new one using the canonical YAML shape in [schema.md § Skill body template](schema.md#skill-body-template-kindskill).
|
|
50
|
+
|
|
51
|
+
If yes on 4, `memory_update` the existing skill:
|
|
52
|
+
- Bump `last_validated` to today's date.
|
|
53
|
+
- Append the new supporting conversation or memory id to `evidence`.
|
|
54
|
+
- Refine `steps` / `checks` if the turn produced a better formulation.
|
|
55
|
+
- If the turn contradicted the skill, either fix it in place or close the memory and create a replacement that references the old id with `superseded-by`.
|
|
56
|
+
|
|
57
|
+
### Lesson → Skill promotion
|
|
58
|
+
|
|
59
|
+
If you find two or more active `kind:lesson` memories pointing at the same corrective direction on the same topic, promote them:
|
|
60
|
+
|
|
61
|
+
1. Write one `kind:skill` that captures the positive behavior (what to do), not just the prohibitions.
|
|
62
|
+
2. Close the source lessons with a body note like `superseded-by: #<new-skill-id>`.
|
|
63
|
+
3. Leave one lesson open if it captures a specific failure worth remembering on its own.
|
|
64
|
+
|
|
65
|
+
## After the review
|
|
66
|
+
|
|
67
|
+
- Give a short confirmation to the user in their current language, naming what was saved, updated, or retired. Example: "已沉淀 1 条 skill、更新 1 条 convention、归档 1 条过期 lesson。"
|
|
68
|
+
- If nothing was worth writing, say so briefly rather than saying nothing — the user needs to know the review ran.
|
|
69
|
+
- Reset your internal "turns since last review" counter.
|
|
70
|
+
|
|
71
|
+
## Review anti-patterns
|
|
72
|
+
|
|
73
|
+
- Saving a play-by-play of the session. That belongs in the `type:conversation` issue, not in durable memory.
|
|
74
|
+
- Saving every tool invocation as a skill. Skills are for non-trivial, reusable procedures.
|
|
75
|
+
- Creating a new `kind:lesson` every time the user nudges phrasing. Trivial style tweaks usually belong in an existing `kind:convention` via `memory_update`.
|
|
76
|
+
- Rewriting a memory from scratch when a small `memory_update` would do.
|
|
77
|
+
- Running only the memory track and skipping the skill track because "nothing seemed worth a skill". Ask the question; do not skip it.
|
|
@@ -57,7 +57,50 @@ If you create a curated memory manually, include:
|
|
|
57
57
|
- If the current schema does not fit and a new label would help future retrieval, coordination, or reuse, create one deliberate new machine-readable label.
|
|
58
58
|
- Do not create translated variants or near-duplicate synonyms of an existing label. Prefer reuse first, then one canonical new label if needed.
|
|
59
59
|
- New labels should be short, general, and likely to apply again across future memories or agents.
|
|
60
|
-
-
|
|
60
|
+
- For plugin-managed memory schema, do not invent random label prefixes. Memory schema evolution must stay within `kind:*` and `topic:*`.
|
|
61
|
+
|
|
62
|
+
## Update vs new: the node-evolution rule
|
|
63
|
+
|
|
64
|
+
Durable knowledge evolves by updating canonical nodes, not by spawning near-duplicates.
|
|
65
|
+
|
|
66
|
+
- Before `memory_store`, `memory_recall` the same topic. If an open memory already covers the same fact, decision, workflow, or policy, use `memory_update` instead. Only open a new node when the new fact is semantically orthogonal to every existing canonical node.
|
|
67
|
+
- When updating, preserve the node's original language unless the user explicitly asks for a rewrite. Refine the `detail` in place, tighten `title`, and add topic labels as coverage expands.
|
|
68
|
+
- When a memory is contradicted by the current turn, choose one of:
|
|
69
|
+
- `memory_update` to record the new canonical truth on the existing node,
|
|
70
|
+
- `memory_forget` (close the issue) if the fact is simply no longer true and has no replacement,
|
|
71
|
+
- or open a new node and close the old one with a body note `superseded-by: #<new-id>` when the semantics are now different enough that one node cannot carry both.
|
|
72
|
+
- Lesson → Skill promotion: when two or more active `kind:lesson` nodes point at the same corrective direction on the same topic, write one `kind:skill` that captures the positive behavior and close the lessons with `superseded-by: #<new-skill-id>`. Keep a single lesson open only if it captures a specific failure worth remembering on its own.
|
|
73
|
+
- Re-validation: when `memory_recall` surfaces a `kind:skill` or `kind:convention` and the current turn re-confirms or re-applies it, `memory_update` to bump the `last_validated` date in the body (see template below) and append the turn's conversation id to `evidence`. Silent success erodes confidence in old nodes.
|
|
74
|
+
|
|
75
|
+
## Skill body template (`kind:skill`)
|
|
76
|
+
|
|
77
|
+
In ClawMem, a "skill" is a `kind:skill` memory — an issue written through `memory_store` / `memory_update`. It is not a file-based skill package (`skills/<name>/SKILL.md`), and the ClawMem turn loop never triggers a file-based skill-creator. When the user says "沉淀成 skill", "存成 skill", or "remember this procedure", write the memory here using the template below. Only produce an on-disk skill package when the user explicitly asks for one.
|
|
78
|
+
|
|
79
|
+
`kind:skill` memories are playbooks and are meant to be re-used and re-updated many times. Give them a stable YAML-on-top body so they remain readable and mergeable. Read this section before your first `memory_store` with `kind:skill` and shape the initial `detail` body using this skeleton — don't save as prose and plan to refactor later.
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
trigger: When this skill applies — the user request shape or situation that should cue it.
|
|
83
|
+
steps:
|
|
84
|
+
- First action, concrete enough to follow without re-deriving.
|
|
85
|
+
- Next action.
|
|
86
|
+
- Final action.
|
|
87
|
+
checks:
|
|
88
|
+
- Signals that the skill succeeded.
|
|
89
|
+
- Signals that the skill is the wrong fit and you should stop.
|
|
90
|
+
last_validated: 2026-04-20
|
|
91
|
+
evidence:
|
|
92
|
+
- "#42" # conversation issue or memory id that supports this skill
|
|
93
|
+
- "#77"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Narrative prose, caveats, and references can follow the YAML block in the same body. The YAML block itself stays flat (no nested maps beyond lists), which matches ClawMem's body parser.
|
|
97
|
+
|
|
98
|
+
When a `kind:skill` is re-used successfully, `memory_update` to:
|
|
99
|
+
- bump `last_validated` to today's date,
|
|
100
|
+
- append the latest supporting id to `evidence`,
|
|
101
|
+
- refine `steps` or `checks` only if the turn produced a clearly better formulation.
|
|
102
|
+
|
|
103
|
+
When a `kind:skill` fails in use, either fix `steps` / `checks` in place or close the node and open a replacement that references the old id with `superseded-by`.
|
|
61
104
|
|
|
62
105
|
## Storage language
|
|
63
106
|
|
package/index.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
filterDirectCollaborators,
|
|
3
|
-
listRepoAccessTeams,
|
|
4
|
-
repoSummaryFullName,
|
|
5
|
-
resolveOrgInvitationRole,
|
|
6
|
-
} from "./collaboration.js";
|
|
7
|
-
|
|
8
|
-
function assert(condition: unknown, message: string): void {
|
|
9
|
-
if (!condition) throw new Error(message);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function testOrgInvitationRoleValidation(): void {
|
|
13
|
-
const fallback = resolveOrgInvitationRole(undefined, "member");
|
|
14
|
-
assert("role" in fallback && fallback.role === "member", "expected undefined role to fall back to member");
|
|
15
|
-
|
|
16
|
-
const owner = resolveOrgInvitationRole("owner", "member");
|
|
17
|
-
assert("role" in owner && owner.role === "owner", "expected owner role to pass through");
|
|
18
|
-
|
|
19
|
-
const invalid = resolveOrgInvitationRole("admin", "member");
|
|
20
|
-
assert("error" in invalid, "expected admin to be rejected because backend expects owner");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function testDirectCollaboratorFiltering(): void {
|
|
24
|
-
const collaborators = filterDirectCollaborators([
|
|
25
|
-
{ login: "Acme" },
|
|
26
|
-
{ login: "alice", outside_collaborator: true },
|
|
27
|
-
{ login: "bob", organization_member: true },
|
|
28
|
-
], "acme");
|
|
29
|
-
|
|
30
|
-
assert(collaborators.length === 2, "expected owner row to be excluded from explicit collaborators");
|
|
31
|
-
assert(collaborators[0]?.login === "alice", "expected alice to remain after owner filtering");
|
|
32
|
-
assert(collaborators[1]?.login === "bob", "expected bob to remain after owner filtering");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function testRepoSummaryFallback(): void {
|
|
36
|
-
assert(repoSummaryFullName({ full_name: "acme/project" }) === "acme/project", "expected explicit full_name to win");
|
|
37
|
-
assert(repoSummaryFullName({ owner: { login: "acme" }, name: "project" }) === "acme/project", "expected owner/name fallback to work");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function testRepoAccessTeamsDerivation(): Promise<void> {
|
|
41
|
-
const result = await listRepoAccessTeams({
|
|
42
|
-
async listOrgTeams() {
|
|
43
|
-
return [
|
|
44
|
-
{ slug: "admins", name: "admins" },
|
|
45
|
-
{ slug: "writers", name: "writers" },
|
|
46
|
-
{ slug: "broken", name: "broken" },
|
|
47
|
-
];
|
|
48
|
-
},
|
|
49
|
-
async listTeamRepos(_org: string, teamSlug: string) {
|
|
50
|
-
if (teamSlug === "admins") return [{ full_name: "acme/project", role_name: "admin" }];
|
|
51
|
-
if (teamSlug === "writers") return [{ owner: { login: "acme" }, name: "project", role_name: "write" }];
|
|
52
|
-
if (teamSlug === "broken") throw new Error("boom");
|
|
53
|
-
return [];
|
|
54
|
-
},
|
|
55
|
-
}, "acme", "acme/project");
|
|
56
|
-
|
|
57
|
-
assert(result.teams.length === 2, "expected two teams with access to be discovered via org->team->repo traversal");
|
|
58
|
-
assert(result.teams[0]?.slug === "admins", "expected admins team to be included");
|
|
59
|
-
assert(result.teams[1]?.slug === "writers", "expected writers team to be included");
|
|
60
|
-
assert(result.notes.length === 1 && result.notes[0]?.includes("broken"), "expected per-team lookup failures to be recorded as notes");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function main(): Promise<void> {
|
|
64
|
-
testOrgInvitationRoleValidation();
|
|
65
|
-
testDirectCollaboratorFiltering();
|
|
66
|
-
testRepoSummaryFallback();
|
|
67
|
-
await testRepoAccessTeamsDerivation();
|
|
68
|
-
console.log("collaboration tests passed");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
await main();
|
package/src/collaboration.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
export type CollaborationPermission = "read" | "write" | "admin";
|
|
2
|
-
export type CollaborationOrgInvitationRole = "member" | "owner";
|
|
3
|
-
|
|
4
|
-
type PermissionMap = Record<string, boolean | undefined>;
|
|
5
|
-
|
|
6
|
-
export type CollaborationRepoSummary = {
|
|
7
|
-
full_name?: string;
|
|
8
|
-
owner?: { login?: string };
|
|
9
|
-
name?: string;
|
|
10
|
-
permissions?: PermissionMap;
|
|
11
|
-
role_name?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type CollaborationTeamSummary = {
|
|
15
|
-
id?: number;
|
|
16
|
-
slug?: string;
|
|
17
|
-
name?: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
privacy?: string;
|
|
20
|
-
permission?: string;
|
|
21
|
-
role_name?: string;
|
|
22
|
-
permissions?: PermissionMap;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type CollaborationCollaboratorSummary = {
|
|
26
|
-
id?: number;
|
|
27
|
-
login?: string;
|
|
28
|
-
name?: string;
|
|
29
|
-
permissions?: PermissionMap;
|
|
30
|
-
role_name?: string;
|
|
31
|
-
organization_member?: boolean;
|
|
32
|
-
outside_collaborator?: boolean;
|
|
33
|
-
type?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type RepoAccessTeamClient = {
|
|
37
|
-
listOrgTeams(org: string): Promise<CollaborationTeamSummary[]>;
|
|
38
|
-
listTeamRepos(org: string, teamSlug: string): Promise<CollaborationRepoSummary[]>;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export function normalizePermissionAlias(value: unknown): "none" | CollaborationPermission | undefined {
|
|
42
|
-
if (typeof value !== "string") return undefined;
|
|
43
|
-
const normalized = value.trim().toLowerCase();
|
|
44
|
-
if (!normalized) return undefined;
|
|
45
|
-
if (normalized === "none") return "none";
|
|
46
|
-
if (normalized === "read" || normalized === "pull" || normalized === "triage") return "read";
|
|
47
|
-
if (normalized === "write" || normalized === "push" || normalized === "maintain") return "write";
|
|
48
|
-
if (normalized === "admin") return "admin";
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function resolveOrgInvitationRole(
|
|
53
|
-
value: unknown,
|
|
54
|
-
fallback: CollaborationOrgInvitationRole,
|
|
55
|
-
): { role: CollaborationOrgInvitationRole } | { error: string } {
|
|
56
|
-
if (value === undefined || value === null || value === "") return { role: fallback };
|
|
57
|
-
if (typeof value !== "string") return { error: "role must be member or owner." };
|
|
58
|
-
const normalized = value.trim().toLowerCase();
|
|
59
|
-
if (normalized === "member" || normalized === "owner") return { role: normalized };
|
|
60
|
-
return { error: `Unsupported role "${value}". Use member or owner.` };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function repoSummaryFullName(repo?: CollaborationRepoSummary): string | undefined {
|
|
64
|
-
const fullName = repo?.full_name?.trim();
|
|
65
|
-
if (fullName) return fullName;
|
|
66
|
-
const owner = repo?.owner?.login?.trim();
|
|
67
|
-
const name = repo?.name?.trim();
|
|
68
|
-
if (owner && name) return `${owner}/${name}`;
|
|
69
|
-
return name || undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function filterDirectCollaborators(
|
|
73
|
-
collaborators: CollaborationCollaboratorSummary[],
|
|
74
|
-
ownerLogin: string,
|
|
75
|
-
): CollaborationCollaboratorSummary[] {
|
|
76
|
-
const owner = ownerLogin.trim().toLowerCase();
|
|
77
|
-
if (!owner) return collaborators;
|
|
78
|
-
return collaborators.filter((collaborator) => (collaborator.login?.trim().toLowerCase() || "") !== owner);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function listRepoAccessTeams(
|
|
82
|
-
client: RepoAccessTeamClient,
|
|
83
|
-
org: string,
|
|
84
|
-
fullName: string,
|
|
85
|
-
): Promise<{ teams: CollaborationTeamSummary[]; notes: string[] }> {
|
|
86
|
-
const notes: string[] = [];
|
|
87
|
-
const teams = await client.listOrgTeams(org);
|
|
88
|
-
const withAccess: CollaborationTeamSummary[] = [];
|
|
89
|
-
for (const team of teams) {
|
|
90
|
-
const teamSlug = team.slug?.trim() || team.name?.trim();
|
|
91
|
-
if (!teamSlug) {
|
|
92
|
-
notes.push(`Skipped a team in org "${org}" because it had no slug or name.`);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
const repos = await client.listTeamRepos(org, teamSlug);
|
|
97
|
-
const matchingRepo = repos.find((repo) => repoSummaryFullName(repo) === fullName);
|
|
98
|
-
if (!matchingRepo) continue;
|
|
99
|
-
withAccess.push({
|
|
100
|
-
...team,
|
|
101
|
-
...(matchingRepo.permissions ? { permissions: matchingRepo.permissions } : {}),
|
|
102
|
-
...(matchingRepo.role_name ? { role_name: matchingRepo.role_name } : {}),
|
|
103
|
-
});
|
|
104
|
-
} catch (error) {
|
|
105
|
-
notes.push(`Team repo lookup failed for ${org}/${teamSlug}: ${String(error)}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return { teams: withAccess, notes };
|
|
109
|
-
}
|
package/src/config.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
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
|
-
memoryAutoRecallLimit: 3,
|
|
31
|
-
summaryWaitTimeoutMs: 120000,
|
|
32
|
-
memoryExtractWaitTimeoutMs: 45000,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function testDefaultRepoResolution(): void {
|
|
37
|
-
const route = resolveAgentRoute(baseConfig(), "main");
|
|
38
|
-
assert(route.defaultRepo === "main/private-memory", "expected per-agent defaultRepo to be preferred");
|
|
39
|
-
assert(route.repo === "main/private-memory", "expected selected repo to default to defaultRepo");
|
|
40
|
-
assert(route.token === "agent-token", "expected per-agent token to be preferred");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function testRepoOverride(): void {
|
|
44
|
-
const route = resolveAgentRoute(baseConfig(), "main", "org/shared-memory");
|
|
45
|
-
assert(route.defaultRepo === "main/private-memory", "expected defaultRepo to remain unchanged");
|
|
46
|
-
assert(route.repo === "org/shared-memory", "expected explicit repo override to win");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function testLegacyRepoFallback(): void {
|
|
50
|
-
const route = resolveAgentRoute(baseConfig(), "legacy");
|
|
51
|
-
assert(route.defaultRepo === "legacy/old-memory", "expected legacy repo to act as defaultRepo fallback");
|
|
52
|
-
assert(route.repo === "legacy/old-memory", "expected selected repo to use the legacy repo fallback");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function testIdentityOnlyStillConfigured(): void {
|
|
56
|
-
const config = baseConfig();
|
|
57
|
-
delete config.defaultRepo;
|
|
58
|
-
delete config.repo;
|
|
59
|
-
const route = resolveAgentRoute(config, "identityOnly");
|
|
60
|
-
assert(isAgentConfigured(route) === true, "expected an identity with baseUrl and token to count as configured");
|
|
61
|
-
assert(hasDefaultRepo(route) === false, "expected no default repo when only credentials are present");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function testBootstrapRegistrationUsesStableDefaults(): void {
|
|
65
|
-
const registration = buildAgentBootstrapRegistration("Main_Coder");
|
|
66
|
-
assert(registration.prefixLogin === "main-coder", "expected agent bootstrap login prefix to match backend format");
|
|
67
|
-
assert(registration.defaultRepoName === DEFAULT_BOOTSTRAP_REPO_NAME, "expected bootstrap repo name to use the stable default");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function testBootstrapRegistrationTrimsLongPrefixes(): void {
|
|
71
|
-
const registration = buildAgentBootstrapRegistration("___THIS_IS_A_SUPER_LONG_AGENT_ID_THAT_SHOULD_BE_TRIMMED___");
|
|
72
|
-
assert(/^[a-z0-9][a-z0-9-]*$/.test(registration.prefixLogin), "expected bootstrap login prefix to satisfy backend validation");
|
|
73
|
-
assert(registration.prefixLogin.length <= 32, "expected bootstrap login prefix to fit backend max length");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
testDefaultRepoResolution();
|
|
77
|
-
testRepoOverride();
|
|
78
|
-
testLegacyRepoFallback();
|
|
79
|
-
testIdentityOnlyStillConfigured();
|
|
80
|
-
testBootstrapRegistrationUsesStableDefaults();
|
|
81
|
-
testBootstrapRegistrationTrimsLongPrefixes();
|
|
82
|
-
|
|
83
|
-
console.log("config tests passed");
|