@memtensor/memos-local-openclaw-plugin 1.0.9-beta.1 → 1.0.9-beta.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/README.md +6 -7
- package/index.ts +64 -13
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/src/ingest/providers/anthropic.ts +14 -29
- package/src/ingest/providers/bedrock.ts +14 -29
- package/src/ingest/providers/gemini.ts +14 -29
- package/src/ingest/providers/index.ts +7 -2
- package/src/ingest/providers/openai.ts +71 -14
- package/src/viewer/html.ts +75 -8
- package/src/viewer/server.ts +21 -7
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/telemetry.credentials.json +0 -5
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@memtensor/memos-local-openclaw-plugin)
|
|
4
4
|
[](https://github.com/MemTensor/MemOS/blob/main/LICENSE)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
6
|
[](https://github.com/MemTensor/MemOS/tree/main/apps/memos-local-openclaw)
|
|
7
7
|
|
|
8
8
|
Persistent local conversation memory for [OpenClaw](https://github.com/nicepkg/openclaw) AI Agents. Every conversation is automatically captured, semantically indexed, and instantly recallable — with **task summarization & skill evolution**, **team sharing for memories and skills**, and **multi-agent collaborative memory**.
|
|
@@ -618,7 +618,7 @@ openclaw plugins install @memtensor/memos-local-openclaw-plugin
|
|
|
618
618
|
|
|
619
619
|
## Troubleshooting
|
|
620
620
|
|
|
621
|
-
> 📖 **详细排查指南 / Detailed troubleshooting guide:** [
|
|
621
|
+
> 📖 **详细排查指南 / Detailed troubleshooting guide:** [Troubleshooting](https://memos-claw.openmem.net/docs/troubleshooting.html) — 包含逐步排查流程、日志查看方法、完全重装步骤等。
|
|
622
622
|
>
|
|
623
623
|
> 📦 **better-sqlite3 official troubleshooting:** [better-sqlite3 Troubleshooting](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md) — the upstream guide for native module build issues.
|
|
624
624
|
|
|
@@ -640,7 +640,7 @@ openclaw plugins install @memtensor/memos-local-openclaw-plugin
|
|
|
640
640
|
Search for `memos-local`, `failed to load`, `Error`, `Cannot find module`.
|
|
641
641
|
|
|
642
642
|
4. **Check environment**
|
|
643
|
-
- Node version: `node -v` (requires **>=
|
|
643
|
+
- Node version: `node -v` (requires **>= 22**)
|
|
644
644
|
- Plugin directory exists: `ls ~/.openclaw/extensions/memos-local-openclaw-plugin/package.json`
|
|
645
645
|
- Dependencies installed: `ls ~/.openclaw/extensions/memos-local-openclaw-plugin/node_modules/@sinclair/typebox`
|
|
646
646
|
If missing: `cd ~/.openclaw/extensions/memos-local-openclaw-plugin && npm install --omit=dev`
|
|
@@ -693,7 +693,7 @@ This section is for contributors who want to develop, test, or modify the plugin
|
|
|
693
693
|
|
|
694
694
|
### Prerequisites
|
|
695
695
|
|
|
696
|
-
- **Node.js >=
|
|
696
|
+
- **Node.js >= 22** (`node -v`)
|
|
697
697
|
- **npm >= 9** (`npm -v`)
|
|
698
698
|
- **C++ build tools** (for `better-sqlite3` native module):
|
|
699
699
|
- macOS: `xcode-select --install`
|
|
@@ -701,7 +701,7 @@ This section is for contributors who want to develop, test, or modify the plugin
|
|
|
701
701
|
- Windows: usually not needed (prebuilt binaries available for LTS Node.js); if build fails, install [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
|
|
702
702
|
- **OpenClaw CLI** installed and available in PATH (`openclaw --version`)
|
|
703
703
|
|
|
704
|
-
> **`better-sqlite3` build issues?** This is the most common installation problem on macOS and Linux. If `npm install` fails, first install the C++ build tools above, then run `npm rebuild better-sqlite3`. For detailed platform-specific solutions, see the [official better-sqlite3 troubleshooting guide](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md) and our [installation troubleshooting page](https://
|
|
704
|
+
> **`better-sqlite3` build issues?** This is the most common installation problem on macOS and Linux. If `npm install` fails, first install the C++ build tools above, then run `npm rebuild better-sqlite3`. For detailed platform-specific solutions, see the [official better-sqlite3 troubleshooting guide](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md) and our [installation troubleshooting page](https://memos-claw.openmem.net/docs/troubleshooting.html).
|
|
705
705
|
|
|
706
706
|
### Clone & Setup
|
|
707
707
|
|
|
@@ -761,8 +761,7 @@ apps/memos-local-openclaw/
|
|
|
761
761
|
| `node_modules/` | npm dependencies | `npm install` |
|
|
762
762
|
| `dist/` | Compiled JavaScript output | `npm run build` |
|
|
763
763
|
| `package-lock.json` | Dependency lock file | `npm install` (auto-generated) |
|
|
764
|
-
| `www/` | Memory Viewer static site
|
|
765
|
-
| `docs/` | Documentation HTML pages | Built from source or viewed at the hosted URL |
|
|
764
|
+
| `www/` | Memory Viewer static site & documentation pages | Started automatically by the plugin |
|
|
766
765
|
| `ppt/` | Presentation files (internal use) | Not needed for development |
|
|
767
766
|
| `.env` | Local environment variables | Copy from `.env.example` |
|
|
768
767
|
|
package/index.ts
CHANGED
|
@@ -389,6 +389,8 @@ const memosLocalPlugin = {
|
|
|
389
389
|
let currentAgentId = "main";
|
|
390
390
|
const getCurrentOwner = () => `agent:${currentAgentId}`;
|
|
391
391
|
|
|
392
|
+
const globalRef = globalThis as any;
|
|
393
|
+
|
|
392
394
|
// ─── Check allowPromptInjection policy ───
|
|
393
395
|
// When allowPromptInjection=false, the prompt mutation fields (such as prependContext) in the hook return value
|
|
394
396
|
// will be stripped by the framework. Skip auto-recall to avoid unnecessary LLM/embedding calls.
|
|
@@ -2382,8 +2384,45 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2382
2384
|
|
|
2383
2385
|
let serviceStarted = false;
|
|
2384
2386
|
|
|
2385
|
-
|
|
2387
|
+
function isGatewayStartCommand(): boolean {
|
|
2388
|
+
const args = process.argv.map(a => String(a || "").toLowerCase());
|
|
2389
|
+
const gIdx = args.lastIndexOf("gateway");
|
|
2390
|
+
if (gIdx === -1) return false;
|
|
2391
|
+
const next = args[gIdx + 1];
|
|
2392
|
+
return !next || next.startsWith("-") || next === "start" || next === "restart";
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
const startServiceCore = async (isHostStart = false) => {
|
|
2396
|
+
if (!isGatewayStartCommand()) {
|
|
2397
|
+
api.logger.info("memos-local: not a gateway start command, skipping service startup.");
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
if (globalRef.__memosLocalPluginStopPromise) {
|
|
2402
|
+
await globalRef.__memosLocalPluginStopPromise;
|
|
2403
|
+
globalRef.__memosLocalPluginStopPromise = undefined;
|
|
2404
|
+
}
|
|
2386
2405
|
if (serviceStarted) return;
|
|
2406
|
+
|
|
2407
|
+
if (!isHostStart) {
|
|
2408
|
+
if (globalRef.__memosLocalPluginActiveService && globalRef.__memosLocalPluginActiveService !== service) {
|
|
2409
|
+
api.logger.info("memos-local: aborting startServiceCore because a newer plugin instance is active.");
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
if (globalRef.__memosLocalPluginActiveService && globalRef.__memosLocalPluginActiveService !== service) {
|
|
2415
|
+
api.logger.info("memos-local: Stopping previous plugin instance due to start of new instance...");
|
|
2416
|
+
const oldService = globalRef.__memosLocalPluginActiveService;
|
|
2417
|
+
globalRef.__memosLocalPluginActiveService = undefined;
|
|
2418
|
+
try {
|
|
2419
|
+
await oldService.stop({ preserveDb: true });
|
|
2420
|
+
} catch (e: any) {
|
|
2421
|
+
api.logger.warn(`memos-local: Error stopping previous instance: ${e}`);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
globalRef.__memosLocalPluginActiveService = service;
|
|
2387
2426
|
serviceStarted = true;
|
|
2388
2427
|
|
|
2389
2428
|
if (hubServer) {
|
|
@@ -2425,33 +2464,45 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2425
2464
|
);
|
|
2426
2465
|
};
|
|
2427
2466
|
|
|
2428
|
-
|
|
2467
|
+
const service = {
|
|
2429
2468
|
id: "memos-local-openclaw-plugin",
|
|
2430
|
-
start: async () => { await startServiceCore(); },
|
|
2431
|
-
stop: async () => {
|
|
2469
|
+
start: async () => { await startServiceCore(true); },
|
|
2470
|
+
stop: async (options?: { preserveDb?: boolean }) => {
|
|
2471
|
+
await viewer.stop();
|
|
2472
|
+
if (globalRef.__memosLocalPluginActiveService === service) {
|
|
2473
|
+
globalRef.__memosLocalPluginActiveService = undefined;
|
|
2474
|
+
}
|
|
2432
2475
|
await worker.flush();
|
|
2433
2476
|
await telemetry.shutdown();
|
|
2434
2477
|
await hubServer?.stop();
|
|
2435
|
-
|
|
2436
|
-
|
|
2478
|
+
|
|
2479
|
+
// If we are hot-reloading, the new instance is already using the SAME
|
|
2480
|
+
// database file on disk. Closing the sqlite store here might kill the
|
|
2481
|
+
// connection for the new instance or cause locking issues depending on
|
|
2482
|
+
// how the native binding manages handles.
|
|
2483
|
+
if (!options?.preserveDb) {
|
|
2484
|
+
store.close();
|
|
2485
|
+
}
|
|
2437
2486
|
api.logger.info("memos-local: stopped");
|
|
2438
2487
|
},
|
|
2439
|
-
}
|
|
2488
|
+
};
|
|
2489
|
+
|
|
2490
|
+
api.registerService(service);
|
|
2440
2491
|
|
|
2441
2492
|
// Fallback: OpenClaw may load this plugin via deferred reload after
|
|
2442
2493
|
// startPluginServices has already run, so service.start() never fires.
|
|
2443
|
-
// Start on
|
|
2444
|
-
//
|
|
2445
|
-
|
|
2446
|
-
const
|
|
2447
|
-
|
|
2448
|
-
if (!serviceStarted) {
|
|
2494
|
+
// Start on a delay instead of next tick so the host has time to call
|
|
2495
|
+
// service.start() during normal startup if this is a fresh launch.
|
|
2496
|
+
const SELF_START_DELAY_MS = 2000;
|
|
2497
|
+
const selfStartTimer = setTimeout(() => {
|
|
2498
|
+
if (!serviceStarted && isGatewayStartCommand()) {
|
|
2449
2499
|
api.logger.info("memos-local: service.start() not called by host, self-starting viewer...");
|
|
2450
2500
|
startServiceCore().catch((err) => {
|
|
2451
2501
|
api.logger.warn(`memos-local: self-start failed: ${err}`);
|
|
2452
2502
|
});
|
|
2453
2503
|
}
|
|
2454
2504
|
}, SELF_START_DELAY_MS);
|
|
2505
|
+
if (selfStartTimer.unref) selfStartTimer.unref();
|
|
2455
2506
|
},
|
|
2456
2507
|
};
|
|
2457
2508
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memtensor/memos-local-openclaw-plugin",
|
|
3
|
-
"version": "1.0.9-beta.
|
|
3
|
+
"version": "1.0.9-beta.2",
|
|
4
4
|
"description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
],
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"engines": {
|
|
48
|
-
"node": ">=
|
|
48
|
+
"node": ">=22.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@huggingface/transformers": "^3.8.0",
|
|
@@ -121,12 +121,12 @@ export async function generateTaskTitleAnthropic(
|
|
|
121
121
|
headers,
|
|
122
122
|
body: JSON.stringify({
|
|
123
123
|
model,
|
|
124
|
-
max_tokens:
|
|
124
|
+
max_tokens: 2000,
|
|
125
125
|
temperature: 0,
|
|
126
126
|
system: TASK_TITLE_PROMPT,
|
|
127
127
|
messages: [{ role: "user", content: text }],
|
|
128
128
|
}),
|
|
129
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
129
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
if (!resp.ok) {
|
|
@@ -189,12 +189,12 @@ export async function judgeNewTopicAnthropic(
|
|
|
189
189
|
headers,
|
|
190
190
|
body: JSON.stringify({
|
|
191
191
|
model,
|
|
192
|
-
max_tokens:
|
|
192
|
+
max_tokens: 2000,
|
|
193
193
|
temperature: 0,
|
|
194
194
|
system: TOPIC_JUDGE_PROMPT,
|
|
195
195
|
messages: [{ role: "user", content: userContent }],
|
|
196
196
|
}),
|
|
197
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
197
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
if (!resp.ok) {
|
|
@@ -223,10 +223,12 @@ RULES:
|
|
|
223
223
|
|
|
224
224
|
OUTPUT — JSON only:
|
|
225
225
|
{"relevant":[1,3],"sufficient":true}
|
|
226
|
-
- "relevant": candidate numbers whose content helps answer the query. [] if none can help.
|
|
227
|
-
- "sufficient": true only if the selected memories fully answer the query
|
|
226
|
+
- "relevant": candidate numbers whose content helps answer the query. [] if none can help. Duplicates removed — only unique information.
|
|
227
|
+
- "sufficient": true only if the selected memories fully answer the query.
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
IMPORTANT FOR REASONING MODELS: After your analysis, you MUST output a valid JSON object in this exact format. Do not output any text after the JSON object.`;
|
|
230
|
+
|
|
231
|
+
import { parseFilterResult, type FilterResult } from "./openai";
|
|
230
232
|
export type { FilterResult } from "./openai";
|
|
231
233
|
|
|
232
234
|
export async function filterRelevantAnthropic(
|
|
@@ -256,12 +258,12 @@ export async function filterRelevantAnthropic(
|
|
|
256
258
|
headers,
|
|
257
259
|
body: JSON.stringify({
|
|
258
260
|
model,
|
|
259
|
-
max_tokens:
|
|
261
|
+
max_tokens: 2000,
|
|
260
262
|
temperature: 0,
|
|
261
263
|
system: FILTER_RELEVANT_PROMPT,
|
|
262
264
|
messages: [{ role: "user", content: `QUERY: ${query}\n\nCANDIDATES:\n${candidateText}` }],
|
|
263
265
|
}),
|
|
264
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
266
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
265
267
|
});
|
|
266
268
|
|
|
267
269
|
if (!resp.ok) {
|
|
@@ -275,23 +277,6 @@ export async function filterRelevantAnthropic(
|
|
|
275
277
|
return parseFilterResult(raw, log);
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
279
|
-
try {
|
|
280
|
-
const match = raw.match(/\{[\s\S]*\}/);
|
|
281
|
-
if (match) {
|
|
282
|
-
const obj = JSON.parse(match[0]);
|
|
283
|
-
if (obj && Array.isArray(obj.relevant)) {
|
|
284
|
-
return {
|
|
285
|
-
relevant: obj.relevant.filter((n: any) => typeof n === "number"),
|
|
286
|
-
sufficient: obj.sufficient === true,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch {}
|
|
291
|
-
log.warn(`filterRelevant: failed to parse LLM output: "${raw}", fallback to all+insufficient`);
|
|
292
|
-
return { relevant: [], sufficient: false };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
280
|
export async function summarizeAnthropic(
|
|
296
281
|
text: string,
|
|
297
282
|
cfg: SummarizerConfig,
|
|
@@ -311,7 +296,7 @@ export async function summarizeAnthropic(
|
|
|
311
296
|
headers,
|
|
312
297
|
body: JSON.stringify({
|
|
313
298
|
model,
|
|
314
|
-
max_tokens:
|
|
299
|
+
max_tokens: 2000,
|
|
315
300
|
temperature: cfg.temperature ?? 0,
|
|
316
301
|
system: SYSTEM_PROMPT,
|
|
317
302
|
messages: [{ role: "user", content: `[TEXT TO SUMMARIZE]\n${text}\n[/TEXT TO SUMMARIZE]` }],
|
|
@@ -358,12 +343,12 @@ export async function judgeDedupAnthropic(
|
|
|
358
343
|
headers,
|
|
359
344
|
body: JSON.stringify({
|
|
360
345
|
model,
|
|
361
|
-
max_tokens:
|
|
346
|
+
max_tokens: 2000,
|
|
362
347
|
temperature: 0,
|
|
363
348
|
system: DEDUP_JUDGE_PROMPT,
|
|
364
349
|
messages: [{ role: "user", content: `NEW MEMORY:\n${newSummary}\n\nEXISTING MEMORIES:\n${candidateText}` }],
|
|
365
350
|
}),
|
|
366
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
351
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
367
352
|
});
|
|
368
353
|
|
|
369
354
|
if (!resp.ok) {
|
|
@@ -126,9 +126,9 @@ export async function generateTaskTitleBedrock(
|
|
|
126
126
|
body: JSON.stringify({
|
|
127
127
|
system: [{ text: TASK_TITLE_PROMPT }],
|
|
128
128
|
messages: [{ role: "user", content: [{ text }] }],
|
|
129
|
-
inferenceConfig: { temperature: 0, maxTokens:
|
|
129
|
+
inferenceConfig: { temperature: 0, maxTokens: 2000 },
|
|
130
130
|
}),
|
|
131
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
131
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
if (!resp.ok) {
|
|
@@ -195,9 +195,9 @@ export async function judgeNewTopicBedrock(
|
|
|
195
195
|
body: JSON.stringify({
|
|
196
196
|
system: [{ text: TOPIC_JUDGE_PROMPT }],
|
|
197
197
|
messages: [{ role: "user", content: [{ text: userContent }] }],
|
|
198
|
-
inferenceConfig: { temperature: 0, maxTokens:
|
|
198
|
+
inferenceConfig: { temperature: 0, maxTokens: 2000 },
|
|
199
199
|
}),
|
|
200
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
200
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
if (!resp.ok) {
|
|
@@ -226,10 +226,12 @@ RULES:
|
|
|
226
226
|
|
|
227
227
|
OUTPUT — JSON only:
|
|
228
228
|
{"relevant":[1,3],"sufficient":true}
|
|
229
|
-
- "relevant": candidate numbers whose content helps answer the query. [] if none can help.
|
|
230
|
-
- "sufficient": true only if the selected memories fully answer the query
|
|
229
|
+
- "relevant": candidate numbers whose content helps answer the query. [] if none can help. Duplicates removed — only unique information.
|
|
230
|
+
- "sufficient": true only if the selected memories fully answer the query.
|
|
231
231
|
|
|
232
|
-
|
|
232
|
+
IMPORTANT FOR REASONING MODELS: After your analysis, you MUST output a valid JSON object in this exact format. Do not output any text after the JSON object.`;
|
|
233
|
+
|
|
234
|
+
import { parseFilterResult, type FilterResult } from "./openai";
|
|
233
235
|
export type { FilterResult } from "./openai";
|
|
234
236
|
|
|
235
237
|
export async function filterRelevantBedrock(
|
|
@@ -263,9 +265,9 @@ export async function filterRelevantBedrock(
|
|
|
263
265
|
body: JSON.stringify({
|
|
264
266
|
system: [{ text: FILTER_RELEVANT_PROMPT }],
|
|
265
267
|
messages: [{ role: "user", content: [{ text: `QUERY: ${query}\n\nCANDIDATES:\n${candidateText}` }] }],
|
|
266
|
-
inferenceConfig: { temperature: 0, maxTokens:
|
|
268
|
+
inferenceConfig: { temperature: 0, maxTokens: 2000 },
|
|
267
269
|
}),
|
|
268
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
270
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
269
271
|
});
|
|
270
272
|
|
|
271
273
|
if (!resp.ok) {
|
|
@@ -279,23 +281,6 @@ export async function filterRelevantBedrock(
|
|
|
279
281
|
return parseFilterResult(raw, log);
|
|
280
282
|
}
|
|
281
283
|
|
|
282
|
-
function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
283
|
-
try {
|
|
284
|
-
const match = raw.match(/\{[\s\S]*\}/);
|
|
285
|
-
if (match) {
|
|
286
|
-
const obj = JSON.parse(match[0]);
|
|
287
|
-
if (obj && Array.isArray(obj.relevant)) {
|
|
288
|
-
return {
|
|
289
|
-
relevant: obj.relevant.filter((n: any) => typeof n === "number"),
|
|
290
|
-
sufficient: obj.sufficient === true,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
} catch {}
|
|
295
|
-
log.warn(`filterRelevant: failed to parse LLM output: "${raw}", fallback to all+insufficient`);
|
|
296
|
-
return { relevant: [], sufficient: false };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
284
|
export async function summarizeBedrock(
|
|
300
285
|
text: string,
|
|
301
286
|
cfg: SummarizerConfig,
|
|
@@ -321,7 +306,7 @@ export async function summarizeBedrock(
|
|
|
321
306
|
messages: [{ role: "user", content: [{ text: `[TEXT TO SUMMARIZE]\n${text}\n[/TEXT TO SUMMARIZE]` }] }],
|
|
322
307
|
inferenceConfig: {
|
|
323
308
|
temperature: cfg.temperature ?? 0,
|
|
324
|
-
maxTokens:
|
|
309
|
+
maxTokens: 2000,
|
|
325
310
|
},
|
|
326
311
|
}),
|
|
327
312
|
signal: AbortSignal.timeout(cfg.timeoutMs ?? 30_000),
|
|
@@ -364,9 +349,9 @@ export async function judgeDedupBedrock(
|
|
|
364
349
|
body: JSON.stringify({
|
|
365
350
|
system: [{ text: DEDUP_JUDGE_PROMPT }],
|
|
366
351
|
messages: [{ role: "user", content: [{ text: `NEW MEMORY:\n${newSummary}\n\nEXISTING MEMORIES:\n${candidateText}` }] }],
|
|
367
|
-
inferenceConfig: { temperature: 0, maxTokens:
|
|
352
|
+
inferenceConfig: { temperature: 0, maxTokens: 2000 },
|
|
368
353
|
}),
|
|
369
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
354
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
370
355
|
});
|
|
371
356
|
|
|
372
357
|
if (!resp.ok) {
|
|
@@ -124,9 +124,9 @@ export async function generateTaskTitleGemini(
|
|
|
124
124
|
body: JSON.stringify({
|
|
125
125
|
systemInstruction: { parts: [{ text: TASK_TITLE_PROMPT }] },
|
|
126
126
|
contents: [{ parts: [{ text }] }],
|
|
127
|
-
generationConfig: { temperature: 0, maxOutputTokens:
|
|
127
|
+
generationConfig: { temperature: 0, maxOutputTokens: 2000 },
|
|
128
128
|
}),
|
|
129
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
129
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
if (!resp.ok) {
|
|
@@ -192,9 +192,9 @@ export async function judgeNewTopicGemini(
|
|
|
192
192
|
body: JSON.stringify({
|
|
193
193
|
systemInstruction: { parts: [{ text: TOPIC_JUDGE_PROMPT }] },
|
|
194
194
|
contents: [{ parts: [{ text: userContent }] }],
|
|
195
|
-
generationConfig: { temperature: 0, maxOutputTokens:
|
|
195
|
+
generationConfig: { temperature: 0, maxOutputTokens: 2000 },
|
|
196
196
|
}),
|
|
197
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
197
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
if (!resp.ok) {
|
|
@@ -223,10 +223,12 @@ RULES:
|
|
|
223
223
|
|
|
224
224
|
OUTPUT — JSON only:
|
|
225
225
|
{"relevant":[1,3],"sufficient":true}
|
|
226
|
-
- "relevant": candidate numbers whose content helps answer the query. [] if none can help.
|
|
227
|
-
- "sufficient": true only if the selected memories fully answer the query
|
|
226
|
+
- "relevant": candidate numbers whose content helps answer the query. [] if none can help. Duplicates removed — only unique information.
|
|
227
|
+
- "sufficient": true only if the selected memories fully answer the query.
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
IMPORTANT FOR REASONING MODELS: After your analysis, you MUST output a valid JSON object in this exact format. Do not output any text after the JSON object.`;
|
|
230
|
+
|
|
231
|
+
import { parseFilterResult, type FilterResult } from "./openai";
|
|
230
232
|
export type { FilterResult } from "./openai";
|
|
231
233
|
|
|
232
234
|
export async function filterRelevantGemini(
|
|
@@ -259,9 +261,9 @@ export async function filterRelevantGemini(
|
|
|
259
261
|
body: JSON.stringify({
|
|
260
262
|
systemInstruction: { parts: [{ text: FILTER_RELEVANT_PROMPT }] },
|
|
261
263
|
contents: [{ parts: [{ text: `QUERY: ${query}\n\nCANDIDATES:\n${candidateText}` }] }],
|
|
262
|
-
generationConfig: { temperature: 0, maxOutputTokens:
|
|
264
|
+
generationConfig: { temperature: 0, maxOutputTokens: 2000 },
|
|
263
265
|
}),
|
|
264
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
266
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
265
267
|
});
|
|
266
268
|
|
|
267
269
|
if (!resp.ok) {
|
|
@@ -275,23 +277,6 @@ export async function filterRelevantGemini(
|
|
|
275
277
|
return parseFilterResult(raw, log);
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
279
|
-
try {
|
|
280
|
-
const match = raw.match(/\{[\s\S]*\}/);
|
|
281
|
-
if (match) {
|
|
282
|
-
const obj = JSON.parse(match[0]);
|
|
283
|
-
if (obj && Array.isArray(obj.relevant)) {
|
|
284
|
-
return {
|
|
285
|
-
relevant: obj.relevant.filter((n: any) => typeof n === "number"),
|
|
286
|
-
sufficient: obj.sufficient === true,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch {}
|
|
291
|
-
log.warn(`filterRelevant: failed to parse LLM output: "${raw}", fallback to all+insufficient`);
|
|
292
|
-
return { relevant: [], sufficient: false };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
280
|
export async function summarizeGemini(
|
|
296
281
|
text: string,
|
|
297
282
|
cfg: SummarizerConfig,
|
|
@@ -314,7 +299,7 @@ export async function summarizeGemini(
|
|
|
314
299
|
body: JSON.stringify({
|
|
315
300
|
systemInstruction: { parts: [{ text: SYSTEM_PROMPT }] },
|
|
316
301
|
contents: [{ parts: [{ text: `[TEXT TO SUMMARIZE]\n${text}\n[/TEXT TO SUMMARIZE]` }] }],
|
|
317
|
-
generationConfig: { temperature: cfg.temperature ?? 0, maxOutputTokens:
|
|
302
|
+
generationConfig: { temperature: cfg.temperature ?? 0, maxOutputTokens: 2000 },
|
|
318
303
|
}),
|
|
319
304
|
signal: AbortSignal.timeout(cfg.timeoutMs ?? 30_000),
|
|
320
305
|
});
|
|
@@ -355,9 +340,9 @@ export async function judgeDedupGemini(
|
|
|
355
340
|
body: JSON.stringify({
|
|
356
341
|
systemInstruction: { parts: [{ text: DEDUP_JUDGE_PROMPT }] },
|
|
357
342
|
contents: [{ parts: [{ text: `NEW MEMORY:\n${newSummary}\n\nEXISTING MEMORIES:\n${candidateText}` }] }],
|
|
358
|
-
generationConfig: { temperature: 0, maxOutputTokens:
|
|
343
|
+
generationConfig: { temperature: 0, maxOutputTokens: 2000 },
|
|
359
344
|
}),
|
|
360
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
345
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
361
346
|
});
|
|
362
347
|
|
|
363
348
|
if (!resp.ok) {
|
|
@@ -230,8 +230,13 @@ export class Summarizer {
|
|
|
230
230
|
return ruleFallback(cleaned);
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
const accept = (s: string | undefined): s is string =>
|
|
234
|
-
|
|
233
|
+
const accept = (s: string | undefined): s is string => {
|
|
234
|
+
if (!s || s.length === 0) return false;
|
|
235
|
+
// Allow the LLM result if it's shorter than input, or if the input is quite short itself (<= 20 chars).
|
|
236
|
+
// Summarizing a very short text (e.g. "hi there") might yield a slightly longer formal summary (e.g. "User said hi").
|
|
237
|
+
if (cleaned.length <= 20) return true;
|
|
238
|
+
return s.length < cleaned.length;
|
|
239
|
+
};
|
|
235
240
|
|
|
236
241
|
let llmCalled = false;
|
|
237
242
|
try {
|
|
@@ -122,13 +122,13 @@ export async function generateTaskTitleOpenAI(
|
|
|
122
122
|
body: JSON.stringify(buildRequestBody(cfg, {
|
|
123
123
|
model,
|
|
124
124
|
temperature: 0,
|
|
125
|
-
max_tokens:
|
|
125
|
+
max_tokens: 1000,
|
|
126
126
|
messages: [
|
|
127
127
|
{ role: "system", content: TASK_TITLE_PROMPT },
|
|
128
128
|
{ role: "user", content: text },
|
|
129
129
|
],
|
|
130
130
|
})),
|
|
131
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
131
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
if (!resp.ok) {
|
|
@@ -233,13 +233,13 @@ export async function judgeNewTopicOpenAI(
|
|
|
233
233
|
body: JSON.stringify(buildRequestBody(cfg, {
|
|
234
234
|
model,
|
|
235
235
|
temperature: 0,
|
|
236
|
-
max_tokens:
|
|
236
|
+
max_tokens: 1000,
|
|
237
237
|
messages: [
|
|
238
238
|
{ role: "system", content: TOPIC_JUDGE_PROMPT },
|
|
239
239
|
{ role: "user", content: userContent },
|
|
240
240
|
],
|
|
241
241
|
})),
|
|
242
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
242
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
if (!resp.ok) {
|
|
@@ -289,13 +289,13 @@ export async function classifyTopicOpenAI(
|
|
|
289
289
|
body: JSON.stringify(buildRequestBody(cfg, {
|
|
290
290
|
model,
|
|
291
291
|
temperature: 0,
|
|
292
|
-
max_tokens:
|
|
292
|
+
max_tokens: 1000,
|
|
293
293
|
messages: [
|
|
294
294
|
{ role: "system", content: TOPIC_CLASSIFIER_PROMPT },
|
|
295
295
|
{ role: "user", content: userContent },
|
|
296
296
|
],
|
|
297
297
|
})),
|
|
298
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
298
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
299
299
|
});
|
|
300
300
|
|
|
301
301
|
if (!resp.ok) {
|
|
@@ -336,13 +336,13 @@ export async function arbitrateTopicSplitOpenAI(
|
|
|
336
336
|
body: JSON.stringify(buildRequestBody(cfg, {
|
|
337
337
|
model,
|
|
338
338
|
temperature: 0,
|
|
339
|
-
max_tokens:
|
|
339
|
+
max_tokens: 1000,
|
|
340
340
|
messages: [
|
|
341
341
|
{ role: "system", content: TOPIC_ARBITRATION_PROMPT },
|
|
342
342
|
{ role: "user", content: userContent },
|
|
343
343
|
],
|
|
344
344
|
})),
|
|
345
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
345
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
if (!resp.ok) {
|
|
@@ -432,13 +432,13 @@ export async function filterRelevantOpenAI(
|
|
|
432
432
|
body: JSON.stringify(buildRequestBody(cfg, {
|
|
433
433
|
model,
|
|
434
434
|
temperature: 0,
|
|
435
|
-
max_tokens:
|
|
435
|
+
max_tokens: 2000,
|
|
436
436
|
messages: [
|
|
437
437
|
{ role: "system", content: FILTER_RELEVANT_PROMPT },
|
|
438
438
|
{ role: "user", content: `QUERY: ${query}\n\nCANDIDATES:\n${candidateText}` },
|
|
439
439
|
],
|
|
440
440
|
})),
|
|
441
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
441
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
442
442
|
});
|
|
443
443
|
|
|
444
444
|
if (!resp.ok) {
|
|
@@ -453,8 +453,13 @@ export async function filterRelevantOpenAI(
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
export function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
456
|
+
let cleaned = raw.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
457
|
+
cleaned = cleaned.replace(/think>[\s\S]*?<\/think>/gi, "");
|
|
458
|
+
// Remove markdown code block markers if present (e.g. ```json ... ```)
|
|
459
|
+
cleaned = cleaned.replace(/```json\s*/gi, "").replace(/```\s*/gi, "");
|
|
460
|
+
|
|
456
461
|
try {
|
|
457
|
-
const match =
|
|
462
|
+
const match = cleaned.match(/\{[\s\S]*\}/);
|
|
458
463
|
if (match) {
|
|
459
464
|
const obj = JSON.parse(match[0]);
|
|
460
465
|
if (obj && Array.isArray(obj.relevant)) {
|
|
@@ -465,6 +470,35 @@ export function parseFilterResult(raw: string, log: Logger): FilterResult {
|
|
|
465
470
|
}
|
|
466
471
|
}
|
|
467
472
|
} catch {}
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
// 尝试匹配可能被截断的 JSON 数组结构:`{"relevant":[` 或 `{"relevant": [1, 2`
|
|
476
|
+
const truncatedMatch = cleaned.match(/\{\s*"relevant"\s*:\s*\[([^\]]*)/);
|
|
477
|
+
if (truncatedMatch) {
|
|
478
|
+
const numbers = truncatedMatch[1]
|
|
479
|
+
.split(",")
|
|
480
|
+
.map(s => parseInt(s.trim(), 10))
|
|
481
|
+
.filter(n => !isNaN(n));
|
|
482
|
+
|
|
483
|
+
// 如果至少成功解析出一个数字,且原字符串确实无法通过正常 JSON 解析,就当做截断处理
|
|
484
|
+
if (numbers.length > 0) {
|
|
485
|
+
log.warn(`filterRelevant: JSON truncated, extracted from partial array: ${numbers.join(", ")}`);
|
|
486
|
+
return { relevant: numbers, sufficient: cleaned.includes('"sufficient":true') || cleaned.includes('"sufficient": true') };
|
|
487
|
+
} else if (cleaned.includes('"relevant":[]') || cleaned.includes('"relevant": []')) {
|
|
488
|
+
return { relevant: [], sufficient: cleaned.includes('"sufficient":true') || cleaned.includes('"sufficient": true') };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} catch {}
|
|
492
|
+
|
|
493
|
+
// Regex fallback for reasoning models that fail to output JSON after thinking
|
|
494
|
+
const candidatesMatch = raw.match(/(?:candidate|候选)[\s::]*(\d+)/gi);
|
|
495
|
+
if (candidatesMatch && candidatesMatch.length > 0) {
|
|
496
|
+
const extracted = candidatesMatch.map(m => parseInt(m.replace(/\D/g, ""), 10));
|
|
497
|
+
const unique = Array.from(new Set(extracted)).filter(n => !isNaN(n));
|
|
498
|
+
log.warn(`filterRelevant: JSON missing, fallback regex extracted: ${unique.join(", ")}`);
|
|
499
|
+
return { relevant: unique, sufficient: false };
|
|
500
|
+
}
|
|
501
|
+
|
|
468
502
|
log.warn(`filterRelevant: failed to parse LLM output: "${raw}", fallback to all+insufficient`);
|
|
469
503
|
return { relevant: [], sufficient: false };
|
|
470
504
|
}
|
|
@@ -524,13 +558,13 @@ export async function judgeDedupOpenAI(
|
|
|
524
558
|
body: JSON.stringify(buildRequestBody(cfg, {
|
|
525
559
|
model,
|
|
526
560
|
temperature: 0,
|
|
527
|
-
max_tokens:
|
|
561
|
+
max_tokens: 2000,
|
|
528
562
|
messages: [
|
|
529
563
|
{ role: "system", content: DEDUP_JUDGE_PROMPT },
|
|
530
564
|
{ role: "user", content: `NEW MEMORY:\n${newSummary}\n\nEXISTING MEMORIES:\n${candidateText}` },
|
|
531
565
|
],
|
|
532
566
|
})),
|
|
533
|
-
signal: AbortSignal.timeout(cfg.timeoutMs ??
|
|
567
|
+
signal: AbortSignal.timeout(cfg.timeoutMs ?? 60_000),
|
|
534
568
|
});
|
|
535
569
|
|
|
536
570
|
if (!resp.ok) {
|
|
@@ -544,8 +578,13 @@ export async function judgeDedupOpenAI(
|
|
|
544
578
|
}
|
|
545
579
|
|
|
546
580
|
export function parseDedupResult(raw: string, log: Logger): DedupResult {
|
|
581
|
+
let cleaned = raw.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
582
|
+
cleaned = cleaned.replace(/think>[\s\S]*?<\/think>/gi, "");
|
|
583
|
+
// Remove markdown code block markers if present
|
|
584
|
+
cleaned = cleaned.replace(/```json\s*/gi, "").replace(/```\s*/gi, "");
|
|
585
|
+
|
|
547
586
|
try {
|
|
548
|
-
const match =
|
|
587
|
+
const match = cleaned.match(/\{[\s\S]*\}/);
|
|
549
588
|
if (match) {
|
|
550
589
|
const obj = JSON.parse(match[0]);
|
|
551
590
|
if (obj && typeof obj.action === "string") {
|
|
@@ -558,6 +597,24 @@ export function parseDedupResult(raw: string, log: Logger): DedupResult {
|
|
|
558
597
|
}
|
|
559
598
|
}
|
|
560
599
|
} catch {}
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
// 处理可能的 JSON 截断:如 `{"action":"DUPLICATE","targetIndex":2`
|
|
603
|
+
const actionMatch = cleaned.match(/"action"\s*:\s*"([^"]+)"/);
|
|
604
|
+
if (actionMatch) {
|
|
605
|
+
const actionStr = actionMatch[1];
|
|
606
|
+
if (actionStr === "DUPLICATE" || actionStr === "UPDATE" || actionStr === "NEW") {
|
|
607
|
+
let targetIndex: number | undefined;
|
|
608
|
+
const targetMatch = cleaned.match(/"targetIndex"\s*:\s*(\d+)/);
|
|
609
|
+
if (targetMatch) {
|
|
610
|
+
targetIndex = parseInt(targetMatch[1], 10);
|
|
611
|
+
}
|
|
612
|
+
log.warn(`judgeDedup: JSON truncated, regex extracted action=${actionStr}, targetIndex=${targetIndex}`);
|
|
613
|
+
return { action: actionStr, targetIndex, reason: "extracted from truncated JSON" };
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
} catch {}
|
|
617
|
+
|
|
561
618
|
log.warn(`judgeDedup: failed to parse LLM output: "${raw}", fallback to NEW`);
|
|
562
619
|
return { action: "NEW", reason: "parse_failed" };
|
|
563
620
|
}
|
package/src/viewer/html.ts
CHANGED
|
@@ -124,7 +124,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
124
124
|
.topbar-center{flex:1;display:flex;justify-content:center}
|
|
125
125
|
.topbar .actions{display:flex;align-items:center;gap:6px;flex-shrink:0}
|
|
126
126
|
|
|
127
|
-
.main-content{display:
|
|
127
|
+
.main-content{display:grid;grid-template-columns:260px 1fr;grid-template-rows:auto 1fr;flex:1;max-width:1400px;margin:0 auto;width:100%;padding:28px 32px;gap:28px}
|
|
128
128
|
|
|
129
129
|
/* ─── Sidebar ─── */
|
|
130
130
|
.sidebar{width:260px;min-width:260px;flex-shrink:0;position:sticky;top:84px;max-height:calc(100vh - 112px);display:flex;flex-direction:column}
|
|
@@ -1500,6 +1500,16 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1500
1500
|
</div>
|
|
1501
1501
|
</div>
|
|
1502
1502
|
<div class="settings-card-body">
|
|
1503
|
+
<div class="emb-banner warning" id="settingsWarningBanner" style="margin: 0 0 24px 0; display: block; border-left: 4px solid #f59e0b; position: relative;">
|
|
1504
|
+
<button onclick="document.getElementById('settingsWarningBanner').style.display='none'" style="position: absolute; right: 12px; top: 12px; background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 16px; opacity: 0.7; transition: opacity 0.2s;" onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.7" title="关闭" aria-label="关闭">\u2715</button>
|
|
1505
|
+
<div style="font-weight: 700; margin-bottom: 8px; font-size: 14px; color: #d97706;" data-i18n-html="settings.warn.title">\u{1F514} 模型配置重要提醒</div>
|
|
1506
|
+
<ul style="margin: 0; padding-left: 20px; line-height: 1.7; color: var(--text-sec); font-size: 13px;">
|
|
1507
|
+
<li data-i18n-html="settings.warn.emb"><strong>嵌入模型 (Embedding):</strong>插件内置模型规模较小。为获得更精准的记忆检索体验,强烈建议配置 <code>bge-m3</code> 等专业嵌入模型。</li>
|
|
1508
|
+
<li data-i18n-html="settings.warn.sum"><strong>摘要模型 (Summarizer):</strong>此项为<strong>必填项</strong>,否则无法自动提取记忆摘要。建议配置<strong>非思考型</strong>大模型,以保障处理速度和流畅度。</li>
|
|
1509
|
+
<li data-i18n-html="settings.warn.skill"><strong>技能模型 (Skill Evolution):</strong>用于自动提取可复用技能。建议配置<strong>思考型</strong>大模型,以获得最佳的生成效果和稳定性。</li>
|
|
1510
|
+
</ul>
|
|
1511
|
+
</div>
|
|
1512
|
+
|
|
1503
1513
|
<!-- Embedding Model section -->
|
|
1504
1514
|
<div class="settings-card-subtitle">\u{1F4E1} <span data-i18n="settings.embedding">Embedding Model</span></div>
|
|
1505
1515
|
<div class="field-hint" style="margin-bottom:10px" data-i18n="settings.embedding.desc">Vector embedding model for memory search and retrieval</div>
|
|
@@ -1609,7 +1619,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1609
1619
|
</div>
|
|
1610
1620
|
<div style="margin-top:14px">
|
|
1611
1621
|
<div class="settings-card-subtitle" style="margin-bottom:4px" data-i18n="settings.skill.model">Skill Dedicated Model</div>
|
|
1612
|
-
<div class="field-hint" style="margin-bottom:12px" data-i18n="settings.skill.model.hint">If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated model here for higher quality skill output.</div>
|
|
1622
|
+
<div class="field-hint" style="margin-bottom:12px" data-i18n="settings.skill.model.hint">If not configured, the main Summarizer Model above will be used for skill generation. Configure a dedicated thinking model here for higher quality skill output.</div>
|
|
1613
1623
|
<div class="settings-grid">
|
|
1614
1624
|
<div class="settings-field">
|
|
1615
1625
|
<label data-i18n="settings.provider">Provider</label>
|
|
@@ -2126,6 +2136,7 @@ const I18N={
|
|
|
2126
2136
|
'skills.search.placeholder':'Search skills...',
|
|
2127
2137
|
'skills.search.local':'Local',
|
|
2128
2138
|
'skills.search.noresult':'No matching skills found',
|
|
2139
|
+
|
|
2129
2140
|
'skills.load.error':'Failed to load skills',
|
|
2130
2141
|
'skills.hub.title':'\u{1F310} Team Skills',
|
|
2131
2142
|
'skills.hub.empty':'No extra team skills to list here — either the hub has none yet, or every hub skill already appears in your local list above (same source skill).',
|
|
@@ -2273,7 +2284,10 @@ const I18N={
|
|
|
2273
2284
|
'confirm.clearall2':'Are you absolutely sure?',
|
|
2274
2285
|
'embed.on':'Embedding: ',
|
|
2275
2286
|
'embed.off':'No embedding model',
|
|
2276
|
-
'embed.warn.local':'Using built-in mini model (384d). Search quality is limited
|
|
2287
|
+
'embed.warn.local':'<strong>Embedding</strong>: Using built-in mini model (384d). Search quality is limited. It is highly recommended to configure a dedicated Embedding model (like bge-m3) in Settings for best results.',
|
|
2288
|
+
'fallback.banner.sum': '<strong>Summarizer</strong>: Summarizer model is not configured, automatic memory summarization is paused. A fast, non-reasoning model is recommended for best performance.',
|
|
2289
|
+
'fallback.banner.skill': '<strong>Skill Evolution</strong>: Skill Evolution model is not configured. A reasoning/thinking model is recommended for best stability and generation quality.',
|
|
2290
|
+
'fallback.banner.goto': 'Configure models',
|
|
2277
2291
|
'embed.err.fail':'Embedding model error detected. Check Settings → Model Health.',
|
|
2278
2292
|
'embed.banner.goto':'Go to Settings',
|
|
2279
2293
|
'lang.switch':'中',
|
|
@@ -2298,6 +2312,10 @@ const I18N={
|
|
|
2298
2312
|
'tab.settings':'\u2699 Settings',
|
|
2299
2313
|
'settings.modelconfig':'Model Configuration',
|
|
2300
2314
|
'settings.models':'AI Models',
|
|
2315
|
+
'settings.warn.title':'\u{1F514} Model Configuration Important Reminder',
|
|
2316
|
+
'settings.warn.emb':'<strong>Embedding Model:</strong> The built-in model is small. For a more accurate memory retrieval experience, it is highly recommended to configure a professional embedding model such as <code>bge-m3</code>.',
|
|
2317
|
+
'settings.warn.sum':'<strong>Summarizer Model:</strong> This is <strong>required</strong>, otherwise automatic memory summarization will fail. A fast, non-reasoning model is recommended for best performance.',
|
|
2318
|
+
'settings.warn.skill':'<strong>Skill Evolution:</strong> Used to automatically extract reusable skills. A reasoning/thinking model is recommended for best stability and generation quality.',
|
|
2301
2319
|
'settings.models.desc':'Configure embedding, summarizer and skill evolution models',
|
|
2302
2320
|
'settings.modelhealth':'Model Health',
|
|
2303
2321
|
'settings.embedding':'Embedding Model',
|
|
@@ -2321,7 +2339,7 @@ const I18N={
|
|
|
2321
2339
|
'settings.skill.confidence':'Min Confidence',
|
|
2322
2340
|
'settings.skill.minchunks':'Min Chunks',
|
|
2323
2341
|
'settings.skill.model':'Skill Dedicated Model',
|
|
2324
|
-
'settings.skill.model.hint':'Leave empty to reuse the Summarizer model. Set a dedicated
|
|
2342
|
+
'settings.skill.model.hint':'Leave empty to reuse the Summarizer model. Set a dedicated thinking model for higher quality.',
|
|
2325
2343
|
'settings.optional':'Optional',
|
|
2326
2344
|
'settings.skill.usemain':'Use Main Summarizer',
|
|
2327
2345
|
'settings.telemetry':'Telemetry',
|
|
@@ -2899,6 +2917,7 @@ const I18N={
|
|
|
2899
2917
|
'skills.search.placeholder':'搜索技能...',
|
|
2900
2918
|
'skills.search.local':'本地',
|
|
2901
2919
|
'skills.search.noresult':'未找到匹配的技能',
|
|
2920
|
+
|
|
2902
2921
|
'skills.load.error':'加载技能失败',
|
|
2903
2922
|
'skills.hub.title':'\u{1F310} 团队共享技能',
|
|
2904
2923
|
'skills.hub.empty':'下方只列出「Hub 上有、但上方本机列表尚未包含」的技能;若 Hub 条目已与本机同源同步,则只会在上方显示,此处为空属正常。',
|
|
@@ -3046,7 +3065,10 @@ const I18N={
|
|
|
3046
3065
|
'confirm.clearall2':'你真的确定吗?',
|
|
3047
3066
|
'embed.on':'嵌入模型:',
|
|
3048
3067
|
'embed.off':'无嵌入模型',
|
|
3049
|
-
'embed.warn.local':'
|
|
3068
|
+
'embed.warn.local':'<strong>嵌入模型 (Embedding)</strong>:当前使用内置迷你模型(384维),搜索效果有限。强烈建议在「设置」中配置专用 Embedding 模型(如bge-m3等模型)以获得最佳效果。',
|
|
3069
|
+
'fallback.banner.sum': '<strong>摘要模型 (Summarizer)</strong>:摘要模型未配置,无法自动提取记忆摘要。建议配置非思考型大模型,以保障处理速度和流畅度。',
|
|
3070
|
+
'fallback.banner.skill': '<strong>技能模型 (Skill Evolution)</strong>:技能模型未配置,建议配置思考型大模型,以获得最佳的生成效果和稳定性。',
|
|
3071
|
+
'fallback.banner.goto': '前往配置',
|
|
3050
3072
|
'embed.err.fail':'Embedding 模型调用异常,请前往「设置 → 模型健康」检查。',
|
|
3051
3073
|
'embed.banner.goto':'前往设置',
|
|
3052
3074
|
'lang.switch':'EN',
|
|
@@ -3071,6 +3093,10 @@ const I18N={
|
|
|
3071
3093
|
'tab.settings':'\u2699 设置',
|
|
3072
3094
|
'settings.modelconfig':'模型配置',
|
|
3073
3095
|
'settings.models':'AI 模型',
|
|
3096
|
+
'settings.warn.title':'\u{1F514} 模型配置重要提醒',
|
|
3097
|
+
'settings.warn.emb':'<strong>嵌入模型 (Embedding):</strong>插件内置模型规模较小。为获得更精准的记忆检索体验,强烈建议配置 <code>bge-m3</code> 等专业嵌入模型。',
|
|
3098
|
+
'settings.warn.sum':'<strong>摘要模型 (Summarizer):</strong>此项为<strong>必填项</strong>,否则无法自动提取记忆摘要。建议配置<strong>非思考型</strong>大模型,以保障处理速度和流畅度。',
|
|
3099
|
+
'settings.warn.skill':'<strong>技能模型 (Skill Evolution):</strong>用于自动提取可复用技能。建议配置<strong>思考型</strong>大模型,以获得最佳的生成效果和稳定性。',
|
|
3074
3100
|
'settings.models.desc':'配置嵌入模型、摘要模型和技能进化模型',
|
|
3075
3101
|
'settings.modelhealth':'模型健康',
|
|
3076
3102
|
'settings.embedding':'嵌入模型',
|
|
@@ -3094,7 +3120,7 @@ const I18N={
|
|
|
3094
3120
|
'settings.skill.confidence':'最低置信度',
|
|
3095
3121
|
'settings.skill.minchunks':'最少记忆片段',
|
|
3096
3122
|
'settings.skill.model':'技能专用模型',
|
|
3097
|
-
'settings.skill.model.hint':'
|
|
3123
|
+
'settings.skill.model.hint':'不配置则复用摘要模型。如需更高质量可单独指定思考模型。',
|
|
3098
3124
|
'settings.optional':'可选',
|
|
3099
3125
|
'settings.skill.usemain':'使用主摘要模型',
|
|
3100
3126
|
'settings.telemetry':'数据统计',
|
|
@@ -3640,6 +3666,10 @@ function applyI18n(){
|
|
|
3640
3666
|
const key=el.getAttribute('data-i18n');
|
|
3641
3667
|
if(key) el.textContent=t(key);
|
|
3642
3668
|
});
|
|
3669
|
+
document.querySelectorAll('[data-i18n-html]').forEach(el=>{
|
|
3670
|
+
const key=el.getAttribute('data-i18n-html');
|
|
3671
|
+
if(key) el.innerHTML=t(key);
|
|
3672
|
+
});
|
|
3643
3673
|
document.querySelectorAll('[data-i18n-ph]').forEach(el=>{
|
|
3644
3674
|
const key=el.getAttribute('data-i18n-ph');
|
|
3645
3675
|
if(key) el.placeholder=t(key);
|
|
@@ -3647,7 +3677,15 @@ function applyI18n(){
|
|
|
3647
3677
|
const step2=document.getElementById('resetStep2Desc');
|
|
3648
3678
|
if(step2) step2.innerHTML=t('reset.step2.desc.pre')+'<span style="font-family:monospace;font-size:12px;color:var(--pri)">password reset token: <strong>a1b2c3d4e5f6...</strong></span>'+t('reset.step2.desc.post');
|
|
3649
3679
|
document.title=t('title')+' - OpenClaw';
|
|
3650
|
-
if(typeof loadStats==='function' && document.getElementById('app').style.display==='flex'){
|
|
3680
|
+
if(typeof loadStats==='function' && document.getElementById('app').style.display==='flex'){
|
|
3681
|
+
_embeddingWarningShown = false;
|
|
3682
|
+
_fallbackWarningShown = false;
|
|
3683
|
+
_lastStatsFp = '';
|
|
3684
|
+
const embB = document.getElementById('embBanner'); if (embB) embB.remove();
|
|
3685
|
+
const fSumB = document.getElementById('fallbackBannerSum'); if (fSumB) fSumB.remove();
|
|
3686
|
+
const fSkillB = document.getElementById('fallbackBannerSkill'); if (fSkillB) fSkillB.remove();
|
|
3687
|
+
loadStats();
|
|
3688
|
+
}
|
|
3651
3689
|
if(document.querySelector('.analytics-view.show') && typeof loadMetrics==='function'){loadMetrics();}
|
|
3652
3690
|
}
|
|
3653
3691
|
|
|
@@ -8263,6 +8301,7 @@ async function loadAll(){
|
|
|
8263
8301
|
}
|
|
8264
8302
|
|
|
8265
8303
|
var _lastStatsFp='';
|
|
8304
|
+
var _fallbackWarningShown=false;
|
|
8266
8305
|
async function loadStats(ownerFilter){
|
|
8267
8306
|
let d;
|
|
8268
8307
|
try{
|
|
@@ -8312,6 +8351,19 @@ async function loadStats(ownerFilter){
|
|
|
8312
8351
|
}).catch(()=>{});
|
|
8313
8352
|
}
|
|
8314
8353
|
|
|
8354
|
+
if(!_fallbackWarningShown){
|
|
8355
|
+
_fallbackWarningShown=true;
|
|
8356
|
+
var sumWarn = (d.summarizerProvider === 'none') ? t('fallback.banner.sum') : '';
|
|
8357
|
+
var skillWarn = (d.skillEvolutionProvider === 'none') ? t('fallback.banner.skill') : '';
|
|
8358
|
+
|
|
8359
|
+
if (sumWarn) {
|
|
8360
|
+
showFallbackBanner('fallbackBannerSum', '<div>'+sumWarn+'</div>', 'warning');
|
|
8361
|
+
}
|
|
8362
|
+
if (skillWarn) {
|
|
8363
|
+
showFallbackBanner('fallbackBannerSkill', '<div>'+skillWarn+'</div>', 'warning');
|
|
8364
|
+
}
|
|
8365
|
+
}
|
|
8366
|
+
|
|
8315
8367
|
const memorySessions=d.sessions||[];
|
|
8316
8368
|
const taskSessions=d.taskSessions||[];
|
|
8317
8369
|
const skillSessions=d.skillSessions||[];
|
|
@@ -9623,7 +9675,22 @@ function showEmbeddingBanner(msg,type){
|
|
|
9623
9675
|
var el=document.createElement('div');
|
|
9624
9676
|
el.id='embBanner';
|
|
9625
9677
|
el.className=cls;
|
|
9626
|
-
el.innerHTML=icon+' <span>'+
|
|
9678
|
+
el.innerHTML=icon+' <span>'+msg+'</span>'+btn+close;
|
|
9679
|
+
var mc=document.querySelector('.main-content');
|
|
9680
|
+
if(mc) mc.parentElement.insertBefore(el,mc);
|
|
9681
|
+
}
|
|
9682
|
+
|
|
9683
|
+
/* ─── Fallback Banner ─── */
|
|
9684
|
+
function showFallbackBanner(id, msgHtml,type){
|
|
9685
|
+
if(document.getElementById(id)) return;
|
|
9686
|
+
var cls=type==='error'?'emb-banner error':'emb-banner warning';
|
|
9687
|
+
var icon=type==='error'?'\\u274C':'\\u26A0\\uFE0F';
|
|
9688
|
+
var btn='<button class="emb-banner-btn" onclick="switchView(\\'settings\\');this.parentElement.remove()" style="margin-left:auto;">'+t('fallback.banner.goto')+'</button>';
|
|
9689
|
+
var close='<button class="emb-banner-close" onclick="this.parentElement.remove()">×</button>';
|
|
9690
|
+
var el=document.createElement('div');
|
|
9691
|
+
el.id=id;
|
|
9692
|
+
el.className=cls;
|
|
9693
|
+
el.innerHTML=icon+' <div style="line-height:1.5;flex:1;">'+msgHtml+'</div>'+btn+close;
|
|
9627
9694
|
var mc=document.querySelector('.main-content');
|
|
9628
9695
|
if(mc) mc.parentElement.insertBefore(el,mc);
|
|
9629
9696
|
}
|
package/src/viewer/server.ts
CHANGED
|
@@ -211,13 +211,24 @@ export class ViewerServer {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
stop(): void {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
214
|
+
stop(): Promise<void> {
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
this.stopHubHeartbeat();
|
|
217
|
+
this.stopNotifPoll();
|
|
218
|
+
for (const c of this.notifSSEClients) { try { c.end(); } catch {} }
|
|
219
|
+
this.notifSSEClients = [];
|
|
220
|
+
if (this.server) {
|
|
221
|
+
if ("closeAllConnections" in this.server) {
|
|
222
|
+
(this.server as any).closeAllConnections();
|
|
223
|
+
}
|
|
224
|
+
this.server.close(() => {
|
|
225
|
+
this.server = null;
|
|
226
|
+
resolve();
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
resolve();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
221
232
|
}
|
|
222
233
|
|
|
223
234
|
getResetToken(): string {
|
|
@@ -904,6 +915,9 @@ export class ViewerServer {
|
|
|
904
915
|
totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
|
|
905
916
|
totalSkills: skillCount, totalTasks: taskCount,
|
|
906
917
|
embeddingProvider: this.embedder.provider,
|
|
918
|
+
summarizerProvider: this.ctx?.config?.summarizer?.provider ?? "none",
|
|
919
|
+
skillEvolutionProvider: this.ctx?.config?.skillEvolution?.enabled ? (this.ctx?.config?.summarizer?.provider ?? "none") : "none",
|
|
920
|
+
isSummarizerDegraded: this.ctx ? !this.hasUsableSummarizerProvider(this.ctx.config) : false,
|
|
907
921
|
dedupBreakdown,
|
|
908
922
|
timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
|
|
909
923
|
sessions: sessionList,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"endpoint": "https://proj-xtrace-e218d9316b328f196a3c640cc7ca84-cn-hangzhou.cn-hangzhou.log.aliyuncs.com/rum/web/v2?workspace=default-cms-1026429231103299-cn-hangzhou&service_id=a3u72ukxmr@066657d42a13a9a9f337f",
|
|
3
|
-
"pid": "a3u72ukxmr@066657d42a13a9a9f337f",
|
|
4
|
-
"env": "prod"
|
|
5
|
-
}
|