@memtensor/memos-local-openclaw-plugin 1.0.7 → 1.0.8-beta
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/.env.example +4 -0
- package/index.ts +69 -48
- package/package.json +4 -3
- package/scripts/postinstall.cjs +59 -25
- package/skill/memos-memory-guide/SKILL.md +5 -2
- package/src/ingest/providers/index.ts +14 -1
- package/src/recall/engine.ts +1 -1
- package/src/shared/llm-call.ts +14 -1
- package/src/storage/sqlite.ts +10 -2
- package/src/viewer/html.ts +226 -34
- package/src/viewer/server.ts +14 -2
- 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/.env.example
CHANGED
|
@@ -18,6 +18,10 @@ SUMMARIZER_TEMPERATURE=0
|
|
|
18
18
|
# Port for the web-based Memory Viewer (default: 18799)
|
|
19
19
|
# VIEWER_PORT=18799
|
|
20
20
|
|
|
21
|
+
# ─── Tavily Search (optional) ───
|
|
22
|
+
# API key for Tavily web search (get from https://app.tavily.com)
|
|
23
|
+
# TAVILY_API_KEY=tvly-your-tavily-api-key
|
|
24
|
+
|
|
21
25
|
# ─── Telemetry (opt-out) ───
|
|
22
26
|
# Anonymous usage analytics to help improve the plugin.
|
|
23
27
|
# No memory content, queries, or personal data is ever sent — only tool names, latencies, and version info.
|
package/index.ts
CHANGED
|
@@ -53,6 +53,45 @@ function deduplicateHits<T extends { summary: string }>(hits: T[]): T[] {
|
|
|
53
53
|
return kept;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
const NEW_SESSION_PROMPT_RE = /A new session was started via \/new or \/reset\./i;
|
|
57
|
+
const INTERNAL_CONTEXT_RE = /OpenClaw runtime context \(internal\):[\s\S]*/i;
|
|
58
|
+
const CONTINUE_PROMPT_RE = /^Continue where you left off\.[\s\S]*/i;
|
|
59
|
+
|
|
60
|
+
function normalizeAutoRecallQuery(rawPrompt: string): string {
|
|
61
|
+
let query = rawPrompt.trim();
|
|
62
|
+
|
|
63
|
+
const senderTag = "Sender (untrusted metadata):";
|
|
64
|
+
const senderPos = query.indexOf(senderTag);
|
|
65
|
+
if (senderPos !== -1) {
|
|
66
|
+
const afterSender = query.slice(senderPos);
|
|
67
|
+
const fenceStart = afterSender.indexOf("```json");
|
|
68
|
+
const fenceEnd = fenceStart >= 0 ? afterSender.indexOf("```\n", fenceStart + 7) : -1;
|
|
69
|
+
if (fenceEnd > 0) {
|
|
70
|
+
query = afterSender.slice(fenceEnd + 4).replace(/^\s*\n/, "").trim();
|
|
71
|
+
} else {
|
|
72
|
+
const firstDblNl = afterSender.indexOf("\n\n");
|
|
73
|
+
if (firstDblNl > 0) {
|
|
74
|
+
query = afterSender.slice(firstDblNl + 2).trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
query = stripInboundMetadata(query);
|
|
80
|
+
query = query.replace(/<[^>]+>/g, "").trim();
|
|
81
|
+
|
|
82
|
+
if (NEW_SESSION_PROMPT_RE.test(query)) {
|
|
83
|
+
query = query.replace(NEW_SESSION_PROMPT_RE, "").trim();
|
|
84
|
+
query = query.replace(/^(Execute|Run) your Session Startup sequence[^\n]*\n?/im, "").trim();
|
|
85
|
+
query = query.replace(/^Current time:[^\n]*(\n|$)/im, "").trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
query = query.replace(INTERNAL_CONTEXT_RE, "").trim();
|
|
89
|
+
query = query.replace(CONTINUE_PROMPT_RE, "").trim();
|
|
90
|
+
|
|
91
|
+
return query;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
56
95
|
const pluginConfigSchema = {
|
|
57
96
|
type: "object" as const,
|
|
58
97
|
additionalProperties: true,
|
|
@@ -278,7 +317,7 @@ const memosLocalPlugin = {
|
|
|
278
317
|
const raw = fs.readFileSync(openclawJsonPath, "utf-8");
|
|
279
318
|
const cfg = JSON.parse(raw);
|
|
280
319
|
const allow: string[] | undefined = cfg?.tools?.allow;
|
|
281
|
-
if (Array.isArray(allow) && allow.length > 0 && !allow.includes("group:plugins")) {
|
|
320
|
+
if (Array.isArray(allow) && allow.length > 0 && !allow.includes("group:plugins") && !allow.includes("*")) {
|
|
282
321
|
const lastEntry = JSON.stringify(allow[allow.length - 1]);
|
|
283
322
|
const patched = raw.replace(
|
|
284
323
|
new RegExp(`(${lastEntry})(\\s*\\])`),
|
|
@@ -307,6 +346,7 @@ const memosLocalPlugin = {
|
|
|
307
346
|
// Current agent ID — updated by hooks, read by tools for owner isolation.
|
|
308
347
|
// Falls back to "main" when no hook has fired yet (single-agent setups).
|
|
309
348
|
let currentAgentId = "main";
|
|
349
|
+
const getCurrentOwner = () => `agent:${currentAgentId}`;
|
|
310
350
|
|
|
311
351
|
// ─── Check allowPromptInjection policy ───
|
|
312
352
|
// When allowPromptInjection=false, the prompt mutation fields (such as prependContext) in the hook return value
|
|
@@ -354,7 +394,6 @@ const memosLocalPlugin = {
|
|
|
354
394
|
}
|
|
355
395
|
};
|
|
356
396
|
|
|
357
|
-
const getCurrentOwner = () => `agent:${currentAgentId}`;
|
|
358
397
|
const resolveMemorySearchScope = (scope?: string): "local" | "group" | "all" =>
|
|
359
398
|
scope === "group" || scope === "all" ? scope : "local";
|
|
360
399
|
const resolveMemoryShareTarget = (target?: string): "agents" | "hub" | "both" =>
|
|
@@ -445,7 +484,7 @@ const memosLocalPlugin = {
|
|
|
445
484
|
// ─── Tool: memory_search ───
|
|
446
485
|
|
|
447
486
|
api.registerTool(
|
|
448
|
-
{
|
|
487
|
+
(context) => ({
|
|
449
488
|
name: "memory_search",
|
|
450
489
|
label: "Memory Search",
|
|
451
490
|
description:
|
|
@@ -461,7 +500,7 @@ const memosLocalPlugin = {
|
|
|
461
500
|
hubAddress: Type.Optional(Type.String({ description: "Optional Hub address override for group/all search." })),
|
|
462
501
|
userToken: Type.Optional(Type.String({ description: "Optional Hub bearer token override for group/all search." })),
|
|
463
502
|
}),
|
|
464
|
-
execute: trackTool("memory_search", async (_toolCallId: any, params: any
|
|
503
|
+
execute: trackTool("memory_search", async (_toolCallId: any, params: any) => {
|
|
465
504
|
const {
|
|
466
505
|
query,
|
|
467
506
|
scope: rawScope,
|
|
@@ -482,9 +521,6 @@ const memosLocalPlugin = {
|
|
|
482
521
|
const role = rawRole === "user" || rawRole === "assistant" || rawRole === "tool" || rawRole === "system" ? rawRole : undefined;
|
|
483
522
|
const minScore = typeof rawMinScore === "number" ? Math.max(0.35, Math.min(1, rawMinScore)) : undefined;
|
|
484
523
|
let searchScope = resolveMemorySearchScope(rawScope);
|
|
485
|
-
if (searchScope === "local" && ctx.config?.sharing?.enabled) {
|
|
486
|
-
searchScope = "all";
|
|
487
|
-
}
|
|
488
524
|
const searchLimit = typeof maxResults === "number" ? Math.max(1, Math.min(20, Math.round(maxResults))) : 10;
|
|
489
525
|
|
|
490
526
|
const agentId = context?.agentId ?? currentAgentId;
|
|
@@ -504,7 +540,7 @@ const memosLocalPlugin = {
|
|
|
504
540
|
|
|
505
541
|
// Split local results: pure-local vs hub-memory (Hub role's hub_memories mixed in by RecallEngine)
|
|
506
542
|
const localHits = result.hits.filter((h) => h.origin !== "hub-memory");
|
|
507
|
-
const hubLocalHits = result.hits.filter((h) => h.origin === "hub-memory");
|
|
543
|
+
const hubLocalHits = searchScope !== "local" ? result.hits.filter((h) => h.origin === "hub-memory") : [];
|
|
508
544
|
|
|
509
545
|
const rawLocalCandidates = localHits.map((h) => ({
|
|
510
546
|
chunkId: h.ref.chunkId,
|
|
@@ -669,14 +705,14 @@ const memosLocalPlugin = {
|
|
|
669
705
|
},
|
|
670
706
|
};
|
|
671
707
|
}),
|
|
672
|
-
},
|
|
708
|
+
}),
|
|
673
709
|
{ name: "memory_search" },
|
|
674
710
|
);
|
|
675
711
|
|
|
676
712
|
// ─── Tool: memory_timeline ───
|
|
677
713
|
|
|
678
714
|
api.registerTool(
|
|
679
|
-
{
|
|
715
|
+
(context) => ({
|
|
680
716
|
name: "memory_timeline",
|
|
681
717
|
label: "Memory Timeline",
|
|
682
718
|
description:
|
|
@@ -686,7 +722,7 @@ const memosLocalPlugin = {
|
|
|
686
722
|
chunkId: Type.String({ description: "The chunkId from a memory_search hit" }),
|
|
687
723
|
window: Type.Optional(Type.Number({ description: "Context window ±N (default 2)" })),
|
|
688
724
|
}),
|
|
689
|
-
execute: trackTool("memory_timeline", async (_toolCallId: any, params: any
|
|
725
|
+
execute: trackTool("memory_timeline", async (_toolCallId: any, params: any) => {
|
|
690
726
|
const agentId = context?.agentId ?? currentAgentId;
|
|
691
727
|
ctx.log.debug(`memory_timeline called (agent=${agentId})`);
|
|
692
728
|
const { chunkId, window: win } = params as {
|
|
@@ -730,14 +766,14 @@ const memosLocalPlugin = {
|
|
|
730
766
|
details: { entries, anchorRef: { sessionKey: anchorChunk.sessionKey, chunkId, turnId: anchorChunk.turnId, seq: anchorChunk.seq } },
|
|
731
767
|
};
|
|
732
768
|
}),
|
|
733
|
-
},
|
|
769
|
+
}),
|
|
734
770
|
{ name: "memory_timeline" },
|
|
735
771
|
);
|
|
736
772
|
|
|
737
773
|
// ─── Tool: memory_get ───
|
|
738
774
|
|
|
739
775
|
api.registerTool(
|
|
740
|
-
{
|
|
776
|
+
(context) => ({
|
|
741
777
|
name: "memory_get",
|
|
742
778
|
label: "Memory Get",
|
|
743
779
|
description:
|
|
@@ -748,7 +784,7 @@ const memosLocalPlugin = {
|
|
|
748
784
|
Type.Number({ description: `Max chars (default ${DEFAULTS.getMaxCharsDefault}, max ${DEFAULTS.getMaxCharsMax})` }),
|
|
749
785
|
),
|
|
750
786
|
}),
|
|
751
|
-
execute: trackTool("memory_get", async (_toolCallId: any, params: any
|
|
787
|
+
execute: trackTool("memory_get", async (_toolCallId: any, params: any) => {
|
|
752
788
|
const { chunkId, maxChars } = params as { chunkId: string; maxChars?: number };
|
|
753
789
|
const limit = Math.min(maxChars ?? DEFAULTS.getMaxCharsDefault, DEFAULTS.getMaxCharsMax);
|
|
754
790
|
|
|
@@ -774,7 +810,7 @@ const memosLocalPlugin = {
|
|
|
774
810
|
},
|
|
775
811
|
};
|
|
776
812
|
}),
|
|
777
|
-
},
|
|
813
|
+
}),
|
|
778
814
|
{ name: "memory_get" },
|
|
779
815
|
);
|
|
780
816
|
|
|
@@ -1296,7 +1332,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1296
1332
|
const viewerPort = (pluginCfg as any).viewerPort ?? (gatewayPort + 10);
|
|
1297
1333
|
|
|
1298
1334
|
api.registerTool(
|
|
1299
|
-
{
|
|
1335
|
+
(context) => ({
|
|
1300
1336
|
name: "memory_viewer",
|
|
1301
1337
|
label: "Open Memory Viewer",
|
|
1302
1338
|
description:
|
|
@@ -1304,10 +1340,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1304
1340
|
"or access their stored memories, or asks where the memory dashboard is. " +
|
|
1305
1341
|
"Returns the URL the user can open in their browser.",
|
|
1306
1342
|
parameters: Type.Object({}),
|
|
1307
|
-
execute: trackTool("memory_viewer", async () => {
|
|
1343
|
+
execute: trackTool("memory_viewer", async (_toolCallId: any, params: any) => {
|
|
1308
1344
|
ctx.log.debug(`memory_viewer called`);
|
|
1309
1345
|
telemetry.trackViewerOpened();
|
|
1310
|
-
const
|
|
1346
|
+
const agentId = context?.agentId ?? context?.profileId ?? currentAgentId;
|
|
1347
|
+
const url = `http://127.0.0.1:${viewerPort}?agentId=${encodeURIComponent(agentId)}`;
|
|
1311
1348
|
return {
|
|
1312
1349
|
content: [
|
|
1313
1350
|
{
|
|
@@ -1328,7 +1365,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1328
1365
|
details: { viewerUrl: url },
|
|
1329
1366
|
};
|
|
1330
1367
|
}),
|
|
1331
|
-
},
|
|
1368
|
+
}),
|
|
1332
1369
|
{ name: "memory_viewer" },
|
|
1333
1370
|
);
|
|
1334
1371
|
|
|
@@ -1401,9 +1438,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1401
1438
|
name: "memory_share",
|
|
1402
1439
|
label: "Share Memory",
|
|
1403
1440
|
description:
|
|
1404
|
-
"Share an existing memory
|
|
1405
|
-
"
|
|
1406
|
-
"
|
|
1441
|
+
"Share an existing stored memory (requires a real chunkId from the database) to the Hub team, or to both targets. " +
|
|
1442
|
+
"If you want to share content from the conversation, please first retrieve the memories related to that content to obtain the correct chunkId(s), then proceed with the sharing. " +
|
|
1443
|
+
"target='agents' (default): when retrieved memories would clearly help other agents in the same OpenClaw workspace, you may share proactively without asking the user. " +
|
|
1444
|
+
"target='hub' or 'both': do not share to the team Hub without explicit user consent when the content would benefit collaborators—explain briefly, ask first, and only call hub/both after they agree (Hub must be configured). " +
|
|
1445
|
+
"To create a brand-new shared note with no existing chunk, use memory_write_public.",
|
|
1407
1446
|
parameters: Type.Object({
|
|
1408
1447
|
chunkId: Type.String({ description: "Existing local memory chunk ID to share" }),
|
|
1409
1448
|
target: Type.Optional(Type.String({ description: "Share target: 'agents' (default), 'hub', or 'both'" })),
|
|
@@ -1557,7 +1596,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1557
1596
|
// ─── Tool: skill_search ───
|
|
1558
1597
|
|
|
1559
1598
|
api.registerTool(
|
|
1560
|
-
{
|
|
1599
|
+
(context) => ({
|
|
1561
1600
|
name: "skill_search",
|
|
1562
1601
|
label: "Skill Search",
|
|
1563
1602
|
description:
|
|
@@ -1567,10 +1606,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1567
1606
|
query: Type.String({ description: "Natural language description of the needed skill" }),
|
|
1568
1607
|
scope: Type.Optional(Type.String({ description: "Search scope: 'mix' (default), 'self', 'public', 'group', or 'all'." })),
|
|
1569
1608
|
}),
|
|
1570
|
-
execute: trackTool("skill_search", async (_toolCallId: any, params: any
|
|
1609
|
+
execute: trackTool("skill_search", async (_toolCallId: any, params: any) => {
|
|
1571
1610
|
const { query: skillQuery, scope: rawScope } = params as { query: string; scope?: string };
|
|
1572
1611
|
const scope = (rawScope === "self" || rawScope === "public") ? rawScope : "mix";
|
|
1573
|
-
const
|
|
1612
|
+
const agentId = context?.agentId ?? currentAgentId;
|
|
1613
|
+
const currentOwner = `agent:${agentId}`;
|
|
1574
1614
|
|
|
1575
1615
|
if (rawScope === "group" || rawScope === "all") {
|
|
1576
1616
|
const [localHits, hub] = await Promise.all([
|
|
@@ -1632,7 +1672,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1632
1672
|
details: { query: skillQuery, scope, hits },
|
|
1633
1673
|
};
|
|
1634
1674
|
}),
|
|
1635
|
-
},
|
|
1675
|
+
}),
|
|
1636
1676
|
{ name: "skill_search" },
|
|
1637
1677
|
);
|
|
1638
1678
|
|
|
@@ -1777,7 +1817,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1777
1817
|
if (!allowPromptInjection) return {};
|
|
1778
1818
|
if (!event.prompt || event.prompt.length < 3) return;
|
|
1779
1819
|
|
|
1780
|
-
const recallAgentId = hookCtx?.agentId ?? "main";
|
|
1820
|
+
const recallAgentId = hookCtx?.agentId ?? (event as any)?.agentId ?? (event as any)?.profileId ?? "main";
|
|
1781
1821
|
currentAgentId = recallAgentId;
|
|
1782
1822
|
const recallOwnerFilter = [`agent:${recallAgentId}`, "public"];
|
|
1783
1823
|
ctx.log.info(`auto-recall: agentId=${recallAgentId} (from hookCtx)`);
|
|
@@ -1789,24 +1829,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1789
1829
|
const rawPrompt = event.prompt;
|
|
1790
1830
|
ctx.log.debug(`auto-recall: rawPrompt="${rawPrompt.slice(0, 300)}"`);
|
|
1791
1831
|
|
|
1792
|
-
|
|
1793
|
-
const senderTag = "Sender (untrusted metadata):";
|
|
1794
|
-
const senderPos = rawPrompt.indexOf(senderTag);
|
|
1795
|
-
if (senderPos !== -1) {
|
|
1796
|
-
const afterSender = rawPrompt.slice(senderPos);
|
|
1797
|
-
const fenceStart = afterSender.indexOf("```json");
|
|
1798
|
-
const fenceEnd = fenceStart >= 0 ? afterSender.indexOf("```\n", fenceStart + 7) : -1;
|
|
1799
|
-
if (fenceEnd > 0) {
|
|
1800
|
-
query = afterSender.slice(fenceEnd + 4).replace(/^\s*\n/, "").trim();
|
|
1801
|
-
} else {
|
|
1802
|
-
const firstDblNl = afterSender.indexOf("\n\n");
|
|
1803
|
-
if (firstDblNl > 0) {
|
|
1804
|
-
query = afterSender.slice(firstDblNl + 2).trim();
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
query = stripInboundMetadata(query);
|
|
1809
|
-
query = query.replace(/<[^>]+>/g, "").trim();
|
|
1832
|
+
const query = normalizeAutoRecallQuery(rawPrompt);
|
|
1810
1833
|
recallQuery = query;
|
|
1811
1834
|
|
|
1812
1835
|
if (query.length < 2) {
|
|
@@ -1988,7 +2011,6 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1988
2011
|
try {
|
|
1989
2012
|
const skillCandidateMap = new Map<string, { name: string; description: string; skillId: string; source: string }>();
|
|
1990
2013
|
|
|
1991
|
-
// Source 1: direct skill search based on user query
|
|
1992
2014
|
try {
|
|
1993
2015
|
const directSkillHits = await engine.searchSkills(query, "mix" as any, getCurrentOwner());
|
|
1994
2016
|
for (const sh of directSkillHits.slice(0, skillLimit + 2)) {
|
|
@@ -2000,7 +2022,6 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2000
2022
|
ctx.log.debug(`auto-recall-skill: direct search failed: ${err}`);
|
|
2001
2023
|
}
|
|
2002
2024
|
|
|
2003
|
-
// Source 2: skills linked to tasks from memory hits
|
|
2004
2025
|
const taskIds = new Set<string>();
|
|
2005
2026
|
for (const h of filteredHits) {
|
|
2006
2027
|
if (h.taskId) {
|
|
@@ -2090,7 +2111,7 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2090
2111
|
if (!event.success || !event.messages || event.messages.length === 0) return;
|
|
2091
2112
|
|
|
2092
2113
|
try {
|
|
2093
|
-
const captureAgentId = hookCtx?.agentId ?? "main";
|
|
2114
|
+
const captureAgentId = hookCtx?.agentId ?? event?.agentId ?? event?.profileId ?? "main";
|
|
2094
2115
|
currentAgentId = captureAgentId;
|
|
2095
2116
|
const captureOwner = `agent:${captureAgentId}`;
|
|
2096
2117
|
const sessionKey = hookCtx?.sessionKey ?? "default";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memtensor/memos-local-openclaw-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8-beta",
|
|
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,12 +45,13 @@
|
|
|
45
45
|
],
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"engines": {
|
|
48
|
-
"node": ">=
|
|
48
|
+
"node": ">=18.0.0 <25.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@huggingface/transformers": "^3.8.0",
|
|
52
52
|
"@sinclair/typebox": "^0.34.48",
|
|
53
|
-
"better-sqlite3": "^12.6.
|
|
53
|
+
"better-sqlite3": "^12.6.3",
|
|
54
|
+
"posthog-node": "^5.28.0",
|
|
54
55
|
"puppeteer": "^24.38.0",
|
|
55
56
|
"semver": "^7.7.4",
|
|
56
57
|
"uuid": "^10.0.0"
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -30,6 +30,9 @@ function normalizePathForMatch(p) {
|
|
|
30
30
|
return path.resolve(p).replace(/^\\\\\?\\/, "").replace(/\\/g, "/").toLowerCase();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
const nodeVersion = process.version;
|
|
34
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
35
|
+
|
|
33
36
|
console.log(`
|
|
34
37
|
${CYAN}${BOLD}┌──────────────────────────────────────────────────┐
|
|
35
38
|
│ MemOS Local Memory — postinstall setup │
|
|
@@ -37,7 +40,13 @@ ${CYAN}${BOLD}┌─────────────────────
|
|
|
37
40
|
`);
|
|
38
41
|
|
|
39
42
|
log(`Plugin dir: ${DIM}${pluginDir}${RESET}`);
|
|
40
|
-
log(`Node: ${
|
|
43
|
+
log(`Node: ${GREEN}${nodeVersion}${RESET} Platform: ${process.platform}-${process.arch}`);
|
|
44
|
+
|
|
45
|
+
if (nodeMajor >= 25) {
|
|
46
|
+
warn(`Node.js ${nodeVersion} detected. This version may have compatibility issues with native modules.`);
|
|
47
|
+
log(`Recommended: Use Node.js LTS (v20 or v22) for best compatibility.`);
|
|
48
|
+
log(`You can use nvm to switch versions: ${CYAN}nvm use 22${RESET}`);
|
|
49
|
+
}
|
|
41
50
|
|
|
42
51
|
/* ═══════════════════════════════════════════════════════════
|
|
43
52
|
* Pre-phase: Clean stale build artifacts on upgrade
|
|
@@ -61,21 +70,30 @@ function cleanStaleArtifacts() {
|
|
|
61
70
|
installedVer = pkg.version || "unknown";
|
|
62
71
|
} catch { /* ignore */ }
|
|
63
72
|
|
|
73
|
+
const nodeMajor = process.versions.node.split(".")[0];
|
|
74
|
+
const currentFingerprint = `${installedVer}+node${nodeMajor}`;
|
|
75
|
+
|
|
64
76
|
const markerPath = path.join(pluginDir, ".installed-version");
|
|
65
|
-
let
|
|
66
|
-
try {
|
|
77
|
+
let prevFingerprint = "";
|
|
78
|
+
try { prevFingerprint = fs.readFileSync(markerPath, "utf-8").trim(); } catch { /* first install */ }
|
|
79
|
+
|
|
80
|
+
const writeMarker = () => {
|
|
81
|
+
try { fs.writeFileSync(markerPath, currentFingerprint + "\n", "utf-8"); } catch { /* ignore */ }
|
|
82
|
+
};
|
|
67
83
|
|
|
68
|
-
if (
|
|
69
|
-
log(`Version unchanged (${
|
|
84
|
+
if (prevFingerprint === currentFingerprint) {
|
|
85
|
+
log(`Version unchanged (${currentFingerprint}), skipping artifact cleanup.`);
|
|
70
86
|
return;
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
if (
|
|
74
|
-
log(`
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
if (!prevFingerprint) {
|
|
90
|
+
log(`Fresh install: ${GREEN}${currentFingerprint}${RESET}`);
|
|
91
|
+
writeMarker();
|
|
92
|
+
return;
|
|
77
93
|
}
|
|
78
94
|
|
|
95
|
+
log(`Environment changed: ${DIM}${prevFingerprint}${RESET} → ${GREEN}${currentFingerprint}${RESET}`);
|
|
96
|
+
|
|
79
97
|
const dirsToClean = ["dist", "node_modules"];
|
|
80
98
|
let cleaned = 0;
|
|
81
99
|
for (const dir of dirsToClean) {
|
|
@@ -99,7 +117,7 @@ function cleanStaleArtifacts() {
|
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
119
|
|
|
102
|
-
|
|
120
|
+
writeMarker();
|
|
103
121
|
|
|
104
122
|
if (cleaned > 0) {
|
|
105
123
|
ok(`Cleaned ${cleaned} stale artifact(s). Fresh install will follow.`);
|
|
@@ -418,23 +436,39 @@ if (sqliteBindingsExist()) {
|
|
|
418
436
|
else { fail(`Rebuild completed but bindings still missing (${elapsed}s).`); fail(`Looked in: ${sqliteModulePath}/build/`); }
|
|
419
437
|
console.log(`
|
|
420
438
|
${YELLOW}${BOLD} ╔══════════════════════════════════════════════════════════════╗
|
|
421
|
-
║ ✖ better-sqlite3 native module build failed
|
|
439
|
+
║ ✖ better-sqlite3 native module build failed ║
|
|
422
440
|
╠══════════════════════════════════════════════════════════════╣${RESET}
|
|
423
|
-
${YELLOW} ║${RESET}
|
|
424
|
-
${YELLOW} ║${RESET} This plugin requires C/C++ build tools to compile
|
|
425
|
-
${YELLOW} ║${RESET} the SQLite native module on first install.
|
|
426
|
-
${YELLOW} ║${RESET}
|
|
427
|
-
${YELLOW} ║${RESET} ${BOLD}Install build tools:${RESET}
|
|
428
|
-
${YELLOW} ║${RESET}
|
|
429
|
-
${YELLOW} ║${RESET} ${CYAN}macOS:${RESET} xcode-select --install
|
|
430
|
-
${YELLOW} ║${RESET} ${CYAN}Ubuntu:${RESET} sudo apt install build-essential python3
|
|
431
|
-
${YELLOW} ║${RESET} ${CYAN}Windows:${RESET} npm install -g windows-build-tools
|
|
432
|
-
${YELLOW} ║${RESET}
|
|
433
|
-
|
|
441
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
442
|
+
${YELLOW} ║${RESET} This plugin requires C/C++ build tools to compile ${YELLOW}║${RESET}
|
|
443
|
+
${YELLOW} ║${RESET} the SQLite native module on first install. ${YELLOW}║${RESET}
|
|
444
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
445
|
+
${YELLOW} ║${RESET} ${BOLD}Install build tools:${RESET} ${YELLOW}║${RESET}
|
|
446
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
447
|
+
${YELLOW} ║${RESET} ${CYAN}macOS:${RESET} xcode-select --install ${YELLOW}║${RESET}
|
|
448
|
+
${YELLOW} ║${RESET} ${CYAN}Ubuntu:${RESET} sudo apt install build-essential python3 ${YELLOW}║${RESET}
|
|
449
|
+
${YELLOW} ║${RESET} ${CYAN}Windows:${RESET} npm install -g windows-build-tools ${YELLOW}║${RESET}
|
|
450
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}`);
|
|
451
|
+
|
|
452
|
+
if (nodeMajor >= 25) {
|
|
453
|
+
console.log(`${YELLOW} ║${RESET} ${BOLD}${RED}Node.js v25+ compatibility issue detected:${RESET} ${YELLOW}║${RESET}
|
|
454
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
455
|
+
${YELLOW} ║${RESET} better-sqlite3 may not have prebuilt binaries for Node 25. ${YELLOW}║${RESET}
|
|
456
|
+
${YELLOW} ║${RESET} ${BOLD}Recommended solutions:${RESET} ${YELLOW}║${RESET}
|
|
457
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
458
|
+
${YELLOW} ║${RESET} 1. Use Node.js LTS (v20 or v22): ${YELLOW}║${RESET}
|
|
459
|
+
${YELLOW} ║${RESET} ${GREEN}nvm install 22 && nvm use 22${RESET} ${YELLOW}║${RESET}
|
|
460
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
461
|
+
${YELLOW} ║${RESET} 2. Or use MemOS Cloud version instead: ${YELLOW}║${RESET}
|
|
462
|
+
${YELLOW} ║${RESET} ${CYAN}https://github.com/MemTensor/MemOS/tree/main/apps/memos-cloud${RESET}
|
|
463
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
console.log(`${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
467
|
+
${YELLOW} ║${RESET} ${BOLD}Then retry:${RESET} ${YELLOW}║${RESET}
|
|
434
468
|
${YELLOW} ║${RESET} ${GREEN}cd ${pluginDir}${RESET}
|
|
435
|
-
${YELLOW} ║${RESET} ${GREEN}npm rebuild better-sqlite3${RESET}
|
|
436
|
-
${YELLOW} ║${RESET} ${GREEN}openclaw gateway stop && openclaw gateway start${RESET}
|
|
437
|
-
${YELLOW} ║${RESET}
|
|
469
|
+
${YELLOW} ║${RESET} ${GREEN}npm rebuild better-sqlite3${RESET} ${YELLOW}║${RESET}
|
|
470
|
+
${YELLOW} ║${RESET} ${GREEN}openclaw gateway stop && openclaw gateway start${RESET} ${YELLOW}║${RESET}
|
|
471
|
+
${YELLOW} ║${RESET} ${YELLOW}║${RESET}
|
|
438
472
|
${YELLOW}${BOLD} ╚══════════════════════════════════════════════════════════════╝${RESET}
|
|
439
473
|
`);
|
|
440
474
|
}
|
|
@@ -73,8 +73,11 @@ Two sharing planes exist and must not be confused:
|
|
|
73
73
|
### memory_share
|
|
74
74
|
|
|
75
75
|
- **What it does:** Share an existing memory either with local OpenClaw agents, to the team, or to both.
|
|
76
|
-
- **When to call:**
|
|
77
|
-
-
|
|
76
|
+
- **When to call:**
|
|
77
|
+
- If you want to share conversation content to team or hub, first retrieve memories related to that content to obtain the right `chunkId`(s), then share.
|
|
78
|
+
- `target='agents'` (default): When those memories would clearly help other agents in the same workspace, you may share proactively without asking the user.
|
|
79
|
+
- `target='hub'` or `'both'`: Only after explicit user consent when the content would benefit collaborators—explain briefly, ask first, then call `hub`/`both` (Hub must be configured). Never silently Hub-share.
|
|
80
|
+
- **Do not use when:** You are creating a brand-new shared note with **no** existing chunk—use `memory_write_public` instead.
|
|
78
81
|
- **Parameters:**
|
|
79
82
|
- `chunkId` (string, **required**) — Existing memory chunk ID.
|
|
80
83
|
- `target` (string, optional) — `'agents'` (default), `'hub'`, or `'both'`.
|
|
@@ -8,6 +8,19 @@ import { summarizeAnthropic, summarizeTaskAnthropic, generateTaskTitleAnthropic,
|
|
|
8
8
|
import { summarizeGemini, summarizeTaskGemini, generateTaskTitleGemini, judgeNewTopicGemini, filterRelevantGemini, judgeDedupGemini } from "./gemini";
|
|
9
9
|
import { summarizeBedrock, summarizeTaskBedrock, generateTaskTitleBedrock, judgeNewTopicBedrock, filterRelevantBedrock, judgeDedupBedrock } from "./bedrock";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a SecretInput (string | SecretRef) to a plain string.
|
|
13
|
+
* Supports env-sourced SecretRef from OpenClaw's credential system.
|
|
14
|
+
*/
|
|
15
|
+
function resolveApiKey(
|
|
16
|
+
input: string | { source: string; provider?: string; id: string } | undefined,
|
|
17
|
+
): string | undefined {
|
|
18
|
+
if (!input) return undefined;
|
|
19
|
+
if (typeof input === "string") return input;
|
|
20
|
+
if (input.source === "env") return process.env[input.id];
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
/**
|
|
12
25
|
* Detect provider type from provider key name or base URL.
|
|
13
26
|
*/
|
|
@@ -68,7 +81,7 @@ function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | undefined {
|
|
|
68
81
|
if (!providerCfg) return undefined;
|
|
69
82
|
|
|
70
83
|
const baseUrl: string | undefined = providerCfg.baseUrl;
|
|
71
|
-
const apiKey
|
|
84
|
+
const apiKey = resolveApiKey(providerCfg.apiKey);
|
|
72
85
|
if (!baseUrl || !apiKey) return undefined;
|
|
73
86
|
|
|
74
87
|
const provider = detectProvider(providerKey, baseUrl);
|
package/src/recall/engine.ts
CHANGED
|
@@ -77,7 +77,7 @@ export class RecallEngine {
|
|
|
77
77
|
}
|
|
78
78
|
const shortTerms = [...new Set([...spaceSplit, ...cjkBigrams])];
|
|
79
79
|
const patternHits = shortTerms.length > 0
|
|
80
|
-
? this.store.patternSearch(shortTerms, { limit: candidatePool })
|
|
80
|
+
? this.store.patternSearch(shortTerms, { limit: candidatePool, ownerFilter })
|
|
81
81
|
: [];
|
|
82
82
|
const patternRanked = patternHits.map((h, i) => ({
|
|
83
83
|
id: h.chunkId,
|
package/src/shared/llm-call.ts
CHANGED
|
@@ -2,6 +2,19 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import type { SummarizerConfig, SummaryProvider, Logger, PluginContext, OpenClawAPI } from "../types";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a SecretInput (string | SecretRef) to a plain string.
|
|
7
|
+
* Supports env-sourced SecretRef from OpenClaw's credential system.
|
|
8
|
+
*/
|
|
9
|
+
function resolveApiKey(
|
|
10
|
+
input: string | { source: string; provider?: string; id: string } | undefined,
|
|
11
|
+
): string | undefined {
|
|
12
|
+
if (!input) return undefined;
|
|
13
|
+
if (typeof input === "string") return input;
|
|
14
|
+
if (input.source === "env") return process.env[input.id];
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
/**
|
|
6
19
|
* Detect provider type from provider key name or base URL.
|
|
7
20
|
*/
|
|
@@ -56,7 +69,7 @@ export function loadOpenClawFallbackConfig(log: Logger): SummarizerConfig | unde
|
|
|
56
69
|
if (!providerCfg) return undefined;
|
|
57
70
|
|
|
58
71
|
const baseUrl: string | undefined = providerCfg.baseUrl;
|
|
59
|
-
const apiKey
|
|
72
|
+
const apiKey = resolveApiKey(providerCfg.apiKey);
|
|
60
73
|
if (!baseUrl || !apiKey) return undefined;
|
|
61
74
|
|
|
62
75
|
const provider = detectProvider(providerKey, baseUrl);
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -1207,7 +1207,7 @@ export class SqliteStore {
|
|
|
1207
1207
|
|
|
1208
1208
|
// ─── Pattern Search (LIKE-based, for CJK text where FTS tokenization is weak) ───
|
|
1209
1209
|
|
|
1210
|
-
patternSearch(patterns: string[], opts: { role?: string; limit?: number } = {}): Array<{ chunkId: string; content: string; role: string; createdAt: number }> {
|
|
1210
|
+
patternSearch(patterns: string[], opts: { role?: string; limit?: number; ownerFilter?: string[] } = {}): Array<{ chunkId: string; content: string; role: string; createdAt: number }> {
|
|
1211
1211
|
if (patterns.length === 0) return [];
|
|
1212
1212
|
const limit = opts.limit ?? 10;
|
|
1213
1213
|
|
|
@@ -1216,13 +1216,21 @@ export class SqliteStore {
|
|
|
1216
1216
|
const roleClause = opts.role ? " AND c.role = ?" : "";
|
|
1217
1217
|
const params: (string | number)[] = patterns.map(p => `%${p}%`);
|
|
1218
1218
|
if (opts.role) params.push(opts.role);
|
|
1219
|
+
|
|
1220
|
+
let ownerClause = "";
|
|
1221
|
+
if (opts.ownerFilter && opts.ownerFilter.length > 0) {
|
|
1222
|
+
const placeholders = opts.ownerFilter.map(() => "?").join(",");
|
|
1223
|
+
ownerClause = ` AND c.owner IN (${placeholders})`;
|
|
1224
|
+
params.push(...opts.ownerFilter);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1219
1227
|
params.push(limit);
|
|
1220
1228
|
|
|
1221
1229
|
try {
|
|
1222
1230
|
const rows = this.db.prepare(`
|
|
1223
1231
|
SELECT c.id as chunk_id, c.content, c.role, c.created_at
|
|
1224
1232
|
FROM chunks c
|
|
1225
|
-
WHERE (${whereClause})${roleClause} AND c.dedup_status = 'active'
|
|
1233
|
+
WHERE (${whereClause})${roleClause}${ownerClause} AND c.dedup_status = 'active'
|
|
1226
1234
|
ORDER BY c.created_at DESC
|
|
1227
1235
|
LIMIT ?
|
|
1228
1236
|
`).all(...params) as Array<{ chunk_id: string; content: string; role: string; created_at: number }>;
|
package/src/viewer/html.ts
CHANGED
|
@@ -697,6 +697,9 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
697
697
|
.skill-card-bottom .tag{display:flex;align-items:center;gap:4px}
|
|
698
698
|
.skill-card-tags{display:flex;gap:4px;flex-wrap:wrap}
|
|
699
699
|
.skill-tag{font-size:10px;padding:2px 8px;border-radius:10px;background:rgba(139,92,246,.1);color:var(--violet);font-weight:500}
|
|
700
|
+
.skill-selection-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px}
|
|
701
|
+
.skill-select-box{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:5px;border:1px solid var(--border);background:var(--bg-card);cursor:pointer;margin-right:8px;vertical-align:middle}
|
|
702
|
+
.skill-select-box input{width:14px;height:14px;cursor:pointer}
|
|
700
703
|
.skill-detail-desc{font-size:13px;color:var(--text-sec);line-height:1.6;margin-bottom:16px;padding:12px 16px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius)}
|
|
701
704
|
.skill-version-item{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:12px 16px}
|
|
702
705
|
.skill-version-header{display:flex;align-items:center;gap:10px;margin-bottom:6px}
|
|
@@ -1251,10 +1254,14 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1251
1254
|
<option value="" data-i18n="filter.allagents">All agents</option>
|
|
1252
1255
|
</select>
|
|
1253
1256
|
<select id="memorySearchScope" class="filter-select" onchange="onMemoryScopeChange()" style="display:none">
|
|
1254
|
-
<option value="local" data-i18n="scope.thisAgent">This Agent</option>
|
|
1255
1257
|
<option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
|
|
1256
1258
|
<option value="hub" data-i18n="scope.hub">Team</option>
|
|
1257
1259
|
</select>
|
|
1260
|
+
<select id="memoryPageSize" class="filter-select" onchange="onMemoryPageSizeChange()">
|
|
1261
|
+
<option value="10">10 / page</option>
|
|
1262
|
+
<option value="20" selected>20 / page</option>
|
|
1263
|
+
<option value="40">40 / page</option>
|
|
1264
|
+
</select>
|
|
1258
1265
|
</div>
|
|
1259
1266
|
<div class="search-meta" id="searchMeta"></div>
|
|
1260
1267
|
<div class="search-meta" id="sharingSearchMeta"></div>
|
|
@@ -1294,8 +1301,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1294
1301
|
<button class="filter-chip" data-task-status="active" onclick="setTaskStatusFilter(this,'active')" data-i18n="tasks.status.active">Active</button>
|
|
1295
1302
|
<button class="filter-chip" data-task-status="completed" onclick="setTaskStatusFilter(this,'completed')" data-i18n="tasks.status.completed">Completed</button>
|
|
1296
1303
|
<button class="filter-chip" data-task-status="skipped" onclick="setTaskStatusFilter(this,'skipped')" data-i18n="tasks.status.skipped">Skipped</button>
|
|
1304
|
+
<select id="tasksPageSize" class="filter-select" onchange="onTasksPageSizeChange()">
|
|
1305
|
+
<option value="10">10 / page</option>
|
|
1306
|
+
<option value="20" selected>20 / page</option>
|
|
1307
|
+
<option value="40">40 / page</option>
|
|
1308
|
+
</select>
|
|
1297
1309
|
<select id="taskSearchScope" class="scope-select" onchange="onTaskScopeChange()" style="display:none">
|
|
1298
|
-
<option value="local" data-i18n="scope.thisAgent">This Agent</option>
|
|
1299
1310
|
<option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
|
|
1300
1311
|
<option value="hub" data-i18n="scope.hub">Team</option>
|
|
1301
1312
|
</select>
|
|
@@ -1337,10 +1348,14 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1337
1348
|
<span class="search-icon">🔍</span>
|
|
1338
1349
|
<input type="text" id="skillSearchInput" placeholder="Search skills..." data-i18n-ph="skills.search.placeholder" oninput="debounceSkillSearch()">
|
|
1339
1350
|
<select id="skillSearchScope" class="scope-select" onchange="onSkillScopeChange()" style="display:none">
|
|
1340
|
-
<option value="local" data-i18n="scope.thisAgent">This Agent</option>
|
|
1341
1351
|
<option value="allLocal" data-i18n="scope.thisDevice">This Device</option>
|
|
1342
1352
|
<option value="hub" data-i18n="scope.hub">Team</option>
|
|
1343
1353
|
</select>
|
|
1354
|
+
<select id="skillsPageSize" class="filter-select" onchange="onSkillsPageSizeChange()">
|
|
1355
|
+
<option value="10">10 / page</option>
|
|
1356
|
+
<option value="20" selected>20 / page</option>
|
|
1357
|
+
<option value="40">40 / page</option>
|
|
1358
|
+
</select>
|
|
1344
1359
|
</div>
|
|
1345
1360
|
<div class="search-meta" id="skillSearchMeta" style="display:none"></div>
|
|
1346
1361
|
<div class="tasks-header">
|
|
@@ -1362,8 +1377,13 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1362
1377
|
<option value="private" data-i18n="filter.private">Private</option>
|
|
1363
1378
|
</select>
|
|
1364
1379
|
</div>
|
|
1380
|
+
<div class="skill-selection-toolbar">
|
|
1381
|
+
<button class="btn btn-sm btn-ghost" id="skillSelectAllBtn" onclick="toggleSelectAllSkills()" data-i18n="skills.selectAll">Select All</button>
|
|
1382
|
+
<button class="btn btn-sm btn-danger" id="skillBulkDeleteBtn" onclick="deleteSelectedSkills()" disabled data-i18n="skills.deleteSelected">Delete Selected</button>
|
|
1383
|
+
</div>
|
|
1365
1384
|
</div>
|
|
1366
1385
|
<div class="tasks-list" id="skillsList"><div class="spinner"></div></div>
|
|
1386
|
+
<div class="pagination" id="skillsPagination"></div>
|
|
1367
1387
|
<div id="hubSkillsSection" style="display:none;margin-top:16px">
|
|
1368
1388
|
<div class="section-title" style="margin-bottom:12px" data-i18n="skills.hub.title">\u{1F310} Team Skills</div>
|
|
1369
1389
|
<div class="tasks-list" id="hubSkillsList"></div>
|
|
@@ -2029,11 +2049,19 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
2029
2049
|
<div class="toast-container" id="toasts"></div>
|
|
2030
2050
|
|
|
2031
2051
|
<script>
|
|
2032
|
-
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=
|
|
2033
|
-
let memorySearchScope='
|
|
2052
|
+
let activeSession=null,activeRole='',editingId=null,searchTimer=null,memoryCache={},currentPage=1,totalPages=1,totalCount=0,PAGE_SIZE=20,metricsDays=30;
|
|
2053
|
+
let memorySearchScope='allLocal',skillSearchScope='allLocal',taskSearchScope='allLocal';
|
|
2034
2054
|
let _lastMemoriesFingerprint='',_lastTasksFingerprint='',_lastSkillsFingerprint='';
|
|
2035
2055
|
let _embeddingWarningShown=false;
|
|
2036
2056
|
let _currentAgentOwner='agent:main';
|
|
2057
|
+
try {
|
|
2058
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
2059
|
+
const agentId = urlParams.get('agentId');
|
|
2060
|
+
if (agentId) {
|
|
2061
|
+
_currentAgentOwner = 'agent:' + agentId;
|
|
2062
|
+
}
|
|
2063
|
+
} catch(e) {}
|
|
2064
|
+
|
|
2037
2065
|
|
|
2038
2066
|
/* ─── i18n ─── */
|
|
2039
2067
|
const I18N={
|
|
@@ -2388,6 +2416,9 @@ const I18N={
|
|
|
2388
2416
|
'skills.status.archived':'Archived',
|
|
2389
2417
|
'skills.updated':'Updated: ',
|
|
2390
2418
|
'skills.task.prefix':'Task: ',
|
|
2419
|
+
'skills.selectAll':'Select All',
|
|
2420
|
+
'skills.unselectAll':'Unselect All',
|
|
2421
|
+
'skills.deleteSelected':'Delete Selected',
|
|
2391
2422
|
'tasks.chunks.label':'chunks',
|
|
2392
2423
|
'tasks.taskid':'Task ID: ',
|
|
2393
2424
|
'tasks.role.user':'You',
|
|
@@ -2738,7 +2769,10 @@ const I18N={
|
|
|
2738
2769
|
'skill.save':'Save',
|
|
2739
2770
|
'skill.cancel':'Cancel',
|
|
2740
2771
|
'skill.delete.confirm':'Are you sure you want to delete this skill? This will also remove all associated files and cannot be undone.',
|
|
2772
|
+
'skill.delete.selected.confirm':'Delete {count} selected skills? This action cannot be undone.',
|
|
2741
2773
|
'skill.delete.error':'Failed to delete skill: ',
|
|
2774
|
+
'skill.delete.partial':'Deleted {ok} skills, failed {fail}.',
|
|
2775
|
+
'skill.delete.success':'Deleted {count} skills.',
|
|
2742
2776
|
'skill.save.error':'Failed to save skill: ',
|
|
2743
2777
|
'update.available':'New version available',
|
|
2744
2778
|
'update.run':'Run',
|
|
@@ -3132,6 +3166,9 @@ const I18N={
|
|
|
3132
3166
|
'skills.status.archived':'已归档',
|
|
3133
3167
|
'skills.updated':'更新于:',
|
|
3134
3168
|
'skills.task.prefix':'任务:',
|
|
3169
|
+
'skills.selectAll':'全选',
|
|
3170
|
+
'skills.unselectAll':'取消全选',
|
|
3171
|
+
'skills.deleteSelected':'删除选中',
|
|
3135
3172
|
'tasks.chunks.label':'条记忆',
|
|
3136
3173
|
'tasks.taskid':'任务 ID:',
|
|
3137
3174
|
'tasks.role.user':'你',
|
|
@@ -3482,7 +3519,10 @@ const I18N={
|
|
|
3482
3519
|
'skill.save':'保存',
|
|
3483
3520
|
'skill.cancel':'取消',
|
|
3484
3521
|
'skill.delete.confirm':'确定要删除此技能吗?关联的文件也会被删除,此操作不可撤销。',
|
|
3522
|
+
'skill.delete.selected.confirm':'确定删除选中的 {count} 个技能吗?此操作不可撤销。',
|
|
3485
3523
|
'skill.delete.error':'删除技能失败:',
|
|
3524
|
+
'skill.delete.partial':'已删除 {ok} 个技能,失败 {fail} 个。',
|
|
3525
|
+
'skill.delete.success':'已删除 {count} 个技能。',
|
|
3486
3526
|
'skill.save.error':'保存技能失败:',
|
|
3487
3527
|
'update.available':'发现新版本',
|
|
3488
3528
|
'update.run':'执行命令',
|
|
@@ -3786,35 +3826,79 @@ function switchView(view){
|
|
|
3786
3826
|
}
|
|
3787
3827
|
|
|
3788
3828
|
function onMemoryScopeChange(){
|
|
3789
|
-
memorySearchScope=document.getElementById('memorySearchScope')?.value||'
|
|
3829
|
+
memorySearchScope=document.getElementById('memorySearchScope')?.value||'allLocal';
|
|
3790
3830
|
try{localStorage.setItem('memos_memorySearchScope',memorySearchScope);}catch(e){}
|
|
3791
3831
|
currentPage=1;
|
|
3792
3832
|
activeSession=null;activeRole='';
|
|
3793
3833
|
_lastMemoriesFingerprint='';
|
|
3794
3834
|
var isHub=memorySearchScope==='hub';
|
|
3795
|
-
var isLocal=memorySearchScope==='local';
|
|
3796
3835
|
var ownerSel=document.getElementById('filterOwner');
|
|
3797
3836
|
var filterBar=document.getElementById('filterBar');
|
|
3798
3837
|
var dateFilter=document.querySelector('.date-filter');
|
|
3799
|
-
if(ownerSel){ownerSel.style.display=
|
|
3838
|
+
if(ownerSel){ownerSel.style.display=isHub?'none':'';if(isHub)ownerSel.value='';}
|
|
3800
3839
|
if(filterBar) filterBar.style.display=isHub?'none':'';
|
|
3801
3840
|
if(dateFilter) dateFilter.style.display=isHub?'none':'';
|
|
3802
3841
|
if(document.getElementById('searchInput').value.trim()) doSearch(document.getElementById('searchInput').value);
|
|
3803
3842
|
else if(isHub) { document.getElementById('sharingSearchMeta').textContent=''; loadHubMemories(); }
|
|
3804
3843
|
else {
|
|
3805
3844
|
document.getElementById('sharingSearchMeta').textContent='';
|
|
3806
|
-
var ownerArg=
|
|
3845
|
+
var ownerArg=undefined;
|
|
3807
3846
|
loadStats(ownerArg); loadMemories();
|
|
3808
3847
|
}
|
|
3809
3848
|
}
|
|
3810
3849
|
|
|
3850
|
+
function normalizePageSize(value,fallback){
|
|
3851
|
+
const v=Number(value);
|
|
3852
|
+
return v===10||v===20||v===40?v:fallback;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
function applyPageSizeFromSelect(selectId,storageKey,fallback,onApply){
|
|
3856
|
+
const el=document.getElementById(selectId);
|
|
3857
|
+
const next=normalizePageSize(el?.value,fallback);
|
|
3858
|
+
onApply(next);
|
|
3859
|
+
try{localStorage.setItem(storageKey,String(next));}catch(e){}
|
|
3860
|
+
return next;
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
function restorePageSizeSetting(storageKey,selectId,fallback,onApply){
|
|
3864
|
+
let next=fallback;
|
|
3865
|
+
try{
|
|
3866
|
+
const raw=localStorage.getItem(storageKey);
|
|
3867
|
+
next=normalizePageSize(raw||String(fallback),fallback);
|
|
3868
|
+
}catch(e){}
|
|
3869
|
+
onApply(next);
|
|
3870
|
+
const el=document.getElementById(selectId);
|
|
3871
|
+
if(el) el.value=String(next);
|
|
3872
|
+
return next;
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
function onMemoryPageSizeChange(){
|
|
3876
|
+
applyPageSizeFromSelect('memoryPageSize','memos_memoryPageSize',20,function(next){PAGE_SIZE=next;});
|
|
3877
|
+
currentPage=1;
|
|
3878
|
+
if(memorySearchScope==='hub') loadHubMemories();
|
|
3879
|
+
else loadMemories();
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3811
3882
|
function onSkillScopeChange(){
|
|
3812
|
-
skillSearchScope=document.getElementById('skillSearchScope')?.value||'
|
|
3883
|
+
skillSearchScope=document.getElementById('skillSearchScope')?.value||'allLocal';
|
|
3884
|
+
skillsPage=0;
|
|
3885
|
+
loadSkills();
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
function onSkillsPageSizeChange(){
|
|
3889
|
+
applyPageSizeFromSelect('skillsPageSize','memos_skillsPageSize',20,function(next){skillsPageSize=next;});
|
|
3890
|
+
skillsPage=0;
|
|
3813
3891
|
loadSkills();
|
|
3814
3892
|
}
|
|
3815
3893
|
|
|
3894
|
+
function onTasksPageSizeChange(){
|
|
3895
|
+
applyPageSizeFromSelect('tasksPageSize','memos_tasksPageSize',20,function(next){tasksPageSize=next;});
|
|
3896
|
+
tasksPage=0;
|
|
3897
|
+
loadTasks();
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3816
3900
|
function onTaskScopeChange(){
|
|
3817
|
-
taskSearchScope=document.getElementById('taskSearchScope')?.value||'
|
|
3901
|
+
taskSearchScope=document.getElementById('taskSearchScope')?.value||'allLocal';
|
|
3818
3902
|
tasksPage=0;
|
|
3819
3903
|
loadTasks();
|
|
3820
3904
|
}
|
|
@@ -5459,6 +5543,7 @@ function localMemoryErrorMessage(err){
|
|
|
5459
5543
|
|
|
5460
5544
|
function debounceSkillSearch(){
|
|
5461
5545
|
clearTimeout(skillSearchTimer);
|
|
5546
|
+
skillsPage=0;
|
|
5462
5547
|
skillSearchTimer=setTimeout(function(){loadSkills();},300);
|
|
5463
5548
|
}
|
|
5464
5549
|
|
|
@@ -5852,7 +5937,7 @@ function dateLoc(){return curLang==='zh'?'zh-CN':'en-US';}
|
|
|
5852
5937
|
/* ─── Tasks View Logic ─── */
|
|
5853
5938
|
let tasksStatusFilter='';
|
|
5854
5939
|
let tasksPage=0;
|
|
5855
|
-
|
|
5940
|
+
let tasksPageSize=20;
|
|
5856
5941
|
|
|
5857
5942
|
function setTaskStatusFilter(btn,status){
|
|
5858
5943
|
document.querySelectorAll('.tasks-filters .filter-chip').forEach(c=>c.classList.remove('active'));
|
|
@@ -5864,16 +5949,14 @@ function setTaskStatusFilter(btn,status){
|
|
|
5864
5949
|
|
|
5865
5950
|
async function loadTasks(silent){
|
|
5866
5951
|
const scope=document.getElementById('taskSearchScope')?document.getElementById('taskSearchScope').value:taskSearchScope;
|
|
5867
|
-
taskSearchScope=scope||'
|
|
5952
|
+
taskSearchScope=scope||'allLocal';
|
|
5868
5953
|
if(taskSearchScope==='hub'){ return loadHubTasks(); }
|
|
5869
5954
|
const list=document.getElementById('tasksList');
|
|
5870
5955
|
if(!silent) list.innerHTML='<div class="spinner"></div>';
|
|
5871
5956
|
try{
|
|
5872
|
-
const params=new URLSearchParams({limit:String(
|
|
5957
|
+
const params=new URLSearchParams({limit:String(tasksPageSize),offset:String(tasksPage*tasksPageSize)});
|
|
5873
5958
|
if(tasksStatusFilter) params.set('status',tasksStatusFilter);
|
|
5874
|
-
if(taskSearchScope==='local') params.set('owner','agent:main');
|
|
5875
5959
|
var baseP=new URLSearchParams();
|
|
5876
|
-
if(taskSearchScope==='local') baseP.set('owner','agent:main');
|
|
5877
5960
|
const [data,allD,activeD,compD,skipD]=await Promise.all([
|
|
5878
5961
|
fetch('/api/tasks?'+params).then(r=>r.json()),
|
|
5879
5962
|
fetch('/api/tasks?limit=1&offset=0&'+baseP).then(r=>r.json()),
|
|
@@ -5953,7 +6036,7 @@ function updateTaskCardBadge(taskId,newScope){
|
|
|
5953
6036
|
|
|
5954
6037
|
function renderTasksPagination(total){
|
|
5955
6038
|
const el=document.getElementById('tasksPagination');
|
|
5956
|
-
const pages=Math.ceil(total/
|
|
6039
|
+
const pages=Math.ceil(total/tasksPageSize);
|
|
5957
6040
|
if(pages<=1){el.innerHTML='';return;}
|
|
5958
6041
|
let html='<button class="pg-btn'+(tasksPage===0?' disabled':'')+'" onclick="tasksPage=Math.max(0,tasksPage-1);loadTasks()">\\u2190</button>';
|
|
5959
6042
|
const start=Math.max(0,tasksPage-2),end=Math.min(pages,tasksPage+3);
|
|
@@ -6150,14 +6233,54 @@ async function deleteTask(taskId){
|
|
|
6150
6233
|
|
|
6151
6234
|
/* ─── Skills View Logic ─── */
|
|
6152
6235
|
let skillsStatusFilter='';
|
|
6236
|
+
let skillsPage=0;
|
|
6237
|
+
let skillsPageSize=20;
|
|
6238
|
+
let selectedSkillIds=new Set();
|
|
6239
|
+
let currentLocalSkills=[];
|
|
6240
|
+
let skillsFilterSignature='';
|
|
6153
6241
|
|
|
6154
6242
|
function setSkillStatusFilter(btn,status){
|
|
6155
6243
|
document.querySelectorAll('.skills-view .tasks-filters .filter-chip').forEach(c=>c.classList.remove('active'));
|
|
6156
6244
|
btn.classList.add('active');
|
|
6157
6245
|
skillsStatusFilter=status;
|
|
6246
|
+
skillsPage=0;
|
|
6158
6247
|
loadSkills();
|
|
6159
6248
|
}
|
|
6160
6249
|
|
|
6250
|
+
function updateSkillSelectionToolbar(){
|
|
6251
|
+
var selectAllBtn=document.getElementById('skillSelectAllBtn');
|
|
6252
|
+
var bulkDeleteBtn=document.getElementById('skillBulkDeleteBtn');
|
|
6253
|
+
var total=currentLocalSkills.length;
|
|
6254
|
+
var selected=selectedSkillIds.size;
|
|
6255
|
+
if(selectAllBtn){
|
|
6256
|
+
selectAllBtn.textContent=t(selected>0&&selected===total&&total>0?'skills.unselectAll':'skills.selectAll');
|
|
6257
|
+
}
|
|
6258
|
+
if(bulkDeleteBtn){
|
|
6259
|
+
bulkDeleteBtn.disabled=selected===0;
|
|
6260
|
+
var base=t('skills.deleteSelected');
|
|
6261
|
+
bulkDeleteBtn.textContent=selected>0?(base+' ('+selected+')'):base;
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
function toggleSkillSelection(skillId,checked){
|
|
6266
|
+
if(checked) selectedSkillIds.add(skillId);
|
|
6267
|
+
else selectedSkillIds.delete(skillId);
|
|
6268
|
+
updateSkillSelectionToolbar();
|
|
6269
|
+
}
|
|
6270
|
+
|
|
6271
|
+
function toggleSelectAllSkills(){
|
|
6272
|
+
var total=currentLocalSkills.length;
|
|
6273
|
+
if(total===0) return;
|
|
6274
|
+
if(selectedSkillIds.size===total){
|
|
6275
|
+
selectedSkillIds.clear();
|
|
6276
|
+
}else{
|
|
6277
|
+
selectedSkillIds=new Set(currentLocalSkills.map(function(s){return s.id;}));
|
|
6278
|
+
}
|
|
6279
|
+
var checks=document.querySelectorAll('#skillsList .skill-select-check');
|
|
6280
|
+
checks.forEach(function(cb){cb.checked=selectedSkillIds.has(cb.value);});
|
|
6281
|
+
updateSkillSelectionToolbar();
|
|
6282
|
+
}
|
|
6283
|
+
|
|
6161
6284
|
function updateSkillCardBadge(skillId,newScope){
|
|
6162
6285
|
var cards=document.querySelectorAll('.skill-card');
|
|
6163
6286
|
for(var i=0;i<cards.length;i++){
|
|
@@ -6174,13 +6297,28 @@ function updateSkillCardBadge(skillId,newScope){
|
|
|
6174
6297
|
}
|
|
6175
6298
|
}
|
|
6176
6299
|
|
|
6300
|
+
function renderSkillsPagination(total){
|
|
6301
|
+
const el=document.getElementById('skillsPagination');
|
|
6302
|
+
if(!el) return;
|
|
6303
|
+
const pages=Math.ceil(total/skillsPageSize);
|
|
6304
|
+
if(pages<=1){el.innerHTML='';return;}
|
|
6305
|
+
let html='<button class="pg-btn'+(skillsPage===0?' disabled':'')+'" onclick="skillsPage=Math.max(0,skillsPage-1);loadSkills()">\\u2190</button>';
|
|
6306
|
+
const start=Math.max(0,skillsPage-2),end=Math.min(pages,skillsPage+3);
|
|
6307
|
+
for(let i=start;i<end;i++){
|
|
6308
|
+
html+='<button class="pg-btn'+(i===skillsPage?' active':'')+'" onclick="skillsPage='+i+';loadSkills()">'+(i+1)+'</button>';
|
|
6309
|
+
}
|
|
6310
|
+
html+='<button class="pg-btn'+(skillsPage>=pages-1?' disabled':'')+'" onclick="skillsPage=Math.min('+(pages-1)+',skillsPage+1);loadSkills()">\\u2192</button>';
|
|
6311
|
+
html+='<span class="pg-info">'+total+' '+t('pagination.total')+'</span>';
|
|
6312
|
+
el.innerHTML=html;
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6177
6315
|
async function loadSkills(silent){
|
|
6178
6316
|
const list=document.getElementById('skillsList');
|
|
6179
6317
|
const hubList=document.getElementById('hubSkillsList');
|
|
6180
6318
|
if(!silent) list.innerHTML='<div class="spinner"></div>';
|
|
6181
6319
|
var hubSection=document.getElementById('hubSkillsSection');
|
|
6182
6320
|
if(hubList){
|
|
6183
|
-
if(skillSearchScope==='
|
|
6321
|
+
if(skillSearchScope==='allLocal'){
|
|
6184
6322
|
if(hubSection) hubSection.style.display='none';
|
|
6185
6323
|
}else{
|
|
6186
6324
|
if(hubSection) hubSection.style.display='block';
|
|
@@ -6190,24 +6328,33 @@ async function loadSkills(silent){
|
|
|
6190
6328
|
|
|
6191
6329
|
const query=(document.getElementById('skillSearchInput')?.value||'').trim();
|
|
6192
6330
|
const scope=document.getElementById('skillSearchScope') ? document.getElementById('skillSearchScope').value : skillSearchScope;
|
|
6193
|
-
skillSearchScope=scope||'
|
|
6331
|
+
skillSearchScope=scope||'allLocal';
|
|
6194
6332
|
|
|
6195
6333
|
try{
|
|
6196
6334
|
const params=new URLSearchParams();
|
|
6197
6335
|
if(skillsStatusFilter) params.set('status',skillsStatusFilter);
|
|
6198
6336
|
const visFilter=document.getElementById('skillVisibilityFilter')?.value;
|
|
6199
6337
|
if(visFilter) params.set('visibility',visFilter);
|
|
6338
|
+
const filterSignature=[query,skillSearchScope,skillsStatusFilter,visFilter||''].join('|');
|
|
6339
|
+
if(!silent&&filterSignature!==skillsFilterSignature){
|
|
6340
|
+
skillsPage=0;
|
|
6341
|
+
}
|
|
6342
|
+
skillsFilterSignature=filterSignature;
|
|
6200
6343
|
|
|
6201
6344
|
const localRes=await fetch('/api/skills?'+params.toString());
|
|
6202
6345
|
const localData=await localRes.json();
|
|
6203
6346
|
let localSkills=Array.isArray(localData.skills)?localData.skills:[];
|
|
6347
|
+
currentLocalSkills=localSkills.slice();
|
|
6204
6348
|
if(query){
|
|
6205
6349
|
const q=query.toLowerCase();
|
|
6206
6350
|
localSkills=localSkills.filter(skill=>{
|
|
6207
6351
|
const haystack=[skill.name,skill.description,skill.tags].filter(Boolean).join(' ').toLowerCase();
|
|
6208
6352
|
return haystack.includes(q);
|
|
6209
6353
|
});
|
|
6354
|
+
currentLocalSkills=localSkills.slice();
|
|
6210
6355
|
}
|
|
6356
|
+
var localIdSet=new Set(localSkills.map(function(s){return s.id;}));
|
|
6357
|
+
selectedSkillIds=new Set(Array.from(selectedSkillIds).filter(function(id){return localIdSet.has(id);}));
|
|
6211
6358
|
if(silent){
|
|
6212
6359
|
var fp=JSON.stringify(localSkills.map(function(s){return s.id+'|'+s.status+'|'+s.version+'|'+(s.visibility||'')}));
|
|
6213
6360
|
if(fp===_lastSkillsFingerprint) return;
|
|
@@ -6231,9 +6378,10 @@ async function loadSkills(silent){
|
|
|
6231
6378
|
const skillIsLocalShared=skill.visibility==='public';
|
|
6232
6379
|
const skillIsTeamShared=!!skill.sharingVisibility;
|
|
6233
6380
|
const skillScope=skillIsTeamShared?'team':skillIsLocalShared?'local':'private';
|
|
6381
|
+
const selectedAttr=selectedSkillIds.has(skill.id)?' checked':'';
|
|
6234
6382
|
return '<div class="skill-card '+installedClass+' '+statusClass+'" onclick="openSkillDetail("'+escAttr(skill.id)+'")">'+
|
|
6235
6383
|
'<div class="skill-card-top">'+
|
|
6236
|
-
'<div class="skill-card-name">🧠 '+esc(skill.name)+'</div>'+
|
|
6384
|
+
'<div class="skill-card-name"><label class="skill-select-box" onclick="event.stopPropagation()"><input class="skill-select-check" type="checkbox" value="'+escAttr(skill.id)+'"'+selectedAttr+' onchange="event.stopPropagation();toggleSkillSelection("'+escAttr(skill.id)+'",this.checked)"></label>🧠 '+esc(skill.name)+'</div>'+
|
|
6237
6385
|
'<div class="skill-card-badges">'+
|
|
6238
6386
|
qsBadge+
|
|
6239
6387
|
'<span class="skill-badge version">v'+skill.version+'</span>'+
|
|
@@ -6249,6 +6397,7 @@ async function loadSkills(silent){
|
|
|
6249
6397
|
(tags.length>0?'<div class="skill-card-tags">'+tags.map(tg=>'<span class="skill-tag">'+esc(tg)+'</span>').join('')+'</div>':'')+
|
|
6250
6398
|
'<span class="card-actions-inline" onclick="event.stopPropagation()">'+
|
|
6251
6399
|
'<button class="btn btn-sm btn-ghost" onclick="openSkillDetail("'+escAttr(skill.id)+'")">'+t('card.expand')+'</button>'+
|
|
6400
|
+
'<button class="btn btn-sm btn-danger" onclick="deleteSkill("'+escAttr(skill.id)+'")">'+t('skill.delete')+'</button>'+
|
|
6252
6401
|
(skill.status==='active'
|
|
6253
6402
|
?'<button class="btn btn-sm btn-ghost" onclick="openSkillScopeModalFromList("'+escAttr(skill.id)+'","'+skillScope+'")">\\u270F '+t('share.shareBtn')+'</button>'
|
|
6254
6403
|
:'<button class="btn btn-sm btn-ghost" style="opacity:0.45;cursor:not-allowed" onclick="toast(t(\\x27share.scope.skillNotActive\\x27),\\x27warn\\x27)">\\u270F '+t('share.shareBtn')+'</button>')+
|
|
@@ -6258,9 +6407,16 @@ async function loadSkills(silent){
|
|
|
6258
6407
|
}).join('');
|
|
6259
6408
|
};
|
|
6260
6409
|
|
|
6261
|
-
|
|
6410
|
+
const totalLocalSkills=localSkills.length;
|
|
6411
|
+
const localPages=Math.ceil(totalLocalSkills/skillsPageSize)||1;
|
|
6412
|
+
if(skillsPage>=localPages) skillsPage=Math.max(0,localPages-1);
|
|
6413
|
+
const startIndex=skillsPage*skillsPageSize;
|
|
6414
|
+
const pageSkills=localSkills.slice(startIndex,startIndex+skillsPageSize);
|
|
6415
|
+
list.innerHTML=renderLocalCards(pageSkills);
|
|
6416
|
+
renderSkillsPagination(totalLocalSkills);
|
|
6417
|
+
updateSkillSelectionToolbar();
|
|
6262
6418
|
|
|
6263
|
-
if(skillSearchScope==='
|
|
6419
|
+
if(skillSearchScope==='allLocal'){
|
|
6264
6420
|
if(hubSection) hubSection.style.display='none';
|
|
6265
6421
|
document.getElementById('skillSearchMeta').textContent=query?(t('skills.search.local')+' '+localSkills.length):'';
|
|
6266
6422
|
document.getElementById('skillsTotalCount').textContent=formatNum(localSkills.length);
|
|
@@ -6293,6 +6449,8 @@ async function loadSkills(silent){
|
|
|
6293
6449
|
const localHits=(data.local&&Array.isArray(data.local.hits))?data.local.hits:[];
|
|
6294
6450
|
const hubHits=(data.hub&&Array.isArray(data.hub.hits))?data.hub.hits:[];
|
|
6295
6451
|
|
|
6452
|
+
const sp=document.getElementById('skillsPagination');
|
|
6453
|
+
if(sp) sp.innerHTML='';
|
|
6296
6454
|
list.innerHTML=localHits.length?localHits.map(function(skill){
|
|
6297
6455
|
return '<div class="hub-skill-card" onclick="openSkillDetail("'+escAttr(skill.skillId)+'")">'+
|
|
6298
6456
|
'<div class="summary">'+esc(skill.name)+'</div>'+
|
|
@@ -6324,8 +6482,11 @@ async function loadSkills(silent){
|
|
|
6324
6482
|
document.getElementById('skillsDraftCount').textContent='0';
|
|
6325
6483
|
document.getElementById('skillsInstalledCount').textContent='-';
|
|
6326
6484
|
document.getElementById('skillsPublicCount').textContent=formatNum(hubHits.filter(function(s){return s.visibility==='public';}).length);
|
|
6485
|
+
updateSkillSelectionToolbar();
|
|
6327
6486
|
}catch(e){
|
|
6328
6487
|
list.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">'+t('skills.load.error')+': '+esc(String(e))+'</div>';
|
|
6488
|
+
const sp=document.getElementById('skillsPagination');
|
|
6489
|
+
if(sp) sp.innerHTML='';
|
|
6329
6490
|
if(hubList){
|
|
6330
6491
|
hubList.innerHTML='<div style="text-align:center;padding:24px;color:var(--rose)">'+t('skills.load.error')+'</div>';
|
|
6331
6492
|
}
|
|
@@ -6337,7 +6498,7 @@ async function loadHubTasks(){
|
|
|
6337
6498
|
if(!list) return;
|
|
6338
6499
|
list.innerHTML='<div class="spinner"></div>';
|
|
6339
6500
|
try{
|
|
6340
|
-
var r=await fetch('/api/sharing/tasks/list?limit=
|
|
6501
|
+
var r=await fetch('/api/sharing/tasks/list?limit='+tasksPageSize);
|
|
6341
6502
|
var d=await r.json();
|
|
6342
6503
|
var tasks=Array.isArray(d.tasks)?d.tasks:[];
|
|
6343
6504
|
hubTasksCache=tasks;
|
|
@@ -7106,12 +7267,38 @@ async function deleteSkill(skillId){
|
|
|
7106
7267
|
const r=await fetch('/api/skill/'+skillId,{method:'DELETE'});
|
|
7107
7268
|
const d=await r.json();
|
|
7108
7269
|
if(!r.ok) throw new Error(d.error||'unknown');
|
|
7270
|
+
selectedSkillIds.delete(skillId);
|
|
7271
|
+
updateSkillSelectionToolbar();
|
|
7109
7272
|
closeSkillDetail();
|
|
7110
7273
|
document.getElementById('skillDetailOverlay').classList.remove('show');
|
|
7111
7274
|
loadSkills();
|
|
7112
7275
|
}catch(e){ alert(t('skill.delete.error')+e.message); }
|
|
7113
7276
|
}
|
|
7114
7277
|
|
|
7278
|
+
async function deleteSelectedSkills(){
|
|
7279
|
+
var ids=Array.from(selectedSkillIds);
|
|
7280
|
+
if(ids.length===0) return;
|
|
7281
|
+
var msg=t('skill.delete.selected.confirm').replace('{count}',String(ids.length));
|
|
7282
|
+
if(!(await confirmModal(msg,{danger:true}))) return;
|
|
7283
|
+
var ok=0;
|
|
7284
|
+
var fail=0;
|
|
7285
|
+
for(var i=0;i<ids.length;i++){
|
|
7286
|
+
try{
|
|
7287
|
+
var r=await fetch('/api/skill/'+ids[i],{method:'DELETE'});
|
|
7288
|
+
var d=await r.json();
|
|
7289
|
+
if(!r.ok) throw new Error(d.error||'unknown');
|
|
7290
|
+
ok++;
|
|
7291
|
+
}catch(e){
|
|
7292
|
+
fail++;
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
selectedSkillIds.clear();
|
|
7296
|
+
updateSkillSelectionToolbar();
|
|
7297
|
+
loadSkills();
|
|
7298
|
+
if(fail>0) toast(t('skill.delete.partial').replace('{ok}',String(ok)).replace('{fail}',String(fail)),'warn');
|
|
7299
|
+
else toast(t('skill.delete.success').replace('{count}',String(ok)),'success');
|
|
7300
|
+
}
|
|
7301
|
+
|
|
7115
7302
|
|
|
7116
7303
|
function formatDuration(ms){
|
|
7117
7304
|
const s=Math.floor(ms/1000);
|
|
@@ -7445,7 +7632,7 @@ async function _livePollTick(){
|
|
|
7445
7632
|
var _searchVal=(document.getElementById('searchInput')||{}).value||'';
|
|
7446
7633
|
if(!_searchVal.trim()){
|
|
7447
7634
|
if(memorySearchScope==='hub') await loadHubMemories(true);
|
|
7448
|
-
else{var _pollOwner=
|
|
7635
|
+
else{var _pollOwner=undefined;await loadStats(_pollOwner);await loadMemories(null,true);}
|
|
7449
7636
|
}
|
|
7450
7637
|
}
|
|
7451
7638
|
else if(_activeView==='tasks') await loadTasks(true);
|
|
@@ -7718,7 +7905,7 @@ function stopNotifPoll(){ }
|
|
|
7718
7905
|
/* ─── Data loading ─── */
|
|
7719
7906
|
async function loadAll(){
|
|
7720
7907
|
await loadStats();
|
|
7721
|
-
var initOwner=
|
|
7908
|
+
var initOwner=undefined;
|
|
7722
7909
|
if(initOwner) await loadStats(initOwner);
|
|
7723
7910
|
await Promise.all([loadMemories(),loadSharingStatus(false)]);
|
|
7724
7911
|
checkMigrateStatus();
|
|
@@ -7738,7 +7925,7 @@ async function loadStats(ownerFilter){
|
|
|
7738
7925
|
d=await r.json();
|
|
7739
7926
|
}catch(e){ d={}; }
|
|
7740
7927
|
if(!d||typeof d!=='object') d={};
|
|
7741
|
-
if(d.currentAgentOwner) _currentAgentOwner=d.currentAgentOwner;
|
|
7928
|
+
if(d.currentAgentOwner && !new URLSearchParams(window.location.search).get('agentId')) _currentAgentOwner=d.currentAgentOwner;
|
|
7742
7929
|
const tm=d.totalMemories||0;
|
|
7743
7930
|
const dedupB=d.dedupBreakdown||{};
|
|
7744
7931
|
const activeCount=dedupB.active||tm;
|
|
@@ -7845,12 +8032,13 @@ function getFilterParams(){
|
|
|
7845
8032
|
if(dt) p.set('dateTo',dt);
|
|
7846
8033
|
const sort=document.getElementById('filterSort').value;
|
|
7847
8034
|
if(sort==='oldest') p.set('sort','oldest');
|
|
7848
|
-
const scope=memorySearchScope||'
|
|
7849
|
-
if(scope==='
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
8035
|
+
const scope=memorySearchScope||'allLocal';
|
|
8036
|
+
if(scope==='allLocal'){
|
|
8037
|
+
const owner=document.getElementById('filterOwner').value;
|
|
8038
|
+
if(owner) {
|
|
8039
|
+
p.set('owner',owner);
|
|
8040
|
+
_currentAgentOwner = owner;
|
|
8041
|
+
}
|
|
7854
8042
|
}
|
|
7855
8043
|
return p;
|
|
7856
8044
|
}
|
|
@@ -7947,7 +8135,7 @@ async function doSearch(query){
|
|
|
7947
8135
|
return;
|
|
7948
8136
|
}
|
|
7949
8137
|
currentPage=1;
|
|
7950
|
-
var scope=document.getElementById('memorySearchScope')?.value||memorySearchScope||'
|
|
8138
|
+
var scope=document.getElementById('memorySearchScope')?.value||memorySearchScope||'allLocal';
|
|
7951
8139
|
var list=document.getElementById('memoryList');
|
|
7952
8140
|
list.innerHTML='<div class="spinner"></div>';
|
|
7953
8141
|
if(scope==='hub'){
|
|
@@ -9126,12 +9314,16 @@ async function checkForUpdate(){
|
|
|
9126
9314
|
/* ─── Init ─── */
|
|
9127
9315
|
try{
|
|
9128
9316
|
var savedScope=localStorage.getItem('memos_memorySearchScope');
|
|
9129
|
-
if(savedScope
|
|
9317
|
+
if(savedScope==='local') savedScope='allLocal';
|
|
9318
|
+
if(savedScope&&(savedScope==='allLocal'||savedScope==='hub')){
|
|
9130
9319
|
memorySearchScope=savedScope;
|
|
9131
9320
|
var scopeEl=document.getElementById('memorySearchScope');
|
|
9132
9321
|
if(scopeEl) scopeEl.value=savedScope;
|
|
9133
9322
|
}
|
|
9134
9323
|
}catch(e){}
|
|
9324
|
+
restorePageSizeSetting('memos_memoryPageSize','memoryPageSize',20,function(next){PAGE_SIZE=next;});
|
|
9325
|
+
restorePageSizeSetting('memos_skillsPageSize','skillsPageSize',20,function(next){skillsPageSize=next;});
|
|
9326
|
+
restorePageSizeSetting('memos_tasksPageSize','tasksPageSize',20,function(next){tasksPageSize=next;});
|
|
9135
9327
|
document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
|
|
9136
9328
|
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';currentPage=1;if(memorySearchScope==='hub')loadHubMemories();else loadMemories();}});
|
|
9137
9329
|
applyI18n();
|
package/src/viewer/server.ts
CHANGED
|
@@ -3258,7 +3258,8 @@ export class ViewerServer {
|
|
|
3258
3258
|
const providerCfg = providerKey
|
|
3259
3259
|
? raw?.models?.providers?.[providerKey]
|
|
3260
3260
|
: Object.values(raw?.models?.providers ?? {})[0] as Record<string, unknown> | undefined;
|
|
3261
|
-
|
|
3261
|
+
const resolvedKey = ViewerServer.resolveApiKeyValue(providerCfg?.apiKey);
|
|
3262
|
+
if (!providerCfg || !providerCfg.baseUrl || !resolvedKey) {
|
|
3262
3263
|
this.jsonResponse(res, { available: false });
|
|
3263
3264
|
return;
|
|
3264
3265
|
}
|
|
@@ -3268,6 +3269,17 @@ export class ViewerServer {
|
|
|
3268
3269
|
}
|
|
3269
3270
|
}
|
|
3270
3271
|
|
|
3272
|
+
private static resolveApiKeyValue(
|
|
3273
|
+
input: unknown,
|
|
3274
|
+
): string | undefined {
|
|
3275
|
+
if (!input) return undefined;
|
|
3276
|
+
if (typeof input === "string") return input;
|
|
3277
|
+
if (typeof input === "object" && input !== null && (input as any).source === "env") {
|
|
3278
|
+
return process.env[(input as any).id];
|
|
3279
|
+
}
|
|
3280
|
+
return undefined;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3271
3283
|
private findPluginPackageJson(): string | null {
|
|
3272
3284
|
let dir = __dirname;
|
|
3273
3285
|
for (let i = 0; i < 6; i++) {
|
|
@@ -3455,7 +3467,7 @@ export class ViewerServer {
|
|
|
3455
3467
|
|
|
3456
3468
|
this.log.info(`update-install: running postinstall...`);
|
|
3457
3469
|
execFile(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
|
|
3458
|
-
|
|
3470
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
3459
3471
|
|
|
3460
3472
|
if (postErr) {
|
|
3461
3473
|
this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
|
|
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
|
-
}
|