@memtensor/memos-local-openclaw-plugin 1.0.8-beta.3 → 1.0.8-beta.5
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/index.ts +56 -37
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/hub/server.ts +8 -2
- package/src/storage/sqlite.ts +5 -0
- package/src/viewer/html.ts +70 -29
- package/src/viewer/server.ts +53 -11
package/index.ts
CHANGED
|
@@ -2339,48 +2339,54 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2339
2339
|
|
|
2340
2340
|
// ─── Service lifecycle ───
|
|
2341
2341
|
|
|
2342
|
-
|
|
2343
|
-
id: "memos-local-openclaw-plugin",
|
|
2344
|
-
start: async () => {
|
|
2345
|
-
if (hubServer) {
|
|
2346
|
-
const hubUrl = await hubServer.start();
|
|
2347
|
-
api.logger.info(`memos-local: hub started at ${hubUrl}`);
|
|
2348
|
-
}
|
|
2342
|
+
let serviceStarted = false;
|
|
2349
2343
|
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
const session = await connectToHub(store, ctx.config, ctx.log);
|
|
2354
|
-
api.logger.info(`memos-local: connected to Hub as "${session.username}" (${session.userId})`);
|
|
2355
|
-
} catch (err) {
|
|
2356
|
-
api.logger.warn(`memos-local: Hub connection failed: ${err}`);
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2344
|
+
const startServiceCore = async () => {
|
|
2345
|
+
if (serviceStarted) return;
|
|
2346
|
+
serviceStarted = true;
|
|
2359
2347
|
|
|
2348
|
+
if (hubServer) {
|
|
2349
|
+
const hubUrl = await hubServer.start();
|
|
2350
|
+
api.logger.info(`memos-local: hub started at ${hubUrl}`);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
if (ctx.config.sharing?.enabled && ctx.config.sharing.role === "client") {
|
|
2360
2354
|
try {
|
|
2361
|
-
const
|
|
2362
|
-
api.logger.info(`memos-local:
|
|
2363
|
-
api.logger.info(`╔══════════════════════════════════════════╗`);
|
|
2364
|
-
api.logger.info(`║ MemOS Memory Viewer ║`);
|
|
2365
|
-
api.logger.info(`║ → ${viewerUrl.padEnd(37)}║`);
|
|
2366
|
-
api.logger.info(`║ Open in browser to manage memories ║`);
|
|
2367
|
-
api.logger.info(`╚══════════════════════════════════════════╝`);
|
|
2368
|
-
api.logger.info(`memos-local: password reset token: ${viewer.getResetToken()}`);
|
|
2369
|
-
api.logger.info(`memos-local: forgot password? Use the reset token on the login page.`);
|
|
2370
|
-
skillEvolver.recoverOrphanedTasks().then((count) => {
|
|
2371
|
-
if (count > 0) api.logger.info(`memos-local: recovered ${count} orphaned skill tasks`);
|
|
2372
|
-
}).catch((err) => {
|
|
2373
|
-
api.logger.warn(`memos-local: skill recovery failed: ${err}`);
|
|
2374
|
-
});
|
|
2355
|
+
const session = await connectToHub(store, ctx.config, ctx.log);
|
|
2356
|
+
api.logger.info(`memos-local: connected to Hub as "${session.username}" (${session.userId})`);
|
|
2375
2357
|
} catch (err) {
|
|
2376
|
-
api.logger.warn(`memos-local:
|
|
2377
|
-
api.logger.info(`memos-local: started (embedding: ${embedder.provider})`);
|
|
2358
|
+
api.logger.warn(`memos-local: Hub connection failed: ${err}`);
|
|
2378
2359
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
);
|
|
2383
|
-
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
try {
|
|
2363
|
+
const viewerUrl = await viewer.start();
|
|
2364
|
+
api.logger.info(`memos-local: started (embedding: ${embedder.provider})`);
|
|
2365
|
+
api.logger.info(`╔══════════════════════════════════════════╗`);
|
|
2366
|
+
api.logger.info(`║ MemOS Memory Viewer ║`);
|
|
2367
|
+
api.logger.info(`║ → ${viewerUrl.padEnd(37)}║`);
|
|
2368
|
+
api.logger.info(`║ Open in browser to manage memories ║`);
|
|
2369
|
+
api.logger.info(`╚══════════════════════════════════════════╝`);
|
|
2370
|
+
api.logger.info(`memos-local: password reset token: ${viewer.getResetToken()}`);
|
|
2371
|
+
api.logger.info(`memos-local: forgot password? Use the reset token on the login page.`);
|
|
2372
|
+
skillEvolver.recoverOrphanedTasks().then((count) => {
|
|
2373
|
+
if (count > 0) api.logger.info(`memos-local: recovered ${count} orphaned skill tasks`);
|
|
2374
|
+
}).catch((err) => {
|
|
2375
|
+
api.logger.warn(`memos-local: skill recovery failed: ${err}`);
|
|
2376
|
+
});
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
api.logger.warn(`memos-local: viewer failed to start: ${err}`);
|
|
2379
|
+
api.logger.info(`memos-local: started (embedding: ${embedder.provider})`);
|
|
2380
|
+
}
|
|
2381
|
+
telemetry.trackPluginStarted(
|
|
2382
|
+
ctx.config.embedding?.provider ?? "local",
|
|
2383
|
+
ctx.config.summarizer?.provider ?? "none",
|
|
2384
|
+
);
|
|
2385
|
+
};
|
|
2386
|
+
|
|
2387
|
+
api.registerService({
|
|
2388
|
+
id: "memos-local-openclaw-plugin",
|
|
2389
|
+
start: async () => { await startServiceCore(); },
|
|
2384
2390
|
stop: async () => {
|
|
2385
2391
|
await worker.flush();
|
|
2386
2392
|
await telemetry.shutdown();
|
|
@@ -2390,6 +2396,19 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2390
2396
|
api.logger.info("memos-local: stopped");
|
|
2391
2397
|
},
|
|
2392
2398
|
});
|
|
2399
|
+
|
|
2400
|
+
// Fallback: OpenClaw may load this plugin via deferred reload after
|
|
2401
|
+
// startPluginServices has already run, so service.start() never fires.
|
|
2402
|
+
// Self-start the viewer after a grace period if it hasn't been started.
|
|
2403
|
+
const SELF_START_DELAY_MS = 3000;
|
|
2404
|
+
setTimeout(() => {
|
|
2405
|
+
if (!serviceStarted) {
|
|
2406
|
+
api.logger.info("memos-local: service.start() not called by host, self-starting viewer...");
|
|
2407
|
+
startServiceCore().catch((err) => {
|
|
2408
|
+
api.logger.warn(`memos-local: self-start failed: ${err}`);
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
}, SELF_START_DELAY_MS);
|
|
2393
2412
|
},
|
|
2394
2413
|
};
|
|
2395
2414
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "MemOS Local Memory",
|
|
4
4
|
"description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency), task summarization, skill evolution, and team sharing (Hub-Client). Provides memory_search, memory_get, task_summary, skill_search, task_share, network_skill_pull, network_team_info, memory_viewer for layered retrieval and team collaboration.",
|
|
5
5
|
"kind": "memory",
|
|
6
|
-
"version": "1.0.8-beta.
|
|
6
|
+
"version": "1.0.8-beta.5",
|
|
7
7
|
"skills": [
|
|
8
8
|
"skill/memos-memory-guide"
|
|
9
9
|
],
|
package/package.json
CHANGED
package/src/hub/server.ts
CHANGED
|
@@ -714,9 +714,14 @@ export class HubServer {
|
|
|
714
714
|
|
|
715
715
|
// Track which IDs are memories vs chunks
|
|
716
716
|
const memoryIdSet = new Set(memFtsHits.map(({ hit }) => hit.id));
|
|
717
|
+
const ftsHitIdSet = new Set<string>();
|
|
718
|
+
for (const { hit } of ftsHits) ftsHitIdSet.add(hit.id);
|
|
719
|
+
for (const { hit } of memFtsHits) ftsHitIdSet.add(hit.id);
|
|
717
720
|
|
|
718
721
|
// Two-stage retrieval: FTS candidates first, then embed + cosine rerank
|
|
719
722
|
let mergedIds: string[];
|
|
723
|
+
/** Vector RRF channel: require min cosine similarity unless id is already an FTS hit. */
|
|
724
|
+
const MIN_VECTOR_SIM = 0.45;
|
|
720
725
|
if (this.opts.embedder) {
|
|
721
726
|
try {
|
|
722
727
|
const [queryVec] = await this.opts.embedder.embed([query]);
|
|
@@ -739,8 +744,9 @@ export class HubServer {
|
|
|
739
744
|
memoryIdSet.add(e.memoryId);
|
|
740
745
|
}
|
|
741
746
|
|
|
742
|
-
scored.
|
|
743
|
-
|
|
747
|
+
const vecCandidates = scored.filter((s) => s.score >= MIN_VECTOR_SIM || ftsHitIdSet.has(s.id));
|
|
748
|
+
vecCandidates.sort((a, b) => b.score - a.score);
|
|
749
|
+
const topScored = vecCandidates.slice(0, maxResults * 2);
|
|
744
750
|
|
|
745
751
|
const K = 60;
|
|
746
752
|
const rrfScores = new Map<string, number>();
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -2724,6 +2724,11 @@ export class SqliteStore {
|
|
|
2724
2724
|
this.db.prepare("UPDATE local_shared_tasks SET hub_task_id = '', hub_instance_id = '', visibility = 'public', group_id = NULL, synced_chunks = 0 WHERE task_id = ?").run(taskId);
|
|
2725
2725
|
}
|
|
2726
2726
|
|
|
2727
|
+
/** Client UI: remove team_shared_chunks rows for all chunks linked to this task (list badge chunk fallback). */
|
|
2728
|
+
clearTeamSharedChunksForTask(taskId: string): void {
|
|
2729
|
+
this.db.prepare("DELETE FROM team_shared_chunks WHERE chunk_id IN (SELECT id FROM chunks WHERE task_id = ?)").run(taskId);
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2727
2732
|
clearAllTeamSharingState(): void {
|
|
2728
2733
|
this.clearTeamSharedChunks();
|
|
2729
2734
|
this.clearTeamSharedSkills();
|
package/src/viewer/html.ts
CHANGED
|
@@ -44,6 +44,7 @@ html{overflow-y:scroll}
|
|
|
44
44
|
[data-theme="light"] .auth-card{box-shadow:0 25px 50px -12px rgba(0,0,0,.08)}
|
|
45
45
|
[data-theme="light"] .topbar{background:rgba(255,255,255,.92);border-bottom-color:var(--border);backdrop-filter:blur(8px)}
|
|
46
46
|
[data-theme="light"] .session-item .count,[data-theme="light"] .session-tag{background:rgba(0,0,0,.05)}
|
|
47
|
+
[data-theme="light"] .owner-tag{background:rgba(99,102,241,.08);border-color:rgba(99,102,241,.18)}
|
|
47
48
|
[data-theme="light"] .card-content pre{background:#f3f4f6;border-color:var(--border)}
|
|
48
49
|
[data-theme="light"] .vscore-badge{background:rgba(79,70,229,.06);color:#4f46e5}
|
|
49
50
|
[data-theme="light"] ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15)}
|
|
@@ -389,6 +390,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
389
390
|
.role-tag.system{background:var(--amber-bg);color:var(--amber);border:1px solid rgba(245,158,11,.2)}
|
|
390
391
|
.card-time{font-size:12px;color:var(--text-sec);display:flex;align-items:center;gap:8px}
|
|
391
392
|
.session-tag{font-size:11px;font-family:ui-monospace,monospace;color:var(--text-muted);background:rgba(0,0,0,.2);padding:3px 8px;border-radius:6px;cursor:default}
|
|
393
|
+
.owner-tag{font-size:11px;font-weight:600;color:var(--pri);background:var(--pri-glow);padding:3px 9px;border-radius:8px;border:1px solid rgba(99,102,241,.15);cursor:default;white-space:nowrap}
|
|
392
394
|
.card-summary{font-size:15px;font-weight:600;color:var(--text);margin-bottom:10px;line-height:1.5;letter-spacing:-.01em;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
|
393
395
|
.card-content{font-size:13px;color:var(--text-sec);line-height:1.65;max-height:0;overflow:hidden;transition:max-height .3s ease}
|
|
394
396
|
.card-content.show{max-height:600px;overflow-y:auto}
|
|
@@ -1818,7 +1820,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1818
1820
|
</div>
|
|
1819
1821
|
<div class="settings-field">
|
|
1820
1822
|
<label data-i18n="settings.taskAutoFinalize">Task Auto-Finalize (hours)</label>
|
|
1821
|
-
<input type="number" id="cfgTaskAutoFinalizeHours" placeholder="4" min="0" step="1" style="max-width:120px">
|
|
1823
|
+
<input type="number" id="cfgTaskAutoFinalizeHours" placeholder="4" min="0" step="1" style="max-width:120px" onkeydown="if(['-','e','E','+'].includes(event.key))event.preventDefault()" oninput="this.value=this.value.replace(/[^0-9]/g,'')">
|
|
1822
1824
|
<div class="field-hint" data-i18n="settings.taskAutoFinalize.hint">Active tasks with no new messages beyond this duration will be automatically summarized and completed when the Tasks page is opened. Set to 0 to disable. Default: 4 hours.</div>
|
|
1823
1825
|
</div>
|
|
1824
1826
|
</div>
|
|
@@ -1901,18 +1903,20 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1901
1903
|
</div>
|
|
1902
1904
|
</div>
|
|
1903
1905
|
|
|
1904
|
-
<div id="migrateActions" style="display:flex;gap:
|
|
1906
|
+
<div id="migrateActions" style="display:flex;gap:10px 16px;align-items:center;flex-wrap:wrap">
|
|
1905
1907
|
<button class="btn" onclick="migrateScan(true)" id="migrateScanBtn" style="background:var(--bg);border:1px solid var(--border);color:var(--text);font-weight:600;padding:7px 18px;cursor:pointer" data-i18n="migrate.scan">Scan Data Sources</button>
|
|
1906
|
-
<
|
|
1907
|
-
|
|
1908
|
-
<span style="
|
|
1909
|
-
|
|
1910
|
-
<
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1908
|
+
<div id="migrateStartConcurrencyWrap" style="display:none;align-items:center;gap:12px;flex-wrap:wrap;flex-shrink:0">
|
|
1909
|
+
<button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:inline-flex" data-i18n="migrate.start">Start Import</button>
|
|
1910
|
+
<span id="migrateConcurrencyRow" style="display:flex;align-items:center;gap:8px;flex-shrink:0;min-width:max-content">
|
|
1911
|
+
<span style="font-size:11px;color:var(--text-muted);white-space:nowrap" data-i18n="migrate.concurrency.label">Concurrent agents</span>
|
|
1912
|
+
<select id="migrateConcurrency" class="filter-select" style="min-width:auto;padding:3px 28px 3px 10px;font-size:11px">
|
|
1913
|
+
<option value="1" selected>1</option>
|
|
1914
|
+
<option value="2">2</option>
|
|
1915
|
+
<option value="4">4</option>
|
|
1916
|
+
<option value="8">8</option>
|
|
1917
|
+
</select>
|
|
1918
|
+
</span>
|
|
1919
|
+
</div>
|
|
1916
1920
|
<span id="migrateStatus" style="font-size:11px;color:var(--text-muted)"></span>
|
|
1917
1921
|
</div>
|
|
1918
1922
|
<div id="migrateConcurrencyWarn" style="display:none;margin-top:8px;padding:8px 12px;background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.2);border-radius:8px;font-size:11px;color:#f59e0b;line-height:1.5">
|
|
@@ -1945,7 +1949,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1945
1949
|
<button class="btn btn-sm" id="ppStopBtn" onclick="ppStop()" style="display:none;background:rgba(239,68,68,.12);color:#ef4444;border:1px solid rgba(239,68,68,.3);font-size:12px;padding:5px 16px;font-weight:600" data-i18n="migrate.stop">\u25A0 Stop</button>
|
|
1946
1950
|
<span style="display:inline-flex;align-items:center;gap:6px">
|
|
1947
1951
|
<span style="font-size:11px;color:var(--text-muted)" data-i18n="pp.concurrency.label">Concurrent agents</span>
|
|
1948
|
-
<select id="ppConcurrency" class="filter-select" style="min-width:auto;padding:3px 10px;font-size:11px">
|
|
1952
|
+
<select id="ppConcurrency" class="filter-select" style="min-width:auto;padding:3px 28px 3px 10px;font-size:11px">
|
|
1949
1953
|
<option value="1" selected>1</option>
|
|
1950
1954
|
<option value="2">2</option>
|
|
1951
1955
|
<option value="4">4</option>
|
|
@@ -5352,6 +5356,42 @@ function openHubSkillDetailFromCache(cacheKey,idx){
|
|
|
5352
5356
|
|
|
5353
5357
|
function escAttr(s){return String(s||'').replace(/&/g,'&').replace(/'/g,''').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>');}
|
|
5354
5358
|
|
|
5359
|
+
function fmtAgentName(owner){
|
|
5360
|
+
if(!owner||owner==='public') return '';
|
|
5361
|
+
var s=String(owner);
|
|
5362
|
+
if(s.startsWith('agent:')) s=s.slice(6);
|
|
5363
|
+
return s;
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5366
|
+
function fmtSessionDisplay(sid){
|
|
5367
|
+
if(!sid) return '';
|
|
5368
|
+
if(sid.startsWith('agent:')){
|
|
5369
|
+
var parts=sid.split(':');
|
|
5370
|
+
// agent:{agentId}:import → "📥 import"
|
|
5371
|
+
if(parts.length===3 && parts[2]==='import') return '\\u{1F4E5} import';
|
|
5372
|
+
// agent:{agentId}:session:{sessionId} → shortened sessionId
|
|
5373
|
+
if(parts.length>=4 && parts[2]==='session'){
|
|
5374
|
+
var sessId=parts.slice(3).join(':');
|
|
5375
|
+
return sessId.length>12?sessId.slice(0,6)+'..'+sessId.slice(-4):sessId;
|
|
5376
|
+
}
|
|
5377
|
+
// agent:{agentId}:{other} → show from second part (e.g. "work:main")
|
|
5378
|
+
return parts.slice(1).join(':');
|
|
5379
|
+
}
|
|
5380
|
+
// Legacy formats
|
|
5381
|
+
if(sid.startsWith('openclaw-import-')) return '\\u{1F4E5} '+sid.slice(16);
|
|
5382
|
+
if(sid.startsWith('openclaw-session-')){
|
|
5383
|
+
var id=sid.slice(17);
|
|
5384
|
+
return id.length>12?id.slice(0,6)+'..'+id.slice(-4):id;
|
|
5385
|
+
}
|
|
5386
|
+
if(sid.length>20) return sid.slice(0,8)+'..'+sid.slice(-6);
|
|
5387
|
+
return sid;
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5390
|
+
function isImportedSession(sid){
|
|
5391
|
+
if(!sid) return false;
|
|
5392
|
+
return sid.startsWith('openclaw-import-')||sid.startsWith('openclaw-session-')||/^agent:[^:]+:(import|session:)/.test(sid);
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5355
5395
|
/* ─── Unified Sharing Scope Selector ─── */
|
|
5356
5396
|
|
|
5357
5397
|
function getScopeLabel(scope){
|
|
@@ -5494,9 +5534,9 @@ function openTaskScopeModal(){
|
|
|
5494
5534
|
var isTeamShared=!!(task.sharingVisibility||task.hubTaskId);
|
|
5495
5535
|
var cs=isTeamShared?'team':isLocalShared?'local':'private';
|
|
5496
5536
|
openScopeSelectorModal('task',task.id,cs,function(s){
|
|
5497
|
-
if(s==='team'){task.sharingVisibility='public';task.hubTaskId=
|
|
5498
|
-
else if(s==='local'){task.sharingVisibility=null;task.owner='public';}
|
|
5499
|
-
else{task.sharingVisibility=null;task.owner=task._origOwner||'agent:main';}
|
|
5537
|
+
if(s==='team'){task.sharingVisibility='public';task.hubTaskId=true;}
|
|
5538
|
+
else if(s==='local'){task.sharingVisibility=null;task.hubTaskId=false;task.owner='public';}
|
|
5539
|
+
else{task.sharingVisibility=null;task.hubTaskId=false;task.owner=task._origOwner||'agent:main';}
|
|
5500
5540
|
renderTaskShareActions(task);
|
|
5501
5541
|
updateTaskCardBadge(task.id,s);
|
|
5502
5542
|
});
|
|
@@ -5513,7 +5553,7 @@ async function shareCurrentTask(){
|
|
|
5513
5553
|
try{
|
|
5514
5554
|
const r=await fetch('/api/sharing/tasks/share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({taskId:currentTaskDetail.id,visibility:visibility})});
|
|
5515
5555
|
const d=await r.json();
|
|
5516
|
-
if(d.ok||d.shared){toast(t('toast.taskShared'),'success');currentTaskDetail.sharingVisibility=visibility;renderTaskShareActions(currentTaskDetail);} else {toast(d.error||t('toast.taskShareFail'),'error');}
|
|
5556
|
+
if(d.ok||d.shared){toast(t('toast.taskShared'),'success');currentTaskDetail.sharingVisibility=visibility;currentTaskDetail.hubTaskId=true;renderTaskShareActions(currentTaskDetail);} else {toast(d.error||t('toast.taskShareFail'),'error');}
|
|
5517
5557
|
}catch(e){toast(t('toast.taskShareFail')+': '+e.message,'error');}
|
|
5518
5558
|
}
|
|
5519
5559
|
|
|
@@ -5522,7 +5562,7 @@ async function unshareCurrentTask(){
|
|
|
5522
5562
|
try{
|
|
5523
5563
|
const r=await fetch('/api/sharing/tasks/unshare',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({taskId:currentTaskDetail.id})});
|
|
5524
5564
|
const d=await r.json();
|
|
5525
|
-
if(d.ok||d.unshared){toast(t('toast.taskUnshared'),'success');currentTaskDetail.sharingVisibility=null;renderTaskShareActions(currentTaskDetail);} else {toast(d.error||t('toast.taskUnshareFail'),'error');}
|
|
5565
|
+
if(d.ok||d.unshared){toast(t('toast.taskUnshared'),'success');currentTaskDetail.sharingVisibility=null;currentTaskDetail.hubTaskId=false;renderTaskShareActions(currentTaskDetail);} else {toast(d.error||t('toast.taskUnshareFail'),'error');}
|
|
5526
5566
|
}catch(e){toast(t('toast.taskUnshareFail')+': '+e.message,'error');}
|
|
5527
5567
|
}
|
|
5528
5568
|
|
|
@@ -7414,7 +7454,7 @@ async function saveGeneralConfig(){
|
|
|
7414
7454
|
const vp=document.getElementById('cfgViewerPort').value.trim();
|
|
7415
7455
|
if(vp) cfg.viewerPort=Number(vp);
|
|
7416
7456
|
const tafh=document.getElementById('cfgTaskAutoFinalizeHours').value.trim();
|
|
7417
|
-
cfg.taskAutoFinalizeHours=tafh!==''?Number(tafh):4;
|
|
7457
|
+
cfg.taskAutoFinalizeHours=tafh!==''?Math.max(0,Number(tafh)):4;
|
|
7418
7458
|
cfg.telemetry={enabled:document.getElementById('cfgTelemetryEnabled').checked};
|
|
7419
7459
|
|
|
7420
7460
|
await doSaveConfig(cfg, saveBtn, 'generalSaved');
|
|
@@ -8606,7 +8646,7 @@ function renderMemories(items){
|
|
|
8606
8646
|
const id=m.id;
|
|
8607
8647
|
const vscore=m._vscore?'<span class="vscore-badge">'+Math.round(m._vscore*100)+'%</span>':'';
|
|
8608
8648
|
const sid=m.session_key||'';
|
|
8609
|
-
const sidShort=
|
|
8649
|
+
const sidShort=fmtSessionDisplay(sid);
|
|
8610
8650
|
const mc=m.merge_count||0;
|
|
8611
8651
|
const cardTitle=esc(rawSummary||rawContent||'');
|
|
8612
8652
|
const mergeBadge=mc>0?'<span class="merge-badge">\\u{1F504} '+t('card.evolved')+' '+mc+t('card.times')+'</span>':'';
|
|
@@ -8614,7 +8654,7 @@ function renderMemories(items){
|
|
|
8614
8654
|
const ds=m.dedup_status||'active';
|
|
8615
8655
|
const isInactive=ds==='merged'||ds==='duplicate';
|
|
8616
8656
|
const dedupBadge=ds==='duplicate'?'<span class="dedup-badge duplicate">'+t('card.dedupDuplicate')+'</span>':ds==='merged'?'<span class="dedup-badge merged">'+t('card.dedupMerged')+'</span>':'';
|
|
8617
|
-
const isImported=
|
|
8657
|
+
const isImported=isImportedSession(sid);
|
|
8618
8658
|
const importBadge=isImported?'<span class="import-badge">\u{1F990} '+t('card.imported')+'</span>':'';
|
|
8619
8659
|
const ownerVal=m.owner||'agent:main';
|
|
8620
8660
|
const isPublicMem=ownerVal==='public';
|
|
@@ -8648,8 +8688,10 @@ function renderMemories(items){
|
|
|
8648
8688
|
}
|
|
8649
8689
|
}catch(e){}
|
|
8650
8690
|
}
|
|
8691
|
+
var ownerName=fmtAgentName(m.owner);
|
|
8692
|
+
var ownerBadge=ownerName?'<span class="owner-tag" title="'+esc(m.owner||'')+'">\\u{1F916} '+esc(ownerName)+'</span>':'';
|
|
8651
8693
|
return '<div class="memory-card'+(isInactive?' dedup-inactive':'')+'">'+
|
|
8652
|
-
'<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span>'+memScopeBadge+importBadge+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
|
|
8694
|
+
'<div class="card-header"><div class="meta"><span class="role-tag '+role+'">'+role+'</span>'+ownerBadge+memScopeBadge+importBadge+dedupBadge+mergeBadge+'</div><span class="card-time"><span class="session-tag" title="'+esc(sid)+'">'+esc(sidShort)+'</span> '+time+updatedAt+'</span></div>'+
|
|
8653
8695
|
'<div class="card-summary">'+selectBoxHtml+cardTitle+'</div>'+
|
|
8654
8696
|
(function(){
|
|
8655
8697
|
if(mc<=0) return '';
|
|
@@ -8789,6 +8831,7 @@ async function showMemoryModal(chunkId){
|
|
|
8789
8831
|
h+='<div class="mm-section"><div class="mm-section-label">'+t('admin.content')+'</div><pre class="mm-content">'+esc(m.content)+'</pre></div>';
|
|
8790
8832
|
}
|
|
8791
8833
|
h+='<div class="mm-meta">';
|
|
8834
|
+
if(m.owner) h+='<div class="mm-meta-chip"><strong>'+t('admin.owner')+'</strong><span>\\u{1F916} '+esc(m.owner)+'</span></div>';
|
|
8792
8835
|
if(m.session_key) h+='<div class="mm-meta-chip"><strong>'+t('admin.session')+'</strong><span>'+esc(m.session_key)+'</span></div>';
|
|
8793
8836
|
h+='<div class="mm-meta-chip"><strong>'+t('memory.detail.created')+'</strong><span>'+fmtModalDate(m.created_at)+'</span></div>';
|
|
8794
8837
|
if(m.updated_at) h+='<div class="mm-meta-chip"><strong>'+t('memory.detail.updated')+'</strong><span>'+fmtModalDate(m.updated_at)+'</span></div>';
|
|
@@ -8966,7 +9009,7 @@ async function migrateScan(showToast){
|
|
|
8966
9009
|
const btn=document.getElementById('migrateScanBtn');
|
|
8967
9010
|
btn.disabled=true;
|
|
8968
9011
|
btn.textContent=t('migrate.scanning');
|
|
8969
|
-
document.getElementById('
|
|
9012
|
+
document.getElementById('migrateStartConcurrencyWrap').style.display='none';
|
|
8970
9013
|
document.getElementById('migrateScanResult').style.display='none';
|
|
8971
9014
|
document.getElementById('migrateConfigWarn').style.display='none';
|
|
8972
9015
|
document.getElementById('migrateProgress').style.display='none';
|
|
@@ -8999,8 +9042,7 @@ async function migrateScan(showToast){
|
|
|
8999
9042
|
const remaining=Math.max(0,(d.totalItems||0)-imported);
|
|
9000
9043
|
|
|
9001
9044
|
if(d.totalItems>0 && d.configReady){
|
|
9002
|
-
document.getElementById('
|
|
9003
|
-
document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
|
|
9045
|
+
document.getElementById('migrateStartConcurrencyWrap').style.display='flex';
|
|
9004
9046
|
if(d.hasImportedData){
|
|
9005
9047
|
document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
|
|
9006
9048
|
}else{
|
|
@@ -9053,11 +9095,10 @@ async function migrateStart(){
|
|
|
9053
9095
|
|
|
9054
9096
|
window._migrateRunning=true;
|
|
9055
9097
|
_migrateStatusChecked=true;
|
|
9056
|
-
document.getElementById('
|
|
9098
|
+
document.getElementById('migrateStartConcurrencyWrap').style.display='none';
|
|
9057
9099
|
document.getElementById('migrateScanBtn').disabled=true;
|
|
9058
9100
|
var hintEl=document.getElementById('migrateImportedHint');
|
|
9059
9101
|
if(hintEl) hintEl.style.display='none';
|
|
9060
|
-
document.getElementById('migrateConcurrencyRow').style.display='none';
|
|
9061
9102
|
document.getElementById('migrateConcurrencyWarn').style.display='none';
|
|
9062
9103
|
document.getElementById('migrateProgress').style.display='block';
|
|
9063
9104
|
document.getElementById('migrateLiveLog').innerHTML='';
|
|
@@ -9142,7 +9183,7 @@ async function checkMigrateStatus(){
|
|
|
9142
9183
|
const progEl=document.getElementById('migrateProgress');
|
|
9143
9184
|
if(!progEl)return;
|
|
9144
9185
|
progEl.style.display='block';
|
|
9145
|
-
document.getElementById('
|
|
9186
|
+
document.getElementById('migrateStartConcurrencyWrap').style.display='none';
|
|
9146
9187
|
document.getElementById('migrateScanBtn').disabled=true;
|
|
9147
9188
|
document.getElementById('migrateStopBtn').disabled=false;
|
|
9148
9189
|
const pct=s.total>0?Math.round((s.processed/s.total)*100):0;
|
|
@@ -9262,7 +9303,7 @@ function onMigrateDone(wasStopped,skipReload){
|
|
|
9262
9303
|
document.getElementById('migrateStopBtn').style.display='none';
|
|
9263
9304
|
if(wasStopped){
|
|
9264
9305
|
document.getElementById('migrateBar').style.background='linear-gradient(90deg,#f59e0b,#fbbf24)';
|
|
9265
|
-
document.getElementById('
|
|
9306
|
+
document.getElementById('migrateStartConcurrencyWrap').style.display='flex';
|
|
9266
9307
|
document.getElementById('migrateStartBtn').textContent=t('migrate.resume');
|
|
9267
9308
|
document.getElementById('migratePhaseLabel').textContent=t('migrate.phase.stopped');
|
|
9268
9309
|
}else{
|
package/src/viewer/server.ts
CHANGED
|
@@ -622,6 +622,7 @@ export class ViewerServer {
|
|
|
622
622
|
const items = tasks.map((t) => {
|
|
623
623
|
const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(t.id) as { skill_status: string | null; owner: string | null } | undefined;
|
|
624
624
|
const hubTask = this.getHubTaskForLocal(t.id);
|
|
625
|
+
const share = this.resolveTaskTeamShareForApi(t.id, hubTask);
|
|
625
626
|
return {
|
|
626
627
|
id: t.id,
|
|
627
628
|
sessionKey: t.sessionKey,
|
|
@@ -633,7 +634,7 @@ export class ViewerServer {
|
|
|
633
634
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
634
635
|
skillStatus: meta?.skill_status ?? null,
|
|
635
636
|
owner: meta?.owner ?? "agent:main",
|
|
636
|
-
sharingVisibility:
|
|
637
|
+
sharingVisibility: share.visibility,
|
|
637
638
|
};
|
|
638
639
|
});
|
|
639
640
|
|
|
@@ -737,6 +738,8 @@ export class ViewerServer {
|
|
|
737
738
|
const t = this.store.getTask(taskId);
|
|
738
739
|
if (!t) return null;
|
|
739
740
|
const meta = db.prepare("SELECT skill_status, owner FROM tasks WHERE id = ?").get(taskId) as { skill_status: string | null; owner: string | null } | undefined;
|
|
741
|
+
const hubTask = this.getHubTaskForLocal(taskId);
|
|
742
|
+
const ts = this.resolveTaskTeamShareForApi(taskId, hubTask);
|
|
740
743
|
return {
|
|
741
744
|
id: t.id, sessionKey: t.sessionKey, title: t.title,
|
|
742
745
|
summary: t.summary ?? "", status: t.status,
|
|
@@ -744,6 +747,7 @@ export class ViewerServer {
|
|
|
744
747
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
745
748
|
skillStatus: meta?.skill_status ?? null,
|
|
746
749
|
owner: meta?.owner ?? "agent:main",
|
|
750
|
+
sharingVisibility: ts.visibility,
|
|
747
751
|
score,
|
|
748
752
|
};
|
|
749
753
|
}).filter(Boolean);
|
|
@@ -780,6 +784,7 @@ export class ViewerServer {
|
|
|
780
784
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId) as
|
|
781
785
|
{ skill_status: string | null; skill_reason: string | null } | undefined;
|
|
782
786
|
const hubTask = this.getHubTaskForLocal(taskId);
|
|
787
|
+
const ts = this.resolveTaskTeamShareForApi(taskId, hubTask);
|
|
783
788
|
|
|
784
789
|
this.jsonResponse(res, {
|
|
785
790
|
id: task.id,
|
|
@@ -794,9 +799,9 @@ export class ViewerServer {
|
|
|
794
799
|
skillStatus: meta?.skill_status ?? null,
|
|
795
800
|
skillReason: meta?.skill_reason ?? null,
|
|
796
801
|
skillLinks,
|
|
797
|
-
sharingVisibility:
|
|
798
|
-
sharingGroupId:
|
|
799
|
-
hubTaskId:
|
|
802
|
+
sharingVisibility: ts.visibility,
|
|
803
|
+
sharingGroupId: ts.groupId,
|
|
804
|
+
hubTaskId: ts.hasHubLink,
|
|
800
805
|
});
|
|
801
806
|
}
|
|
802
807
|
|
|
@@ -1616,7 +1621,8 @@ export class ViewerServer {
|
|
|
1616
1621
|
|
|
1617
1622
|
const isLocalShared = task.owner === "public";
|
|
1618
1623
|
const hubTask = this.getHubTaskForLocal(taskId);
|
|
1619
|
-
const
|
|
1624
|
+
const taskShareUi = this.resolveTaskTeamShareForApi(taskId, hubTask);
|
|
1625
|
+
const isTeamShared = taskShareUi.hasHubLink;
|
|
1620
1626
|
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1621
1627
|
|
|
1622
1628
|
if (scope === currentScope) {
|
|
@@ -1676,6 +1682,7 @@ export class ViewerServer {
|
|
|
1676
1682
|
});
|
|
1677
1683
|
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1678
1684
|
else this.store.downgradeTeamSharedTaskToLocal(taskId);
|
|
1685
|
+
this.store.clearTeamSharedChunksForTask(taskId);
|
|
1679
1686
|
hubSynced = true;
|
|
1680
1687
|
} catch (err) { this.log.warn(`Failed to unshare task from team: ${err}`); }
|
|
1681
1688
|
}
|
|
@@ -1689,6 +1696,8 @@ export class ViewerServer {
|
|
|
1689
1696
|
});
|
|
1690
1697
|
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1691
1698
|
else if (!isLocalShared) this.store.unmarkTaskShared(taskId);
|
|
1699
|
+
else this.store.downgradeTeamSharedTaskToLocal(taskId);
|
|
1700
|
+
this.store.clearTeamSharedChunksForTask(taskId);
|
|
1692
1701
|
hubSynced = true;
|
|
1693
1702
|
} catch (err) { this.log.warn(`Failed to unshare task from team: ${err}`); }
|
|
1694
1703
|
}
|
|
@@ -1816,6 +1825,39 @@ export class ViewerServer {
|
|
|
1816
1825
|
return scopedHubInstanceId === currentHubInstanceId;
|
|
1817
1826
|
}
|
|
1818
1827
|
|
|
1828
|
+
/**
|
|
1829
|
+
* Task list/detail/search: derive team-share badge when getHubTaskForLocal misses (e.g. client
|
|
1830
|
+
* hub_instance_id drift, or empty hub_task_id from hub while synced_chunks was recorded).
|
|
1831
|
+
*/
|
|
1832
|
+
private resolveTaskTeamShareForApi(taskId: string, hubTask: any): { visibility: string | null; hasHubLink: boolean; groupId: string | null } {
|
|
1833
|
+
if (hubTask) {
|
|
1834
|
+
return {
|
|
1835
|
+
visibility: hubTask.visibility ?? null,
|
|
1836
|
+
hasHubLink: true,
|
|
1837
|
+
groupId: hubTask.group_id ?? null,
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
const lst = this.store.getLocalSharedTask(taskId);
|
|
1841
|
+
if (lst) {
|
|
1842
|
+
const hid = String(lst.hubTaskId ?? "").trim();
|
|
1843
|
+
const teamLinked = hid.length > 0 || (lst.syncedChunks ?? 0) > 0;
|
|
1844
|
+
if (teamLinked) return { visibility: lst.visibility || null, hasHubLink: true, groupId: lst.groupId ?? null };
|
|
1845
|
+
}
|
|
1846
|
+
try {
|
|
1847
|
+
const db = (this.store as any).db;
|
|
1848
|
+
const chunkTeam = db.prepare(`
|
|
1849
|
+
SELECT t.visibility AS v, t.group_id AS g FROM team_shared_chunks t
|
|
1850
|
+
INNER JOIN chunks c ON c.id = t.chunk_id
|
|
1851
|
+
WHERE c.task_id = ?
|
|
1852
|
+
LIMIT 1
|
|
1853
|
+
`).get(taskId) as { v: string; g: string | null } | undefined;
|
|
1854
|
+
if (chunkTeam) {
|
|
1855
|
+
return { visibility: chunkTeam.v || null, hasHubLink: true, groupId: chunkTeam.g ?? null };
|
|
1856
|
+
}
|
|
1857
|
+
} catch { /* schema / db edge */ }
|
|
1858
|
+
return { visibility: null, hasHubLink: false, groupId: null };
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1819
1861
|
private getHubMemoryForChunk(chunkId: string): any {
|
|
1820
1862
|
if (this.sharingRole === "hub") {
|
|
1821
1863
|
const db = (this.store as any).db;
|
|
@@ -3982,7 +4024,7 @@ export class ViewerServer {
|
|
|
3982
4024
|
try {
|
|
3983
4025
|
if (this.store) {
|
|
3984
4026
|
importedSessions = this.store.getDistinctSessionKeys()
|
|
3985
|
-
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-"));
|
|
4027
|
+
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-") || /^agent:[^:]+:(import|session:)/.test(sk));
|
|
3986
4028
|
if (importedSessions.length > 0) {
|
|
3987
4029
|
const placeholders = importedSessions.map(() => "?").join(",");
|
|
3988
4030
|
const row = (this.store as any).db.prepare(
|
|
@@ -4195,7 +4237,7 @@ export class ViewerServer {
|
|
|
4195
4237
|
totalProcessed++;
|
|
4196
4238
|
|
|
4197
4239
|
const contentHash = crypto.createHash("sha256").update(row.text).digest("hex");
|
|
4198
|
-
if (this.store.chunkExistsByContent(`openclaw-import-${agentId}`, "assistant", row.text)) {
|
|
4240
|
+
if (this.store.chunkExistsByContent(`agent:${agentId}:import`, "assistant", row.text) || this.store.chunkExistsByContent(`openclaw-import-${agentId}`, "assistant", row.text)) {
|
|
4199
4241
|
totalSkipped++;
|
|
4200
4242
|
send("item", {
|
|
4201
4243
|
index: i + 1,
|
|
@@ -4295,7 +4337,7 @@ export class ViewerServer {
|
|
|
4295
4337
|
const chunkId = uuid();
|
|
4296
4338
|
const chunk: Chunk = {
|
|
4297
4339
|
id: chunkId,
|
|
4298
|
-
sessionKey: `
|
|
4340
|
+
sessionKey: `agent:${agentId}:import`,
|
|
4299
4341
|
turnId: `import-${row.id}`,
|
|
4300
4342
|
seq: 0,
|
|
4301
4343
|
role: "assistant",
|
|
@@ -4450,8 +4492,8 @@ export class ViewerServer {
|
|
|
4450
4492
|
const idx = incIdx();
|
|
4451
4493
|
totalProcessed++;
|
|
4452
4494
|
|
|
4453
|
-
const sessionKey = `
|
|
4454
|
-
if (this.store.chunkExistsByContent(sessionKey, msgRole, content)) {
|
|
4495
|
+
const sessionKey = `agent:${agentId}:session:${sessionId}`;
|
|
4496
|
+
if (this.store.chunkExistsByContent(sessionKey, msgRole, content) || this.store.chunkExistsByContent(`openclaw-session-${sessionId}`, msgRole, content)) {
|
|
4455
4497
|
totalSkipped++;
|
|
4456
4498
|
send("item", { index: idx, total: totalMsgs, status: "skipped", preview: content.slice(0, 120), source: file, agent: agentId, role: msgRole, reason: "duplicate" });
|
|
4457
4499
|
continue;
|
|
@@ -4702,7 +4744,7 @@ export class ViewerServer {
|
|
|
4702
4744
|
const ctx = this.ctx!;
|
|
4703
4745
|
|
|
4704
4746
|
const importSessions = this.store.getDistinctSessionKeys()
|
|
4705
|
-
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-"));
|
|
4747
|
+
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-") || /^agent:[^:]+:(import|session:)/.test(sk));
|
|
4706
4748
|
|
|
4707
4749
|
type PendingItem = { sessionKey: string; action: "full" | "skill-only"; owner: string };
|
|
4708
4750
|
const pendingItems: PendingItem[] = [];
|