@mobcode/openclaw-plugin 0.1.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobcode/openclaw-plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "MobCode integration plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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(update);
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(createPushPayloadFromTranscript(update));
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
+
@@ -827,6 +827,24 @@ export class MobcodeStateStore {
827
827
  `[mobcode-store] ensureSessionIndexed loader_result session=${normalizedSessionKey} messages=${Array.isArray(messages) ? messages.length : 0}`,
828
828
  );
829
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
+ }
830
848
  const now = new Date().toISOString();
831
849
  this._db()
832
850
  .prepare(
@@ -838,7 +856,7 @@ export class MobcodeStateStore {
838
856
  )
839
857
  .run(normalizedSessionKey, now, now);
840
858
  this.logger?.info?.(
841
- `[mobcode-store] ensureSessionIndexed complete session=${normalizedSessionKey}`,
859
+ `[mobcode-store] ensureSessionIndexed complete session=${normalizedSessionKey} indexedMessages=${indexedCount}`,
842
860
  );
843
861
  }
844
862
 
@@ -947,8 +965,14 @@ export class MobcodeStateStore {
947
965
  }
948
966
 
949
967
  async appendMessage(update) {
950
- const sessionKey = normalizeSessionKey(update?.sessionKey, update?.sessionFile);
968
+ const sessionKey = String(update?.sessionKey ?? "").trim();
951
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
+ }
952
976
  this.logger?.info?.(
953
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() || "-"}`,
954
978
  );
@@ -956,7 +980,7 @@ export class MobcodeStateStore {
956
980
  const row = this._db()
957
981
  .prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
958
982
  .get(sessionKey);
959
- return { sessionKey, count: Number(row?.count ?? 0) };
983
+ return { sessionKey, count: Number(row?.count ?? 0), skipped: false };
960
984
  }
961
985
 
962
986
  async pageTranscriptMessages(sessionKey) {