@rethinkingstudio/clawpilot 2.1.7-internal.1 → 2.1.7-internal.11
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/dist/ccconnect/api-client.d.ts +13 -0
- package/dist/ccconnect/api-client.js +3 -0
- package/dist/ccconnect/api-client.js.map +1 -1
- package/dist/ccconnect/project-config.d.ts +25 -0
- package/dist/ccconnect/project-config.js +309 -0
- package/dist/ccconnect/project-config.js.map +1 -0
- package/dist/hermes/model-catalog.js +5 -1
- package/dist/hermes/model-catalog.js.map +1 -1
- package/dist/media/assistant-attachments.d.ts +2 -0
- package/dist/media/assistant-attachments.js +18 -18
- package/dist/media/assistant-attachments.js.map +1 -1
- package/dist/platform/service-manager.js +9 -0
- package/dist/platform/service-manager.js.map +1 -1
- package/dist/relay/ccconnect-relay-manager.js +694 -22
- package/dist/relay/ccconnect-relay-manager.js.map +1 -1
- package/dist/relay/hermes-relay-manager.js +43 -6
- package/dist/relay/hermes-relay-manager.js.map +1 -1
- package/dist/relay/relay-manager.js +3 -0
- package/dist/relay/relay-manager.js.map +1 -1
- package/dist/relay/relay-watchdog.d.ts +2 -0
- package/dist/relay/relay-watchdog.js +31 -0
- package/dist/relay/relay-watchdog.js.map +1 -0
- package/package.json +1 -1
- package/scripts/hermes_agent_bridge.py +12 -1
|
@@ -4,6 +4,9 @@ import { handleLocalCommand } from "../commands/local-handlers.js";
|
|
|
4
4
|
import { handleProviderCommand } from "../commands/provider-handlers.js";
|
|
5
5
|
import { getServicePlatform } from "../platform/service-manager.js";
|
|
6
6
|
import { CCConnectManagementClient, decodeCCConnectSessionKey, encodeCCConnectSessionKey, timestampMs, } from "../ccconnect/api-client.js";
|
|
7
|
+
import { createCCConnectProject, discoverCCConnectProjects, restartCCConnect } from "../ccconnect/project-config.js";
|
|
8
|
+
import { uploadMediaBlocks } from "../media/assistant-attachments.js";
|
|
9
|
+
import { installRelayWatchdog } from "./relay-watchdog.js";
|
|
7
10
|
const DEFAULT_CONTEXT_TOKENS = 200_000;
|
|
8
11
|
export async function runCCConnectRelayManager(opts) {
|
|
9
12
|
const wsUrl = buildRelayUrl(opts.relayServerUrl, opts.gatewayId, opts.relaySecret);
|
|
@@ -19,7 +22,10 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
19
22
|
let bridgePingTimer = null;
|
|
20
23
|
let bridgeReconnectAttempts = 0;
|
|
21
24
|
const rawToEncoded = new Map();
|
|
25
|
+
const rawToProject = new Map();
|
|
22
26
|
const activeRunByEncoded = new Map();
|
|
27
|
+
const previewHandleToRun = new Map();
|
|
28
|
+
const pendingInteractiveReplyByEncoded = new Set();
|
|
23
29
|
try {
|
|
24
30
|
relayWs = new WebSocket(wsUrl);
|
|
25
31
|
}
|
|
@@ -28,6 +34,7 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
28
34
|
resolve(true);
|
|
29
35
|
return;
|
|
30
36
|
}
|
|
37
|
+
const uninstallWatchdog = installRelayWatchdog(relayWs, `ccconnect:${opts.gatewayId}`);
|
|
31
38
|
function send(msg) {
|
|
32
39
|
if (relayWs.readyState === WebSocket.OPEN) {
|
|
33
40
|
relayWs.send(JSON.stringify(msg));
|
|
@@ -106,8 +113,25 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
106
113
|
ws.send(JSON.stringify({
|
|
107
114
|
type: "register",
|
|
108
115
|
platform: "pocketclaw",
|
|
109
|
-
capabilities: [
|
|
110
|
-
|
|
116
|
+
capabilities: [
|
|
117
|
+
"text",
|
|
118
|
+
"typing",
|
|
119
|
+
"preview",
|
|
120
|
+
"update_message",
|
|
121
|
+
"delete_message",
|
|
122
|
+
"reconstruct_reply",
|
|
123
|
+
"buttons",
|
|
124
|
+
"card",
|
|
125
|
+
"image",
|
|
126
|
+
"file",
|
|
127
|
+
"audio",
|
|
128
|
+
],
|
|
129
|
+
metadata: {
|
|
130
|
+
version: "1.0.0",
|
|
131
|
+
description: "PocketClaw relay adapter",
|
|
132
|
+
progress_style: "card",
|
|
133
|
+
supports_progress_card_payload: true,
|
|
134
|
+
},
|
|
111
135
|
}));
|
|
112
136
|
});
|
|
113
137
|
ws.on("message", (raw) => {
|
|
@@ -142,6 +166,9 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
142
166
|
});
|
|
143
167
|
}
|
|
144
168
|
function handleBridgeMessage(msg) {
|
|
169
|
+
void handleBridgeMessageAsync(msg);
|
|
170
|
+
}
|
|
171
|
+
async function handleBridgeMessageAsync(msg) {
|
|
145
172
|
const record = msg;
|
|
146
173
|
if (msg.type === "register_ack") {
|
|
147
174
|
bridgeReady = Boolean(msg.ok);
|
|
@@ -157,11 +184,15 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
157
184
|
}
|
|
158
185
|
return;
|
|
159
186
|
}
|
|
187
|
+
if (msg.type === "pong")
|
|
188
|
+
return;
|
|
160
189
|
const rawSessionKey = stringValue(record.session_key) ?? "";
|
|
161
|
-
const encodedSessionKey =
|
|
190
|
+
const encodedSessionKey = await resolveEncodedBridgeSession(record);
|
|
162
191
|
if (!encodedSessionKey)
|
|
163
192
|
return;
|
|
164
|
-
const
|
|
193
|
+
const previewHandle = stringValue(record.preview_handle);
|
|
194
|
+
const runInfo = resolveRunForBridgeMessage(encodedSessionKey, previewHandle);
|
|
195
|
+
let runId = runInfo.runId;
|
|
165
196
|
if (msg.type === "typing_start") {
|
|
166
197
|
emitChat(runId, encodedSessionKey, "delta", undefined, true);
|
|
167
198
|
return;
|
|
@@ -170,48 +201,141 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
170
201
|
return;
|
|
171
202
|
}
|
|
172
203
|
if (msg.type === "preview_start") {
|
|
173
|
-
|
|
204
|
+
const content = stringValue(record.content) ?? "";
|
|
205
|
+
const sideChannel = isProgressContent(content);
|
|
206
|
+
if (sideChannel) {
|
|
207
|
+
runId = randomUUID();
|
|
208
|
+
}
|
|
209
|
+
emitContentChat(runId, encodedSessionKey, "delta", normalizeBridgeContent(content), false, sideChannel);
|
|
174
210
|
const refId = stringValue(record.ref_id);
|
|
211
|
+
const handle = `pocketclaw-${runId}`;
|
|
212
|
+
previewHandleToRun.set(handle, { encodedSessionKey, runId, sideChannel });
|
|
175
213
|
if (refId) {
|
|
176
214
|
bridgeWs?.send(JSON.stringify({
|
|
177
215
|
type: "preview_ack",
|
|
178
216
|
ref_id: refId,
|
|
179
|
-
preview_handle:
|
|
217
|
+
preview_handle: handle,
|
|
180
218
|
}));
|
|
181
219
|
}
|
|
182
220
|
return;
|
|
183
221
|
}
|
|
184
222
|
if (msg.type === "update_message") {
|
|
185
|
-
|
|
223
|
+
const content = stringValue(record.content) ?? "";
|
|
224
|
+
const normalized = normalizeBridgeContent(content);
|
|
225
|
+
const state = runInfo.sideChannel && progressContentIsCompleted(content) ? "final" : "delta";
|
|
226
|
+
emitContentChat(runId, encodedSessionKey, state, normalized, false, runInfo.sideChannel);
|
|
227
|
+
if (state === "final" && previewHandle)
|
|
228
|
+
previewHandleToRun.delete(previewHandle);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (msg.type === "delete_message") {
|
|
232
|
+
if (previewHandle)
|
|
233
|
+
previewHandleToRun.delete(previewHandle);
|
|
234
|
+
send({
|
|
235
|
+
type: "event",
|
|
236
|
+
event: "message.delete",
|
|
237
|
+
payload: {
|
|
238
|
+
runId,
|
|
239
|
+
sessionKey: encodedSessionKey,
|
|
240
|
+
previewHandle,
|
|
241
|
+
keepTurn: true,
|
|
242
|
+
sideChannel: runInfo.sideChannel,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
186
245
|
return;
|
|
187
246
|
}
|
|
188
247
|
if (msg.type === "reply_stream") {
|
|
189
248
|
const text = stringValue(record.full_text) ?? stringValue(record.delta) ?? "";
|
|
190
249
|
const done = Boolean(record.done);
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
250
|
+
const hasProgressPreview = hasActiveSideChannelPreview(encodedSessionKey);
|
|
251
|
+
const state = done && !isIntermediateReply(text) && !hasProgressPreview ? "final" : "delta";
|
|
252
|
+
emitChat(runId, encodedSessionKey, state, text, false);
|
|
253
|
+
if (state === "final")
|
|
254
|
+
clearActiveBridgeRun(encodedSessionKey, runId);
|
|
194
255
|
return;
|
|
195
256
|
}
|
|
196
257
|
if (msg.type === "reply") {
|
|
197
|
-
|
|
198
|
-
|
|
258
|
+
const content = stringValue(record.content) ?? "";
|
|
259
|
+
const permissionFallback = permissionHintActions(content, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey);
|
|
260
|
+
const keepTurnOpen = consumeInteractiveReply(encodedSessionKey) ||
|
|
261
|
+
isIntermediateReply(content) ||
|
|
262
|
+
hasActiveSideChannelPreview(encodedSessionKey);
|
|
263
|
+
const state = keepTurnOpen ? "delta" : "final";
|
|
264
|
+
if (permissionFallback) {
|
|
265
|
+
emitStructuredChat(runId, encodedSessionKey, state, {
|
|
266
|
+
text: content,
|
|
267
|
+
blocks: [textBlock(content), permissionFallback].filter(Boolean),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
emitChat(runId, encodedSessionKey, state, content, false);
|
|
272
|
+
}
|
|
273
|
+
if (state === "final")
|
|
274
|
+
clearActiveBridgeRun(encodedSessionKey, runId);
|
|
199
275
|
return;
|
|
200
276
|
}
|
|
201
277
|
if (msg.type === "buttons") {
|
|
202
|
-
|
|
203
|
-
|
|
278
|
+
const actions = normalizeBridgeButtons(record.buttons);
|
|
279
|
+
console.log(`[runtime:ccconnect] bridge buttons session=${rawSessionKey} actions=${actions.length}`);
|
|
280
|
+
emitStructuredChat(runId, encodedSessionKey, "delta", {
|
|
281
|
+
text: stringValue(record.content) ?? "",
|
|
282
|
+
blocks: [
|
|
283
|
+
textBlock(stringValue(record.content) ?? ""),
|
|
284
|
+
actionsBlock(actions, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey),
|
|
285
|
+
].filter(Boolean),
|
|
286
|
+
});
|
|
204
287
|
return;
|
|
205
288
|
}
|
|
206
289
|
if (msg.type === "card") {
|
|
207
|
-
|
|
208
|
-
|
|
290
|
+
console.log(`[runtime:ccconnect] bridge card session=${rawSessionKey}`);
|
|
291
|
+
emitStructuredChat(runId, encodedSessionKey, "delta", normalizeBridgeCard(record.card, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (msg.type === "image" || msg.type === "file" || msg.type === "audio") {
|
|
295
|
+
const attachment = await uploadBridgeMedia(msg);
|
|
296
|
+
emitStructuredChat(runId, encodedSessionKey, "final", {
|
|
297
|
+
text: attachment.text,
|
|
298
|
+
attachments: attachment.attachments,
|
|
299
|
+
blocks: attachment.text ? [textBlock(attachment.text)] : [],
|
|
300
|
+
});
|
|
301
|
+
clearActiveBridgeRun(encodedSessionKey, runId);
|
|
209
302
|
return;
|
|
210
303
|
}
|
|
211
304
|
if (msg.type === "error") {
|
|
212
305
|
emitChat(runId, encodedSessionKey, "error", stringValue(record.message) ?? "cc-connect bridge error", false);
|
|
213
|
-
|
|
306
|
+
clearActiveBridgeRun(encodedSessionKey, runId);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function resolveEncodedBridgeSession(record) {
|
|
310
|
+
const rawSessionKey = stringValue(record.session_key);
|
|
311
|
+
if (!rawSessionKey) {
|
|
312
|
+
if (activeRunByEncoded.size === 1) {
|
|
313
|
+
return activeRunByEncoded.keys().next().value;
|
|
314
|
+
}
|
|
315
|
+
console.warn(`[runtime:ccconnect] dropped bridge ${String(record.type)} without session_key`);
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
const existing = rawToEncoded.get(rawSessionKey);
|
|
319
|
+
if (existing)
|
|
320
|
+
return existing;
|
|
321
|
+
const explicitProject = stringValue(record.project) ?? rawToProject.get(rawSessionKey);
|
|
322
|
+
if (explicitProject) {
|
|
323
|
+
const encoded = encodeCCConnectSessionKey(explicitProject, rawSessionKey, rawSessionKey);
|
|
324
|
+
rawToEncoded.set(rawSessionKey, encoded);
|
|
325
|
+
rawToProject.set(rawSessionKey, explicitProject);
|
|
326
|
+
console.warn(`[runtime:ccconnect] recovered bridge session mapping project=${explicitProject} rawSessionKey=${rawSessionKey}`);
|
|
327
|
+
return encoded;
|
|
214
328
|
}
|
|
329
|
+
if (activeRunByEncoded.size === 1) {
|
|
330
|
+
const encoded = activeRunByEncoded.keys().next().value;
|
|
331
|
+
if (encoded) {
|
|
332
|
+
rawToEncoded.set(rawSessionKey, encoded);
|
|
333
|
+
console.warn(`[runtime:ccconnect] inferred bridge session mapping rawSessionKey=${rawSessionKey}`);
|
|
334
|
+
return encoded;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
console.warn(`[runtime:ccconnect] dropped bridge ${String(record.type)} for unmapped session_key=${rawSessionKey}`);
|
|
338
|
+
return undefined;
|
|
215
339
|
}
|
|
216
340
|
function ensureRunId(encodedSessionKey, preferred) {
|
|
217
341
|
const existing = activeRunByEncoded.get(encodedSessionKey);
|
|
@@ -221,6 +345,39 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
221
345
|
activeRunByEncoded.set(encodedSessionKey, runId);
|
|
222
346
|
return runId;
|
|
223
347
|
}
|
|
348
|
+
function resolveRunForBridgeMessage(encodedSessionKey, previewHandle) {
|
|
349
|
+
if (previewHandle) {
|
|
350
|
+
const preview = previewHandleToRun.get(previewHandle);
|
|
351
|
+
if (preview) {
|
|
352
|
+
return { runId: preview.runId, sideChannel: preview.sideChannel };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return { runId: ensureRunId(encodedSessionKey) };
|
|
356
|
+
}
|
|
357
|
+
function clearActiveBridgeRun(encodedSessionKey, runId) {
|
|
358
|
+
if (activeRunByEncoded.get(encodedSessionKey) === runId) {
|
|
359
|
+
activeRunByEncoded.delete(encodedSessionKey);
|
|
360
|
+
}
|
|
361
|
+
pendingInteractiveReplyByEncoded.delete(encodedSessionKey);
|
|
362
|
+
for (const [handle, preview] of previewHandleToRun) {
|
|
363
|
+
if (preview.runId === runId)
|
|
364
|
+
previewHandleToRun.delete(handle);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function hasActiveSideChannelPreview(encodedSessionKey) {
|
|
368
|
+
for (const preview of previewHandleToRun.values()) {
|
|
369
|
+
if (preview.encodedSessionKey === encodedSessionKey && preview.sideChannel) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
function consumeInteractiveReply(encodedSessionKey) {
|
|
376
|
+
if (!pendingInteractiveReplyByEncoded.has(encodedSessionKey))
|
|
377
|
+
return false;
|
|
378
|
+
pendingInteractiveReplyByEncoded.delete(encodedSessionKey);
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
224
381
|
function emitChat(runId, sessionKey, state, text, loading) {
|
|
225
382
|
const payload = { runId, sessionKey, state };
|
|
226
383
|
if (loading)
|
|
@@ -232,6 +389,48 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
232
389
|
payload.errorMessage = text ?? "cc-connect error";
|
|
233
390
|
send({ type: "event", event: "chat", payload });
|
|
234
391
|
}
|
|
392
|
+
function emitContentChat(runId, sessionKey, state, content, loading, sideChannel) {
|
|
393
|
+
const payload = { runId, sessionKey, state };
|
|
394
|
+
if (loading)
|
|
395
|
+
payload.loading = true;
|
|
396
|
+
if (sideChannel)
|
|
397
|
+
payload.sideChannel = true;
|
|
398
|
+
payload.message = { content: content.blocks?.length ? content.blocks : [{ type: "text", text: content.text }] };
|
|
399
|
+
if (state === "error")
|
|
400
|
+
payload.errorMessage = content.text || "cc-connect error";
|
|
401
|
+
send({ type: "event", event: "chat", payload });
|
|
402
|
+
}
|
|
403
|
+
function emitStructuredChat(runId, sessionKey, state, data) {
|
|
404
|
+
const blocks = data.blocks?.length ? data.blocks : data.text !== undefined ? [textBlock(data.text)] : undefined;
|
|
405
|
+
const payload = { runId, sessionKey, state };
|
|
406
|
+
if (blocks)
|
|
407
|
+
payload.message = { content: blocks };
|
|
408
|
+
if (data.attachments)
|
|
409
|
+
payload.attachments = data.attachments;
|
|
410
|
+
if (state === "error")
|
|
411
|
+
payload.errorMessage = data.text ?? "cc-connect error";
|
|
412
|
+
send({ type: "event", event: "chat", payload });
|
|
413
|
+
}
|
|
414
|
+
async function uploadBridgeMedia(msg) {
|
|
415
|
+
const data = stringValue(msg.data);
|
|
416
|
+
const fileName = stringValue(msg.file_name);
|
|
417
|
+
const mimeType = bridgeMediaMimeType(msg);
|
|
418
|
+
const label = fileName || (msg.type === "audio" ? "Audio message" : msg.type === "image" ? "Image" : "File");
|
|
419
|
+
if (!data) {
|
|
420
|
+
return { text: `[${label} missing data]`, attachments: [] };
|
|
421
|
+
}
|
|
422
|
+
const block = {
|
|
423
|
+
mimeType,
|
|
424
|
+
fileName,
|
|
425
|
+
data: Buffer.from(data.replace(/^data:[^;]+;base64,/, ""), "base64"),
|
|
426
|
+
};
|
|
427
|
+
const uploaded = await uploadMediaBlocks([block], opts.relayServerUrl, opts.gatewayId, opts.relaySecret);
|
|
428
|
+
const attachments = uploaded.map((att) => ({
|
|
429
|
+
...att,
|
|
430
|
+
fileName: fileName ?? undefined,
|
|
431
|
+
}));
|
|
432
|
+
return { text: label, attachments };
|
|
433
|
+
}
|
|
235
434
|
async function respondSessionsList(requestId) {
|
|
236
435
|
const projects = await client.listProjects();
|
|
237
436
|
const sessions = [];
|
|
@@ -248,18 +447,80 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
248
447
|
count: sessions.length,
|
|
249
448
|
});
|
|
250
449
|
}
|
|
450
|
+
async function respondProjectsList(requestId) {
|
|
451
|
+
const projects = await client.listProjects();
|
|
452
|
+
const detailed = await Promise.all(projects.map(async (project) => {
|
|
453
|
+
try {
|
|
454
|
+
const detail = await client.getProject(project.name);
|
|
455
|
+
return {
|
|
456
|
+
name: detail.name,
|
|
457
|
+
agentType: detail.agent_type,
|
|
458
|
+
workDir: detail.work_dir,
|
|
459
|
+
mode: detail.agent_mode || detail.mode,
|
|
460
|
+
sessionsCount: detail.sessions_count ?? project.sessions_count ?? 0,
|
|
461
|
+
platforms: detail.platforms ?? project.platforms ?? [],
|
|
462
|
+
heartbeatEnabled: detail.heartbeat_enabled ?? project.heartbeat_enabled ?? false,
|
|
463
|
+
activeSessionKeys: detail.active_session_keys ?? [],
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return {
|
|
468
|
+
name: project.name,
|
|
469
|
+
agentType: project.agent_type,
|
|
470
|
+
sessionsCount: project.sessions_count ?? 0,
|
|
471
|
+
platforms: project.platforms ?? [],
|
|
472
|
+
heartbeatEnabled: project.heartbeat_enabled ?? false,
|
|
473
|
+
activeSessionKeys: [],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}));
|
|
477
|
+
sendResponse(requestId, { projects: detailed, count: detailed.length });
|
|
478
|
+
}
|
|
479
|
+
async function respondProjectsDiscover(requestId) {
|
|
480
|
+
const projects = await client.listProjects();
|
|
481
|
+
const detailed = await Promise.all(projects.map(async (project) => {
|
|
482
|
+
try {
|
|
483
|
+
const detail = await client.getProject(project.name);
|
|
484
|
+
return { name: detail.name, workDir: detail.work_dir };
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return { name: project.name, workDir: undefined };
|
|
488
|
+
}
|
|
489
|
+
}));
|
|
490
|
+
const directories = discoverCCConnectProjects(detailed);
|
|
491
|
+
sendResponse(requestId, { directories, count: directories.length });
|
|
492
|
+
}
|
|
493
|
+
async function respondProjectCreate(requestId, params) {
|
|
494
|
+
const p = params;
|
|
495
|
+
const created = createCCConnectProject(p);
|
|
496
|
+
restartCCConnect(created.configPath);
|
|
497
|
+
sendResponse(requestId, {
|
|
498
|
+
project: {
|
|
499
|
+
name: created.name,
|
|
500
|
+
agentType: created.agentType,
|
|
501
|
+
workDir: created.workDir,
|
|
502
|
+
mode: created.mode,
|
|
503
|
+
sessionsCount: 0,
|
|
504
|
+
platforms: ["line", "bridge"],
|
|
505
|
+
heartbeatEnabled: false,
|
|
506
|
+
activeSessionKeys: [],
|
|
507
|
+
},
|
|
508
|
+
restarted: true,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
251
511
|
async function respondMessagesHistory(requestId, params) {
|
|
252
512
|
const p = params;
|
|
253
513
|
if (!p.sessionKey)
|
|
254
514
|
throw new Error("sessionKey required");
|
|
255
515
|
const decoded = decodeCCConnectSessionKey(p.sessionKey);
|
|
256
|
-
const
|
|
516
|
+
const sessionId = await resolveCCConnectSessionId(decoded.project, decoded.sessionKey, decoded.sessionId);
|
|
517
|
+
const detail = await client.getSession(decoded.project, sessionId, Math.min(p.limit ?? 50, 200));
|
|
257
518
|
const history = detail.history ?? [];
|
|
258
519
|
const messages = history.map((entry, index) => ({
|
|
259
520
|
id: index + 1,
|
|
260
521
|
role: entry.role === "user" ? "user" : "assistant",
|
|
261
522
|
content: entry.content ?? "",
|
|
262
|
-
runId: entry.role === "user" ? undefined : `${
|
|
523
|
+
runId: entry.role === "user" ? undefined : `${sessionId}-${index + 1}`,
|
|
263
524
|
state: "final",
|
|
264
525
|
createdAt: timestampMs(entry.timestamp),
|
|
265
526
|
updatedAt: timestampMs(entry.timestamp),
|
|
@@ -292,11 +553,15 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
292
553
|
throw new Error("sessionKey required");
|
|
293
554
|
if (!p.message)
|
|
294
555
|
throw new Error("message required");
|
|
295
|
-
const decoded =
|
|
556
|
+
const decoded = await resolveCCConnectSendTarget(p.sessionKey);
|
|
296
557
|
rawToEncoded.set(decoded.sessionKey, p.sessionKey);
|
|
558
|
+
rawToProject.set(decoded.sessionKey, decoded.project);
|
|
559
|
+
activeRunByEncoded.delete(p.sessionKey);
|
|
560
|
+
pendingInteractiveReplyByEncoded.delete(p.sessionKey);
|
|
297
561
|
const runId = ensureRunId(p.sessionKey, p.idempotencyKey);
|
|
298
562
|
emitChat(runId, p.sessionKey, "delta", undefined, true);
|
|
299
563
|
if (bridgeReady && bridgeWs?.readyState === WebSocket.OPEN) {
|
|
564
|
+
console.log(`[runtime:ccconnect] chat.send via bridge project=${decoded.project} rawSessionKey=${decoded.sessionKey}`);
|
|
300
565
|
bridgeWs.send(JSON.stringify({
|
|
301
566
|
type: "message",
|
|
302
567
|
msg_id: `pocketclaw-${Date.now()}`,
|
|
@@ -306,17 +571,71 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
306
571
|
content: p.message,
|
|
307
572
|
reply_ctx: decoded.sessionKey,
|
|
308
573
|
project: decoded.project,
|
|
574
|
+
...bridgeAttachments(params),
|
|
309
575
|
}));
|
|
310
576
|
return;
|
|
311
577
|
}
|
|
312
578
|
scheduleBridgeReconnect("bridge not ready during chat.send");
|
|
579
|
+
console.log(`[runtime:ccconnect] chat.send via management fallback project=${decoded.project} rawSessionKey=${decoded.sessionKey}`);
|
|
313
580
|
await client.send(decoded.project, decoded.sessionKey, p.message);
|
|
314
581
|
}
|
|
582
|
+
async function handleChatAction(params) {
|
|
583
|
+
const p = params;
|
|
584
|
+
if (!p.sessionKey)
|
|
585
|
+
throw new Error("sessionKey required");
|
|
586
|
+
if (!p.action)
|
|
587
|
+
throw new Error("action required");
|
|
588
|
+
if (!bridgeReady || bridgeWs?.readyState !== WebSocket.OPEN) {
|
|
589
|
+
throw new Error("cc-connect bridge not ready");
|
|
590
|
+
}
|
|
591
|
+
const decoded = await resolveCCConnectSendTarget(p.sessionKey);
|
|
592
|
+
if (isInteractiveAction(p.action)) {
|
|
593
|
+
pendingInteractiveReplyByEncoded.add(p.sessionKey);
|
|
594
|
+
}
|
|
595
|
+
bridgeWs.send(JSON.stringify({
|
|
596
|
+
type: "card_action",
|
|
597
|
+
session_key: decoded.sessionKey,
|
|
598
|
+
action: p.action,
|
|
599
|
+
reply_ctx: p.replyCtx || decoded.sessionKey,
|
|
600
|
+
project: decoded.project,
|
|
601
|
+
}));
|
|
602
|
+
}
|
|
603
|
+
async function resolveCCConnectSendTarget(sessionKey) {
|
|
604
|
+
try {
|
|
605
|
+
return decodeCCConnectSessionKey(sessionKey);
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
const projects = await client.listProjects();
|
|
609
|
+
for (const project of projects) {
|
|
610
|
+
const sessions = await client.listSessions(project.name);
|
|
611
|
+
const preferred = sessions.find((session) => session.active || session.live) ?? sessions[0];
|
|
612
|
+
if (preferred?.session_key) {
|
|
613
|
+
console.warn(`[runtime:ccconnect] non-ccconnect sessionKey received (${sessionKey}); using ${project.name}/${preferred.session_key}`);
|
|
614
|
+
return {
|
|
615
|
+
project: project.name,
|
|
616
|
+
sessionKey: preferred.session_key,
|
|
617
|
+
sessionId: preferred.id || preferred.session_key,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
throw new Error(`invalid cc-connect session key: ${sessionKey}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
315
624
|
function rememberSession(project, session) {
|
|
316
|
-
const encoded = encodeCCConnectSessionKey(project, session.session_key, session.
|
|
317
|
-
rawToEncoded.
|
|
625
|
+
const encoded = encodeCCConnectSessionKey(project, session.session_key, session.session_key);
|
|
626
|
+
if (!rawToEncoded.has(session.session_key)) {
|
|
627
|
+
rawToEncoded.set(session.session_key, encoded);
|
|
628
|
+
rawToProject.set(session.session_key, project);
|
|
629
|
+
}
|
|
318
630
|
return encoded;
|
|
319
631
|
}
|
|
632
|
+
async function resolveCCConnectSessionId(project, sessionKey, sessionId) {
|
|
633
|
+
if (sessionId && sessionId !== sessionKey)
|
|
634
|
+
return sessionId;
|
|
635
|
+
const sessions = await client.listSessions(project);
|
|
636
|
+
const matched = sessions.find((session) => session.session_key === sessionKey);
|
|
637
|
+
return matched?.id || sessionId || sessionKey;
|
|
638
|
+
}
|
|
320
639
|
function toRuntimeSession(project, session, encoded) {
|
|
321
640
|
const platform = session.platform || session.session_key.split(":")[0] || "cc-connect";
|
|
322
641
|
const title = session.name || session.chat_name || session.user_name || session.session_key || session.id;
|
|
@@ -383,6 +702,18 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
383
702
|
}
|
|
384
703
|
try {
|
|
385
704
|
switch (msg.method) {
|
|
705
|
+
case "projects.list":
|
|
706
|
+
if (requestId)
|
|
707
|
+
await respondProjectsList(requestId);
|
|
708
|
+
return;
|
|
709
|
+
case "projects.discover":
|
|
710
|
+
if (requestId)
|
|
711
|
+
await respondProjectsDiscover(requestId);
|
|
712
|
+
return;
|
|
713
|
+
case "projects.create":
|
|
714
|
+
if (requestId)
|
|
715
|
+
await respondProjectCreate(requestId, msg.params ?? {});
|
|
716
|
+
return;
|
|
386
717
|
case "sessions.list":
|
|
387
718
|
if (requestId)
|
|
388
719
|
await respondSessionsList(requestId);
|
|
@@ -400,6 +731,11 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
400
731
|
if (requestId)
|
|
401
732
|
sendResponse(requestId, { ok: true });
|
|
402
733
|
return;
|
|
734
|
+
case "chat.action":
|
|
735
|
+
await handleChatAction(msg.params ?? {});
|
|
736
|
+
if (requestId)
|
|
737
|
+
sendResponse(requestId, { ok: true });
|
|
738
|
+
return;
|
|
403
739
|
case "chat.abort":
|
|
404
740
|
if (requestId)
|
|
405
741
|
sendResponse(requestId, { ok: true });
|
|
@@ -426,6 +762,7 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
426
762
|
}
|
|
427
763
|
});
|
|
428
764
|
relayWs.on("close", (code, reason) => {
|
|
765
|
+
uninstallWatchdog();
|
|
429
766
|
console.log(`cc-connect relay connection closed: ${code} ${reason.toString()}`);
|
|
430
767
|
opts.onDisconnected?.();
|
|
431
768
|
clearBridgeTimers();
|
|
@@ -445,4 +782,339 @@ function buildRelayUrl(base, gatewayId, relaySecret) {
|
|
|
445
782
|
function stringValue(value) {
|
|
446
783
|
return typeof value === "string" ? value : undefined;
|
|
447
784
|
}
|
|
785
|
+
function textBlock(text) {
|
|
786
|
+
return text ? { type: "text", text } : undefined;
|
|
787
|
+
}
|
|
788
|
+
const PROGRESS_CARD_PREFIX = "__cc_connect_progress_card_v1__:";
|
|
789
|
+
function isProgressContent(content) {
|
|
790
|
+
return content.startsWith(PROGRESS_CARD_PREFIX) || content.trimStart().startsWith("⏳ **Progress**");
|
|
791
|
+
}
|
|
792
|
+
function progressContentIsCompleted(content) {
|
|
793
|
+
const payload = parseProgressPayload(content);
|
|
794
|
+
return payload?.state === "completed" || payload?.state === "failed";
|
|
795
|
+
}
|
|
796
|
+
function normalizeBridgeContent(content) {
|
|
797
|
+
const progress = parseProgressPayload(content);
|
|
798
|
+
if (!progress)
|
|
799
|
+
return { text: content };
|
|
800
|
+
const text = renderProgressPayload(progress);
|
|
801
|
+
return {
|
|
802
|
+
text,
|
|
803
|
+
blocks: [
|
|
804
|
+
{
|
|
805
|
+
type: "markdown",
|
|
806
|
+
text,
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function parseProgressPayload(content) {
|
|
812
|
+
if (!content.startsWith(PROGRESS_CARD_PREFIX))
|
|
813
|
+
return undefined;
|
|
814
|
+
try {
|
|
815
|
+
const parsed = JSON.parse(content.slice(PROGRESS_CARD_PREFIX.length));
|
|
816
|
+
const items = Array.isArray(parsed.items)
|
|
817
|
+
? parsed.items.map((item) => ({
|
|
818
|
+
kind: stringValue(item.kind),
|
|
819
|
+
text: stringValue(item.text)?.trim() ?? "",
|
|
820
|
+
tool: stringValue(item.tool),
|
|
821
|
+
status: stringValue(item.status),
|
|
822
|
+
})).filter((item) => item.text)
|
|
823
|
+
: [];
|
|
824
|
+
const entries = items.length > 0
|
|
825
|
+
? items
|
|
826
|
+
: (Array.isArray(parsed.entries) ? parsed.entries.map((entry) => ({
|
|
827
|
+
kind: undefined,
|
|
828
|
+
text: typeof entry === "string" ? entry.trim() : "",
|
|
829
|
+
})).filter((entry) => entry.text) : []);
|
|
830
|
+
if (entries.length === 0)
|
|
831
|
+
return undefined;
|
|
832
|
+
return {
|
|
833
|
+
state: stringValue(parsed.state),
|
|
834
|
+
truncated: Boolean(parsed.truncated),
|
|
835
|
+
entries,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
catch {
|
|
839
|
+
return undefined;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function renderProgressPayload(payload) {
|
|
843
|
+
const icon = payload.state === "completed" ? "✅" : payload.state === "failed" ? "❌" : "⏳";
|
|
844
|
+
const lines = [`${icon} **Progress**`];
|
|
845
|
+
if (payload.truncated)
|
|
846
|
+
lines.push("_Showing latest updates only._");
|
|
847
|
+
for (const [index, entry] of payload.entries.entries()) {
|
|
848
|
+
const prefix = progressEntryPrefix(entry.kind, entry.tool);
|
|
849
|
+
const text = entry.text.replace(/\n/g, "\n ");
|
|
850
|
+
lines.push(`\n${index + 1}. ${prefix}${text}`);
|
|
851
|
+
}
|
|
852
|
+
return lines.join("\n");
|
|
853
|
+
}
|
|
854
|
+
function progressEntryPrefix(kind, tool) {
|
|
855
|
+
if (kind === "thinking")
|
|
856
|
+
return "💭 ";
|
|
857
|
+
if (kind === "tool_use")
|
|
858
|
+
return tool ? `🔧 **工具: ${tool}**\n ` : "🔧 ";
|
|
859
|
+
if (kind === "tool_result")
|
|
860
|
+
return tool ? `🧾 **结果: ${tool}**\n ` : "🧾 ";
|
|
861
|
+
if (kind === "error")
|
|
862
|
+
return "❌ ";
|
|
863
|
+
return "";
|
|
864
|
+
}
|
|
865
|
+
function actionsBlock(buttons, sessionKey, replyCtx) {
|
|
866
|
+
if (buttons.length === 0)
|
|
867
|
+
return undefined;
|
|
868
|
+
return {
|
|
869
|
+
type: "actions",
|
|
870
|
+
actions: buttons,
|
|
871
|
+
sessionKey,
|
|
872
|
+
replyCtx,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function normalizeBridgeButtons(buttons) {
|
|
876
|
+
if (!Array.isArray(buttons))
|
|
877
|
+
return [];
|
|
878
|
+
const actions = [];
|
|
879
|
+
const rows = buttons.some(Array.isArray) ? buttons : [buttons];
|
|
880
|
+
for (const row of rows) {
|
|
881
|
+
const buttonRow = Array.isArray(row) ? row : [row];
|
|
882
|
+
for (const button of buttonRow) {
|
|
883
|
+
const action = normalizeBridgeAction(button);
|
|
884
|
+
if (action)
|
|
885
|
+
actions.push(action);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return actions;
|
|
889
|
+
}
|
|
890
|
+
function normalizeBridgeCard(card, sessionKey, replyCtx) {
|
|
891
|
+
if (!isRecord(card)) {
|
|
892
|
+
return { text: "[card message]", blocks: [textBlock("[card message]")].filter(Boolean) };
|
|
893
|
+
}
|
|
894
|
+
const blocks = [];
|
|
895
|
+
const textParts = [];
|
|
896
|
+
const header = isRecord(card.header) ? stringValue(card.header.title) : undefined;
|
|
897
|
+
if (header) {
|
|
898
|
+
blocks.push({ type: "text", text: `**${header}**` });
|
|
899
|
+
textParts.push(header);
|
|
900
|
+
}
|
|
901
|
+
const elements = Array.isArray(card.elements) ? card.elements : [];
|
|
902
|
+
for (const element of elements) {
|
|
903
|
+
if (!isRecord(element))
|
|
904
|
+
continue;
|
|
905
|
+
const type = stringValue(element.type);
|
|
906
|
+
if (type === "markdown") {
|
|
907
|
+
const text = stringValue(element.content) ?? "";
|
|
908
|
+
if (text) {
|
|
909
|
+
blocks.push({ type: "markdown", text });
|
|
910
|
+
textParts.push(text);
|
|
911
|
+
}
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (type === "note") {
|
|
915
|
+
const text = stringValue(element.text) ?? "";
|
|
916
|
+
if (text) {
|
|
917
|
+
blocks.push({ type: "text", text });
|
|
918
|
+
textParts.push(text);
|
|
919
|
+
}
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
if (type === "divider") {
|
|
923
|
+
blocks.push({ type: "divider" });
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (type === "actions") {
|
|
927
|
+
const actions = normalizeCardActionButtons(element.buttons);
|
|
928
|
+
const block = actionsBlock(actions, sessionKey, replyCtx);
|
|
929
|
+
if (block)
|
|
930
|
+
blocks.push(block);
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
if (type === "list_item") {
|
|
934
|
+
const text = stringValue(element.text) ?? "";
|
|
935
|
+
if (text)
|
|
936
|
+
textParts.push(text);
|
|
937
|
+
const btnText = stringValue(element.btn_text);
|
|
938
|
+
const btnValue = stringValue(element.btn_value);
|
|
939
|
+
blocks.push({
|
|
940
|
+
type: "list_item",
|
|
941
|
+
text,
|
|
942
|
+
action: btnText && btnValue ? {
|
|
943
|
+
id: btnValue,
|
|
944
|
+
label: btnText,
|
|
945
|
+
value: btnValue,
|
|
946
|
+
style: actionStyle(btnValue, stringValue(element.btn_type)),
|
|
947
|
+
} : undefined,
|
|
948
|
+
sessionKey,
|
|
949
|
+
replyCtx,
|
|
950
|
+
});
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
if (type === "select") {
|
|
954
|
+
const options = Array.isArray(element.options) ? element.options.filter(isRecord).map((option) => ({
|
|
955
|
+
id: stringValue(option.value) ?? "",
|
|
956
|
+
label: stringValue(option.text) ?? "",
|
|
957
|
+
value: stringValue(option.value) ?? "",
|
|
958
|
+
style: "default",
|
|
959
|
+
})).filter((option) => option.label && option.value) : [];
|
|
960
|
+
blocks.push({
|
|
961
|
+
type: "select",
|
|
962
|
+
placeholder: stringValue(element.placeholder) ?? "Select",
|
|
963
|
+
initValue: stringValue(element.init_value),
|
|
964
|
+
actions: options,
|
|
965
|
+
sessionKey,
|
|
966
|
+
replyCtx,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return { text: textParts.join("\n\n"), blocks };
|
|
971
|
+
}
|
|
972
|
+
function isInteractiveAction(action) {
|
|
973
|
+
const normalized = action.toLowerCase();
|
|
974
|
+
return (normalized.startsWith("perm:")
|
|
975
|
+
|| normalized.startsWith("askq:")
|
|
976
|
+
|| normalized === "allow"
|
|
977
|
+
|| normalized === "deny"
|
|
978
|
+
|| normalized === "allow all");
|
|
979
|
+
}
|
|
980
|
+
function normalizeCardActionButtons(buttons) {
|
|
981
|
+
if (!Array.isArray(buttons))
|
|
982
|
+
return [];
|
|
983
|
+
const actions = [];
|
|
984
|
+
for (const button of buttons) {
|
|
985
|
+
const action = normalizeBridgeAction(button);
|
|
986
|
+
if (action)
|
|
987
|
+
actions.push(action);
|
|
988
|
+
}
|
|
989
|
+
return actions;
|
|
990
|
+
}
|
|
991
|
+
function normalizeBridgeAction(button) {
|
|
992
|
+
if (!isRecord(button))
|
|
993
|
+
return undefined;
|
|
994
|
+
const label = stringValue(button.text)
|
|
995
|
+
?? stringValue(button.label)
|
|
996
|
+
?? stringValue(button.title);
|
|
997
|
+
const value = stringValue(button.data)
|
|
998
|
+
?? stringValue(button.value)
|
|
999
|
+
?? stringValue(button.id)
|
|
1000
|
+
?? inferActionValue(label);
|
|
1001
|
+
if (!label || !value)
|
|
1002
|
+
return undefined;
|
|
1003
|
+
return {
|
|
1004
|
+
id: value,
|
|
1005
|
+
label,
|
|
1006
|
+
value,
|
|
1007
|
+
style: actionStyle(value, stringValue(button.btn_type) ?? stringValue(button.style)),
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function permissionHintActions(content, sessionKey, replyCtx) {
|
|
1011
|
+
if (!isPermissionHint(content))
|
|
1012
|
+
return undefined;
|
|
1013
|
+
return actionsBlock([
|
|
1014
|
+
{ id: "perm:allow", label: "允许", value: "perm:allow", style: "primary" },
|
|
1015
|
+
{ id: "perm:deny", label: "拒绝", value: "perm:deny", style: "danger" },
|
|
1016
|
+
{ id: "perm:allow_all", label: "允许所有", value: "perm:allow_all", style: "default" },
|
|
1017
|
+
], sessionKey, replyCtx);
|
|
1018
|
+
}
|
|
1019
|
+
function isPermissionHint(content) {
|
|
1020
|
+
const normalized = content.toLowerCase();
|
|
1021
|
+
return (normalized.includes("等待权限响应") || normalized.includes("permission"))
|
|
1022
|
+
&& (normalized.includes("允许所有") || normalized.includes("allow all"))
|
|
1023
|
+
&& (normalized.includes("拒绝") || normalized.includes("deny"));
|
|
1024
|
+
}
|
|
1025
|
+
function isIntermediateReply(content) {
|
|
1026
|
+
const normalized = content.toLowerCase().trim();
|
|
1027
|
+
if (!normalized)
|
|
1028
|
+
return false;
|
|
1029
|
+
if (isPermissionHint(content))
|
|
1030
|
+
return true;
|
|
1031
|
+
return (normalized.includes("继续执行")
|
|
1032
|
+
|| normalized.includes("继续处理中")
|
|
1033
|
+
|| normalized.includes("continuing")
|
|
1034
|
+
|| normalized.includes("continue execution")
|
|
1035
|
+
|| normalized.includes("permission granted")
|
|
1036
|
+
|| normalized.includes("已允许")
|
|
1037
|
+
|| normalized.includes("已开启自动批准")
|
|
1038
|
+
|| normalized.includes("自动批准")
|
|
1039
|
+
|| normalized.includes("权限请求将自动允许"));
|
|
1040
|
+
}
|
|
1041
|
+
function inferActionValue(label) {
|
|
1042
|
+
if (!label)
|
|
1043
|
+
return undefined;
|
|
1044
|
+
const normalized = label.toLowerCase().replace(/\s+/g, "");
|
|
1045
|
+
if (normalized.includes("允许所有") || normalized.includes("全部允许") || normalized.includes("allowall")) {
|
|
1046
|
+
return "perm:allow_all";
|
|
1047
|
+
}
|
|
1048
|
+
if (normalized.includes("拒绝") || normalized.includes("deny") || normalized.includes("reject")) {
|
|
1049
|
+
return "perm:deny";
|
|
1050
|
+
}
|
|
1051
|
+
if (normalized.includes("允许") || normalized.includes("同意") || normalized.includes("allow") || normalized.includes("approve")) {
|
|
1052
|
+
return "perm:allow";
|
|
1053
|
+
}
|
|
1054
|
+
return undefined;
|
|
1055
|
+
}
|
|
1056
|
+
function actionStyle(value, explicit) {
|
|
1057
|
+
if (explicit === "primary" || explicit === "danger")
|
|
1058
|
+
return explicit;
|
|
1059
|
+
if (value.includes("deny") || value.includes("reject") || value.includes("cancel"))
|
|
1060
|
+
return "danger";
|
|
1061
|
+
if (value.includes("allow"))
|
|
1062
|
+
return "primary";
|
|
1063
|
+
return "default";
|
|
1064
|
+
}
|
|
1065
|
+
function bridgeMediaMimeType(msg) {
|
|
1066
|
+
const record = msg;
|
|
1067
|
+
const explicit = stringValue(record.mime_type);
|
|
1068
|
+
if (explicit)
|
|
1069
|
+
return explicit;
|
|
1070
|
+
if (msg.type === "audio") {
|
|
1071
|
+
const format = stringValue(record.format) ?? "mpeg";
|
|
1072
|
+
return format.includes("/") ? format : `audio/${format}`;
|
|
1073
|
+
}
|
|
1074
|
+
if (msg.type === "image")
|
|
1075
|
+
return "image/png";
|
|
1076
|
+
return "application/octet-stream";
|
|
1077
|
+
}
|
|
1078
|
+
function bridgeAttachments(params) {
|
|
1079
|
+
const attachments = isRecord(params) && Array.isArray(params.attachments) ? params.attachments : [];
|
|
1080
|
+
const images = [];
|
|
1081
|
+
const files = [];
|
|
1082
|
+
let audio;
|
|
1083
|
+
for (const attachment of attachments) {
|
|
1084
|
+
if (!isRecord(attachment))
|
|
1085
|
+
continue;
|
|
1086
|
+
const content = stringValue(attachment.content);
|
|
1087
|
+
const mimeType = stringValue(attachment.mimeType) ?? "application/octet-stream";
|
|
1088
|
+
const fileName = stringValue(attachment.fileName);
|
|
1089
|
+
if (!content)
|
|
1090
|
+
continue;
|
|
1091
|
+
const payload = {
|
|
1092
|
+
mime_type: mimeType,
|
|
1093
|
+
data: content.replace(/^data:[^;]+;base64,/, ""),
|
|
1094
|
+
file_name: fileName,
|
|
1095
|
+
};
|
|
1096
|
+
if (mimeType.startsWith("image/")) {
|
|
1097
|
+
images.push(payload);
|
|
1098
|
+
}
|
|
1099
|
+
else if (mimeType.startsWith("audio/") && !audio) {
|
|
1100
|
+
audio = {
|
|
1101
|
+
mime_type: mimeType,
|
|
1102
|
+
data: payload.data,
|
|
1103
|
+
format: mimeType.replace(/^audio\//, ""),
|
|
1104
|
+
file_name: fileName,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
files.push(payload);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
...(images.length > 0 ? { images } : {}),
|
|
1113
|
+
...(files.length > 0 ? { files } : {}),
|
|
1114
|
+
...(audio ? { audio } : {}),
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function isRecord(value) {
|
|
1118
|
+
return typeof value === "object" && value !== null;
|
|
1119
|
+
}
|
|
448
1120
|
//# sourceMappingURL=ccconnect-relay-manager.js.map
|