@persistio/openclaw-plugin 0.1.0 → 0.1.2
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/dist/client.d.ts +16 -0
- package/dist/client.js +12 -0
- package/dist/index.js +109 -19
- package/openclaw.plugin.json +27 -9
- package/package.json +2 -2
- package/src/client.ts +29 -1
- package/src/index.ts +120 -18
package/dist/client.d.ts
CHANGED
|
@@ -13,6 +13,20 @@ export interface PersistioMemory {
|
|
|
13
13
|
categories: string[];
|
|
14
14
|
confidence: number;
|
|
15
15
|
}
|
|
16
|
+
export interface RecallBundle {
|
|
17
|
+
user_rules: string[];
|
|
18
|
+
user_preferences: string[];
|
|
19
|
+
task_patterns: string[];
|
|
20
|
+
workflows: string[];
|
|
21
|
+
project: string[];
|
|
22
|
+
constraints: string[];
|
|
23
|
+
decisions: string[];
|
|
24
|
+
system_facts: string[];
|
|
25
|
+
domain_knowledge: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface RecallBundleResponse {
|
|
28
|
+
bundle: RecallBundle;
|
|
29
|
+
}
|
|
16
30
|
export declare class PersistioClient {
|
|
17
31
|
private readonly baseURL;
|
|
18
32
|
private readonly apiKey;
|
|
@@ -21,9 +35,11 @@ export declare class PersistioClient {
|
|
|
21
35
|
constructor(config: PersistioConfig);
|
|
22
36
|
private headers;
|
|
23
37
|
recall(query: string): Promise<PersistioMemory[]>;
|
|
38
|
+
recallBundle(query: string, topK?: number): Promise<RecallBundle>;
|
|
24
39
|
ingest(sessionId: string, chunks: Array<{
|
|
25
40
|
role: string;
|
|
26
41
|
content: string;
|
|
42
|
+
timestamp: string;
|
|
27
43
|
}>): Promise<void>;
|
|
28
44
|
addMemory(data: string, subject: string): Promise<void>;
|
|
29
45
|
deleteMemory(id: string): Promise<void>;
|
package/dist/client.js
CHANGED
|
@@ -27,6 +27,18 @@ export class PersistioClient {
|
|
|
27
27
|
const data = await res.json();
|
|
28
28
|
return data.memories ?? [];
|
|
29
29
|
}
|
|
30
|
+
async recallBundle(query, topK) {
|
|
31
|
+
const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: this.headers(),
|
|
34
|
+
body: JSON.stringify({ query, top_k: topK ?? this.recallTopK }),
|
|
35
|
+
signal: AbortSignal.timeout(this.recallTimeout),
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok)
|
|
38
|
+
throw new Error(`Persistio recallBundle failed: ${res.status}`);
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
return data.bundle;
|
|
41
|
+
}
|
|
30
42
|
async ingest(sessionId, chunks) {
|
|
31
43
|
if (chunks.length === 0)
|
|
32
44
|
return;
|
package/dist/index.js
CHANGED
|
@@ -14,18 +14,106 @@ function resolveConfig(raw) {
|
|
|
14
14
|
function estimateTokens(text) {
|
|
15
15
|
return Math.ceil(text.length / 4);
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
if (
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
function truncate(text, maxLength) {
|
|
18
|
+
if (text.length <= maxLength)
|
|
19
|
+
return text;
|
|
20
|
+
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
21
|
+
}
|
|
22
|
+
function detectTaskType(text) {
|
|
23
|
+
const normalized = text.toLowerCase();
|
|
24
|
+
if (/(error|bug|fail|failing|issue|broken|debug|debugging|trace|stack)/.test(normalized)) {
|
|
25
|
+
return 'troubleshooting';
|
|
26
|
+
}
|
|
27
|
+
if (/(code|coding|typescript|javascript|python|implement|refactor|function|class|api|build|test)/.test(normalized)) {
|
|
28
|
+
return 'coding';
|
|
29
|
+
}
|
|
30
|
+
if (/(plan|planning|roadmap|strategy|steps|milestone|timeline|organize)/.test(normalized)) {
|
|
31
|
+
return 'planning';
|
|
32
|
+
}
|
|
33
|
+
if (/(write|writing|draft|edit|copy|blog|essay|summary|summarize|document)/.test(normalized)) {
|
|
34
|
+
return 'writing';
|
|
35
|
+
}
|
|
36
|
+
return 'general';
|
|
37
|
+
}
|
|
38
|
+
function buildRecallQuery(event) {
|
|
39
|
+
const relevantMessages = Array.isArray(event.messages)
|
|
40
|
+
? event.messages
|
|
41
|
+
.map((msg) => {
|
|
42
|
+
if (typeof msg !== 'object' || msg === null)
|
|
43
|
+
return null;
|
|
44
|
+
const m = msg;
|
|
45
|
+
const role = m['role'];
|
|
46
|
+
if (role !== 'user' && role !== 'assistant')
|
|
47
|
+
return null;
|
|
48
|
+
const text = extractTextFromMessage(msg);
|
|
49
|
+
if (!text)
|
|
50
|
+
return null;
|
|
51
|
+
return { role, text: text.replace(/\s+/g, ' ').trim() };
|
|
52
|
+
})
|
|
53
|
+
.filter((msg) => msg !== null && msg.text.length > 0)
|
|
54
|
+
: [];
|
|
55
|
+
const lastUserIndex = (() => {
|
|
56
|
+
for (let i = relevantMessages.length - 1; i >= 0; i -= 1) {
|
|
57
|
+
if (relevantMessages[i].role === 'user')
|
|
58
|
+
return i;
|
|
59
|
+
}
|
|
60
|
+
return -1;
|
|
61
|
+
})();
|
|
62
|
+
const lastUserMessage = lastUserIndex >= 0
|
|
63
|
+
? relevantMessages[lastUserIndex].text
|
|
64
|
+
: event.prompt?.replace(/\s+/g, ' ').trim() || 'recent context';
|
|
65
|
+
const primary = truncate(lastUserMessage, 300);
|
|
66
|
+
const contextStart = Math.max(0, lastUserIndex - 6);
|
|
67
|
+
const contextMessages = lastUserIndex >= 0
|
|
68
|
+
? relevantMessages.slice(contextStart, lastUserIndex)
|
|
69
|
+
: relevantMessages.slice(-6);
|
|
70
|
+
const contextSummary = truncate(contextMessages
|
|
71
|
+
.map((msg) => `${msg.role === 'user' ? 'U' : 'A'}:${msg.text}`)
|
|
72
|
+
.join(' | '), 200);
|
|
73
|
+
const taskType = detectTaskType(`${primary} ${event.prompt ?? ''}`);
|
|
74
|
+
const parts = [primary];
|
|
75
|
+
if (contextSummary.length > 0)
|
|
76
|
+
parts.push(`Context: ${contextSummary}`);
|
|
77
|
+
parts.push(`[task: ${taskType}]`);
|
|
78
|
+
return truncate(parts.join('\n'), 600);
|
|
79
|
+
}
|
|
80
|
+
function buildMemoryBlock(bundle, budget) {
|
|
81
|
+
const sections = [
|
|
82
|
+
{ title: 'Behavioural rules', items: bundle.user_rules },
|
|
83
|
+
{ title: 'Preferences', items: bundle.user_preferences },
|
|
84
|
+
{ title: 'Task patterns', items: bundle.task_patterns },
|
|
85
|
+
{ title: 'Workflows', items: bundle.workflows },
|
|
86
|
+
{ title: 'Project', items: bundle.project },
|
|
87
|
+
{ title: 'Constraints', items: bundle.constraints },
|
|
88
|
+
{ title: 'Decisions', items: bundle.decisions },
|
|
89
|
+
{ title: 'System facts', items: bundle.system_facts },
|
|
90
|
+
{ title: 'Domain knowledge', items: bundle.domain_knowledge },
|
|
91
|
+
];
|
|
92
|
+
const intro = 'Use the following as prior context and preferences. If they conflict with current instructions, follow the current instructions.';
|
|
93
|
+
const lines = [intro];
|
|
94
|
+
let used = estimateTokens(intro);
|
|
95
|
+
for (const section of sections) {
|
|
96
|
+
const candidates = section.items.filter((item) => item.trim().length > 0);
|
|
97
|
+
if (candidates.length === 0)
|
|
98
|
+
continue;
|
|
99
|
+
const header = `## ${section.title}`;
|
|
100
|
+
const tentativeLines = [...lines, '', header];
|
|
101
|
+
let tentativeUsed = used + estimateTokens(`\n\n${header}`);
|
|
102
|
+
const includedItems = [];
|
|
103
|
+
for (const item of candidates) {
|
|
104
|
+
const line = `- ${item}`;
|
|
105
|
+
const cost = estimateTokens(`\n${line}`);
|
|
106
|
+
if (tentativeUsed + cost > budget) {
|
|
107
|
+
return lines.length > 1 ? lines.join('\n') : '';
|
|
108
|
+
}
|
|
109
|
+
includedItems.push(line);
|
|
110
|
+
tentativeUsed += cost;
|
|
111
|
+
}
|
|
112
|
+
if (includedItems.length > 0) {
|
|
113
|
+
tentativeLines.push(...includedItems);
|
|
114
|
+
lines.splice(0, lines.length, ...tentativeLines);
|
|
115
|
+
used = tentativeUsed;
|
|
116
|
+
}
|
|
29
117
|
}
|
|
30
118
|
return lines.length > 1 ? lines.join('\n') : '';
|
|
31
119
|
}
|
|
@@ -73,12 +161,9 @@ export default definePluginEntry({
|
|
|
73
161
|
// -------------------------------------------------------------------------
|
|
74
162
|
api.on('before_prompt_build', async (event) => {
|
|
75
163
|
try {
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
if (memories.length === 0)
|
|
80
|
-
return;
|
|
81
|
-
const block = buildMemoryBlock(memories, cfg.tokenBudget);
|
|
164
|
+
const query = buildRecallQuery(event);
|
|
165
|
+
const bundle = await client.recallBundle(query);
|
|
166
|
+
const block = buildMemoryBlock(bundle, cfg.tokenBudget);
|
|
82
167
|
if (!block)
|
|
83
168
|
return;
|
|
84
169
|
return { appendSystemContext: block };
|
|
@@ -102,8 +187,13 @@ export default definePluginEntry({
|
|
|
102
187
|
if (role !== 'user' && role !== 'assistant')
|
|
103
188
|
continue;
|
|
104
189
|
const text = extractTextFromMessage(msg);
|
|
190
|
+
const ts = typeof m['timestamp'] === 'number'
|
|
191
|
+
? new Date(m['timestamp']).toISOString()
|
|
192
|
+
: typeof m['timestamp'] === 'string'
|
|
193
|
+
? m['timestamp']
|
|
194
|
+
: new Date().toISOString();
|
|
105
195
|
if (text && text.length > 0) {
|
|
106
|
-
chunks.push({ role: role, content: text });
|
|
196
|
+
chunks.push({ role: role, content: text, timestamp: ts });
|
|
107
197
|
}
|
|
108
198
|
}
|
|
109
199
|
if (chunks.length === 0)
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "openclaw-persistio",
|
|
3
3
|
"name": "Persistio Memory",
|
|
4
4
|
"description": "Persistent semantic memory for OpenClaw via Persistio",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.2",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"activation": {
|
|
8
8
|
"onStartup": true
|
|
9
9
|
},
|
|
10
10
|
"contracts": {
|
|
11
|
-
"tools": [
|
|
11
|
+
"tools": [
|
|
12
|
+
"memory_search",
|
|
13
|
+
"memory_add",
|
|
14
|
+
"memory_delete",
|
|
15
|
+
"memory_list"
|
|
16
|
+
]
|
|
12
17
|
},
|
|
13
18
|
"configSchema": {
|
|
14
19
|
"type": "object",
|
|
15
20
|
"additionalProperties": false,
|
|
16
21
|
"properties": {
|
|
17
|
-
"baseURL": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
+
"baseURL": {
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
"apiKey": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"tokenBudget": {
|
|
29
|
+
"type": "number"
|
|
30
|
+
},
|
|
31
|
+
"recallTopK": {
|
|
32
|
+
"type": "number"
|
|
33
|
+
},
|
|
34
|
+
"recallTimeout": {
|
|
35
|
+
"type": "number"
|
|
36
|
+
}
|
|
22
37
|
},
|
|
23
|
-
"required": [
|
|
38
|
+
"required": [
|
|
39
|
+
"baseURL",
|
|
40
|
+
"apiKey"
|
|
41
|
+
]
|
|
24
42
|
}
|
|
25
43
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@persistio/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "OpenClaw plugin for Persistio
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "OpenClaw plugin for Persistio \u2014 persistent semantic memory for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"exports": {
|
package/src/client.ts
CHANGED
|
@@ -15,6 +15,22 @@ export interface PersistioMemory {
|
|
|
15
15
|
confidence: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface RecallBundle {
|
|
19
|
+
user_rules: string[];
|
|
20
|
+
user_preferences: string[];
|
|
21
|
+
task_patterns: string[];
|
|
22
|
+
workflows: string[];
|
|
23
|
+
project: string[];
|
|
24
|
+
constraints: string[];
|
|
25
|
+
decisions: string[];
|
|
26
|
+
system_facts: string[];
|
|
27
|
+
domain_knowledge: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RecallBundleResponse {
|
|
31
|
+
bundle: RecallBundle;
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
export class PersistioClient {
|
|
19
35
|
private readonly baseURL: string;
|
|
20
36
|
private readonly apiKey: string;
|
|
@@ -47,7 +63,19 @@ export class PersistioClient {
|
|
|
47
63
|
return data.memories ?? [];
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
async
|
|
66
|
+
async recallBundle(query: string, topK?: number): Promise<RecallBundle> {
|
|
67
|
+
const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: this.headers(),
|
|
70
|
+
body: JSON.stringify({ query, top_k: topK ?? this.recallTopK }),
|
|
71
|
+
signal: AbortSignal.timeout(this.recallTimeout),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) throw new Error(`Persistio recallBundle failed: ${res.status}`);
|
|
74
|
+
const data = await res.json() as RecallBundleResponse;
|
|
75
|
+
return data.bundle;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async ingest(sessionId: string, chunks: Array<{ role: string; content: string; timestamp: string }>): Promise<void> {
|
|
51
79
|
if (chunks.length === 0) return;
|
|
52
80
|
const res = await fetch(`${this.baseURL}/v1/ingest`, {
|
|
53
81
|
method: 'POST',
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
|
|
2
2
|
import { Type } from '@sinclair/typebox';
|
|
3
|
-
import { PersistioClient, type PersistioConfig } from './client.js';
|
|
3
|
+
import { PersistioClient, type PersistioConfig, type RecallBundle } from './client.js';
|
|
4
4
|
|
|
5
5
|
function resolveConfig(raw: unknown): PersistioConfig {
|
|
6
6
|
const c = (raw ?? {}) as Record<string, unknown>;
|
|
@@ -17,17 +17,116 @@ function estimateTokens(text: string): number {
|
|
|
17
17
|
return Math.ceil(text.length / 4);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
function truncate(text: string, maxLength: number): string {
|
|
21
|
+
if (text.length <= maxLength) return text;
|
|
22
|
+
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function detectTaskType(text: string): 'troubleshooting' | 'coding' | 'planning' | 'writing' | 'general' {
|
|
26
|
+
const normalized = text.toLowerCase();
|
|
27
|
+
if (/(error|bug|fail|failing|issue|broken|debug|debugging|trace|stack)/.test(normalized)) {
|
|
28
|
+
return 'troubleshooting';
|
|
29
|
+
}
|
|
30
|
+
if (/(code|coding|typescript|javascript|python|implement|refactor|function|class|api|build|test)/.test(normalized)) {
|
|
31
|
+
return 'coding';
|
|
32
|
+
}
|
|
33
|
+
if (/(plan|planning|roadmap|strategy|steps|milestone|timeline|organize)/.test(normalized)) {
|
|
34
|
+
return 'planning';
|
|
30
35
|
}
|
|
36
|
+
if (/(write|writing|draft|edit|copy|blog|essay|summary|summarize|document)/.test(normalized)) {
|
|
37
|
+
return 'writing';
|
|
38
|
+
}
|
|
39
|
+
return 'general';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildRecallQuery(event: { prompt?: string; messages?: unknown[] }): string {
|
|
43
|
+
const relevantMessages = Array.isArray(event.messages)
|
|
44
|
+
? event.messages
|
|
45
|
+
.map((msg) => {
|
|
46
|
+
if (typeof msg !== 'object' || msg === null) return null;
|
|
47
|
+
const m = msg as Record<string, unknown>;
|
|
48
|
+
const role = m['role'];
|
|
49
|
+
if (role !== 'user' && role !== 'assistant') return null;
|
|
50
|
+
const text = extractTextFromMessage(msg);
|
|
51
|
+
if (!text) return null;
|
|
52
|
+
return { role, text: text.replace(/\s+/g, ' ').trim() };
|
|
53
|
+
})
|
|
54
|
+
.filter((msg): msg is { role: 'user' | 'assistant'; text: string } => msg !== null && msg.text.length > 0)
|
|
55
|
+
: [];
|
|
56
|
+
|
|
57
|
+
const lastUserIndex = (() => {
|
|
58
|
+
for (let i = relevantMessages.length - 1; i >= 0; i -= 1) {
|
|
59
|
+
if (relevantMessages[i]!.role === 'user') return i;
|
|
60
|
+
}
|
|
61
|
+
return -1;
|
|
62
|
+
})();
|
|
63
|
+
|
|
64
|
+
const lastUserMessage = lastUserIndex >= 0
|
|
65
|
+
? relevantMessages[lastUserIndex]!.text
|
|
66
|
+
: event.prompt?.replace(/\s+/g, ' ').trim() || 'recent context';
|
|
67
|
+
const primary = truncate(lastUserMessage, 300);
|
|
68
|
+
|
|
69
|
+
const contextStart = Math.max(0, lastUserIndex - 6);
|
|
70
|
+
const contextMessages = lastUserIndex >= 0
|
|
71
|
+
? relevantMessages.slice(contextStart, lastUserIndex)
|
|
72
|
+
: relevantMessages.slice(-6);
|
|
73
|
+
const contextSummary = truncate(
|
|
74
|
+
contextMessages
|
|
75
|
+
.map((msg) => `${msg.role === 'user' ? 'U' : 'A'}:${msg.text}`)
|
|
76
|
+
.join(' | '),
|
|
77
|
+
200,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const taskType = detectTaskType(`${primary} ${event.prompt ?? ''}`);
|
|
81
|
+
const parts = [primary];
|
|
82
|
+
if (contextSummary.length > 0) parts.push(`Context: ${contextSummary}`);
|
|
83
|
+
parts.push(`[task: ${taskType}]`);
|
|
84
|
+
return truncate(parts.join('\n'), 600);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildMemoryBlock(bundle: RecallBundle, budget: number): string {
|
|
88
|
+
const sections: Array<{ title: string; items: string[] }> = [
|
|
89
|
+
{ title: 'Behavioural rules', items: bundle.user_rules },
|
|
90
|
+
{ title: 'Preferences', items: bundle.user_preferences },
|
|
91
|
+
{ title: 'Task patterns', items: bundle.task_patterns },
|
|
92
|
+
{ title: 'Workflows', items: bundle.workflows },
|
|
93
|
+
{ title: 'Project', items: bundle.project },
|
|
94
|
+
{ title: 'Constraints', items: bundle.constraints },
|
|
95
|
+
{ title: 'Decisions', items: bundle.decisions },
|
|
96
|
+
{ title: 'System facts', items: bundle.system_facts },
|
|
97
|
+
{ title: 'Domain knowledge', items: bundle.domain_knowledge },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const intro = 'Use the following as prior context and preferences. If they conflict with current instructions, follow the current instructions.';
|
|
101
|
+
const lines: string[] = [intro];
|
|
102
|
+
let used = estimateTokens(intro);
|
|
103
|
+
|
|
104
|
+
for (const section of sections) {
|
|
105
|
+
const candidates = section.items.filter((item) => item.trim().length > 0);
|
|
106
|
+
if (candidates.length === 0) continue;
|
|
107
|
+
|
|
108
|
+
const header = `## ${section.title}`;
|
|
109
|
+
const tentativeLines = [...lines, '', header];
|
|
110
|
+
let tentativeUsed = used + estimateTokens(`\n\n${header}`);
|
|
111
|
+
const includedItems: string[] = [];
|
|
112
|
+
|
|
113
|
+
for (const item of candidates) {
|
|
114
|
+
const line = `- ${item}`;
|
|
115
|
+
const cost = estimateTokens(`\n${line}`);
|
|
116
|
+
if (tentativeUsed + cost > budget) {
|
|
117
|
+
return lines.length > 1 ? lines.join('\n') : '';
|
|
118
|
+
}
|
|
119
|
+
includedItems.push(line);
|
|
120
|
+
tentativeUsed += cost;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (includedItems.length > 0) {
|
|
124
|
+
tentativeLines.push(...includedItems);
|
|
125
|
+
lines.splice(0, lines.length, ...tentativeLines);
|
|
126
|
+
used = tentativeUsed;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
31
130
|
return lines.length > 1 ? lines.join('\n') : '';
|
|
32
131
|
}
|
|
33
132
|
|
|
@@ -77,11 +176,9 @@ export default definePluginEntry({
|
|
|
77
176
|
// -------------------------------------------------------------------------
|
|
78
177
|
api.on('before_prompt_build', async (event) => {
|
|
79
178
|
try {
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
if (memories.length === 0) return;
|
|
84
|
-
const block = buildMemoryBlock(memories, cfg.tokenBudget);
|
|
179
|
+
const query = buildRecallQuery(event);
|
|
180
|
+
const bundle = await client.recallBundle(query);
|
|
181
|
+
const block = buildMemoryBlock(bundle, cfg.tokenBudget);
|
|
85
182
|
if (!block) return;
|
|
86
183
|
return { appendSystemContext: block };
|
|
87
184
|
} catch (err) {
|
|
@@ -97,15 +194,20 @@ export default definePluginEntry({
|
|
|
97
194
|
api.on('agent_end', async (event) => {
|
|
98
195
|
try {
|
|
99
196
|
const sessionId = event.runId ?? 'unknown-session';
|
|
100
|
-
const chunks: Array<{ role: string; content: string }> = [];
|
|
197
|
+
const chunks: Array<{ role: string; content: string; timestamp: string }> = [];
|
|
101
198
|
|
|
102
199
|
for (const msg of event.messages) {
|
|
103
200
|
const m = msg as Record<string, unknown>;
|
|
104
201
|
const role = m['role'];
|
|
105
202
|
if (role !== 'user' && role !== 'assistant') continue;
|
|
106
203
|
const text = extractTextFromMessage(msg);
|
|
204
|
+
const ts = typeof m['timestamp'] === 'number'
|
|
205
|
+
? new Date(m['timestamp']).toISOString()
|
|
206
|
+
: typeof m['timestamp'] === 'string'
|
|
207
|
+
? m['timestamp']
|
|
208
|
+
: new Date().toISOString();
|
|
107
209
|
if (text && text.length > 0) {
|
|
108
|
-
chunks.push({ role: role as string, content: text });
|
|
210
|
+
chunks.push({ role: role as string, content: text, timestamp: ts });
|
|
109
211
|
}
|
|
110
212
|
}
|
|
111
213
|
|