@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 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, context?: 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, context?: 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, context?: 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 url = `http://127.0.0.1:${viewerPort}`;
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 either with local OpenClaw agents, to the Hub team, or to both targets. " +
1405
- "Use this only for an existing chunkId. Use target='agents' for local multi-agent sharing, target='hub' for team sharing, or target='both' for both. " +
1406
- "If you need to create a brand new shared memory instead of exposing an existing one, use memory_write_public.",
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, context?: 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 currentOwner = getCurrentOwner();
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
- let query = rawPrompt;
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.7",
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": ">=22.0.0"
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.2",
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"
@@ -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: ${process.version} Platform: ${process.platform}-${process.arch}`);
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 prevVer = "";
66
- try { prevVer = fs.readFileSync(markerPath, "utf-8").trim(); } catch { /* first install */ }
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 (prevVer === installedVer) {
69
- log(`Version unchanged (${installedVer}), skipping artifact cleanup.`);
84
+ if (prevFingerprint === currentFingerprint) {
85
+ log(`Version unchanged (${currentFingerprint}), skipping artifact cleanup.`);
70
86
  return;
71
87
  }
72
88
 
73
- if (prevVer) {
74
- log(`Upgrade detected: ${DIM}${prevVer}${RESET} → ${GREEN}${installedVer}${RESET}`);
75
- } else {
76
- log(`Fresh install: ${GREEN}${installedVer}${RESET}`);
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
- try { fs.writeFileSync(markerPath, installedVer + "\n", "utf-8"); } catch { /* ignore */ }
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} ${YELLOW}║${RESET}
424
- ${YELLOW} ║${RESET} This plugin requires C/C++ build tools to compile ${YELLOW}║${RESET}
425
- ${YELLOW} ║${RESET} the SQLite native module on first install. ${YELLOW}║${RESET}
426
- ${YELLOW} ║${RESET} ${YELLOW}║${RESET}
427
- ${YELLOW} ║${RESET} ${BOLD}Install build tools:${RESET} ${YELLOW}║${RESET}
428
- ${YELLOW} ║${RESET} ${YELLOW}║${RESET}
429
- ${YELLOW} ║${RESET} ${CYAN}macOS:${RESET} xcode-select --install ${YELLOW}║${RESET}
430
- ${YELLOW} ║${RESET} ${CYAN}Ubuntu:${RESET} sudo apt install build-essential python3 ${YELLOW}║${RESET}
431
- ${YELLOW} ║${RESET} ${CYAN}Windows:${RESET} npm install -g windows-build-tools ${YELLOW}║${RESET}
432
- ${YELLOW} ║${RESET} ${YELLOW}║${RESET}
433
- ${YELLOW} ║${RESET} ${BOLD}Then retry:${RESET} ${YELLOW}║${RESET}
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} ${YELLOW}║${RESET}
436
- ${YELLOW} ║${RESET} ${GREEN}openclaw gateway stop && openclaw gateway start${RESET} ${YELLOW}║${RESET}
437
- ${YELLOW} ║${RESET} ${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:** You already have a useful memory chunk and want to expose it beyond the current agent.
77
- - **Do not use when:** You are creating a new shared note from scratch. In that case use `memory_write_public`.
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: string | undefined = providerCfg.apiKey;
84
+ const apiKey = resolveApiKey(providerCfg.apiKey);
72
85
  if (!baseUrl || !apiKey) return undefined;
73
86
 
74
87
  const provider = detectProvider(providerKey, baseUrl);
@@ -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,
@@ -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: string | undefined = providerCfg.apiKey;
72
+ const apiKey = resolveApiKey(providerCfg.apiKey);
60
73
  if (!baseUrl || !apiKey) return undefined;
61
74
 
62
75
  const provider = detectProvider(providerKey, baseUrl);
@@ -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 }>;
@@ -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=40,metricsDays=30;
2033
- let memorySearchScope='local',skillSearchScope='local',taskSearchScope='local';
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||'local';
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=(isHub||isLocal)?'none':'';if(isHub||isLocal)ownerSel.value='';}
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=isLocal?_currentAgentOwner:undefined;
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||'local';
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||'local';
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
- const TASKS_PER_PAGE=20;
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||'local';
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(TASKS_PER_PAGE),offset:String(tasksPage*TASKS_PER_PAGE)});
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/TASKS_PER_PAGE);
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==='local'||skillSearchScope==='allLocal'){
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||'local';
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(&quot;'+escAttr(skill.id)+'&quot;)">'+
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(&quot;'+escAttr(skill.id)+'&quot;,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(&quot;'+escAttr(skill.id)+'&quot;)">'+t('card.expand')+'</button>'+
6400
+ '<button class="btn btn-sm btn-danger" onclick="deleteSkill(&quot;'+escAttr(skill.id)+'&quot;)">'+t('skill.delete')+'</button>'+
6252
6401
  (skill.status==='active'
6253
6402
  ?'<button class="btn btn-sm btn-ghost" onclick="openSkillScopeModalFromList(&quot;'+escAttr(skill.id)+'&quot;,&quot;'+skillScope+'&quot;)">\\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
- list.innerHTML=renderLocalCards(localSkills);
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==='local'||skillSearchScope==='allLocal'){
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(&quot;'+escAttr(skill.skillId)+'&quot;)">'+
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=40');
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=memorySearchScope==='local'?_currentAgentOwner:undefined;await loadStats(_pollOwner);await loadMemories(null,true);}
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=memorySearchScope==='local'?_currentAgentOwner:undefined;
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||'local';
7849
- if(scope==='local'){
7850
- p.set('owner',_currentAgentOwner);
7851
- }else if(scope==='allLocal'){
7852
- const owner=document.getElementById('filterOwner').value;
7853
- if(owner) p.set('owner',owner);
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||'local';
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&&(savedScope==='local'||savedScope==='allLocal'||savedScope==='hub')){
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();
@@ -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
- if (!providerCfg || !providerCfg.baseUrl || !providerCfg.apiKey) {
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
- cleanupTmpDir();
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}`);
@@ -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
- }