@mobcode/openclaw-plugin 0.1.3 → 0.1.6
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 +7 -1
- package/src/runtime-events.js +22 -2
- package/src/session-key-resolver.js +103 -0
- package/src/state-store.js +44 -2
package/package.json
CHANGED
|
@@ -46,6 +46,9 @@ async function readSessionMessagesThroughChatHistory(config, sessionKey, logger)
|
|
|
46
46
|
if (!normalizedSessionKey) {
|
|
47
47
|
return [];
|
|
48
48
|
}
|
|
49
|
+
logger?.info?.(
|
|
50
|
+
`[mobcode-history] chat.history request session=${normalizedSessionKey} limit=1000`,
|
|
51
|
+
);
|
|
49
52
|
const payload = await requestThroughOperatorApprovalsGateway({
|
|
50
53
|
config,
|
|
51
54
|
method: "chat.history",
|
|
@@ -57,7 +60,7 @@ async function readSessionMessagesThroughChatHistory(config, sessionKey, logger)
|
|
|
57
60
|
});
|
|
58
61
|
const messages = payload?.messages;
|
|
59
62
|
logger?.info?.(
|
|
60
|
-
`[mobcode-history] chat.history session=${normalizedSessionKey} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
63
|
+
`[mobcode-history] chat.history response session=${normalizedSessionKey} sessionId=${String(payload?.sessionId ?? "").trim() || "-"} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
61
64
|
);
|
|
62
65
|
return Array.isArray(messages) ? messages : [];
|
|
63
66
|
}
|
|
@@ -78,6 +81,9 @@ export async function readSessionTranscriptMessages(config, sessionKey, logger)
|
|
|
78
81
|
);
|
|
79
82
|
// Fall back to adjacent-source runtime imports for development checkouts.
|
|
80
83
|
}
|
|
84
|
+
logger?.info?.(
|
|
85
|
+
`[mobcode-history] falling back to internal transcript reader session=${String(sessionKey ?? "").trim()}`,
|
|
86
|
+
);
|
|
81
87
|
const internalMessages = await readSessionTranscriptMessagesFromInternals(sessionKey);
|
|
82
88
|
logger?.info?.(
|
|
83
89
|
`[mobcode-history] internal fallback session=${String(sessionKey ?? "").trim()} messages=${internalMessages.length}`,
|
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,11 +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
|
+
}
|
|
48
|
+
api.logger?.info?.(
|
|
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}`,
|
|
50
|
+
);
|
|
42
51
|
if (retainMessages && update?.message !== undefined) {
|
|
43
|
-
await store.appendMessage(
|
|
52
|
+
const indexed = await store.appendMessage({
|
|
53
|
+
...update,
|
|
54
|
+
...(resolvedSessionKey ? { sessionKey: resolvedSessionKey } : {}),
|
|
55
|
+
});
|
|
56
|
+
api.logger?.info?.(
|
|
57
|
+
`[mobcode-runtime] transcript indexed sessionKey=${indexed?.sessionKey ?? "-"} total=${String(indexed?.count ?? 0)} skipped=${indexed?.skipped === true}`,
|
|
58
|
+
);
|
|
44
59
|
}
|
|
45
60
|
if (pushEnabled) {
|
|
46
|
-
await store.enqueuePush(
|
|
61
|
+
await store.enqueuePush(
|
|
62
|
+
createPushPayloadFromTranscript({
|
|
63
|
+
...update,
|
|
64
|
+
...(resolvedSessionKey ? { sessionKey: resolvedSessionKey } : {}),
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
47
67
|
}
|
|
48
68
|
});
|
|
49
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,10 +814,37 @@ 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
|
+
this.logger?.info?.(
|
|
818
|
+
`[mobcode-store] ensureSessionIndexed skip session=${normalizedSessionKey} reason=already_backfilled`,
|
|
819
|
+
);
|
|
817
820
|
return;
|
|
818
821
|
}
|
|
822
|
+
this.logger?.info?.(
|
|
823
|
+
`[mobcode-store] ensureSessionIndexed start session=${normalizedSessionKey}`,
|
|
824
|
+
);
|
|
819
825
|
const messages = Array.isArray(loader) ? loader : await loader();
|
|
826
|
+
this.logger?.info?.(
|
|
827
|
+
`[mobcode-store] ensureSessionIndexed loader_result session=${normalizedSessionKey} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
828
|
+
);
|
|
820
829
|
await this.indexSessionMessages(normalizedSessionKey, messages);
|
|
830
|
+
const indexedCountRow = this._db()
|
|
831
|
+
.prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
|
|
832
|
+
.get(normalizedSessionKey);
|
|
833
|
+
const indexedCount = Number(indexedCountRow?.count ?? 0);
|
|
834
|
+
if (indexedCount <= 0) {
|
|
835
|
+
this.logger?.warn?.(
|
|
836
|
+
`[mobcode-store] ensureSessionIndexed incomplete session=${normalizedSessionKey} reason=no_messages_indexed`,
|
|
837
|
+
);
|
|
838
|
+
const now = new Date().toISOString();
|
|
839
|
+
this._db()
|
|
840
|
+
.prepare(
|
|
841
|
+
`INSERT INTO indexed_sessions(session_key, updated_at)
|
|
842
|
+
VALUES(?, ?)
|
|
843
|
+
ON CONFLICT(session_key) DO UPDATE SET updated_at=excluded.updated_at`,
|
|
844
|
+
)
|
|
845
|
+
.run(normalizedSessionKey, now);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
821
848
|
const now = new Date().toISOString();
|
|
822
849
|
this._db()
|
|
823
850
|
.prepare(
|
|
@@ -828,11 +855,17 @@ export class MobcodeStateStore {
|
|
|
828
855
|
updated_at=excluded.updated_at`,
|
|
829
856
|
)
|
|
830
857
|
.run(normalizedSessionKey, now, now);
|
|
858
|
+
this.logger?.info?.(
|
|
859
|
+
`[mobcode-store] ensureSessionIndexed complete session=${normalizedSessionKey} indexedMessages=${indexedCount}`,
|
|
860
|
+
);
|
|
831
861
|
}
|
|
832
862
|
|
|
833
863
|
async indexSessionMessages(sessionKey, messages) {
|
|
834
864
|
const normalizedSessionKey = String(sessionKey ?? "").trim();
|
|
835
865
|
if (!normalizedSessionKey || !Array.isArray(messages) || messages.length == 0) {
|
|
866
|
+
this.logger?.info?.(
|
|
867
|
+
`[mobcode-store] indexSessionMessages noop session=${normalizedSessionKey || "-"} messages=${Array.isArray(messages) ? messages.length : 0}`,
|
|
868
|
+
);
|
|
836
869
|
return;
|
|
837
870
|
}
|
|
838
871
|
|
|
@@ -932,13 +965,22 @@ export class MobcodeStateStore {
|
|
|
932
965
|
}
|
|
933
966
|
|
|
934
967
|
async appendMessage(update) {
|
|
935
|
-
const sessionKey =
|
|
968
|
+
const sessionKey = String(update?.sessionKey ?? "").trim();
|
|
936
969
|
const normalizedMessage = normalizeMessageObject(update?.message, update?.messageId);
|
|
970
|
+
if (!sessionKey) {
|
|
971
|
+
this.logger?.warn?.(
|
|
972
|
+
`[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`,
|
|
973
|
+
);
|
|
974
|
+
return { sessionKey: null, count: 0, skipped: true };
|
|
975
|
+
}
|
|
976
|
+
this.logger?.info?.(
|
|
977
|
+
`[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() || "-"}`,
|
|
978
|
+
);
|
|
937
979
|
await this.indexSessionMessages(sessionKey, [normalizedMessage]);
|
|
938
980
|
const row = this._db()
|
|
939
981
|
.prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
|
|
940
982
|
.get(sessionKey);
|
|
941
|
-
return { sessionKey, count: Number(row?.count ?? 0) };
|
|
983
|
+
return { sessionKey, count: Number(row?.count ?? 0), skipped: false };
|
|
942
984
|
}
|
|
943
985
|
|
|
944
986
|
async pageTranscriptMessages(sessionKey) {
|