@mobcode/openclaw-plugin 0.1.4 → 0.1.7
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/package.json +1 -1
- package/src/openclaw-session-reader.js +1 -1
- package/src/runtime-events.js +18 -4
- package/src/session-key-resolver.js +103 -0
- package/src/state-store.js +37 -4
package/package.json
CHANGED
|
@@ -60,7 +60,7 @@ async function readSessionMessagesThroughChatHistory(config, sessionKey, logger)
|
|
|
60
60
|
});
|
|
61
61
|
const messages = payload?.messages;
|
|
62
62
|
logger?.info?.(
|
|
63
|
-
`[mobcode-history] chat.history response session=${normalizedSessionKey} sessionId=${String(payload?.sessionId ?? "").trim() || "-"} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
63
|
+
`[mobcode-history] chat.history response session=${normalizedSessionKey} sessionId=${String(payload?.sessionId ?? "").trim() || "-"} thinkingLevel=${String(payload?.thinkingLevel ?? "").trim() || "-"} fastMode=${String(payload?.fastMode ?? "").trim() || "-"} verboseLevel=${String(payload?.verboseLevel ?? "").trim() || "-"} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
64
64
|
);
|
|
65
65
|
return Array.isArray(messages) ? messages : [];
|
|
66
66
|
}
|
package/src/runtime-events.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tryCreateOperatorApprovalsGatewayClient } from "./openclaw-gateway-runtime.js";
|
|
2
|
+
import { resolveSessionKeyFromTranscriptFile } from "./session-key-resolver.js";
|
|
2
3
|
|
|
3
4
|
function createPushPayloadFromTranscript(update) {
|
|
4
5
|
return {
|
|
@@ -39,17 +40,30 @@ export function registerMobcodeRuntimeObservers({ api, store, pluginConfig }) {
|
|
|
39
40
|
await store.init();
|
|
40
41
|
|
|
41
42
|
api.runtime.events.onSessionTranscriptUpdate(async (update) => {
|
|
43
|
+
let resolvedSessionKey = String(update?.sessionKey ?? "").trim();
|
|
44
|
+
if (!resolvedSessionKey && update?.sessionFile) {
|
|
45
|
+
resolvedSessionKey =
|
|
46
|
+
(await resolveSessionKeyFromTranscriptFile(update.sessionFile, api.logger)) ?? "";
|
|
47
|
+
}
|
|
42
48
|
api.logger?.info?.(
|
|
43
|
-
`[mobcode-runtime] transcript update sessionKey=${String(update?.sessionKey ?? "").trim() || "-"} sessionFile=${String(update?.sessionFile ?? "").trim() || "-"} messageId=${String(update?.messageId ?? "").trim() || "-"} hasMessage=${update?.message !== undefined}`,
|
|
49
|
+
`[mobcode-runtime] transcript update sessionKey=${resolvedSessionKey || "-"} rawSessionKey=${String(update?.sessionKey ?? "").trim() || "-"} sessionFile=${String(update?.sessionFile ?? "").trim() || "-"} messageId=${String(update?.messageId ?? "").trim() || "-"} hasMessage=${update?.message !== undefined}`,
|
|
44
50
|
);
|
|
45
51
|
if (retainMessages && update?.message !== undefined) {
|
|
46
|
-
const indexed = await store.appendMessage(
|
|
52
|
+
const indexed = await store.appendMessage({
|
|
53
|
+
...update,
|
|
54
|
+
...(resolvedSessionKey ? { sessionKey: resolvedSessionKey } : {}),
|
|
55
|
+
});
|
|
47
56
|
api.logger?.info?.(
|
|
48
|
-
`[mobcode-runtime] transcript indexed sessionKey=${indexed?.sessionKey ?? "-"} total=${String(indexed?.count ?? 0)}`,
|
|
57
|
+
`[mobcode-runtime] transcript indexed sessionKey=${indexed?.sessionKey ?? "-"} total=${String(indexed?.count ?? 0)} skipped=${indexed?.skipped === true}`,
|
|
49
58
|
);
|
|
50
59
|
}
|
|
51
60
|
if (pushEnabled) {
|
|
52
|
-
await store.enqueuePush(
|
|
61
|
+
await store.enqueuePush(
|
|
62
|
+
createPushPayloadFromTranscript({
|
|
63
|
+
...update,
|
|
64
|
+
...(resolvedSessionKey ? { sessionKey: resolvedSessionKey } : {}),
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
53
67
|
}
|
|
54
68
|
});
|
|
55
69
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const RESOLVED_SESSION_KEY_CACHE = new Map();
|
|
5
|
+
|
|
6
|
+
async function resolvePathForComparison(value) {
|
|
7
|
+
const trimmed = String(value ?? "").trim();
|
|
8
|
+
if (!trimmed) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const resolved = path.resolve(trimmed);
|
|
12
|
+
try {
|
|
13
|
+
return await fs.realpath(resolved);
|
|
14
|
+
} catch {
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function extractSessionsDir(sessionFile) {
|
|
20
|
+
const normalized = String(sessionFile ?? "").trim();
|
|
21
|
+
if (!normalized) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const sessionsDir = path.dirname(path.resolve(normalized));
|
|
25
|
+
return path.basename(sessionsDir) === "sessions" ? sessionsDir : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function readSessionStore(storePath) {
|
|
29
|
+
try {
|
|
30
|
+
const raw = await fs.readFile(storePath, "utf-8");
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildTranscriptCandidates(entry, sessionsDir) {
|
|
39
|
+
const candidates = [];
|
|
40
|
+
const rawSessionFile = String(entry?.sessionFile ?? "").trim();
|
|
41
|
+
if (rawSessionFile) {
|
|
42
|
+
candidates.push(
|
|
43
|
+
path.isAbsolute(rawSessionFile)
|
|
44
|
+
? rawSessionFile
|
|
45
|
+
: path.resolve(sessionsDir, rawSessionFile),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const sessionId = String(entry?.sessionId ?? "").trim();
|
|
49
|
+
if (sessionId) {
|
|
50
|
+
candidates.push(path.join(sessionsDir, `${sessionId}.jsonl`));
|
|
51
|
+
}
|
|
52
|
+
return candidates;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function resolveSessionKeyFromTranscriptFile(sessionFile, logger) {
|
|
56
|
+
const targetPath = await resolvePathForComparison(sessionFile);
|
|
57
|
+
if (!targetPath) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const cached = RESOLVED_SESSION_KEY_CACHE.get(targetPath);
|
|
62
|
+
if (cached) {
|
|
63
|
+
logger?.info?.(
|
|
64
|
+
`[mobcode-session-key] cache-hit sessionFile=${targetPath} sessionKey=${cached}`,
|
|
65
|
+
);
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sessionsDir = extractSessionsDir(targetPath);
|
|
70
|
+
if (!sessionsDir) {
|
|
71
|
+
logger?.warn?.(
|
|
72
|
+
`[mobcode-session-key] unresolved sessionFile=${targetPath} reason=no_sessions_dir`,
|
|
73
|
+
);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const storePath = path.join(sessionsDir, "sessions.json");
|
|
78
|
+
const store = await readSessionStore(storePath);
|
|
79
|
+
const entries = Object.entries(store);
|
|
80
|
+
logger?.info?.(
|
|
81
|
+
`[mobcode-session-key] lookup sessionFile=${targetPath} storePath=${storePath} entries=${entries.length}`,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
for (const [sessionKey, entry] of entries) {
|
|
85
|
+
const candidates = buildTranscriptCandidates(entry, sessionsDir);
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
const candidatePath = await resolvePathForComparison(candidate);
|
|
88
|
+
if (candidatePath === targetPath) {
|
|
89
|
+
RESOLVED_SESSION_KEY_CACHE.set(targetPath, sessionKey);
|
|
90
|
+
logger?.info?.(
|
|
91
|
+
`[mobcode-session-key] resolved sessionFile=${targetPath} sessionKey=${sessionKey}`,
|
|
92
|
+
);
|
|
93
|
+
return sessionKey;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logger?.warn?.(
|
|
99
|
+
`[mobcode-session-key] unresolved sessionFile=${targetPath} reason=no_matching_store_entry`,
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
package/src/state-store.js
CHANGED
|
@@ -814,8 +814,11 @@ export class MobcodeStateStore {
|
|
|
814
814
|
const alreadyBackfilled =
|
|
815
815
|
typeof existing?.last_backfill_at === "string" && existing.last_backfill_at.trim().length > 0;
|
|
816
816
|
if (alreadyBackfilled) {
|
|
817
|
+
const messageCountRow = this._db()
|
|
818
|
+
.prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
|
|
819
|
+
.get(normalizedSessionKey);
|
|
817
820
|
this.logger?.info?.(
|
|
818
|
-
`[mobcode-store] ensureSessionIndexed skip session=${normalizedSessionKey} reason=already_backfilled`,
|
|
821
|
+
`[mobcode-store] ensureSessionIndexed skip session=${normalizedSessionKey} reason=already_backfilled lastBackfillAt=${existing?.last_backfill_at ?? "-"} messageRows=${Number(messageCountRow?.count ?? 0)}`,
|
|
819
822
|
);
|
|
820
823
|
return;
|
|
821
824
|
}
|
|
@@ -827,6 +830,24 @@ export class MobcodeStateStore {
|
|
|
827
830
|
`[mobcode-store] ensureSessionIndexed loader_result session=${normalizedSessionKey} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
828
831
|
);
|
|
829
832
|
await this.indexSessionMessages(normalizedSessionKey, messages);
|
|
833
|
+
const indexedCountRow = this._db()
|
|
834
|
+
.prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
|
|
835
|
+
.get(normalizedSessionKey);
|
|
836
|
+
const indexedCount = Number(indexedCountRow?.count ?? 0);
|
|
837
|
+
if (indexedCount <= 0) {
|
|
838
|
+
this.logger?.warn?.(
|
|
839
|
+
`[mobcode-store] ensureSessionIndexed incomplete session=${normalizedSessionKey} reason=no_messages_indexed`,
|
|
840
|
+
);
|
|
841
|
+
const now = new Date().toISOString();
|
|
842
|
+
this._db()
|
|
843
|
+
.prepare(
|
|
844
|
+
`INSERT INTO indexed_sessions(session_key, updated_at)
|
|
845
|
+
VALUES(?, ?)
|
|
846
|
+
ON CONFLICT(session_key) DO UPDATE SET updated_at=excluded.updated_at`,
|
|
847
|
+
)
|
|
848
|
+
.run(normalizedSessionKey, now);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
830
851
|
const now = new Date().toISOString();
|
|
831
852
|
this._db()
|
|
832
853
|
.prepare(
|
|
@@ -838,7 +859,7 @@ export class MobcodeStateStore {
|
|
|
838
859
|
)
|
|
839
860
|
.run(normalizedSessionKey, now, now);
|
|
840
861
|
this.logger?.info?.(
|
|
841
|
-
`[mobcode-store] ensureSessionIndexed complete session=${normalizedSessionKey}`,
|
|
862
|
+
`[mobcode-store] ensureSessionIndexed complete session=${normalizedSessionKey} indexedMessages=${indexedCount}`,
|
|
842
863
|
);
|
|
843
864
|
}
|
|
844
865
|
|
|
@@ -947,8 +968,14 @@ export class MobcodeStateStore {
|
|
|
947
968
|
}
|
|
948
969
|
|
|
949
970
|
async appendMessage(update) {
|
|
950
|
-
const sessionKey =
|
|
971
|
+
const sessionKey = String(update?.sessionKey ?? "").trim();
|
|
951
972
|
const normalizedMessage = normalizeMessageObject(update?.message, update?.messageId);
|
|
973
|
+
if (!sessionKey) {
|
|
974
|
+
this.logger?.warn?.(
|
|
975
|
+
`[mobcode-store] appendMessage skipped rawSessionKey=${String(update?.sessionKey ?? "").trim() || "-"} sessionFile=${String(update?.sessionFile ?? "").trim() || "-"} messageId=${String(update?.messageId ?? normalizedMessage?.id ?? "").trim() || "-"} reason=missing_session_key`,
|
|
976
|
+
);
|
|
977
|
+
return { sessionKey: null, count: 0, skipped: true };
|
|
978
|
+
}
|
|
952
979
|
this.logger?.info?.(
|
|
953
980
|
`[mobcode-store] appendMessage sessionKey=${sessionKey} rawSessionKey=${String(update?.sessionKey ?? "").trim() || "-"} sessionFile=${String(update?.sessionFile ?? "").trim() || "-"} messageId=${String(update?.messageId ?? normalizedMessage?.id ?? "").trim() || "-"} role=${String(normalizedMessage?.role ?? "").trim() || "-"}`,
|
|
954
981
|
);
|
|
@@ -956,7 +983,7 @@ export class MobcodeStateStore {
|
|
|
956
983
|
const row = this._db()
|
|
957
984
|
.prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
|
|
958
985
|
.get(sessionKey);
|
|
959
|
-
return { sessionKey, count: Number(row?.count ?? 0) };
|
|
986
|
+
return { sessionKey, count: Number(row?.count ?? 0), skipped: false };
|
|
960
987
|
}
|
|
961
988
|
|
|
962
989
|
async pageTranscriptMessages(sessionKey) {
|
|
@@ -969,6 +996,9 @@ export class MobcodeStateStore {
|
|
|
969
996
|
ORDER BY id ASC`,
|
|
970
997
|
)
|
|
971
998
|
.all(normalizedSessionKey);
|
|
999
|
+
this.logger?.info?.(
|
|
1000
|
+
`[mobcode-store] pageTranscriptMessages session=${normalizedSessionKey} rows=${rows.length}`,
|
|
1001
|
+
);
|
|
972
1002
|
return rows.map((row) => {
|
|
973
1003
|
const message = fromJson(row.raw_json, {});
|
|
974
1004
|
if (message && typeof message === "object" && !Array.isArray(message) && !message.id) {
|
|
@@ -985,6 +1015,9 @@ export class MobcodeStateStore {
|
|
|
985
1015
|
typeof beforeId === "number" && Number.isFinite(beforeId) ? beforeId : null;
|
|
986
1016
|
const transcriptMessages = await this.pageTranscriptMessages(normalizedSessionKey);
|
|
987
1017
|
const projected = projectConversationMessages(transcriptMessages, normalizedSessionKey);
|
|
1018
|
+
this.logger?.info?.(
|
|
1019
|
+
`[mobcode-store] pageSessionMessages session=${normalizedSessionKey} transcriptMessages=${transcriptMessages.length} projectedMessages=${projected.length} limit=${normalizedLimit} beforeId=${normalizedBeforeId ?? "-"}`
|
|
1020
|
+
);
|
|
988
1021
|
const endExclusive = normalizedBeforeId == null
|
|
989
1022
|
? projected.length
|
|
990
1023
|
: Math.max(0, Math.min(projected.length, normalizedBeforeId - 1));
|