@mobcode/openclaw-plugin 0.1.7 → 0.1.10

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.7",
3
+ "version": "0.1.10",
4
4
  "description": "MobCode integration plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,92 +1,17 @@
1
- import { requestThroughOperatorApprovalsGateway } from "./openclaw-gateway-runtime.js";
2
-
3
- async function tryImport(modulePath) {
4
- try {
5
- return await import(modulePath);
6
- } catch {
7
- return null;
8
- }
9
- }
10
-
11
- async function loadSessionUtilsRuntime() {
12
- return (
13
- (await tryImport("../../openclaw/src/gateway/session-utils.js")) ??
14
- (await tryImport("../openclaw/src/gateway/session-utils.js"))
15
- );
16
- }
17
-
18
- async function readSessionTranscriptMessagesFromInternals(sessionKey) {
19
- const runtime = await loadSessionUtilsRuntime();
20
- if (!runtime?.loadSessionEntry || !runtime?.readSessionMessages) {
21
- return [];
22
- }
23
-
24
- const normalizedSessionKey = String(sessionKey ?? "").trim();
25
- if (!normalizedSessionKey) {
26
- return [];
27
- }
28
-
29
- const loaded = runtime.loadSessionEntry(normalizedSessionKey);
30
- const entry = loaded?.entry;
31
- const storePath = loaded?.storePath;
32
- const sessionId =
33
- typeof entry?.sessionId === "string" && entry.sessionId.trim()
34
- ? entry.sessionId.trim()
35
- : "";
36
- if (!sessionId) {
37
- return [];
38
- }
39
-
40
- const rawMessages = runtime.readSessionMessages(sessionId, storePath, entry?.sessionFile);
41
- return Array.isArray(rawMessages) ? rawMessages : [];
42
- }
43
-
44
- async function readSessionMessagesThroughChatHistory(config, sessionKey, logger) {
45
- const normalizedSessionKey = String(sessionKey ?? "").trim();
46
- if (!normalizedSessionKey) {
47
- return [];
48
- }
49
- logger?.info?.(
50
- `[mobcode-history] chat.history request session=${normalizedSessionKey} limit=1000`,
51
- );
52
- const payload = await requestThroughOperatorApprovalsGateway({
53
- config,
54
- method: "chat.history",
55
- requestParams: {
56
- sessionKey: normalizedSessionKey,
57
- limit: 1000,
58
- },
59
- clientDisplayName: "MobCode History Backfill",
1
+ import { readSessionMessagesFromRuntime } from "./session-transcript-runtime.js";
2
+
3
+ export async function readSessionTranscriptMessages(runtime, _config, sessionKey, logger) {
4
+ const runtimeMessages = await readSessionMessagesFromRuntime({
5
+ runtime,
6
+ config: runtime?.config?.loadConfig?.(),
7
+ sessionKey,
8
+ logger,
60
9
  });
61
- const messages = payload?.messages;
62
- logger?.info?.(
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
- );
65
- return Array.isArray(messages) ? messages : [];
66
- }
67
-
68
- export async function readSessionTranscriptMessages(config, sessionKey, logger) {
69
- try {
70
- const gatewayMessages = await readSessionMessagesThroughChatHistory(
71
- config,
72
- sessionKey,
73
- logger,
74
- );
75
- if (gatewayMessages.length > 0) {
76
- return gatewayMessages;
77
- }
78
- } catch (error) {
79
- logger?.warn?.(
80
- `[mobcode-history] chat.history failed session=${String(sessionKey ?? "").trim()}: ${error instanceof Error ? error.message : String(error)}`,
81
- );
82
- // Fall back to adjacent-source runtime imports for development checkouts.
10
+ if (runtimeMessages.length > 0) {
11
+ return runtimeMessages;
83
12
  }
84
13
  logger?.info?.(
85
- `[mobcode-history] falling back to internal transcript reader session=${String(sessionKey ?? "").trim()}`,
86
- );
87
- const internalMessages = await readSessionTranscriptMessagesFromInternals(sessionKey);
88
- logger?.info?.(
89
- `[mobcode-history] internal fallback session=${String(sessionKey ?? "").trim()} messages=${internalMessages.length}`,
14
+ `[mobcode-history] no transcript history resolved session=${String(sessionKey ?? "").trim()}`,
90
15
  );
91
- return internalMessages;
16
+ return [];
92
17
  }
@@ -37,6 +37,7 @@ export function createMobcodePluginDefinition() {
37
37
  ensureSessionMessages: async (sessionKey) => {
38
38
  await store.ensureSessionIndexed(sessionKey, async () =>
39
39
  readSessionTranscriptMessages(
40
+ api.runtime,
40
41
  api.runtime.config.loadConfig(),
41
42
  sessionKey,
42
43
  api.logger,
@@ -0,0 +1,160 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ function trim(value) {
5
+ return typeof value === "string" ? value.trim() : "";
6
+ }
7
+
8
+ function parseAgentId(sessionKey, config) {
9
+ const raw = trim(sessionKey).toLowerCase();
10
+ if (raw.startsWith("agent:")) {
11
+ const parts = raw.split(":");
12
+ if (parts.length >= 3 && parts[1]) {
13
+ return parts[1];
14
+ }
15
+ }
16
+ const configuredAgents = Array.isArray(config?.agents?.list) ? config.agents.list : [];
17
+ const defaultAgentId =
18
+ configuredAgents.find((agent) => agent?.default)?.id ??
19
+ configuredAgents[0]?.id ??
20
+ "main";
21
+ return trim(defaultAgentId) || "main";
22
+ }
23
+
24
+ function resolveStoreCandidates(runtime, config, sessionKey) {
25
+ const parsedAgentId = parseAgentId(sessionKey, config);
26
+ const configuredAgents = Array.isArray(config?.agents?.list) ? config.agents.list : [];
27
+ const ids = new Set([parsedAgentId]);
28
+ for (const agent of configuredAgents) {
29
+ const id = trim(agent?.id);
30
+ if (id) {
31
+ ids.add(id);
32
+ }
33
+ }
34
+ const candidates = [];
35
+ for (const agentId of ids) {
36
+ try {
37
+ const storePath = runtime.agent.session.resolveStorePath(config?.session?.store, { agentId });
38
+ if (trim(storePath)) {
39
+ candidates.push({
40
+ agentId,
41
+ storePath,
42
+ });
43
+ }
44
+ } catch {
45
+ // Ignore invalid store path resolutions and continue scanning other agents.
46
+ }
47
+ }
48
+ return candidates;
49
+ }
50
+
51
+ function attachTranscriptMeta(message, meta) {
52
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
53
+ return message;
54
+ }
55
+ const record = message;
56
+ const existing =
57
+ record.__openclaw && typeof record.__openclaw === "object" && !Array.isArray(record.__openclaw)
58
+ ? record.__openclaw
59
+ : {};
60
+ return {
61
+ ...record,
62
+ __openclaw: {
63
+ ...existing,
64
+ ...meta,
65
+ },
66
+ };
67
+ }
68
+
69
+ function readTranscriptMessages(filePath) {
70
+ if (!trim(filePath) || !fs.existsSync(filePath)) {
71
+ return [];
72
+ }
73
+ const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/);
74
+ const messages = [];
75
+ let seq = 0;
76
+ for (const line of lines) {
77
+ if (!trim(line)) {
78
+ continue;
79
+ }
80
+ try {
81
+ const parsed = JSON.parse(line);
82
+ if (parsed?.message) {
83
+ seq += 1;
84
+ messages.push(
85
+ attachTranscriptMeta(parsed.message, {
86
+ ...(trim(parsed?.id) ? { id: trim(parsed.id) } : {}),
87
+ seq,
88
+ }),
89
+ );
90
+ continue;
91
+ }
92
+ if (parsed?.type === "compaction") {
93
+ seq += 1;
94
+ messages.push({
95
+ role: "system",
96
+ content: [{ type: "text", text: "Compaction" }],
97
+ __openclaw: {
98
+ kind: "compaction",
99
+ ...(trim(parsed?.id) ? { id: trim(parsed.id) } : {}),
100
+ seq,
101
+ },
102
+ });
103
+ }
104
+ } catch {
105
+ // Ignore malformed transcript lines.
106
+ }
107
+ }
108
+ return messages;
109
+ }
110
+
111
+ export async function readSessionMessagesFromRuntime({ runtime, config, sessionKey, logger }) {
112
+ const normalizedSessionKey = trim(sessionKey);
113
+ if (!normalizedSessionKey || !runtime?.agent?.session) {
114
+ return [];
115
+ }
116
+ const candidates = resolveStoreCandidates(runtime, config, normalizedSessionKey);
117
+ logger?.info?.(
118
+ `[mobcode-history] runtime reader start session=${normalizedSessionKey} storeCandidates=${candidates.map((candidate) => `${candidate.agentId}:${candidate.storePath}`).join(",") || "-"}`,
119
+ );
120
+ for (const candidate of candidates) {
121
+ try {
122
+ const store = runtime.agent.session.loadSessionStore(candidate.storePath, {
123
+ skipCache: true,
124
+ });
125
+ const entry = store?.[normalizedSessionKey];
126
+ if (!entry) {
127
+ logger?.info?.(
128
+ `[mobcode-history] runtime reader miss session=${normalizedSessionKey} agentId=${candidate.agentId} storePath=${candidate.storePath}`,
129
+ );
130
+ continue;
131
+ }
132
+ const sessionId = trim(entry?.sessionId);
133
+ if (!sessionId) {
134
+ logger?.warn?.(
135
+ `[mobcode-history] runtime reader invalid_entry session=${normalizedSessionKey} agentId=${candidate.agentId} storePath=${candidate.storePath} reason=missing_session_id`,
136
+ );
137
+ continue;
138
+ }
139
+ const transcriptFile = trim(entry?.sessionFile)
140
+ ? path.resolve(trim(entry.sessionFile))
141
+ : runtime.agent.session.resolveSessionFilePath(sessionId, entry, {
142
+ sessionsDir: path.dirname(candidate.storePath),
143
+ agentId: candidate.agentId,
144
+ });
145
+ const messages = readTranscriptMessages(transcriptFile);
146
+ logger?.info?.(
147
+ `[mobcode-history] runtime reader hit session=${normalizedSessionKey} agentId=${candidate.agentId} storePath=${candidate.storePath} transcriptFile=${transcriptFile} messages=${messages.length}`,
148
+ );
149
+ return messages;
150
+ } catch (error) {
151
+ logger?.warn?.(
152
+ `[mobcode-history] runtime reader error session=${normalizedSessionKey} agentId=${candidate.agentId} storePath=${candidate.storePath}: ${error instanceof Error ? error.message : String(error)}`,
153
+ );
154
+ }
155
+ }
156
+ logger?.warn?.(
157
+ `[mobcode-history] runtime reader unresolved session=${normalizedSessionKey}`,
158
+ );
159
+ return [];
160
+ }
@@ -811,17 +811,23 @@ export class MobcodeStateStore {
811
811
  LIMIT 1`,
812
812
  )
813
813
  .get(normalizedSessionKey);
814
+ const messageCountRow = this._db()
815
+ .prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
816
+ .get(normalizedSessionKey);
817
+ const messageRows = Number(messageCountRow?.count ?? 0);
814
818
  const alreadyBackfilled =
815
819
  typeof existing?.last_backfill_at === "string" && existing.last_backfill_at.trim().length > 0;
816
- if (alreadyBackfilled) {
817
- const messageCountRow = this._db()
818
- .prepare(`SELECT COUNT(*) AS count FROM messages WHERE session_key=?`)
819
- .get(normalizedSessionKey);
820
+ if (alreadyBackfilled && messageRows > 0) {
820
821
  this.logger?.info?.(
821
- `[mobcode-store] ensureSessionIndexed skip session=${normalizedSessionKey} reason=already_backfilled lastBackfillAt=${existing?.last_backfill_at ?? "-"} messageRows=${Number(messageCountRow?.count ?? 0)}`,
822
+ `[mobcode-store] ensureSessionIndexed skip session=${normalizedSessionKey} reason=already_backfilled lastBackfillAt=${existing?.last_backfill_at ?? "-"} messageRows=${messageRows}`,
822
823
  );
823
824
  return;
824
825
  }
826
+ if (alreadyBackfilled && messageRows <= 0) {
827
+ this.logger?.warn?.(
828
+ `[mobcode-store] ensureSessionIndexed retry session=${normalizedSessionKey} reason=stale_backfill_marker lastBackfillAt=${existing?.last_backfill_at ?? "-"} messageRows=${messageRows}`,
829
+ );
830
+ }
825
831
  this.logger?.info?.(
826
832
  `[mobcode-store] ensureSessionIndexed start session=${normalizedSessionKey}`,
827
833
  );