@memtensor/memos-local-openclaw-plugin 1.0.2 → 1.0.4-beta.0
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 +38 -21
- package/dist/client/connector.d.ts +26 -0
- package/dist/client/connector.d.ts.map +1 -0
- package/dist/client/connector.js +127 -0
- package/dist/client/connector.js.map +1 -0
- package/dist/client/hub.d.ts +61 -0
- package/dist/client/hub.d.ts.map +1 -0
- package/dist/client/hub.js +148 -0
- package/dist/client/hub.js.map +1 -0
- package/dist/client/skill-sync.d.ts +29 -0
- package/dist/client/skill-sync.d.ts.map +1 -0
- package/dist/client/skill-sync.js +216 -0
- package/dist/client/skill-sync.js.map +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +70 -3
- package/dist/config.js.map +1 -1
- package/dist/embedding/index.d.ts +4 -2
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +21 -4
- package/dist/embedding/index.js.map +1 -1
- package/dist/hub/auth.d.ts +19 -0
- package/dist/hub/auth.d.ts.map +1 -0
- package/dist/hub/auth.js +70 -0
- package/dist/hub/auth.js.map +1 -0
- package/dist/hub/server.d.ts +41 -0
- package/dist/hub/server.d.ts.map +1 -0
- package/dist/hub/server.js +742 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/user-manager.d.ts +28 -0
- package/dist/hub/user-manager.d.ts.map +1 -0
- package/dist/hub/user-manager.js +112 -0
- package/dist/hub/user-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/index.d.ts +10 -2
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +242 -8
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/ingest/providers/openai.d.ts +1 -0
- package/dist/ingest/providers/openai.d.ts.map +1 -1
- package/dist/ingest/providers/openai.js +1 -0
- package/dist/ingest/providers/openai.js.map +1 -1
- package/dist/ingest/task-processor.js +1 -1
- package/dist/ingest/task-processor.js.map +1 -1
- package/dist/openclaw-api.d.ts +53 -0
- package/dist/openclaw-api.d.ts.map +1 -0
- package/dist/openclaw-api.js +189 -0
- package/dist/openclaw-api.js.map +1 -0
- package/dist/recall/engine.js +2 -2
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts +4 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +15 -0
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/sharing/types.contract.d.ts +2 -0
- package/dist/sharing/types.contract.d.ts.map +1 -0
- package/dist/sharing/types.contract.js +3 -0
- package/dist/sharing/types.contract.js.map +1 -0
- package/dist/sharing/types.d.ts +80 -0
- package/dist/sharing/types.d.ts.map +1 -0
- package/dist/sharing/types.js +3 -0
- package/dist/sharing/types.js.map +1 -0
- package/dist/skill/evaluator.d.ts.map +1 -1
- package/dist/skill/evaluator.js +2 -2
- package/dist/skill/evaluator.js.map +1 -1
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +4 -4
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/upgrader.js +1 -1
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.js +1 -1
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +294 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +902 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -2
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +48 -7
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/tools/network-memory-detail.d.ts +4 -0
- package/dist/tools/network-memory-detail.d.ts.map +1 -0
- package/dist/tools/network-memory-detail.js +34 -0
- package/dist/tools/network-memory-detail.js.map +1 -0
- package/dist/types.d.ts +47 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +0 -1
- package/dist/update-check.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +2396 -289
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +43 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1180 -33
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +445 -25
- package/openclaw.plugin.json +2 -1
- package/package.json +2 -1
- package/scripts/postinstall.cjs +282 -45
- package/skill/memos-memory-guide/SKILL.md +26 -2
- package/src/client/connector.ts +124 -0
- package/src/client/hub.ts +189 -0
- package/src/client/skill-sync.ts +202 -0
- package/src/config.ts +92 -3
- package/src/embedding/index.ts +25 -3
- package/src/hub/auth.ts +78 -0
- package/src/hub/server.ts +734 -0
- package/src/hub/user-manager.ts +126 -0
- package/src/index.ts +7 -4
- package/src/ingest/providers/index.ts +279 -8
- package/src/ingest/providers/openai.ts +1 -1
- package/src/ingest/task-processor.ts +1 -1
- package/src/openclaw-api.ts +287 -0
- package/src/recall/engine.ts +2 -2
- package/src/shared/llm-call.ts +19 -1
- package/src/sharing/types.contract.ts +40 -0
- package/src/sharing/types.ts +102 -0
- package/src/skill/evaluator.ts +3 -2
- package/src/skill/generator.ts +6 -4
- package/src/skill/upgrader.ts +1 -1
- package/src/skill/validator.ts +1 -1
- package/src/storage/sqlite.ts +1167 -7
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-search.ts +57 -8
- package/src/tools/network-memory-detail.ts +34 -0
- package/src/types.ts +48 -2
- package/src/update-check.ts +0 -1
- package/src/viewer/html.ts +2396 -289
- package/src/viewer/server.ts +1087 -34
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { randomUUID, createHash } from "crypto";
|
|
2
|
+
import { issueUserToken } from "./auth";
|
|
3
|
+
import type { Logger } from "../types";
|
|
4
|
+
import type { UserInfo } from "../sharing/types";
|
|
5
|
+
import type { SqliteStore } from "../storage/sqlite";
|
|
6
|
+
|
|
7
|
+
type ManagedHubUser = UserInfo & { tokenHash: string; createdAt: number; approvedAt: number | null };
|
|
8
|
+
|
|
9
|
+
export class HubUserManager {
|
|
10
|
+
constructor(private store: SqliteStore, private log: Logger) {}
|
|
11
|
+
|
|
12
|
+
createPendingUser(input: { username: string; deviceName?: string }): ManagedHubUser {
|
|
13
|
+
const user = {
|
|
14
|
+
id: randomUUID(),
|
|
15
|
+
username: input.username,
|
|
16
|
+
deviceName: input.deviceName,
|
|
17
|
+
role: "member" as const,
|
|
18
|
+
status: "pending" as const,
|
|
19
|
+
groups: [],
|
|
20
|
+
tokenHash: "",
|
|
21
|
+
createdAt: Date.now(),
|
|
22
|
+
approvedAt: null,
|
|
23
|
+
};
|
|
24
|
+
this.store.upsertHubUser(user);
|
|
25
|
+
return user;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
listPendingUsers(): ManagedHubUser[] {
|
|
29
|
+
return this.store.listHubUsers("pending");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
approveUser(userId: string, token: string): ManagedHubUser | null {
|
|
33
|
+
const user = this.store.getHubUser(userId);
|
|
34
|
+
if (!user) return null;
|
|
35
|
+
const updated = {
|
|
36
|
+
...user,
|
|
37
|
+
status: "active" as const,
|
|
38
|
+
tokenHash: createHash("sha256").update(token).digest("hex"),
|
|
39
|
+
approvedAt: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
this.store.upsertHubUser(updated);
|
|
42
|
+
return updated;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ensureBootstrapAdmin(secret: string, username = "admin", bootstrapUserId?: string, bootstrapToken?: string): { user: ManagedHubUser; token: string } {
|
|
46
|
+
if (bootstrapUserId) {
|
|
47
|
+
const bootstrapUser = this.store.getHubUser(bootstrapUserId);
|
|
48
|
+
if (bootstrapUser && bootstrapUser.role === "admin" && bootstrapUser.status === "active") {
|
|
49
|
+
if (bootstrapToken && bootstrapUser.tokenHash === createHash("sha256").update(bootstrapToken).digest("hex")) {
|
|
50
|
+
return { user: bootstrapUser, token: bootstrapToken };
|
|
51
|
+
}
|
|
52
|
+
const refreshedToken = issueUserToken(
|
|
53
|
+
{ userId: bootstrapUser.id, username: bootstrapUser.username, role: bootstrapUser.role, status: bootstrapUser.status },
|
|
54
|
+
secret,
|
|
55
|
+
3650 * 24 * 60 * 60 * 1000,
|
|
56
|
+
);
|
|
57
|
+
const refreshedUser: ManagedHubUser = {
|
|
58
|
+
...bootstrapUser,
|
|
59
|
+
tokenHash: createHash("sha256").update(refreshedToken).digest("hex"),
|
|
60
|
+
};
|
|
61
|
+
this.store.upsertHubUser(refreshedUser);
|
|
62
|
+
return { user: refreshedUser, token: refreshedToken };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const existing = this.store.listHubUsers().find((user) => user.role === "admin" && user.status === "active");
|
|
67
|
+
if (existing) {
|
|
68
|
+
const refreshedToken = issueUserToken(
|
|
69
|
+
{ userId: existing.id, username: existing.username, role: existing.role, status: existing.status },
|
|
70
|
+
secret,
|
|
71
|
+
3650 * 24 * 60 * 60 * 1000,
|
|
72
|
+
);
|
|
73
|
+
const refreshedUser: ManagedHubUser = {
|
|
74
|
+
...existing,
|
|
75
|
+
tokenHash: createHash("sha256").update(refreshedToken).digest("hex"),
|
|
76
|
+
};
|
|
77
|
+
this.store.upsertHubUser(refreshedUser);
|
|
78
|
+
return { user: refreshedUser, token: refreshedToken };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const user: ManagedHubUser = {
|
|
82
|
+
id: randomUUID(),
|
|
83
|
+
username,
|
|
84
|
+
deviceName: "hub",
|
|
85
|
+
role: "admin",
|
|
86
|
+
status: "active",
|
|
87
|
+
groups: [],
|
|
88
|
+
tokenHash: "",
|
|
89
|
+
createdAt: Date.now(),
|
|
90
|
+
approvedAt: Date.now(),
|
|
91
|
+
};
|
|
92
|
+
const token = issueUserToken(
|
|
93
|
+
{ userId: user.id, username: user.username, role: user.role, status: user.status },
|
|
94
|
+
secret,
|
|
95
|
+
3650 * 24 * 60 * 60 * 1000,
|
|
96
|
+
);
|
|
97
|
+
user.tokenHash = createHash("sha256").update(token).digest("hex");
|
|
98
|
+
this.store.upsertHubUser(user);
|
|
99
|
+
return { user, token };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isUsernameTaken(username: string, excludeUserId?: string): boolean {
|
|
103
|
+
const users = this.store.listHubUsers();
|
|
104
|
+
return users.some(u => u.username === username && u.id !== excludeUserId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
updateUsername(userId: string, newUsername: string): ManagedHubUser | null {
|
|
108
|
+
const user = this.store.getHubUser(userId);
|
|
109
|
+
if (!user) return null;
|
|
110
|
+
const updated = { ...user, username: newUsername };
|
|
111
|
+
this.store.upsertHubUser(updated);
|
|
112
|
+
return updated;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
rejectUser(userId: string): ManagedHubUser | null {
|
|
116
|
+
const user = this.store.getHubUser(userId);
|
|
117
|
+
if (!user) return null;
|
|
118
|
+
const updated = {
|
|
119
|
+
...user,
|
|
120
|
+
status: "rejected" as const,
|
|
121
|
+
approvedAt: Date.now(),
|
|
122
|
+
};
|
|
123
|
+
this.store.upsertHubUser(updated);
|
|
124
|
+
return updated;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,9 @@ import { Embedder } from "./embedding";
|
|
|
5
5
|
import { IngestWorker } from "./ingest/worker";
|
|
6
6
|
import { RecallEngine } from "./recall/engine";
|
|
7
7
|
import { captureMessages } from "./capture";
|
|
8
|
-
import { createMemorySearchTool, createMemoryTimelineTool, createMemoryGetTool } from "./tools";
|
|
8
|
+
import { createMemorySearchTool, createMemoryTimelineTool, createMemoryGetTool, createNetworkMemoryDetailTool } from "./tools";
|
|
9
9
|
import type { MemosLocalConfig, ToolDefinition, Logger } from "./types";
|
|
10
|
+
import type { HostModelsConfig } from "./openclaw-api";
|
|
10
11
|
|
|
11
12
|
export interface MemosLocalPlugin {
|
|
12
13
|
id: string;
|
|
@@ -22,6 +23,7 @@ export interface PluginInitOptions {
|
|
|
22
23
|
workspaceDir?: string;
|
|
23
24
|
config?: Partial<MemosLocalConfig>;
|
|
24
25
|
log?: Logger;
|
|
26
|
+
hostModels?: HostModelsConfig;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -50,19 +52,20 @@ export interface PluginInitOptions {
|
|
|
50
52
|
export function initPlugin(opts: PluginInitOptions = {}): MemosLocalPlugin {
|
|
51
53
|
const stateDir = opts.stateDir ?? defaultStateDir();
|
|
52
54
|
const workspaceDir = opts.workspaceDir ?? process.cwd();
|
|
53
|
-
const ctx = buildContext(stateDir, workspaceDir, opts.config, opts.log);
|
|
55
|
+
const ctx = buildContext(stateDir, workspaceDir, opts.config, opts.log, opts.hostModels);
|
|
54
56
|
|
|
55
57
|
ctx.log.info("Initializing memos-local plugin...");
|
|
56
58
|
|
|
57
59
|
const store = new SqliteStore(ctx.config.storage!.dbPath!, ctx.log);
|
|
58
|
-
const embedder = new Embedder(ctx.config.embedding, ctx.log);
|
|
60
|
+
const embedder = new Embedder(ctx.config.embedding, ctx.log, ctx.openclawAPI);
|
|
59
61
|
const worker = new IngestWorker(store, embedder, ctx);
|
|
60
62
|
const engine = new RecallEngine(store, embedder, ctx);
|
|
61
63
|
|
|
62
64
|
const tools: ToolDefinition[] = [
|
|
63
|
-
createMemorySearchTool(engine),
|
|
65
|
+
createMemorySearchTool(engine, store, ctx),
|
|
64
66
|
createMemoryTimelineTool(store),
|
|
65
67
|
createMemoryGetTool(store),
|
|
68
|
+
createNetworkMemoryDetailTool(store, ctx),
|
|
66
69
|
];
|
|
67
70
|
|
|
68
71
|
ctx.log.info(`Plugin ready. DB: ${ctx.config.storage!.dbPath}, Embedding: ${embedder.provider}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import type { SummarizerConfig, Logger } from "../../types";
|
|
4
|
-
import { summarizeOpenAI, summarizeTaskOpenAI, generateTaskTitleOpenAI, judgeNewTopicOpenAI, filterRelevantOpenAI, judgeDedupOpenAI } from "./openai";
|
|
3
|
+
import type { SummarizerConfig, Logger, OpenClawAPI } from "../../types";
|
|
4
|
+
import { summarizeOpenAI, summarizeTaskOpenAI, generateTaskTitleOpenAI, judgeNewTopicOpenAI, filterRelevantOpenAI, judgeDedupOpenAI, parseFilterResult, parseDedupResult } from "./openai";
|
|
5
5
|
import type { FilterResult, DedupResult } from "./openai";
|
|
6
6
|
export type { FilterResult, DedupResult } from "./openai";
|
|
7
7
|
import { summarizeAnthropic, summarizeTaskAnthropic, generateTaskTitleAnthropic, judgeNewTopicAnthropic, filterRelevantAnthropic, judgeDedupAnthropic } from "./anthropic";
|
|
@@ -120,6 +120,7 @@ export class Summarizer {
|
|
|
120
120
|
constructor(
|
|
121
121
|
private cfg: SummarizerConfig | undefined,
|
|
122
122
|
private log: Logger,
|
|
123
|
+
private openclawAPI?: OpenClawAPI,
|
|
123
124
|
strongCfg?: SummarizerConfig,
|
|
124
125
|
) {
|
|
125
126
|
this.strongCfg = strongCfg;
|
|
@@ -129,11 +130,20 @@ export class Summarizer {
|
|
|
129
130
|
/**
|
|
130
131
|
* Ordered config chain: strongCfg → cfg → fallbackCfg (OpenClaw native model).
|
|
131
132
|
* Returns configs that are defined, in priority order.
|
|
133
|
+
* Openclaw configs without hostCompletion capability or without openclawAPI are excluded.
|
|
132
134
|
*/
|
|
133
135
|
private getConfigChain(): SummarizerConfig[] {
|
|
134
136
|
const chain: SummarizerConfig[] = [];
|
|
135
137
|
if (this.strongCfg) chain.push(this.strongCfg);
|
|
136
|
-
if (this.cfg)
|
|
138
|
+
if (this.cfg) {
|
|
139
|
+
if (this.cfg.provider === "openclaw") {
|
|
140
|
+
if (this.cfg.capabilities?.hostCompletion === true && this.openclawAPI) {
|
|
141
|
+
chain.push(this.cfg);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
chain.push(this.cfg);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
137
147
|
if (this.fallbackCfg) chain.push(this.fallbackCfg);
|
|
138
148
|
return chain;
|
|
139
149
|
}
|
|
@@ -186,8 +196,9 @@ export class Summarizer {
|
|
|
186
196
|
return resultCleaned;
|
|
187
197
|
}
|
|
188
198
|
|
|
189
|
-
if (resultCleaned !== undefined) {
|
|
190
|
-
|
|
199
|
+
if (resultCleaned !== undefined && resultCleaned !== null) {
|
|
200
|
+
const len: number = (resultCleaned as string).length;
|
|
201
|
+
this.log.warn(`summarize: result (${len}) >= input (${cleaned.length}), retrying`);
|
|
191
202
|
}
|
|
192
203
|
} catch (err) {
|
|
193
204
|
this.log.warn(`summarize primary failed: ${err}`);
|
|
@@ -216,7 +227,9 @@ export class Summarizer {
|
|
|
216
227
|
return taskFallback(text);
|
|
217
228
|
}
|
|
218
229
|
|
|
219
|
-
const result = await this.tryChain("summarizeTask", (cfg) =>
|
|
230
|
+
const result = await this.tryChain("summarizeTask", (cfg) =>
|
|
231
|
+
cfg.provider === "openclaw" ? this.summarizeTaskOpenClaw(text) : callSummarizeTask(cfg, text, this.log),
|
|
232
|
+
);
|
|
220
233
|
return result ?? taskFallback(text);
|
|
221
234
|
}
|
|
222
235
|
|
|
@@ -255,7 +268,11 @@ export class Summarizer {
|
|
|
255
268
|
if (!this.cfg && !this.fallbackCfg) return null;
|
|
256
269
|
if (candidates.length === 0) return { relevant: [], sufficient: true };
|
|
257
270
|
|
|
258
|
-
const result = await this.tryChain("filterRelevant", (cfg) =>
|
|
271
|
+
const result = await this.tryChain("filterRelevant", (cfg) =>
|
|
272
|
+
cfg.provider === "openclaw"
|
|
273
|
+
? this.filterRelevantOpenClaw(query, candidates)
|
|
274
|
+
: callFilterRelevant(cfg, query, candidates, this.log),
|
|
275
|
+
);
|
|
259
276
|
return result ?? null;
|
|
260
277
|
}
|
|
261
278
|
|
|
@@ -266,13 +283,144 @@ export class Summarizer {
|
|
|
266
283
|
if (!this.cfg && !this.fallbackCfg) return null;
|
|
267
284
|
if (candidates.length === 0) return null;
|
|
268
285
|
|
|
269
|
-
const result = await this.tryChain("judgeDedup", (cfg) =>
|
|
286
|
+
const result = await this.tryChain("judgeDedup", (cfg) =>
|
|
287
|
+
cfg.provider === "openclaw"
|
|
288
|
+
? this.judgeDedupOpenClaw(newSummary, candidates)
|
|
289
|
+
: callJudgeDedup(cfg, newSummary, candidates, this.log),
|
|
290
|
+
);
|
|
270
291
|
return result ?? { action: "NEW", reason: "all_models_failed" };
|
|
271
292
|
}
|
|
272
293
|
|
|
273
294
|
getStrongConfig(): SummarizerConfig | undefined {
|
|
274
295
|
return this.strongCfg;
|
|
275
296
|
}
|
|
297
|
+
|
|
298
|
+
// ─── OpenClaw API Implementation ───
|
|
299
|
+
|
|
300
|
+
private requireOpenClawAPI(): void {
|
|
301
|
+
if (!this.openclawAPI) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
"OpenClaw API not available. Ensure sharing.capabilities.hostCompletion is enabled in config."
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private async summarizeOpenClaw(text: string): Promise<string> {
|
|
309
|
+
this.requireOpenClawAPI();
|
|
310
|
+
const prompt = [
|
|
311
|
+
`Summarize the text in ONE concise sentence (max 120 characters). IMPORTANT: Use the SAME language as the input text — if the input is Chinese, write Chinese; if English, write English. Preserve exact names, commands, error codes. No bullet points, no preamble — output only the sentence.`,
|
|
312
|
+
``,
|
|
313
|
+
text.slice(0, 2000),
|
|
314
|
+
].join("\n");
|
|
315
|
+
|
|
316
|
+
const response = await this.openclawAPI!.complete({
|
|
317
|
+
prompt,
|
|
318
|
+
maxTokens: 100,
|
|
319
|
+
temperature: 0,
|
|
320
|
+
model: this.cfg?.model,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return response.text.trim().slice(0, 200);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private async summarizeTaskOpenClaw(text: string): Promise<string> {
|
|
327
|
+
this.requireOpenClawAPI();
|
|
328
|
+
const prompt = [
|
|
329
|
+
OPENCLAW_TASK_SUMMARY_PROMPT,
|
|
330
|
+
``,
|
|
331
|
+
text,
|
|
332
|
+
].join("\n");
|
|
333
|
+
|
|
334
|
+
const response = await this.openclawAPI!.complete({
|
|
335
|
+
prompt,
|
|
336
|
+
maxTokens: 4096,
|
|
337
|
+
temperature: 0.1,
|
|
338
|
+
model: this.cfg?.model,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return response.text.trim();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private async judgeNewTopicOpenClaw(currentContext: string, newMessage: string): Promise<boolean> {
|
|
345
|
+
this.requireOpenClawAPI();
|
|
346
|
+
const prompt = [
|
|
347
|
+
OPENCLAW_TOPIC_JUDGE_PROMPT,
|
|
348
|
+
``,
|
|
349
|
+
`CURRENT CONVERSATION SUMMARY:`,
|
|
350
|
+
currentContext,
|
|
351
|
+
``,
|
|
352
|
+
`NEW USER MESSAGE:`,
|
|
353
|
+
newMessage,
|
|
354
|
+
].join("\n");
|
|
355
|
+
|
|
356
|
+
const response = await this.openclawAPI!.complete({
|
|
357
|
+
prompt,
|
|
358
|
+
maxTokens: 10,
|
|
359
|
+
temperature: 0,
|
|
360
|
+
model: this.cfg?.model,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const answer = response.text.trim().toUpperCase();
|
|
364
|
+
this.log.debug(`Topic judge result: "${answer}"`);
|
|
365
|
+
return answer.startsWith("NEW");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private async filterRelevantOpenClaw(
|
|
369
|
+
query: string,
|
|
370
|
+
candidates: Array<{ index: number; role: string; content: string; time?: string }>,
|
|
371
|
+
): Promise<FilterResult> {
|
|
372
|
+
this.requireOpenClawAPI();
|
|
373
|
+
const candidateText = candidates
|
|
374
|
+
.map((c) => `${c.index}. [${c.role}] ${c.content}`)
|
|
375
|
+
.join("\n");
|
|
376
|
+
|
|
377
|
+
const prompt = [
|
|
378
|
+
OPENCLAW_FILTER_RELEVANT_PROMPT,
|
|
379
|
+
``,
|
|
380
|
+
`QUERY: ${query}`,
|
|
381
|
+
``,
|
|
382
|
+
`CANDIDATES:`,
|
|
383
|
+
candidateText,
|
|
384
|
+
].join("\n");
|
|
385
|
+
|
|
386
|
+
const response = await this.openclawAPI!.complete({
|
|
387
|
+
prompt,
|
|
388
|
+
maxTokens: 200,
|
|
389
|
+
temperature: 0,
|
|
390
|
+
model: this.cfg?.model,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return parseFilterResult(response.text.trim(), this.log);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private async judgeDedupOpenClaw(
|
|
397
|
+
newSummary: string,
|
|
398
|
+
candidates: Array<{ index: number; summary: string; chunkId: string }>,
|
|
399
|
+
): Promise<DedupResult> {
|
|
400
|
+
this.requireOpenClawAPI();
|
|
401
|
+
const candidateText = candidates
|
|
402
|
+
.map((c) => `${c.index}. ${c.summary}`)
|
|
403
|
+
.join("\n");
|
|
404
|
+
|
|
405
|
+
const prompt = [
|
|
406
|
+
OPENCLAW_DEDUP_JUDGE_PROMPT,
|
|
407
|
+
``,
|
|
408
|
+
`NEW MEMORY:`,
|
|
409
|
+
newSummary,
|
|
410
|
+
``,
|
|
411
|
+
`EXISTING MEMORIES:`,
|
|
412
|
+
candidateText,
|
|
413
|
+
].join("\n");
|
|
414
|
+
|
|
415
|
+
const response = await this.openclawAPI!.complete({
|
|
416
|
+
prompt,
|
|
417
|
+
maxTokens: 300,
|
|
418
|
+
temperature: 0,
|
|
419
|
+
model: this.cfg?.model,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
return parseDedupResult(response.text.trim(), this.log);
|
|
423
|
+
}
|
|
276
424
|
}
|
|
277
425
|
|
|
278
426
|
// ─── Dispatch helpers ───
|
|
@@ -282,6 +430,12 @@ function callSummarize(cfg: SummarizerConfig, text: string, log: Logger): Promis
|
|
|
282
430
|
case "openai":
|
|
283
431
|
case "openai_compatible":
|
|
284
432
|
case "azure_openai":
|
|
433
|
+
case "zhipu":
|
|
434
|
+
case "siliconflow":
|
|
435
|
+
case "bailian":
|
|
436
|
+
case "cohere":
|
|
437
|
+
case "mistral":
|
|
438
|
+
case "voyage":
|
|
285
439
|
return summarizeOpenAI(text, cfg, log);
|
|
286
440
|
case "anthropic":
|
|
287
441
|
return summarizeAnthropic(text, cfg, log);
|
|
@@ -299,6 +453,12 @@ function callSummarizeTask(cfg: SummarizerConfig, text: string, log: Logger): Pr
|
|
|
299
453
|
case "openai":
|
|
300
454
|
case "openai_compatible":
|
|
301
455
|
case "azure_openai":
|
|
456
|
+
case "zhipu":
|
|
457
|
+
case "siliconflow":
|
|
458
|
+
case "bailian":
|
|
459
|
+
case "cohere":
|
|
460
|
+
case "mistral":
|
|
461
|
+
case "voyage":
|
|
302
462
|
return summarizeTaskOpenAI(text, cfg, log);
|
|
303
463
|
case "anthropic":
|
|
304
464
|
return summarizeTaskAnthropic(text, cfg, log);
|
|
@@ -316,6 +476,12 @@ function callGenerateTaskTitle(cfg: SummarizerConfig, text: string, log: Logger)
|
|
|
316
476
|
case "openai":
|
|
317
477
|
case "openai_compatible":
|
|
318
478
|
case "azure_openai":
|
|
479
|
+
case "zhipu":
|
|
480
|
+
case "siliconflow":
|
|
481
|
+
case "bailian":
|
|
482
|
+
case "cohere":
|
|
483
|
+
case "mistral":
|
|
484
|
+
case "voyage":
|
|
319
485
|
return generateTaskTitleOpenAI(text, cfg, log);
|
|
320
486
|
case "anthropic":
|
|
321
487
|
return generateTaskTitleAnthropic(text, cfg, log);
|
|
@@ -333,6 +499,12 @@ function callTopicJudge(cfg: SummarizerConfig, currentContext: string, newMessag
|
|
|
333
499
|
case "openai":
|
|
334
500
|
case "openai_compatible":
|
|
335
501
|
case "azure_openai":
|
|
502
|
+
case "zhipu":
|
|
503
|
+
case "siliconflow":
|
|
504
|
+
case "bailian":
|
|
505
|
+
case "cohere":
|
|
506
|
+
case "mistral":
|
|
507
|
+
case "voyage":
|
|
336
508
|
return judgeNewTopicOpenAI(currentContext, newMessage, cfg, log);
|
|
337
509
|
case "anthropic":
|
|
338
510
|
return judgeNewTopicAnthropic(currentContext, newMessage, cfg, log);
|
|
@@ -350,6 +522,12 @@ function callFilterRelevant(cfg: SummarizerConfig, query: string, candidates: Ar
|
|
|
350
522
|
case "openai":
|
|
351
523
|
case "openai_compatible":
|
|
352
524
|
case "azure_openai":
|
|
525
|
+
case "zhipu":
|
|
526
|
+
case "siliconflow":
|
|
527
|
+
case "bailian":
|
|
528
|
+
case "cohere":
|
|
529
|
+
case "mistral":
|
|
530
|
+
case "voyage":
|
|
353
531
|
return filterRelevantOpenAI(query, candidates, cfg, log);
|
|
354
532
|
case "anthropic":
|
|
355
533
|
return filterRelevantAnthropic(query, candidates, cfg, log);
|
|
@@ -367,6 +545,12 @@ function callJudgeDedup(cfg: SummarizerConfig, newSummary: string, candidates: A
|
|
|
367
545
|
case "openai":
|
|
368
546
|
case "openai_compatible":
|
|
369
547
|
case "azure_openai":
|
|
548
|
+
case "zhipu":
|
|
549
|
+
case "siliconflow":
|
|
550
|
+
case "bailian":
|
|
551
|
+
case "cohere":
|
|
552
|
+
case "mistral":
|
|
553
|
+
case "voyage":
|
|
370
554
|
return judgeDedupOpenAI(newSummary, candidates, cfg, log);
|
|
371
555
|
case "anthropic":
|
|
372
556
|
return judgeDedupAnthropic(newSummary, candidates, cfg, log);
|
|
@@ -412,3 +596,90 @@ function wordCount(text: string): number {
|
|
|
412
596
|
return count;
|
|
413
597
|
}
|
|
414
598
|
|
|
599
|
+
// ─── OpenClaw Prompt Templates ───
|
|
600
|
+
|
|
601
|
+
const OPENCLAW_TASK_SUMMARY_PROMPT = `You create a DETAILED task summary from a multi-turn conversation. This summary will be the ONLY record of this conversation, so it must preserve ALL important information.
|
|
602
|
+
|
|
603
|
+
CRITICAL LANGUAGE RULE: You MUST write in the SAME language as the user's messages. Chinese input → Chinese output. English input → English output. NEVER mix languages.
|
|
604
|
+
|
|
605
|
+
Output EXACTLY this structure:
|
|
606
|
+
|
|
607
|
+
📌 Title
|
|
608
|
+
A short, descriptive title (10-30 characters). Like a chat group name.
|
|
609
|
+
|
|
610
|
+
🎯 Goal
|
|
611
|
+
One sentence: what the user wanted to accomplish.
|
|
612
|
+
|
|
613
|
+
📋 Key Steps
|
|
614
|
+
- Describe each meaningful step in detail
|
|
615
|
+
- Include the ACTUAL content produced: code snippets, commands, config blocks, formulas, key paragraphs
|
|
616
|
+
- For code: include the function signature and core logic (up to ~30 lines per block), use fenced code blocks
|
|
617
|
+
- For configs: include the actual config values and structure
|
|
618
|
+
- For lists/instructions: include the actual items, not just "provided a list"
|
|
619
|
+
- Merge only truly trivial back-and-forth (like "ok" / "sure")
|
|
620
|
+
- Do NOT over-summarize: "provided a function" is BAD; show the actual function
|
|
621
|
+
|
|
622
|
+
✅ Result
|
|
623
|
+
What was the final outcome? Include the final version of any code/config/content produced.
|
|
624
|
+
|
|
625
|
+
💡 Key Details
|
|
626
|
+
- Decisions made, trade-offs discussed, caveats noted, alternative approaches mentioned
|
|
627
|
+
- Specific values: numbers, versions, thresholds, URLs, file paths, model names
|
|
628
|
+
- Omit this section only if there truly are no noteworthy details
|
|
629
|
+
|
|
630
|
+
RULES:
|
|
631
|
+
- This summary is a KNOWLEDGE BASE ENTRY, not a brief note. Be thorough.
|
|
632
|
+
- PRESERVE verbatim: code, commands, URLs, file paths, error messages, config values, version numbers, names, amounts
|
|
633
|
+
- DISCARD only: greetings, filler, the assistant explaining what it will do before doing it
|
|
634
|
+
- Replace secrets (API keys, tokens, passwords) with [REDACTED]
|
|
635
|
+
- Target length: 30-50% of the original conversation length. Longer conversations need longer summaries.
|
|
636
|
+
- Output summary only, no preamble.`;
|
|
637
|
+
|
|
638
|
+
const OPENCLAW_TOPIC_JUDGE_PROMPT = `You are a conversation topic boundary detector. Given a summary of the CURRENT conversation and a NEW user message, determine if the new message starts a DIFFERENT topic/task.
|
|
639
|
+
|
|
640
|
+
Answer ONLY "NEW" or "SAME".
|
|
641
|
+
|
|
642
|
+
Rules:
|
|
643
|
+
- "NEW" = the new message is about a completely different subject, project, or task
|
|
644
|
+
- "SAME" = the new message continues, follows up on, or is closely related to the current topic
|
|
645
|
+
- Follow-up questions, clarifications, refinements, bug fixes, or next steps on the same task = SAME
|
|
646
|
+
- Greetings or meta-questions like "你好" or "谢谢" without new substance = SAME
|
|
647
|
+
- A clearly unrelated request (e.g., current topic is deployment, new message asks about cooking) = NEW
|
|
648
|
+
|
|
649
|
+
Output exactly one word: NEW or SAME`;
|
|
650
|
+
|
|
651
|
+
const OPENCLAW_FILTER_RELEVANT_PROMPT = `You are a memory relevance judge. Given a user's QUERY and a list of CANDIDATE memory summaries, do two things:
|
|
652
|
+
|
|
653
|
+
1. Select ALL candidates that could be useful for answering the query. When in doubt, INCLUDE the candidate.
|
|
654
|
+
- For questions about lists, history, or "what/where/who" across multiple items, include ALL matching items.
|
|
655
|
+
- For factual lookups, a single direct answer is enough.
|
|
656
|
+
2. Judge whether the selected memories are SUFFICIENT to fully answer the query WITHOUT fetching additional context.
|
|
657
|
+
|
|
658
|
+
IMPORTANT for "sufficient" judgment:
|
|
659
|
+
- sufficient=true ONLY when the memories contain a concrete ANSWER, fact, decision, or actionable information that directly addresses the query.
|
|
660
|
+
- sufficient=false when the memories only repeat the question, show related topics but lack the specific detail, or contain partial information.
|
|
661
|
+
|
|
662
|
+
Output a JSON object with exactly two fields:
|
|
663
|
+
{"relevant":[1,3,5],"sufficient":true}
|
|
664
|
+
|
|
665
|
+
- "relevant": array of candidate numbers that are useful. Empty array [] if none are relevant.
|
|
666
|
+
- "sufficient": true ONLY if the memories contain a direct answer; false otherwise.
|
|
667
|
+
|
|
668
|
+
Output ONLY the JSON object, nothing else.`;
|
|
669
|
+
|
|
670
|
+
const OPENCLAW_DEDUP_JUDGE_PROMPT = `You are a memory deduplication system. Given a NEW memory summary and several EXISTING memory summaries, determine the relationship.
|
|
671
|
+
|
|
672
|
+
For each EXISTING memory, the NEW memory is either:
|
|
673
|
+
- "DUPLICATE": NEW is fully covered by an EXISTING memory — no new information at all
|
|
674
|
+
- "UPDATE": NEW contains information that supplements or updates an EXISTING memory (new data, status change, additional detail)
|
|
675
|
+
- "NEW": NEW is a different topic/event despite surface similarity
|
|
676
|
+
|
|
677
|
+
Pick the BEST match among all candidates. If none match well, choose "NEW".
|
|
678
|
+
|
|
679
|
+
Output a single JSON object:
|
|
680
|
+
- If DUPLICATE: {"action":"DUPLICATE","targetIndex":2,"reason":"..."}
|
|
681
|
+
- If UPDATE: {"action":"UPDATE","targetIndex":3,"reason":"...","mergedSummary":"a combined summary preserving all info from both old and new, same language as input"}
|
|
682
|
+
- If NEW: {"action":"NEW","reason":"..."}
|
|
683
|
+
|
|
684
|
+
CRITICAL: mergedSummary must use the SAME language as the input. Output ONLY the JSON object.`;
|
|
685
|
+
|
|
@@ -316,7 +316,7 @@ export async function filterRelevantOpenAI(
|
|
|
316
316
|
return parseFilterResult(raw, log);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
319
|
+
export function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
320
320
|
try {
|
|
321
321
|
const match = raw.match(/\{[\s\S]*\}/);
|
|
322
322
|
if (match) {
|
|
@@ -39,7 +39,7 @@ export class TaskProcessor {
|
|
|
39
39
|
private ctx: PluginContext,
|
|
40
40
|
) {
|
|
41
41
|
const strongCfg = ctx.config.skillEvolution?.summarizer;
|
|
42
|
-
this.summarizer = new Summarizer(ctx.config.summarizer, ctx.log, strongCfg);
|
|
42
|
+
this.summarizer = new Summarizer(ctx.config.summarizer, ctx.log, ctx.openclawAPI, strongCfg);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
onTaskCompleted(cb: (task: Task) => void): void {
|