@memtensor/memos-local-openclaw-plugin 1.0.8-beta.7 → 1.0.8-beta.9
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 +334 -391
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -4
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/scripts/postinstall.cjs +25 -59
- package/src/context-engine/index.ts +321 -0
- package/src/hub/server.ts +6 -13
- package/src/ingest/providers/anthropic.ts +6 -9
- package/src/ingest/providers/bedrock.ts +6 -9
- package/src/ingest/providers/gemini.ts +6 -9
- package/src/ingest/providers/index.ts +22 -123
- package/src/ingest/providers/openai.ts +6 -141
- package/src/ingest/task-processor.ts +41 -61
- package/src/ingest/worker.ts +11 -32
- package/src/recall/engine.ts +1 -2
- package/src/sharing/types.ts +0 -1
- package/src/storage/sqlite.ts +11 -194
- package/src/types.ts +0 -3
- package/src/viewer/html.ts +266 -892
- package/src/viewer/server.ts +20 -293
- package/telemetry.credentials.json +5 -0
package/src/viewer/server.ts
CHANGED
|
@@ -144,8 +144,6 @@ export class ViewerServer {
|
|
|
144
144
|
private lastKnownNotifCount = 0;
|
|
145
145
|
private hubHeartbeatTimer?: ReturnType<typeof setInterval>;
|
|
146
146
|
private static readonly HUB_HEARTBEAT_INTERVAL_MS = 45_000;
|
|
147
|
-
private static readonly STALE_TASK_TIMEOUT_MS = 4 * 60 * 60 * 1000;
|
|
148
|
-
private staleFinalizeRunning = false;
|
|
149
147
|
|
|
150
148
|
constructor(opts: ViewerServerOptions) {
|
|
151
149
|
this.store = opts.store;
|
|
@@ -317,7 +315,6 @@ export class ViewerServer {
|
|
|
317
315
|
else if (p === "/api/tool-metrics") this.serveToolMetrics(res, url);
|
|
318
316
|
else if (p === "/api/search") this.serveSearch(req, res, url);
|
|
319
317
|
else if (p === "/api/tasks" && req.method === "GET") this.serveTasks(res, url);
|
|
320
|
-
else if (p === "/api/task-search" && req.method === "GET") this.serveTaskSearch(res, url);
|
|
321
318
|
else if (p.match(/^\/api\/task\/[^/]+\/retry-skill$/) && req.method === "POST") this.handleTaskRetrySkill(req, res, p);
|
|
322
319
|
else if (p.startsWith("/api/task/") && req.method === "DELETE") this.handleTaskDelete(res, p);
|
|
323
320
|
else if (p.startsWith("/api/task/") && req.method === "PUT") this.handleTaskUpdate(req, res, p);
|
|
@@ -326,8 +323,6 @@ export class ViewerServer {
|
|
|
326
323
|
else if (p.match(/^\/api\/skill\/[^/]+\/download$/) && req.method === "GET") this.serveSkillDownload(res, p);
|
|
327
324
|
else if (p.match(/^\/api\/skill\/[^/]+\/files$/) && req.method === "GET") this.serveSkillFiles(res, p);
|
|
328
325
|
else if (p.match(/^\/api\/skill\/[^/]+\/visibility$/) && req.method === "PUT") this.handleSkillVisibility(req, res, p);
|
|
329
|
-
else if (p.match(/^\/api\/skill\/[^/]+\/disable$/) && req.method === "PUT") this.handleSkillDisable(res, p);
|
|
330
|
-
else if (p.match(/^\/api\/skill\/[^/]+\/enable$/) && req.method === "PUT") this.handleSkillEnable(res, p);
|
|
331
326
|
else if (p.startsWith("/api/skill/") && req.method === "DELETE") this.handleSkillDelete(res, p);
|
|
332
327
|
else if (p.startsWith("/api/skill/") && req.method === "PUT") this.handleSkillUpdate(req, res, p);
|
|
333
328
|
else if (p.startsWith("/api/skill/") && req.method === "GET") this.serveSkillDetail(res, p);
|
|
@@ -613,16 +608,14 @@ export class ViewerServer {
|
|
|
613
608
|
this.store.recordViewerEvent("tasks_list");
|
|
614
609
|
const status = url.searchParams.get("status") ?? undefined;
|
|
615
610
|
const owner = url.searchParams.get("owner") ?? undefined;
|
|
616
|
-
const session = url.searchParams.get("session") ?? undefined;
|
|
617
611
|
const limit = Math.min(100, Math.max(1, Number(url.searchParams.get("limit")) || 50));
|
|
618
612
|
const offset = Math.max(0, Number(url.searchParams.get("offset")) || 0);
|
|
619
|
-
const { tasks, total } = this.store.listTasks({ status, limit, offset, owner
|
|
613
|
+
const { tasks, total } = this.store.listTasks({ status, limit, offset, owner });
|
|
620
614
|
|
|
621
615
|
const db = (this.store as any).db;
|
|
622
616
|
const items = tasks.map((t) => {
|
|
623
617
|
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
618
|
const hubTask = this.getHubTaskForLocal(t.id);
|
|
625
|
-
const share = this.resolveTaskTeamShareForApi(t.id, hubTask);
|
|
626
619
|
return {
|
|
627
620
|
id: t.id,
|
|
628
621
|
sessionKey: t.sessionKey,
|
|
@@ -634,125 +627,11 @@ export class ViewerServer {
|
|
|
634
627
|
chunkCount: this.store.countChunksByTask(t.id),
|
|
635
628
|
skillStatus: meta?.skill_status ?? null,
|
|
636
629
|
owner: meta?.owner ?? "agent:main",
|
|
637
|
-
sharingVisibility:
|
|
630
|
+
sharingVisibility: hubTask?.visibility ?? null,
|
|
638
631
|
};
|
|
639
632
|
});
|
|
640
633
|
|
|
641
|
-
this.backfillTaskEmbeddings(items);
|
|
642
634
|
this.jsonResponse(res, { tasks: items, total, limit, offset });
|
|
643
|
-
this.autoFinalizeStaleTasks();
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
private getTaskAutoFinalizeMs(): number {
|
|
647
|
-
const hours = this.ctx?.config?.taskAutoFinalizeHours;
|
|
648
|
-
if (hours !== undefined && hours !== null) return hours * 60 * 60 * 1000;
|
|
649
|
-
try {
|
|
650
|
-
const cfgPath = this.getOpenClawConfigPath();
|
|
651
|
-
if (fs.existsSync(cfgPath)) {
|
|
652
|
-
const raw = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
653
|
-
const entries = raw?.plugins?.entries ?? {};
|
|
654
|
-
const pluginCfg = entries["memos-local-openclaw-plugin"]?.config
|
|
655
|
-
?? entries["memos-local"]?.config ?? {};
|
|
656
|
-
if (pluginCfg.taskAutoFinalizeHours !== undefined) return pluginCfg.taskAutoFinalizeHours * 60 * 60 * 1000;
|
|
657
|
-
}
|
|
658
|
-
} catch { /* fall through */ }
|
|
659
|
-
return ViewerServer.STALE_TASK_TIMEOUT_MS;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
private autoFinalizeStaleTasks(): void {
|
|
663
|
-
if (this.staleFinalizeRunning || !this.ctx) return;
|
|
664
|
-
const thresholdMs = this.getTaskAutoFinalizeMs();
|
|
665
|
-
if (thresholdMs <= 0) return;
|
|
666
|
-
const db = (this.store as any).db;
|
|
667
|
-
const now = Date.now();
|
|
668
|
-
let staleTasks: Array<{ id: string }>;
|
|
669
|
-
try {
|
|
670
|
-
staleTasks = db.prepare(`
|
|
671
|
-
SELECT t.id
|
|
672
|
-
FROM tasks t
|
|
673
|
-
LEFT JOIN chunks c ON c.task_id = t.id
|
|
674
|
-
WHERE t.status = 'active'
|
|
675
|
-
GROUP BY t.id
|
|
676
|
-
HAVING (? - COALESCE(MAX(c.created_at), t.started_at)) > ?
|
|
677
|
-
`).all(now, thresholdMs) as Array<{ id: string }>;
|
|
678
|
-
} catch { return; }
|
|
679
|
-
if (staleTasks.length === 0) return;
|
|
680
|
-
|
|
681
|
-
this.staleFinalizeRunning = true;
|
|
682
|
-
const hours = Math.round(thresholdMs / 3600000);
|
|
683
|
-
this.log.info(`Auto-finalizing ${staleTasks.length} stale active task(s) (idle > ${hours}h)`);
|
|
684
|
-
const tp = new TaskProcessor(this.store, this.ctx);
|
|
685
|
-
(async () => {
|
|
686
|
-
for (const row of staleTasks) {
|
|
687
|
-
const task = this.store.getTask(row.id);
|
|
688
|
-
if (!task || task.status !== "active") continue;
|
|
689
|
-
try {
|
|
690
|
-
await tp.finalizeTask(task);
|
|
691
|
-
this.log.info(`Auto-finalized stale task=${task.id}`);
|
|
692
|
-
} catch (err) {
|
|
693
|
-
this.log.warn(`Failed to auto-finalize task=${task.id}: ${err}`);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
})().catch((err) => this.log.warn(`autoFinalizeStaleTasks error: ${err}`))
|
|
697
|
-
.finally(() => { this.staleFinalizeRunning = false; });
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
private async serveTaskSearch(res: http.ServerResponse, url: URL): Promise<void> {
|
|
701
|
-
const q = (url.searchParams.get("q") ?? "").trim();
|
|
702
|
-
if (!q) { this.jsonResponse(res, { tasks: [], total: 0 }); return; }
|
|
703
|
-
|
|
704
|
-
const owner = url.searchParams.get("owner") ?? undefined;
|
|
705
|
-
const maxResults = Math.min(50, Math.max(1, Number(url.searchParams.get("limit")) || 20));
|
|
706
|
-
|
|
707
|
-
const scoreMap = new Map<string, number>();
|
|
708
|
-
|
|
709
|
-
if (this.embedder) {
|
|
710
|
-
try {
|
|
711
|
-
const [queryVec] = await this.embedder.embed([q]);
|
|
712
|
-
const allEmb = this.store.getTaskEmbeddings(owner);
|
|
713
|
-
for (const { taskId, vector } of allEmb) {
|
|
714
|
-
let dot = 0, normA = 0, normB = 0;
|
|
715
|
-
for (let i = 0; i < queryVec.length && i < vector.length; i++) {
|
|
716
|
-
dot += queryVec[i] * vector[i];
|
|
717
|
-
normA += queryVec[i] * queryVec[i];
|
|
718
|
-
normB += vector[i] * vector[i];
|
|
719
|
-
}
|
|
720
|
-
const sim = normA > 0 && normB > 0 ? dot / (Math.sqrt(normA) * Math.sqrt(normB)) : 0;
|
|
721
|
-
if (sim > 0.3) scoreMap.set(taskId, sim);
|
|
722
|
-
}
|
|
723
|
-
} catch { /* embedding unavailable, fall through to FTS */ }
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const ftsResults = this.store.taskFtsSearch(q, maxResults, owner);
|
|
727
|
-
for (const { taskId, score } of ftsResults) {
|
|
728
|
-
const existing = scoreMap.get(taskId) ?? 0;
|
|
729
|
-
scoreMap.set(taskId, Math.max(existing, score * 0.8));
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
const sorted = [...scoreMap.entries()]
|
|
733
|
-
.sort((a, b) => b[1] - a[1])
|
|
734
|
-
.slice(0, maxResults);
|
|
735
|
-
|
|
736
|
-
const db = (this.store as any).db;
|
|
737
|
-
const tasks = sorted.map(([taskId, score]) => {
|
|
738
|
-
const t = this.store.getTask(taskId);
|
|
739
|
-
if (!t) return null;
|
|
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);
|
|
743
|
-
return {
|
|
744
|
-
id: t.id, sessionKey: t.sessionKey, title: t.title,
|
|
745
|
-
summary: t.summary ?? "", status: t.status,
|
|
746
|
-
startedAt: t.startedAt, endedAt: t.endedAt,
|
|
747
|
-
chunkCount: this.store.countChunksByTask(t.id),
|
|
748
|
-
skillStatus: meta?.skill_status ?? null,
|
|
749
|
-
owner: meta?.owner ?? "agent:main",
|
|
750
|
-
sharingVisibility: ts.visibility,
|
|
751
|
-
score,
|
|
752
|
-
};
|
|
753
|
-
}).filter(Boolean);
|
|
754
|
-
|
|
755
|
-
this.jsonResponse(res, { tasks, total: tasks.length });
|
|
756
635
|
}
|
|
757
636
|
|
|
758
637
|
private serveTaskDetail(res: http.ServerResponse, urlPath: string): void {
|
|
@@ -784,7 +663,6 @@ export class ViewerServer {
|
|
|
784
663
|
const meta = db.prepare("SELECT skill_status, skill_reason FROM tasks WHERE id = ?").get(taskId) as
|
|
785
664
|
{ skill_status: string | null; skill_reason: string | null } | undefined;
|
|
786
665
|
const hubTask = this.getHubTaskForLocal(taskId);
|
|
787
|
-
const ts = this.resolveTaskTeamShareForApi(taskId, hubTask);
|
|
788
666
|
|
|
789
667
|
this.jsonResponse(res, {
|
|
790
668
|
id: task.id,
|
|
@@ -799,15 +677,15 @@ export class ViewerServer {
|
|
|
799
677
|
skillStatus: meta?.skill_status ?? null,
|
|
800
678
|
skillReason: meta?.skill_reason ?? null,
|
|
801
679
|
skillLinks,
|
|
802
|
-
sharingVisibility:
|
|
803
|
-
sharingGroupId:
|
|
804
|
-
hubTaskId:
|
|
680
|
+
sharingVisibility: hubTask?.visibility ?? null,
|
|
681
|
+
sharingGroupId: hubTask?.group_id ?? null,
|
|
682
|
+
hubTaskId: hubTask ? true : false,
|
|
805
683
|
});
|
|
806
684
|
}
|
|
807
685
|
|
|
808
686
|
private serveStats(res: http.ServerResponse, url?: URL): void {
|
|
809
687
|
const emptyStats = {
|
|
810
|
-
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
688
|
+
totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
|
|
811
689
|
embeddingProvider: this.embedder?.provider ?? "none",
|
|
812
690
|
dedupBreakdown: {},
|
|
813
691
|
timeRange: { earliest: null, latest: null },
|
|
@@ -850,30 +728,9 @@ export class ViewerServer {
|
|
|
850
728
|
}
|
|
851
729
|
const sessionList = db.prepare(sessionQuery).all(...sessionParams) as any[];
|
|
852
730
|
|
|
853
|
-
let taskSessionList: Array<{ session_key: string; count: number; earliest: number | null; latest: number | null }> = [];
|
|
854
|
-
try {
|
|
855
|
-
taskSessionList = db.prepare(
|
|
856
|
-
"SELECT session_key, COUNT(*) as count, MIN(started_at) as earliest, MAX(COALESCE(updated_at, started_at)) as latest FROM tasks GROUP BY session_key ORDER BY latest DESC",
|
|
857
|
-
).all() as any[];
|
|
858
|
-
} catch { /* tasks table may not exist yet */ }
|
|
859
|
-
|
|
860
|
-
let skillSessionList: Array<{ session_key: string; count: number; earliest: number | null; latest: number | null }> = [];
|
|
861
|
-
try {
|
|
862
|
-
skillSessionList = db.prepare(
|
|
863
|
-
`SELECT t.session_key as session_key, COUNT(DISTINCT ts.skill_id) as count,
|
|
864
|
-
MIN(t.started_at) as earliest, MAX(COALESCE(t.updated_at, t.started_at)) as latest
|
|
865
|
-
FROM task_skills ts JOIN tasks t ON t.id = ts.task_id
|
|
866
|
-
GROUP BY t.session_key
|
|
867
|
-
ORDER BY latest DESC`,
|
|
868
|
-
).all() as any[];
|
|
869
|
-
} catch { /* task_skills may not exist yet */ }
|
|
870
|
-
|
|
871
731
|
let skillCount = 0;
|
|
872
732
|
try { skillCount = (db.prepare("SELECT COUNT(*) as count FROM skills").get() as any).count; } catch { /* table may not exist yet */ }
|
|
873
733
|
|
|
874
|
-
let taskCount = 0;
|
|
875
|
-
try { taskCount = (db.prepare("SELECT COUNT(*) as count FROM tasks").get() as any).count; } catch { /* table may not exist yet */ }
|
|
876
|
-
|
|
877
734
|
let dedupBreakdown: Record<string, number> = {};
|
|
878
735
|
try {
|
|
879
736
|
const dedupRows = db.prepare("SELECT dedup_status, COUNT(*) as count FROM chunks GROUP BY dedup_status").all() as any[];
|
|
@@ -882,15 +739,7 @@ export class ViewerServer {
|
|
|
882
739
|
|
|
883
740
|
let owners: string[] = [];
|
|
884
741
|
try {
|
|
885
|
-
const ownerRows = db.prepare(
|
|
886
|
-
SELECT DISTINCT owner FROM (
|
|
887
|
-
SELECT owner FROM chunks WHERE owner IS NOT NULL AND owner LIKE 'agent:%'
|
|
888
|
-
UNION
|
|
889
|
-
SELECT owner FROM tasks WHERE owner IS NOT NULL AND owner LIKE 'agent:%'
|
|
890
|
-
UNION
|
|
891
|
-
SELECT owner FROM skills WHERE owner IS NOT NULL AND owner LIKE 'agent:%'
|
|
892
|
-
) ORDER BY owner
|
|
893
|
-
`).all() as any[];
|
|
742
|
+
const ownerRows = db.prepare("SELECT DISTINCT owner FROM chunks WHERE owner IS NOT NULL AND owner LIKE 'agent:%' ORDER BY owner").all() as any[];
|
|
894
743
|
owners = ownerRows.map((o: any) => o.owner);
|
|
895
744
|
} catch { /* column may not exist yet */ }
|
|
896
745
|
|
|
@@ -902,13 +751,11 @@ export class ViewerServer {
|
|
|
902
751
|
|
|
903
752
|
this.jsonResponse(res, {
|
|
904
753
|
totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
|
|
905
|
-
totalSkills: skillCount,
|
|
754
|
+
totalSkills: skillCount,
|
|
906
755
|
embeddingProvider: this.embedder.provider,
|
|
907
756
|
dedupBreakdown,
|
|
908
757
|
timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
|
|
909
758
|
sessions: sessionList,
|
|
910
|
-
taskSessions: taskSessionList,
|
|
911
|
-
skillSessions: skillSessionList,
|
|
912
759
|
owners,
|
|
913
760
|
currentAgentOwner,
|
|
914
761
|
});
|
|
@@ -1018,9 +865,7 @@ export class ViewerServer {
|
|
|
1018
865
|
private serveSkills(res: http.ServerResponse, url: URL): void {
|
|
1019
866
|
const status = url.searchParams.get("status") ?? undefined;
|
|
1020
867
|
const visibility = url.searchParams.get("visibility") ?? undefined;
|
|
1021
|
-
|
|
1022
|
-
const owner = url.searchParams.get("owner") ?? undefined;
|
|
1023
|
-
let skills = this.store.listSkills({ status, session, owner });
|
|
868
|
+
let skills = this.store.listSkills({ status });
|
|
1024
869
|
if (visibility) {
|
|
1025
870
|
skills = skills.filter(s => s.visibility === visibility);
|
|
1026
871
|
}
|
|
@@ -1259,27 +1104,6 @@ export class ViewerServer {
|
|
|
1259
1104
|
});
|
|
1260
1105
|
}
|
|
1261
1106
|
|
|
1262
|
-
private embedTaskInBackground(taskId: string, text: string): void {
|
|
1263
|
-
if (!this.embedder || !text.trim()) return;
|
|
1264
|
-
this.embedder.embed([text]).then((vecs: number[][]) => {
|
|
1265
|
-
if (vecs.length > 0) this.store.upsertTaskEmbedding(taskId, vecs[0]);
|
|
1266
|
-
}).catch(() => {});
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
private backfillTaskEmbeddings(tasks: Array<{ id: string; summary: string; title: string }>): void {
|
|
1270
|
-
if (!this.embedder) return;
|
|
1271
|
-
const db = (this.store as any).db;
|
|
1272
|
-
for (const t of tasks) {
|
|
1273
|
-
try {
|
|
1274
|
-
const exists = db.prepare("SELECT 1 FROM task_embeddings WHERE task_id = ?").get(t.id);
|
|
1275
|
-
if (!exists) {
|
|
1276
|
-
const text = `${t.title ?? ""}: ${t.summary ?? ""}`.trim();
|
|
1277
|
-
if (text.length > 1) this.embedTaskInBackground(t.id, text);
|
|
1278
|
-
}
|
|
1279
|
-
} catch { /* best-effort */ }
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
1107
|
private handleTaskDelete(res: http.ServerResponse, urlPath: string): void {
|
|
1284
1108
|
const taskId = urlPath.replace("/api/task/", "");
|
|
1285
1109
|
const deleted = this.store.deleteTask(taskId);
|
|
@@ -1294,15 +1118,12 @@ export class ViewerServer {
|
|
|
1294
1118
|
const data = JSON.parse(body);
|
|
1295
1119
|
const task = this.store.getTask(taskId);
|
|
1296
1120
|
if (!task) { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Task not found" })); return; }
|
|
1297
|
-
const newTitle = data.title ?? task.title;
|
|
1298
|
-
const newSummary = data.summary ?? task.summary;
|
|
1299
1121
|
this.store.updateTask(taskId, {
|
|
1300
|
-
title:
|
|
1301
|
-
summary:
|
|
1122
|
+
title: data.title ?? task.title,
|
|
1123
|
+
summary: data.summary ?? task.summary,
|
|
1302
1124
|
status: data.status ?? task.status,
|
|
1303
1125
|
endedAt: task.endedAt ?? undefined,
|
|
1304
1126
|
});
|
|
1305
|
-
this.embedTaskInBackground(taskId, `${newTitle ?? ""}: ${newSummary ?? ""}`);
|
|
1306
1127
|
this.jsonResponse(res, { ok: true, taskId });
|
|
1307
1128
|
} catch (err) {
|
|
1308
1129
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
@@ -1359,58 +1180,6 @@ export class ViewerServer {
|
|
|
1359
1180
|
});
|
|
1360
1181
|
}
|
|
1361
1182
|
|
|
1362
|
-
private async handleSkillDisable(res: http.ServerResponse, urlPath: string): Promise<void> {
|
|
1363
|
-
const skillId = urlPath.split("/")[3];
|
|
1364
|
-
const skill = this.store.getSkill(skillId);
|
|
1365
|
-
if (!skill) { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Skill not found" })); return; }
|
|
1366
|
-
if (skill.status === "archived") { this.jsonResponse(res, { ok: true, skillId, message: "already disabled" }); return; }
|
|
1367
|
-
|
|
1368
|
-
try {
|
|
1369
|
-
if (skill.visibility === "public") {
|
|
1370
|
-
this.store.setSkillVisibility(skillId, "private");
|
|
1371
|
-
}
|
|
1372
|
-
const hub = this.resolveHubConnection();
|
|
1373
|
-
if (hub) {
|
|
1374
|
-
await hubRequestJson(hub.hubUrl, hub.userToken, "/api/v1/hub/skills/unpublish", {
|
|
1375
|
-
method: "POST",
|
|
1376
|
-
body: JSON.stringify({ sourceSkillId: skillId }),
|
|
1377
|
-
}).catch(() => {});
|
|
1378
|
-
}
|
|
1379
|
-
} catch (_) {}
|
|
1380
|
-
|
|
1381
|
-
try {
|
|
1382
|
-
const workspaceSkillsDir = path.join(this.dataDir, "workspace", "skills");
|
|
1383
|
-
const installedDir = path.join(workspaceSkillsDir, skill.name);
|
|
1384
|
-
if (fs.existsSync(installedDir)) {
|
|
1385
|
-
fs.rmSync(installedDir, { recursive: true, force: true });
|
|
1386
|
-
}
|
|
1387
|
-
} catch (_) {}
|
|
1388
|
-
|
|
1389
|
-
this.store.disableSkill(skillId);
|
|
1390
|
-
this.jsonResponse(res, { ok: true, skillId });
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
private handleSkillEnable(res: http.ServerResponse, urlPath: string): void {
|
|
1394
|
-
const skillId = urlPath.split("/")[3];
|
|
1395
|
-
const skill = this.store.getSkill(skillId);
|
|
1396
|
-
if (!skill) { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Skill not found" })); return; }
|
|
1397
|
-
if (skill.status !== "archived") { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Only disabled (archived) skills can be enabled" })); return; }
|
|
1398
|
-
|
|
1399
|
-
this.store.enableSkill(skillId);
|
|
1400
|
-
|
|
1401
|
-
if (this.embedder) {
|
|
1402
|
-
const sv = this.store.getLatestSkillVersion(skillId);
|
|
1403
|
-
if (sv) {
|
|
1404
|
-
const text = `${skill.name}: ${skill.description}`;
|
|
1405
|
-
this.embedder.embed([text]).then((vecs: number[][]) => {
|
|
1406
|
-
if (vecs.length > 0) this.store.upsertSkillEmbedding(skillId, vecs[0]);
|
|
1407
|
-
}).catch(() => {});
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
this.jsonResponse(res, { ok: true, skillId });
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
1183
|
// ─── CRUD ───
|
|
1415
1184
|
|
|
1416
1185
|
private serveMemoryDetail(res: http.ServerResponse, urlPath: string): void {
|
|
@@ -1545,7 +1314,7 @@ export class ViewerServer {
|
|
|
1545
1314
|
const refreshedChunk = db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId) as any;
|
|
1546
1315
|
const response = await hubRequestJson(hubClient.hubUrl, hubClient.userToken, "/api/v1/hub/memories/share", {
|
|
1547
1316
|
method: "POST",
|
|
1548
|
-
body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id,
|
|
1317
|
+
body: JSON.stringify({ memory: { sourceChunkId: refreshedChunk.id, role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary, kind: refreshedChunk.kind, groupId: null, visibility: "public" } }),
|
|
1549
1318
|
});
|
|
1550
1319
|
if (!isLocalShared) this.store.markMemorySharedLocally(chunkId);
|
|
1551
1320
|
const memoryId = String((response as any)?.memoryId ?? "");
|
|
@@ -1555,7 +1324,6 @@ export class ViewerServer {
|
|
|
1555
1324
|
this.store.upsertHubMemory({
|
|
1556
1325
|
id: memoryId || existing?.id || crypto.randomUUID(),
|
|
1557
1326
|
sourceChunkId: chunkId, sourceUserId: hubClient.userId,
|
|
1558
|
-
sourceAgent: refreshedChunk.owner || "",
|
|
1559
1327
|
role: refreshedChunk.role, content: refreshedChunk.content, summary: refreshedChunk.summary ?? "",
|
|
1560
1328
|
kind: refreshedChunk.kind, groupId: null, visibility: "public",
|
|
1561
1329
|
createdAt: existing?.createdAt ?? Date.now(), updatedAt: Date.now(),
|
|
@@ -1621,8 +1389,7 @@ export class ViewerServer {
|
|
|
1621
1389
|
|
|
1622
1390
|
const isLocalShared = task.owner === "public";
|
|
1623
1391
|
const hubTask = this.getHubTaskForLocal(taskId);
|
|
1624
|
-
const
|
|
1625
|
-
const isTeamShared = taskShareUi.hasHubLink;
|
|
1392
|
+
const isTeamShared = !!hubTask;
|
|
1626
1393
|
const currentScope = isTeamShared ? "team" : isLocalShared ? "local" : "private";
|
|
1627
1394
|
|
|
1628
1395
|
if (scope === currentScope) {
|
|
@@ -1682,7 +1449,6 @@ export class ViewerServer {
|
|
|
1682
1449
|
});
|
|
1683
1450
|
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1684
1451
|
else this.store.downgradeTeamSharedTaskToLocal(taskId);
|
|
1685
|
-
this.store.clearTeamSharedChunksForTask(taskId);
|
|
1686
1452
|
hubSynced = true;
|
|
1687
1453
|
} catch (err) { this.log.warn(`Failed to unshare task from team: ${err}`); }
|
|
1688
1454
|
}
|
|
@@ -1696,8 +1462,6 @@ export class ViewerServer {
|
|
|
1696
1462
|
});
|
|
1697
1463
|
if (this.sharingRole === "hub" && hubClient.userId) this.store.deleteHubTaskBySource(hubClient.userId, taskId);
|
|
1698
1464
|
else if (!isLocalShared) this.store.unmarkTaskShared(taskId);
|
|
1699
|
-
else this.store.downgradeTeamSharedTaskToLocal(taskId);
|
|
1700
|
-
this.store.clearTeamSharedChunksForTask(taskId);
|
|
1701
1465
|
hubSynced = true;
|
|
1702
1466
|
} catch (err) { this.log.warn(`Failed to unshare task from team: ${err}`); }
|
|
1703
1467
|
}
|
|
@@ -1825,39 +1589,6 @@ export class ViewerServer {
|
|
|
1825
1589
|
return scopedHubInstanceId === currentHubInstanceId;
|
|
1826
1590
|
}
|
|
1827
1591
|
|
|
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
|
-
|
|
1861
1592
|
private getHubMemoryForChunk(chunkId: string): any {
|
|
1862
1593
|
if (this.sharingRole === "hub") {
|
|
1863
1594
|
const db = (this.store as any).db;
|
|
@@ -2402,7 +2133,6 @@ export class ViewerServer {
|
|
|
2402
2133
|
ref: { sessionKey: row.session_key, chunkId: row.id, turnId: row.turn_id, seq: row.seq },
|
|
2403
2134
|
taskId: row.task_id ?? null,
|
|
2404
2135
|
skillId: row.skill_id ?? null,
|
|
2405
|
-
owner: row.owner || "",
|
|
2406
2136
|
}));
|
|
2407
2137
|
return { hits, meta: { total: hits.length, usedMaxResults: maxResults } };
|
|
2408
2138
|
}
|
|
@@ -2512,7 +2242,6 @@ export class ViewerServer {
|
|
|
2512
2242
|
body: JSON.stringify({
|
|
2513
2243
|
memory: {
|
|
2514
2244
|
sourceChunkId: chunk.id,
|
|
2515
|
-
sourceAgent: chunk.owner || "",
|
|
2516
2245
|
role: chunk.role,
|
|
2517
2246
|
content: chunk.content,
|
|
2518
2247
|
summary: chunk.summary,
|
|
@@ -2530,7 +2259,6 @@ export class ViewerServer {
|
|
|
2530
2259
|
id: mid || existing?.id || crypto.randomUUID(),
|
|
2531
2260
|
sourceChunkId: chunk.id,
|
|
2532
2261
|
sourceUserId: hubClient.userId,
|
|
2533
|
-
sourceAgent: chunk.owner || "",
|
|
2534
2262
|
role: chunk.role,
|
|
2535
2263
|
content: chunk.content,
|
|
2536
2264
|
summary: chunk.summary ?? "",
|
|
@@ -3079,7 +2807,6 @@ export class ViewerServer {
|
|
|
3079
2807
|
if (newCfg.summarizer) config.summarizer = newCfg.summarizer;
|
|
3080
2808
|
if (newCfg.skillEvolution) config.skillEvolution = newCfg.skillEvolution;
|
|
3081
2809
|
if (newCfg.viewerPort) config.viewerPort = newCfg.viewerPort;
|
|
3082
|
-
if (newCfg.taskAutoFinalizeHours !== undefined) config.taskAutoFinalizeHours = newCfg.taskAutoFinalizeHours;
|
|
3083
2810
|
if (newCfg.telemetry !== undefined) config.telemetry = newCfg.telemetry;
|
|
3084
2811
|
if (newCfg.sharing !== undefined) {
|
|
3085
2812
|
const existing = (config.sharing as Record<string, unknown>) || {};
|
|
@@ -3740,7 +3467,7 @@ export class ViewerServer {
|
|
|
3740
3467
|
|
|
3741
3468
|
this.log.info(`update-install: running postinstall...`);
|
|
3742
3469
|
execFile(process.execPath, ["scripts/postinstall.cjs"], { cwd: extDir, timeout: 180_000 }, (postErr, postOut, postStderr) => {
|
|
3743
|
-
|
|
3470
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
3744
3471
|
|
|
3745
3472
|
if (postErr) {
|
|
3746
3473
|
this.log.warn(`update-install: postinstall failed: ${postErr.message}`);
|
|
@@ -4024,7 +3751,7 @@ export class ViewerServer {
|
|
|
4024
3751
|
try {
|
|
4025
3752
|
if (this.store) {
|
|
4026
3753
|
importedSessions = this.store.getDistinctSessionKeys()
|
|
4027
|
-
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-")
|
|
3754
|
+
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-"));
|
|
4028
3755
|
if (importedSessions.length > 0) {
|
|
4029
3756
|
const placeholders = importedSessions.map(() => "?").join(",");
|
|
4030
3757
|
const row = (this.store as any).db.prepare(
|
|
@@ -4237,7 +3964,7 @@ export class ViewerServer {
|
|
|
4237
3964
|
totalProcessed++;
|
|
4238
3965
|
|
|
4239
3966
|
const contentHash = crypto.createHash("sha256").update(row.text).digest("hex");
|
|
4240
|
-
if (this.store.chunkExistsByContent(`
|
|
3967
|
+
if (this.store.chunkExistsByContent(`openclaw-import-${agentId}`, "assistant", row.text)) {
|
|
4241
3968
|
totalSkipped++;
|
|
4242
3969
|
send("item", {
|
|
4243
3970
|
index: i + 1,
|
|
@@ -4337,7 +4064,7 @@ export class ViewerServer {
|
|
|
4337
4064
|
const chunkId = uuid();
|
|
4338
4065
|
const chunk: Chunk = {
|
|
4339
4066
|
id: chunkId,
|
|
4340
|
-
sessionKey: `
|
|
4067
|
+
sessionKey: `openclaw-import-${agentId}`,
|
|
4341
4068
|
turnId: `import-${row.id}`,
|
|
4342
4069
|
seq: 0,
|
|
4343
4070
|
role: "assistant",
|
|
@@ -4492,8 +4219,8 @@ export class ViewerServer {
|
|
|
4492
4219
|
const idx = incIdx();
|
|
4493
4220
|
totalProcessed++;
|
|
4494
4221
|
|
|
4495
|
-
const sessionKey = `
|
|
4496
|
-
if (this.store.chunkExistsByContent(sessionKey, msgRole, content)
|
|
4222
|
+
const sessionKey = `openclaw-session-${sessionId}`;
|
|
4223
|
+
if (this.store.chunkExistsByContent(sessionKey, msgRole, content)) {
|
|
4497
4224
|
totalSkipped++;
|
|
4498
4225
|
send("item", { index: idx, total: totalMsgs, status: "skipped", preview: content.slice(0, 120), source: file, agent: agentId, role: msgRole, reason: "duplicate" });
|
|
4499
4226
|
continue;
|
|
@@ -4744,7 +4471,7 @@ export class ViewerServer {
|
|
|
4744
4471
|
const ctx = this.ctx!;
|
|
4745
4472
|
|
|
4746
4473
|
const importSessions = this.store.getDistinctSessionKeys()
|
|
4747
|
-
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-")
|
|
4474
|
+
.filter((sk: string) => sk.startsWith("openclaw-import-") || sk.startsWith("openclaw-session-"));
|
|
4748
4475
|
|
|
4749
4476
|
type PendingItem = { sessionKey: string; action: "full" | "skill-only"; owner: string };
|
|
4750
4477
|
const pendingItems: PendingItem[] = [];
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
}
|