@clawmem-ai/clawmem 0.1.15 → 0.1.17
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 +6 -4
- package/openclaw.plugin.json +11 -11
- package/package.json +12 -2
- package/skills/clawmem/SKILL.md +5 -3
- package/skills/clawmem/references/collaboration.md +43 -1
- package/skills/clawmem/references/schema.md +2 -1
- package/src/config.test.ts +1 -1
- package/src/config.ts +1 -1
- package/src/conversation.test.ts +63 -13
- package/src/conversation.ts +100 -188
- package/src/github-client.test.ts +101 -0
- package/src/github-client.ts +59 -0
- package/src/memory.test.ts +154 -39
- package/src/memory.ts +139 -246
- package/src/runtime-env.ts +12 -0
- package/src/service.test.ts +118 -0
- package/src/service.ts +765 -200
- package/src/state.test.ts +119 -0
- package/src/state.ts +124 -25
- package/src/types.ts +33 -6
- package/src/utils.ts +19 -0
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
**What it does:**
|
|
6
6
|
- Creates one `type:conversation` issue per session, mirrors the full transcript as comments.
|
|
7
|
-
- During request-scoped hooks: best-effort extracts durable memories and stores each as a `type:memory` issue.
|
|
8
7
|
- On session start: searches active memories by relevance and injects them into context.
|
|
8
|
+
- On session reset/end: best-effort writes a final conversation summary/title and stores durable memory candidates as `type:memory` issues.
|
|
9
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.
|
|
10
10
|
|
|
11
11
|
---
|
|
@@ -135,8 +135,8 @@ Full config with all options:
|
|
|
135
135
|
token: "<token>"
|
|
136
136
|
}
|
|
137
137
|
},
|
|
138
|
-
turnCommentDelayMs: 1000,
|
|
139
138
|
summaryWaitTimeoutMs: 120000,
|
|
139
|
+
memoryExtractWaitTimeoutMs: 45000,
|
|
140
140
|
memoryRecallLimit: 5,
|
|
141
141
|
memoryAutoRecallLimit: 3
|
|
142
142
|
}
|
|
@@ -151,11 +151,13 @@ Full config with all options:
|
|
|
151
151
|
## Notes
|
|
152
152
|
|
|
153
153
|
- Conversation comments exclude tool calls, tool results, system messages, and heartbeat noise.
|
|
154
|
-
-
|
|
154
|
+
- Each `agent_end` mirrors conversation comments only; no background subagent-derived memory work runs after turns.
|
|
155
|
+
- Finalization performs one request-scoped summarize-and-capture pass: generate the final issue summary/title plus durable memory candidates, then store exact-deduplicated memories.
|
|
156
|
+
- Summary or memory-capture failures do not block finalization; the conversation issue still closes, and the mirrored transcript remains the durable source of truth for manual follow-up.
|
|
155
157
|
- Memory search and auto-recall only return open `type:memory` issues. Closed memory issues are treated as stale.
|
|
156
158
|
- ClawMem automatically injects a small set of relevant memories before each turn using the agent's default repo and the backend recall API. Auto-recall is best-effort and quietly skips injection when backend recall is unavailable.
|
|
157
159
|
- `memory_recall` uses the backend `/api/v3/search/issues` endpoint scoped to the current repo plus `label:"type:memory"`. When backend recall is unavailable, use `memory_list` or `memory_get` to inspect memories explicitly.
|
|
158
|
-
-
|
|
160
|
+
- Automatic durable capture happens when the session resets or ends. If a fact must be available immediately for later turns, use `memory_store` or `memory_update` explicitly instead of waiting for finalization.
|
|
159
161
|
- 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.
|
|
160
162
|
- Route resolution is now: agent identity supplies credentials, `defaultRepo` is the fallback memory space, and explicit tool calls may override repo per operation.
|
|
161
163
|
- `memory_store` accepts optional schema hints such as kind and topics; the plugin normalizes them into managed `kind:*` and `topic:*` labels.
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "clawmem",
|
|
3
3
|
"name": "ClawMem",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "0.1.17",
|
|
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": [
|
|
8
8
|
"./skills"
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"
|
|
65
|
+
"summaryWaitTimeoutMs": {
|
|
66
66
|
"type": "integer",
|
|
67
|
-
"minimum":
|
|
68
|
-
"maximum":
|
|
67
|
+
"minimum": 1000,
|
|
68
|
+
"maximum": 600000
|
|
69
69
|
},
|
|
70
|
-
"
|
|
70
|
+
"memoryExtractWaitTimeoutMs": {
|
|
71
71
|
"type": "integer",
|
|
72
72
|
"minimum": 1000,
|
|
73
73
|
"maximum": 600000
|
|
@@ -113,13 +113,13 @@
|
|
|
113
113
|
"label": "Agent Routes",
|
|
114
114
|
"help": "Per-agent ClawMem identities keyed by agent id. Each identity has credentials plus an optional defaultRepo."
|
|
115
115
|
},
|
|
116
|
-
"turnCommentDelayMs": {
|
|
117
|
-
"label": "Turn Sync Delay (ms)",
|
|
118
|
-
"help": "Small delay so transcript writes settle before a turn comment is mirrored."
|
|
119
|
-
},
|
|
120
116
|
"summaryWaitTimeoutMs": {
|
|
121
117
|
"label": "Summary Wait Timeout (ms)",
|
|
122
|
-
"help": "How long clawmem waits for the
|
|
118
|
+
"help": "How long clawmem waits for the final conversation summary subagent during session finalization."
|
|
119
|
+
},
|
|
120
|
+
"memoryExtractWaitTimeoutMs": {
|
|
121
|
+
"label": "Finalize Memory Timeout (ms)",
|
|
122
|
+
"help": "How long clawmem waits while extracting durable memory candidates during session finalization."
|
|
123
123
|
},
|
|
124
124
|
"memoryRecallLimit": {
|
|
125
125
|
"label": "Memory Recall Limit",
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawmem-ai/clawmem",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.17",
|
|
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",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"openclaw",
|
|
9
|
+
"clawmem",
|
|
10
|
+
"memory",
|
|
11
|
+
"long-term-memory",
|
|
12
|
+
"auto-recall",
|
|
13
|
+
"conversation-memory",
|
|
14
|
+
"github-compatible",
|
|
15
|
+
"plugin"
|
|
16
|
+
],
|
|
7
17
|
"repository": {
|
|
8
18
|
"type": "git",
|
|
9
19
|
"url": "git+https://github.com/clawmem-ai/clawmem-openclaw-plugin.git"
|
package/skills/clawmem/SKILL.md
CHANGED
|
@@ -26,7 +26,7 @@ The ClawMem plugin automatically handles:
|
|
|
26
26
|
- Per-agent provisioning of credentials plus a default memory repo
|
|
27
27
|
- Session mirroring into `type:conversation` issues
|
|
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
31
|
|
|
32
32
|
## Mandatory turn loop
|
|
@@ -48,12 +48,14 @@ On every user turn, run this loop:
|
|
|
48
48
|
- Never treat a `memory_recall` miss by itself as proof that no relevant memory exists.
|
|
49
49
|
2. After answering, ask: did this turn create durable knowledge?
|
|
50
50
|
- Default to yes for corrections, preferences, decisions, workflows, lessons, and status changes.
|
|
51
|
+
- Automatic capture happens at session finalization and is best-effort. If a fact must be durable immediately, or the next turn will depend on it, write it explicitly with `memory_store` or `memory_update` instead of waiting for session end.
|
|
51
52
|
- Prefer one durable fact per memory. If a turn contains several independent facts, save them separately instead of bundling them into one summary memory.
|
|
52
53
|
- Use `memory_update` when the same canonical fact or ongoing task should keep evolving as one node.
|
|
53
54
|
- When updating an existing memory, preserve that node's current language unless the user explicitly asks for a rewrite.
|
|
54
55
|
- Use `memory_store` when this is a genuinely new memory.
|
|
55
56
|
- When using `memory_store`, pass both `title` and `detail` when you can. Keep the title concise and human-readable, and keep `detail` as the full durable fact.
|
|
56
57
|
- When using `memory_update`, pass `title` as well if the existing title is too short, outdated, or less precise than the current canonical fact.
|
|
58
|
+
- Keep one durable fact per memory. Do not bundle unrelated facts, temporary requests, tool chatter, or startup boilerplate into one saved node.
|
|
57
59
|
- For new memories, write the memory title and body in the user's current language by default.
|
|
58
60
|
- Use `memory_forget` when a memory is stale, superseded, or harmful if reused.
|
|
59
61
|
3. Keep the user posted.
|
|
@@ -61,12 +63,12 @@ On every user turn, run this loop:
|
|
|
61
63
|
- Include the memory id and title only when they help with debugging, traceability, or an explicit user request.
|
|
62
64
|
- After creating or updating a memory, give a short confirmation in the user's current language instead of forcing fixed English phrasing.
|
|
63
65
|
|
|
64
|
-
Bias toward saving, and use explicit retrieval whenever auto-recall is absent, weak, cross-repo, or too ambiguous to trust on its own.
|
|
66
|
+
Bias toward saving, and use explicit retrieval whenever auto-recall is absent, weak, cross-repo, or too ambiguous to trust on its own. Do not assume a just-finished turn has already been captured as durable memory unless you explicitly wrote it or later verified it after the session finalized.
|
|
65
67
|
|
|
66
68
|
## Retrieval and storage rules
|
|
67
69
|
|
|
68
70
|
- Before inventing a new `kind` or `topic`, call `memory_labels` and reuse the existing schema when possible.
|
|
69
|
-
- If
|
|
71
|
+
- If no current label fits, create one new stable machine-readable label within `kind:*` or `topic:*`. Do not create translated variants or near-duplicate synonyms of an existing label.
|
|
70
72
|
- Reuse stable labels over one-off labels.
|
|
71
73
|
- Private personal memory usually belongs in the agent's `defaultRepo`.
|
|
72
74
|
- Project memory belongs in the relevant project repo.
|
|
@@ -24,8 +24,12 @@ Use this reference when the user asks to:
|
|
|
24
24
|
- invite someone into an organization
|
|
25
25
|
- inspect, accept, or decline an invitation sent to the current user
|
|
26
26
|
- create or manage a team
|
|
27
|
+
- inspect or remove organization members or membership state
|
|
28
|
+
- revoke a pending organization invitation from the org side
|
|
27
29
|
- add or remove a repository collaborator
|
|
28
30
|
- grant a team access to a repo
|
|
31
|
+
- rename or delete a team, or inspect team members
|
|
32
|
+
- move an existing memory repo into an organization
|
|
29
33
|
- inspect outside collaborators
|
|
30
34
|
- create a shared team memory repo or org-owned memory space
|
|
31
35
|
- debug why a user can or cannot access a repo
|
|
@@ -45,7 +49,11 @@ Do not use this workflow for ordinary memory recall or save actions unless the u
|
|
|
45
49
|
Tool-first rule:
|
|
46
50
|
- Read-only inspection:
|
|
47
51
|
- `collaboration_orgs`
|
|
52
|
+
- `collaboration_org_members`
|
|
53
|
+
- `collaboration_org_membership`
|
|
48
54
|
- `collaboration_teams`
|
|
55
|
+
- `collaboration_team`
|
|
56
|
+
- `collaboration_team_members`
|
|
49
57
|
- `collaboration_team_repos`
|
|
50
58
|
- `collaboration_repo_collaborators`
|
|
51
59
|
- `collaboration_repo_invitations`
|
|
@@ -56,16 +64,22 @@ Tool-first rule:
|
|
|
56
64
|
- `collaboration_repo_access_inspect`
|
|
57
65
|
- Mutations:
|
|
58
66
|
- `collaboration_org_create`
|
|
67
|
+
- `collaboration_org_member_remove`
|
|
68
|
+
- `collaboration_org_membership_remove`
|
|
59
69
|
- `collaboration_team_create`
|
|
70
|
+
- `collaboration_team_update`
|
|
71
|
+
- `collaboration_team_delete`
|
|
60
72
|
- `collaboration_team_membership_set`
|
|
61
73
|
- `collaboration_team_membership_remove`
|
|
62
74
|
- `collaboration_team_repo_set`
|
|
63
75
|
- `collaboration_team_repo_remove`
|
|
76
|
+
- `collaboration_repo_transfer`
|
|
64
77
|
- `collaboration_repo_collaborator_set`
|
|
65
78
|
- `collaboration_repo_collaborator_remove`
|
|
66
79
|
- `collaboration_user_repo_invitation_accept`
|
|
67
80
|
- `collaboration_user_repo_invitation_decline`
|
|
68
81
|
- `collaboration_org_invitation_create`
|
|
82
|
+
- `collaboration_org_invitation_revoke`
|
|
69
83
|
- `collaboration_user_org_invitation_accept`
|
|
70
84
|
- `collaboration_user_org_invitation_decline`
|
|
71
85
|
|
|
@@ -103,6 +117,7 @@ Reason with these rules before every collaboration action:
|
|
|
103
117
|
- Accepting a repository invitation is what turns a pending share into visible repo access for the invitee.
|
|
104
118
|
- Outside collaborators are non-members who still have direct collaborator access to at least one org-owned repo.
|
|
105
119
|
- Accepting an org invitation creates org membership, joins invited teams as `member`, and removes the pending invitation.
|
|
120
|
+
- Org default repository permission can still grant repo access to active org members even after direct collaborator or team grants are removed.
|
|
106
121
|
- If a user becomes an org member, any outside-collaborator row for that org should disappear.
|
|
107
122
|
- The system-managed `admins` team is an implementation mechanism, not a user-facing product primitive.
|
|
108
123
|
|
|
@@ -114,8 +129,10 @@ Use this decision map:
|
|
|
114
129
|
|---|---|
|
|
115
130
|
| Give one user access to one repo without org membership | Direct collaborator |
|
|
116
131
|
| Bring one user into the org | Org invitation |
|
|
132
|
+
| Inspect whether a user already has org membership or only a pending org invite | Org membership inspection |
|
|
117
133
|
| Grant a group access to selected repos | Team + team-repo grant |
|
|
118
134
|
| Create a shared team memory space | Org-owned repo + team-repo grant |
|
|
135
|
+
| Move an existing memory repo under org governance | Repo transfer into org |
|
|
119
136
|
| Create another memory space under the current agent identity | `memory_repo_create` |
|
|
120
137
|
| Inspect non-members who still have repo access | Outside collaborator listing |
|
|
121
138
|
|
|
@@ -152,6 +169,24 @@ Translate user intent like this:
|
|
|
152
169
|
- `Bring Alice into the org and platform team`
|
|
153
170
|
- inspect teams first with `collaboration_teams`
|
|
154
171
|
- then use `collaboration_org_invitation_create`
|
|
172
|
+
- `Show me who is in this org`
|
|
173
|
+
- use `collaboration_org_members`
|
|
174
|
+
- if you need one person's exact state, use `collaboration_org_membership`
|
|
175
|
+
- `Remove Alice from the org`
|
|
176
|
+
- inspect `collaboration_org_membership` first
|
|
177
|
+
- if Alice is an active org member, use `collaboration_org_member_remove`
|
|
178
|
+
- if you want one command that also handles pending invites, use `collaboration_org_membership_remove`
|
|
179
|
+
- `Revoke the pending org invite for Alice`
|
|
180
|
+
- inspect `collaboration_org_invitations` to get the invitation id
|
|
181
|
+
- then use `collaboration_org_invitation_revoke`
|
|
182
|
+
- `Rename or delete this team`
|
|
183
|
+
- inspect the team with `collaboration_team`
|
|
184
|
+
- use `collaboration_team_update` or `collaboration_team_delete`
|
|
185
|
+
- `Who is in team platform?`
|
|
186
|
+
- use `collaboration_team_members`
|
|
187
|
+
- `Move this repo into org acme so team access can govern it`
|
|
188
|
+
- ensure the target org already exists and the actor has org admin rights
|
|
189
|
+
- then use `collaboration_repo_transfer`
|
|
155
190
|
- `Someone shared a memory repo with me; can you see it and accept it?`
|
|
156
191
|
- start with `collaboration_user_repo_invitations`
|
|
157
192
|
- do not treat a `memory_repos` miss as proof that no share exists
|
|
@@ -161,9 +196,12 @@ Translate user intent like this:
|
|
|
161
196
|
- if needed, have the repo owner inspect `collaboration_repo_invitations`
|
|
162
197
|
- `Why can Bob still see this repo?`
|
|
163
198
|
- start with `collaboration_repo_access_inspect`
|
|
199
|
+
- if you know the username, pass it so the tool can check org membership and org-base access explicitly
|
|
164
200
|
- then drill into `collaboration_repo_collaborators`, `collaboration_repo_invitations`, `collaboration_team_repos`, `collaboration_outside_collaborators`, and `collaboration_org_invitations` as needed
|
|
165
201
|
- `Remove Carol from org-shared memory access`
|
|
166
|
-
- identify whether access comes from a direct collaborator grant, a team repo grant, or a pending invitation
|
|
202
|
+
- identify whether access comes from org default permission, a direct collaborator grant, a team repo grant, or a pending invitation
|
|
203
|
+
- inspect `collaboration_org_membership` when the repo is org-owned
|
|
204
|
+
- if org default permission still applies, remove org membership with `collaboration_org_member_remove` or `collaboration_org_membership_remove`
|
|
167
205
|
- remove the actual source of access rather than guessing
|
|
168
206
|
|
|
169
207
|
## Team memory quality bar
|
|
@@ -206,6 +244,10 @@ After the repo exists:
|
|
|
206
244
|
- use the main memory tools with explicit `repo` targeting for read and write flows
|
|
207
245
|
- reuse [manual-ops.md](manual-ops.md) only if you need raw memory issue control after the repo already exists
|
|
208
246
|
|
|
247
|
+
If the repo already exists under a personal owner and should become org-governed instead of creating a fresh repo:
|
|
248
|
+
- use `collaboration_repo_transfer`
|
|
249
|
+
- then continue with team grants and explicit `repo` targeting against the new org-owned full name
|
|
250
|
+
|
|
209
251
|
## Fallback mode
|
|
210
252
|
|
|
211
253
|
If the collaboration tools are unavailable, use `gh api` against the ClawMem host; fall back to `curl` only when `gh` is unavailable or broken.
|
|
@@ -54,7 +54,8 @@ If you create a curated memory manually, include:
|
|
|
54
54
|
|
|
55
55
|
- Before inventing a new `kind` or `topic`, call `memory_labels`.
|
|
56
56
|
- Reuse current schema when it already fits.
|
|
57
|
-
- If the current schema does not fit and a new label would help future retrieval, coordination, or reuse, create
|
|
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
|
+
- Do not create translated variants or near-duplicate synonyms of an existing label. Prefer reuse first, then one canonical new label if needed.
|
|
58
59
|
- New labels should be short, general, and likely to apply again across future memories or agents.
|
|
59
60
|
- Do not invent random label prefixes. Schema evolution must stay within `kind:*` and `topic:*`.
|
|
60
61
|
|
package/src/config.test.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -47,8 +47,8 @@ export function resolvePluginConfig(api: OpenClawPluginApi): ClawMemPluginConfig
|
|
|
47
47
|
agents,
|
|
48
48
|
memoryRecallLimit: clamp(num(raw.memoryRecallLimit, 5), 1, 20),
|
|
49
49
|
memoryAutoRecallLimit: clamp(num(raw.memoryAutoRecallLimit, 3), 1, 20),
|
|
50
|
-
turnCommentDelayMs: num(raw.turnCommentDelayMs, 1000),
|
|
51
50
|
summaryWaitTimeoutMs: clamp(num(raw.summaryWaitTimeoutMs, 120000), 1000, 600000),
|
|
51
|
+
memoryExtractWaitTimeoutMs: clamp(num(raw.memoryExtractWaitTimeoutMs, 45000), 1000, 600000),
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
package/src/conversation.test.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
// Tests for conversation title derivation logic.
|
|
2
|
-
import { deriveInitialTitle } from "./conversation.js";
|
|
3
|
-
import type { NormalizedMessage } from "./types.js";
|
|
2
|
+
import { ConversationMirror, buildFinalizeArtifactsPrompt, deriveInitialTitle } from "./conversation.js";
|
|
3
|
+
import type { MemorySchema, NormalizedMessage, SessionMirrorState } from "./types.js";
|
|
4
4
|
|
|
5
5
|
function msg(role: string, text: string): NormalizedMessage {
|
|
6
6
|
return { role, text };
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function assert(condition: unknown, message: string): void {
|
|
10
|
+
if (!condition) throw new Error(message);
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
const tests: Array<{ name: string; messages: NormalizedMessage[]; sessionId: string; expected: string }> = [
|
|
10
14
|
{
|
|
11
15
|
name: "returns placeholder regardless of user message content",
|
|
@@ -54,17 +58,63 @@ const tests: Array<{ name: string; messages: NormalizedMessage[]; sessionId: str
|
|
|
54
58
|
let passed = 0;
|
|
55
59
|
let failed = 0;
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
async function testLoadSnapshotPrefersFallbackMessages(): Promise<void> {
|
|
62
|
+
const mirror = new ConversationMirror(
|
|
63
|
+
{} as never,
|
|
64
|
+
{ logger: { warn() {}, info() {} } } as never,
|
|
65
|
+
{} as never,
|
|
66
|
+
);
|
|
67
|
+
const session: SessionMirrorState = {
|
|
68
|
+
sessionId: "sync-session",
|
|
69
|
+
sessionFile: "/tmp/does-not-need-to-exist.jsonl",
|
|
70
|
+
lastMirroredCount: 0,
|
|
71
|
+
turnCount: 0,
|
|
72
|
+
};
|
|
73
|
+
const snapshot = await mirror.loadSnapshot(session, [{ role: "user", text: "Use the in-request transcript." }]);
|
|
74
|
+
assert(snapshot.messages.length === 1, "expected loadSnapshot to return fallback messages");
|
|
75
|
+
assert(snapshot.messages[0]?.text === "Use the in-request transcript.", "expected loadSnapshot to prefer in-request messages over transcript files");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function testBuildFinalizeArtifactsPromptIncludesSchemaReuseRules(): void {
|
|
79
|
+
const schema: MemorySchema = {
|
|
80
|
+
kinds: ["core-fact", "convention"],
|
|
81
|
+
topics: ["redis", "rate-limits"],
|
|
82
|
+
};
|
|
83
|
+
const prompt = buildFinalizeArtifactsPrompt({
|
|
84
|
+
sessionId: "finalize-session",
|
|
85
|
+
messages: [
|
|
86
|
+
msg("user", "请记住我们现在统一用 Redis 做限流。"),
|
|
87
|
+
msg("assistant", "好的,我会按这个约定处理。"),
|
|
88
|
+
],
|
|
89
|
+
}, schema);
|
|
90
|
+
|
|
91
|
+
assert(prompt.includes("Candidate titles and details must be in the same language as the majority of the conversation content."), "expected language guidance for candidate text");
|
|
92
|
+
assert(prompt.includes("Prefer a concise explicit title for each candidate"), "expected explicit title guidance for candidates");
|
|
93
|
+
assert(prompt.includes("Reuse existing schema labels when one already fits."), "expected schema reuse guidance");
|
|
94
|
+
assert(prompt.includes("Only create a new label when none of the current labels matches"), "expected controlled schema creation guidance");
|
|
95
|
+
assert(prompt.includes("- kind:core-fact"), "expected kinds to be embedded in finalize prompt");
|
|
96
|
+
assert(prompt.includes("- topic:redis"), "expected topics to be embedded in finalize prompt");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function main(): Promise<void> {
|
|
100
|
+
for (const t of tests) {
|
|
101
|
+
const got = deriveInitialTitle(t.messages, t.sessionId);
|
|
102
|
+
const ok = got === t.expected;
|
|
103
|
+
if (!ok) {
|
|
104
|
+
console.error(`FAIL: ${t.name}\n got: ${JSON.stringify(got)}\n expected: ${JSON.stringify(t.expected)}`);
|
|
105
|
+
failed++;
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`PASS: ${t.name}`);
|
|
108
|
+
passed++;
|
|
109
|
+
}
|
|
66
110
|
}
|
|
111
|
+
await testLoadSnapshotPrefersFallbackMessages();
|
|
112
|
+
console.log("PASS: loadSnapshot prefers fallback messages");
|
|
113
|
+
testBuildFinalizeArtifactsPromptIncludesSchemaReuseRules();
|
|
114
|
+
console.log("PASS: buildFinalizeArtifactsPrompt includes schema reuse rules");
|
|
115
|
+
|
|
116
|
+
console.log(`\n${passed + 2} passed, ${failed} failed`);
|
|
117
|
+
if (failed > 0) process.exit(1);
|
|
67
118
|
}
|
|
68
119
|
|
|
69
|
-
|
|
70
|
-
if (failed > 0) process.exit(1);
|
|
120
|
+
await main();
|