@mobcode/openclaw-plugin 0.1.16 → 0.1.19
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/gateway-methods.js +67 -10
- package/src/state-store.js +66 -389
package/package.json
CHANGED
package/src/gateway-methods.js
CHANGED
|
@@ -5,6 +5,30 @@ export function registerMobcodeGatewayMethods({
|
|
|
5
5
|
pluginMetadata,
|
|
6
6
|
api,
|
|
7
7
|
}) {
|
|
8
|
+
const respondInvalid = (respond, message, details) => {
|
|
9
|
+
respond(
|
|
10
|
+
false,
|
|
11
|
+
undefined,
|
|
12
|
+
{
|
|
13
|
+
code: "INVALID_REQUEST",
|
|
14
|
+
message,
|
|
15
|
+
...(details !== undefined ? { details } : {}),
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const respondUnavailable = (respond, message, details) => {
|
|
21
|
+
respond(
|
|
22
|
+
false,
|
|
23
|
+
undefined,
|
|
24
|
+
{
|
|
25
|
+
code: "UNAVAILABLE",
|
|
26
|
+
message,
|
|
27
|
+
...(details !== undefined ? { details } : {}),
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
8
32
|
api.registerGatewayMethod("mobcode.capabilities", async ({ respond }) => {
|
|
9
33
|
respond(true, {
|
|
10
34
|
ok: true,
|
|
@@ -31,7 +55,7 @@ export function registerMobcodeGatewayMethods({
|
|
|
31
55
|
api.registerGatewayMethod("mobcode.provider.models.list", async ({ params, respond }) => {
|
|
32
56
|
const providerId = typeof params?.providerId === "string" ? params.providerId.trim() : "";
|
|
33
57
|
if (!providerId) {
|
|
34
|
-
respond
|
|
58
|
+
respondInvalid(respond, "providerId required");
|
|
35
59
|
return;
|
|
36
60
|
}
|
|
37
61
|
const payload = await builders.providerModels(providerId);
|
|
@@ -44,7 +68,7 @@ export function registerMobcodeGatewayMethods({
|
|
|
44
68
|
api.registerGatewayMethod("mobcode.messages.page", async ({ params, respond }) => {
|
|
45
69
|
const sessionKey = typeof params?.sessionKey === "string" ? params.sessionKey.trim() : "";
|
|
46
70
|
if (!sessionKey) {
|
|
47
|
-
respond
|
|
71
|
+
respondInvalid(respond, "sessionKey required");
|
|
48
72
|
return;
|
|
49
73
|
}
|
|
50
74
|
api.logger?.info?.(
|
|
@@ -84,12 +108,12 @@ export function registerMobcodeGatewayMethods({
|
|
|
84
108
|
api.registerGatewayMethod("mobcode.artifacts.get", async ({ params, respond }) => {
|
|
85
109
|
const artifactId = typeof params?.artifactId === "string" ? params.artifactId.trim() : "";
|
|
86
110
|
if (!artifactId) {
|
|
87
|
-
respond
|
|
111
|
+
respondInvalid(respond, "artifactId required");
|
|
88
112
|
return;
|
|
89
113
|
}
|
|
90
114
|
const artifact = await store.readArtifactById(artifactId);
|
|
91
115
|
if (!artifact?.document) {
|
|
92
|
-
respond
|
|
116
|
+
respondInvalid(respond, "artifact not found");
|
|
93
117
|
return;
|
|
94
118
|
}
|
|
95
119
|
respond(true, {
|
|
@@ -107,12 +131,12 @@ export function registerMobcodeGatewayMethods({
|
|
|
107
131
|
api.registerGatewayMethod("mobcode.artifacts.resolveBlob", async ({ params, respond }) => {
|
|
108
132
|
const blobId = typeof params?.blobId === "string" ? params.blobId.trim() : "";
|
|
109
133
|
if (!blobId) {
|
|
110
|
-
respond
|
|
134
|
+
respondInvalid(respond, "blobId required");
|
|
111
135
|
return;
|
|
112
136
|
}
|
|
113
137
|
const path = await store.resolveArtifactBlobPath(blobId);
|
|
114
138
|
if (!path) {
|
|
115
|
-
respond
|
|
139
|
+
respondInvalid(respond, "blob not found");
|
|
116
140
|
return;
|
|
117
141
|
}
|
|
118
142
|
respond(true, {
|
|
@@ -122,10 +146,43 @@ export function registerMobcodeGatewayMethods({
|
|
|
122
146
|
});
|
|
123
147
|
});
|
|
124
148
|
|
|
149
|
+
api.registerGatewayMethod("mobcode.tool_result.get", async ({ params, respond }) => {
|
|
150
|
+
const sessionKey = typeof params?.sessionKey === "string" ? params.sessionKey.trim() : "";
|
|
151
|
+
const toolCallId = typeof params?.toolCallId === "string" ? params.toolCallId.trim() : "";
|
|
152
|
+
if (!sessionKey || !toolCallId) {
|
|
153
|
+
respondInvalid(respond, "sessionKey and toolCallId are required");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const timeoutMs =
|
|
157
|
+
typeof params?.waitMs === "number"
|
|
158
|
+
? params.waitMs
|
|
159
|
+
: typeof params?.timeoutMs === "number"
|
|
160
|
+
? params.timeoutMs
|
|
161
|
+
: 0;
|
|
162
|
+
const result = await store.waitForToolResult(sessionKey, toolCallId, {
|
|
163
|
+
timeoutMs,
|
|
164
|
+
});
|
|
165
|
+
if (!result?.message) {
|
|
166
|
+
respondUnavailable(respond, "tool result not found", {
|
|
167
|
+
sessionKey,
|
|
168
|
+
toolCallId,
|
|
169
|
+
timeoutMs,
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
respond(true, {
|
|
174
|
+
ok: true,
|
|
175
|
+
sessionKey,
|
|
176
|
+
toolCallId,
|
|
177
|
+
messageId: result.messageId,
|
|
178
|
+
message: result.message,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
125
182
|
api.registerGatewayMethod("mobcode.client.register", async ({ params, respond }) => {
|
|
126
183
|
const clientId = typeof params?.clientId === "string" ? params.clientId.trim() : "";
|
|
127
184
|
if (!clientId) {
|
|
128
|
-
respond
|
|
185
|
+
respondInvalid(respond, "clientId required");
|
|
129
186
|
return;
|
|
130
187
|
}
|
|
131
188
|
const result = await store.registerClientSession({
|
|
@@ -146,7 +203,7 @@ export function registerMobcodeGatewayMethods({
|
|
|
146
203
|
api.registerGatewayMethod("mobcode.client.actions.poll", async ({ params, respond }) => {
|
|
147
204
|
const clientId = typeof params?.clientId === "string" ? params.clientId.trim() : "";
|
|
148
205
|
if (!clientId) {
|
|
149
|
-
respond
|
|
206
|
+
respondInvalid(respond, "clientId required");
|
|
150
207
|
return;
|
|
151
208
|
}
|
|
152
209
|
const actions = await store.pollClientActions(clientId, {
|
|
@@ -161,7 +218,7 @@ export function registerMobcodeGatewayMethods({
|
|
|
161
218
|
api.registerGatewayMethod("mobcode.client.actions.wait", async ({ params, respond }) => {
|
|
162
219
|
const clientId = typeof params?.clientId === "string" ? params.clientId.trim() : "";
|
|
163
220
|
if (!clientId) {
|
|
164
|
-
respond
|
|
221
|
+
respondInvalid(respond, "clientId required");
|
|
165
222
|
return;
|
|
166
223
|
}
|
|
167
224
|
const action = await store.waitForClientAction(clientId, {
|
|
@@ -178,7 +235,7 @@ export function registerMobcodeGatewayMethods({
|
|
|
178
235
|
const actionId = typeof params?.actionId === "string" ? params.actionId.trim() : "";
|
|
179
236
|
const status = typeof params?.status === "string" ? params.status.trim() : "";
|
|
180
237
|
if (!clientId || !actionId || !status) {
|
|
181
|
-
respond
|
|
238
|
+
respondInvalid(respond, "clientId, actionId, and status are required");
|
|
182
239
|
return;
|
|
183
240
|
}
|
|
184
241
|
respond(true, {
|
package/src/state-store.js
CHANGED
|
@@ -157,122 +157,6 @@ function decodeMobcodePayload(value) {
|
|
|
157
157
|
return null;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
function extractMobcodeContentBlocksFromToolResult(rawResult) {
|
|
161
|
-
const payload = decodeMobcodePayload(rawResult);
|
|
162
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
return Array.isArray(payload.content_blocks)
|
|
166
|
-
? cloneJson(payload.content_blocks) ?? []
|
|
167
|
-
: [];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function timelineTextPreview(timeline) {
|
|
171
|
-
if (!Array.isArray(timeline) || timeline.length === 0) {
|
|
172
|
-
return "";
|
|
173
|
-
}
|
|
174
|
-
const parts = [];
|
|
175
|
-
for (const item of timeline) {
|
|
176
|
-
if (!item || typeof item !== "object") continue;
|
|
177
|
-
const kind = String(item.kind ?? "").trim().toLowerCase();
|
|
178
|
-
if (kind === "text") {
|
|
179
|
-
const text = String(item.text ?? "");
|
|
180
|
-
if (text.trim()) parts.push(text);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (kind === "step") {
|
|
184
|
-
const title = String(item.title ?? "");
|
|
185
|
-
const detail = String(item.detail ?? "");
|
|
186
|
-
if (title.trim()) parts.push(title);
|
|
187
|
-
if (detail.trim()) parts.push(detail);
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
if (kind === "artifact") {
|
|
191
|
-
const title = String(item?.block?.title ?? "");
|
|
192
|
-
if (title.trim()) parts.push(title);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (kind === "question") {
|
|
196
|
-
const title = String(item.title ?? item?.payload?.title ?? "");
|
|
197
|
-
if (title.trim()) parts.push(title);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return parts.join("\n");
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function stableId(prefix, seed) {
|
|
204
|
-
const digest = crypto.createHash("sha1").update(`${prefix}\n${seed}`).digest("hex");
|
|
205
|
-
return `${prefix}-${digest.slice(0, 12)}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function createTextTimelineItem({ id, text, status = "done" }) {
|
|
209
|
-
return {
|
|
210
|
-
id,
|
|
211
|
-
kind: "text",
|
|
212
|
-
status,
|
|
213
|
-
text,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function createToolTimelineItem({ id, toolCall }) {
|
|
218
|
-
const status = toolCall.isError
|
|
219
|
-
? "error"
|
|
220
|
-
: toolCall.phase === "result"
|
|
221
|
-
? "done"
|
|
222
|
-
: "streaming";
|
|
223
|
-
return {
|
|
224
|
-
id,
|
|
225
|
-
kind: "tool",
|
|
226
|
-
status,
|
|
227
|
-
toolCallId: toolCall.toolCallId,
|
|
228
|
-
...(toolCall.approvalId ? { approvalId: toolCall.approvalId } : {}),
|
|
229
|
-
name: toolCall.name,
|
|
230
|
-
args: cloneJson(toolCall.args),
|
|
231
|
-
result: cloneJson(toolCall.result),
|
|
232
|
-
isError: toolCall.isError === true,
|
|
233
|
-
phase: toolCall.phase,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function timelineItemFromContentBlock(block, seed) {
|
|
238
|
-
if (!block || typeof block !== "object") {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
const type = String(block.type ?? "").trim().toLowerCase();
|
|
242
|
-
if (type === "text") {
|
|
243
|
-
const text = String(block.text ?? "");
|
|
244
|
-
if (!text.trim()) return null;
|
|
245
|
-
return createTextTimelineItem({
|
|
246
|
-
id: stableId("timeline-text", `${seed}\n${text}`),
|
|
247
|
-
text,
|
|
248
|
-
status: "done",
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
if (type === "artifact_ref") {
|
|
252
|
-
return {
|
|
253
|
-
id: stableId("timeline-artifact", `${seed}\n${JSON.stringify(block)}`),
|
|
254
|
-
kind: "artifact",
|
|
255
|
-
status: "done",
|
|
256
|
-
block: cloneJson(block),
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
if (type === "question") {
|
|
260
|
-
return {
|
|
261
|
-
id: stableId("timeline-question", `${seed}\n${JSON.stringify(block)}`),
|
|
262
|
-
kind: "question",
|
|
263
|
-
status: "done",
|
|
264
|
-
payload: cloneJson(block),
|
|
265
|
-
...cloneJson(block),
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
id: stableId("timeline-unknown", `${seed}\n${JSON.stringify(block)}`),
|
|
270
|
-
kind: "unknown",
|
|
271
|
-
status: "error",
|
|
272
|
-
block: cloneJson(block),
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
160
|
function extractArtifactDocumentFromMessage(message) {
|
|
277
161
|
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
278
162
|
return null;
|
|
@@ -295,273 +179,6 @@ function extractArtifactDocumentFromMessage(message) {
|
|
|
295
179
|
return null;
|
|
296
180
|
}
|
|
297
181
|
|
|
298
|
-
function createConversationMessage({ id, role, text = "", replyToId = null, replyPreview = null }) {
|
|
299
|
-
return {
|
|
300
|
-
id,
|
|
301
|
-
role,
|
|
302
|
-
text,
|
|
303
|
-
status: "done",
|
|
304
|
-
toolCalls: [],
|
|
305
|
-
contentBlocks: [],
|
|
306
|
-
timeline: [],
|
|
307
|
-
...(replyToId ? { replyToId } : {}),
|
|
308
|
-
...(replyPreview ? { replyPreview } : {}),
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function upsertToolCall(message, toolCall, seed) {
|
|
313
|
-
const toolCalls = Array.isArray(message.toolCalls) ? [...message.toolCalls] : [];
|
|
314
|
-
const toolIndex = toolCalls.findIndex((item) => item?.toolCallId === toolCall.toolCallId);
|
|
315
|
-
if (toolIndex >= 0) {
|
|
316
|
-
toolCalls[toolIndex] = { ...toolCalls[toolIndex], ...cloneJson(toolCall) };
|
|
317
|
-
} else {
|
|
318
|
-
toolCalls.push(cloneJson(toolCall));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const timeline = Array.isArray(message.timeline) ? [...message.timeline] : [];
|
|
322
|
-
const timelineIndex = timeline.findIndex(
|
|
323
|
-
(item) => item?.kind === "tool" && item?.toolCallId === toolCall.toolCallId,
|
|
324
|
-
);
|
|
325
|
-
const toolItem = createToolTimelineItem({
|
|
326
|
-
id: timelineIndex >= 0 ? timeline[timelineIndex].id : stableId("timeline-tool", seed),
|
|
327
|
-
toolCall,
|
|
328
|
-
});
|
|
329
|
-
if (timelineIndex >= 0) {
|
|
330
|
-
timeline[timelineIndex] = toolItem;
|
|
331
|
-
} else {
|
|
332
|
-
timeline.push(toolItem);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return {
|
|
336
|
-
...message,
|
|
337
|
-
toolCalls,
|
|
338
|
-
timeline,
|
|
339
|
-
text: timelineTextPreview(timeline) || message.text || "",
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function appendAssistantTextSegment(message, text, seed) {
|
|
344
|
-
if (!String(text ?? "").trim()) {
|
|
345
|
-
return message;
|
|
346
|
-
}
|
|
347
|
-
const normalizedText = String(text);
|
|
348
|
-
const timeline = Array.isArray(message.timeline) ? [...message.timeline] : [];
|
|
349
|
-
const last = timeline.length > 0 ? timeline[timeline.length - 1] : null;
|
|
350
|
-
if (last?.kind === "text" && last?.status === "streaming") {
|
|
351
|
-
timeline[timeline.length - 1] = {
|
|
352
|
-
...last,
|
|
353
|
-
text: `${String(last.text ?? "")}${normalizedText}`,
|
|
354
|
-
status: "done",
|
|
355
|
-
};
|
|
356
|
-
} else {
|
|
357
|
-
timeline.push(
|
|
358
|
-
createTextTimelineItem({
|
|
359
|
-
id: stableId("timeline-text", `${seed}\n${timeline.length}`),
|
|
360
|
-
text: normalizedText,
|
|
361
|
-
status: "done",
|
|
362
|
-
}),
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
...message,
|
|
367
|
-
timeline,
|
|
368
|
-
text: timelineTextPreview(timeline) || message.text || "",
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function appendToolResult(message, { toolCallId, result, isError }, seed) {
|
|
373
|
-
const existingTool =
|
|
374
|
-
(Array.isArray(message.toolCalls)
|
|
375
|
-
? message.toolCalls.find((item) => item?.toolCallId === toolCallId)
|
|
376
|
-
: null) ?? {
|
|
377
|
-
toolCallId,
|
|
378
|
-
name: "unknown",
|
|
379
|
-
phase: "result",
|
|
380
|
-
result: null,
|
|
381
|
-
isError: false,
|
|
382
|
-
};
|
|
383
|
-
let next = upsertToolCall(
|
|
384
|
-
message,
|
|
385
|
-
{
|
|
386
|
-
...existingTool,
|
|
387
|
-
result: cloneJson(result),
|
|
388
|
-
isError: isError === true,
|
|
389
|
-
phase: "result",
|
|
390
|
-
},
|
|
391
|
-
`${seed}\n${toolCallId}`,
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
const blocks = extractMobcodeContentBlocksFromToolResult(result);
|
|
395
|
-
if (blocks.length === 0) {
|
|
396
|
-
return next;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const contentBlocks = Array.isArray(next.contentBlocks) ? [...next.contentBlocks] : [];
|
|
400
|
-
const timeline = Array.isArray(next.timeline) ? [...next.timeline] : [];
|
|
401
|
-
const seenBlockKeys = new Set(contentBlocks.map((block) => JSON.stringify(block)));
|
|
402
|
-
const seenTimelineKeys = new Set(timeline.map((item) => JSON.stringify(item)));
|
|
403
|
-
for (const block of blocks) {
|
|
404
|
-
const blockKey = JSON.stringify(block);
|
|
405
|
-
if (!seenBlockKeys.has(blockKey)) {
|
|
406
|
-
seenBlockKeys.add(blockKey);
|
|
407
|
-
contentBlocks.push(cloneJson(block));
|
|
408
|
-
}
|
|
409
|
-
const item = timelineItemFromContentBlock(block, `${seed}\n${blockKey}`);
|
|
410
|
-
if (!item) continue;
|
|
411
|
-
const timelineKey = JSON.stringify(item);
|
|
412
|
-
if (!seenTimelineKeys.has(timelineKey)) {
|
|
413
|
-
seenTimelineKeys.add(timelineKey);
|
|
414
|
-
timeline.push(item);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return {
|
|
418
|
-
...next,
|
|
419
|
-
contentBlocks,
|
|
420
|
-
timeline,
|
|
421
|
-
text: timelineTextPreview(timeline) || next.text || "",
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function appendAssistantTranscriptContent(message, content, seed) {
|
|
426
|
-
if (!Array.isArray(content)) {
|
|
427
|
-
return appendAssistantTextSegment(
|
|
428
|
-
message,
|
|
429
|
-
textToDisplayForAssistant(extractMessageText({ content })),
|
|
430
|
-
seed,
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
let next = { ...message };
|
|
435
|
-
for (const rawBlock of content) {
|
|
436
|
-
if (!rawBlock || typeof rawBlock !== "object") continue;
|
|
437
|
-
const block = cloneJson(rawBlock) ?? {};
|
|
438
|
-
const type = String(block.type ?? "").trim().toLowerCase();
|
|
439
|
-
if (type === "text" || type === "output_text" || type === "input_text") {
|
|
440
|
-
next = appendAssistantTextSegment(
|
|
441
|
-
next,
|
|
442
|
-
textToDisplayForAssistant(String(block.text ?? block.content ?? "")),
|
|
443
|
-
`${seed}\ntext`,
|
|
444
|
-
);
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
if (type === "toolcall" || type === "tool_call") {
|
|
448
|
-
const toolCallId = String(block.id ?? "").trim();
|
|
449
|
-
if (!toolCallId) continue;
|
|
450
|
-
next = upsertToolCall(
|
|
451
|
-
next,
|
|
452
|
-
{
|
|
453
|
-
toolCallId,
|
|
454
|
-
approvalId: String(block.approvalId ?? block.approval_id ?? "").trim() || null,
|
|
455
|
-
name: String(block.name ?? "unknown"),
|
|
456
|
-
args: cloneJson(block.arguments ?? block.args),
|
|
457
|
-
result: null,
|
|
458
|
-
isError: false,
|
|
459
|
-
phase: "start",
|
|
460
|
-
},
|
|
461
|
-
`${seed}\n${toolCallId}`,
|
|
462
|
-
);
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (type === "toolresult" || type === "tool_result") {
|
|
466
|
-
const toolCallId = String(block.toolCallId ?? block.tool_call_id ?? "").trim();
|
|
467
|
-
if (!toolCallId) continue;
|
|
468
|
-
next = appendToolResult(
|
|
469
|
-
next,
|
|
470
|
-
{
|
|
471
|
-
toolCallId,
|
|
472
|
-
result: block.content ?? block.result ?? block.output,
|
|
473
|
-
isError: block.isError === true || block.is_error === true,
|
|
474
|
-
},
|
|
475
|
-
`${seed}\n${toolCallId}`,
|
|
476
|
-
);
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
return next;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function projectConversationMessages(rawMessages, sessionKey) {
|
|
484
|
-
const projected = [];
|
|
485
|
-
const assistantIndexByRunId = new Map();
|
|
486
|
-
|
|
487
|
-
for (const rawMessage of rawMessages) {
|
|
488
|
-
const message = normalizeMessageObject(rawMessage, rawMessage?.id ?? null);
|
|
489
|
-
const role = String(message?.role ?? "").trim().toLowerCase();
|
|
490
|
-
const runId = String(message?.runId ?? message?.run_id ?? "").trim();
|
|
491
|
-
const messageId =
|
|
492
|
-
String(message?.id ?? "").trim() ||
|
|
493
|
-
stableId("message", `${sessionKey}\n${role}\n${JSON.stringify(message)}`);
|
|
494
|
-
|
|
495
|
-
if (role === "user" || role === "system") {
|
|
496
|
-
projected.push(
|
|
497
|
-
createConversationMessage({
|
|
498
|
-
id: messageId,
|
|
499
|
-
role,
|
|
500
|
-
text: extractMessageText(message),
|
|
501
|
-
}),
|
|
502
|
-
);
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (role === "assistant") {
|
|
507
|
-
const assistantIndex = runId ? assistantIndexByRunId.get(runId) : undefined;
|
|
508
|
-
const current =
|
|
509
|
-
assistantIndex == null
|
|
510
|
-
? createConversationMessage({
|
|
511
|
-
id: messageId,
|
|
512
|
-
role: "assistant",
|
|
513
|
-
text: "",
|
|
514
|
-
})
|
|
515
|
-
: projected[assistantIndex];
|
|
516
|
-
let next = appendAssistantTranscriptContent(
|
|
517
|
-
{
|
|
518
|
-
...current,
|
|
519
|
-
thinking: extractThinkingText(message) ?? current.thinking ?? null,
|
|
520
|
-
},
|
|
521
|
-
message.content,
|
|
522
|
-
`${sessionKey}\n${messageId}`,
|
|
523
|
-
);
|
|
524
|
-
if (assistantIndex == null) {
|
|
525
|
-
projected.push(next);
|
|
526
|
-
if (runId) {
|
|
527
|
-
assistantIndexByRunId.set(runId, projected.length - 1);
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
projected[assistantIndex] = next;
|
|
531
|
-
}
|
|
532
|
-
continue;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (role === "toolresult" || role === "tool_result") {
|
|
536
|
-
const toolCallId = String(message?.toolCallId ?? message?.tool_call_id ?? "").trim();
|
|
537
|
-
if (!toolCallId) {
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
const assistantIndex = runId
|
|
541
|
-
? assistantIndexByRunId.get(runId)
|
|
542
|
-
: projected.map((item) => item.role).lastIndexOf("assistant");
|
|
543
|
-
if (assistantIndex == null || assistantIndex < 0) {
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
projected[assistantIndex] = appendToolResult(
|
|
547
|
-
projected[assistantIndex],
|
|
548
|
-
{
|
|
549
|
-
toolCallId,
|
|
550
|
-
result: decodeMobcodePayload(message.content) ?? message.content,
|
|
551
|
-
isError: message.isError === true || message.is_error === true,
|
|
552
|
-
},
|
|
553
|
-
`${sessionKey}\n${messageId}`,
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return projected.map((item) => ({
|
|
559
|
-
...item,
|
|
560
|
-
status: "done",
|
|
561
|
-
text: timelineTextPreview(item.timeline) || item.text || "",
|
|
562
|
-
}));
|
|
563
|
-
}
|
|
564
|
-
|
|
565
182
|
function createMessageSourceKey(sessionKey, messageId, normalizedMessage) {
|
|
566
183
|
if (messageId) {
|
|
567
184
|
return `mid:${sessionKey}:${messageId}`;
|
|
@@ -1150,15 +767,14 @@ export class MobcodeStateStore {
|
|
|
1150
767
|
const normalizedBeforeId =
|
|
1151
768
|
typeof beforeId === "number" && Number.isFinite(beforeId) ? beforeId : null;
|
|
1152
769
|
const transcriptMessages = await this.pageTranscriptMessages(normalizedSessionKey);
|
|
1153
|
-
const projected = projectConversationMessages(transcriptMessages, normalizedSessionKey);
|
|
1154
770
|
this.logger?.info?.(
|
|
1155
|
-
`[mobcode-store] pageSessionMessages session=${normalizedSessionKey} transcriptMessages=${transcriptMessages.length}
|
|
771
|
+
`[mobcode-store] pageSessionMessages session=${normalizedSessionKey} transcriptMessages=${transcriptMessages.length} limit=${normalizedLimit} beforeId=${normalizedBeforeId ?? "-"}`
|
|
1156
772
|
);
|
|
1157
773
|
const endExclusive = normalizedBeforeId == null
|
|
1158
|
-
?
|
|
1159
|
-
: Math.max(0, Math.min(
|
|
774
|
+
? transcriptMessages.length
|
|
775
|
+
: Math.max(0, Math.min(transcriptMessages.length, normalizedBeforeId - 1));
|
|
1160
776
|
const start = Math.max(0, endExclusive - normalizedLimit);
|
|
1161
|
-
const items =
|
|
777
|
+
const items = transcriptMessages.slice(start, endExclusive);
|
|
1162
778
|
const hasMore = start > 0;
|
|
1163
779
|
const nextBeforeId = hasMore ? start + 1 : null;
|
|
1164
780
|
|
|
@@ -1168,7 +784,7 @@ export class MobcodeStateStore {
|
|
|
1168
784
|
rawMessages: transcriptMessages,
|
|
1169
785
|
nextBeforeId,
|
|
1170
786
|
hasMore,
|
|
1171
|
-
total:
|
|
787
|
+
total: transcriptMessages.length,
|
|
1172
788
|
};
|
|
1173
789
|
}
|
|
1174
790
|
|
|
@@ -1177,6 +793,67 @@ export class MobcodeStateStore {
|
|
|
1177
793
|
return page.items;
|
|
1178
794
|
}
|
|
1179
795
|
|
|
796
|
+
async readToolResult(sessionKey, toolCallId) {
|
|
797
|
+
const normalizedSessionKey = String(sessionKey ?? "").trim();
|
|
798
|
+
const normalizedToolCallId = String(toolCallId ?? "").trim();
|
|
799
|
+
if (!normalizedSessionKey || !normalizedToolCallId) {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
const row = this._db()
|
|
803
|
+
.prepare(
|
|
804
|
+
`SELECT message_id, raw_json
|
|
805
|
+
FROM messages
|
|
806
|
+
WHERE session_key=?
|
|
807
|
+
AND role='toolResult'
|
|
808
|
+
ORDER BY id DESC`,
|
|
809
|
+
)
|
|
810
|
+
.all(normalizedSessionKey)
|
|
811
|
+
.find((candidate) => {
|
|
812
|
+
const message = fromJson(candidate.raw_json, null);
|
|
813
|
+
return (
|
|
814
|
+
message &&
|
|
815
|
+
typeof message === "object" &&
|
|
816
|
+
!Array.isArray(message) &&
|
|
817
|
+
String(message.toolCallId ?? message.tool_call_id ?? "").trim() ===
|
|
818
|
+
normalizedToolCallId
|
|
819
|
+
);
|
|
820
|
+
});
|
|
821
|
+
if (!row) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
const message = fromJson(row.raw_json, null);
|
|
825
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
messageId:
|
|
830
|
+
typeof row.message_id === "string" && row.message_id.trim()
|
|
831
|
+
? row.message_id.trim()
|
|
832
|
+
: null,
|
|
833
|
+
message,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
async waitForToolResult(sessionKey, toolCallId, { timeoutMs = 0, pollMs = 75 } = {}) {
|
|
838
|
+
const normalizedTimeout = Math.max(0, Number(timeoutMs) || 0);
|
|
839
|
+
const normalizedPoll = Math.max(25, Number(pollMs) || 75);
|
|
840
|
+
const deadline = Date.now() + normalizedTimeout;
|
|
841
|
+
while (true) {
|
|
842
|
+
const found = await this.readToolResult(sessionKey, toolCallId);
|
|
843
|
+
if (found) {
|
|
844
|
+
return found;
|
|
845
|
+
}
|
|
846
|
+
if (Date.now() >= deadline) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
const sleepMs = Math.min(normalizedPoll, Math.max(0, deadline - Date.now()));
|
|
850
|
+
if (sleepMs <= 0) {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
await new Promise((resolve) => setTimeout(resolve, sleepMs));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
1180
857
|
async recordApprovalRequested(payload) {
|
|
1181
858
|
const request = payload?.request && typeof payload.request === "object" ? payload.request : {};
|
|
1182
859
|
const approvalId = String(payload?.id ?? "").trim();
|