@rethinkingstudio/clawpilot 2.1.7-internal.1 → 2.1.7-internal.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.
@@ -4,6 +4,8 @@ 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";
7
9
  const DEFAULT_CONTEXT_TOKENS = 200_000;
8
10
  export async function runCCConnectRelayManager(opts) {
9
11
  const wsUrl = buildRelayUrl(opts.relayServerUrl, opts.gatewayId, opts.relaySecret);
@@ -19,6 +21,7 @@ export async function runCCConnectRelayManager(opts) {
19
21
  let bridgePingTimer = null;
20
22
  let bridgeReconnectAttempts = 0;
21
23
  const rawToEncoded = new Map();
24
+ const rawToProject = new Map();
22
25
  const activeRunByEncoded = new Map();
23
26
  try {
24
27
  relayWs = new WebSocket(wsUrl);
@@ -106,7 +109,19 @@ export async function runCCConnectRelayManager(opts) {
106
109
  ws.send(JSON.stringify({
107
110
  type: "register",
108
111
  platform: "pocketclaw",
109
- capabilities: ["text", "typing", "preview", "update_message", "reconstruct_reply"],
112
+ capabilities: [
113
+ "text",
114
+ "typing",
115
+ "preview",
116
+ "update_message",
117
+ "delete_message",
118
+ "reconstruct_reply",
119
+ "buttons",
120
+ "card",
121
+ "image",
122
+ "file",
123
+ "audio",
124
+ ],
110
125
  metadata: { version: "1.0.0", description: "PocketClaw relay adapter" },
111
126
  }));
112
127
  });
@@ -142,6 +157,9 @@ export async function runCCConnectRelayManager(opts) {
142
157
  });
143
158
  }
144
159
  function handleBridgeMessage(msg) {
160
+ void handleBridgeMessageAsync(msg);
161
+ }
162
+ async function handleBridgeMessageAsync(msg) {
145
163
  const record = msg;
146
164
  if (msg.type === "register_ack") {
147
165
  bridgeReady = Boolean(msg.ok);
@@ -157,8 +175,10 @@ export async function runCCConnectRelayManager(opts) {
157
175
  }
158
176
  return;
159
177
  }
178
+ if (msg.type === "pong")
179
+ return;
160
180
  const rawSessionKey = stringValue(record.session_key) ?? "";
161
- const encodedSessionKey = rawSessionKey ? rawToEncoded.get(rawSessionKey) : undefined;
181
+ const encodedSessionKey = await resolveEncodedBridgeSession(record);
162
182
  if (!encodedSessionKey)
163
183
  return;
164
184
  const runId = ensureRunId(encodedSessionKey);
@@ -185,27 +205,69 @@ export async function runCCConnectRelayManager(opts) {
185
205
  emitChat(runId, encodedSessionKey, "delta", stringValue(record.content) ?? "", false);
186
206
  return;
187
207
  }
208
+ if (msg.type === "delete_message") {
209
+ send({
210
+ type: "event",
211
+ event: "message.delete",
212
+ payload: {
213
+ runId,
214
+ sessionKey: encodedSessionKey,
215
+ previewHandle: stringValue(record.preview_handle),
216
+ },
217
+ });
218
+ activeRunByEncoded.delete(encodedSessionKey);
219
+ return;
220
+ }
188
221
  if (msg.type === "reply_stream") {
189
222
  const text = stringValue(record.full_text) ?? stringValue(record.delta) ?? "";
190
223
  const done = Boolean(record.done);
191
- emitChat(runId, encodedSessionKey, done ? "final" : "delta", text, false);
192
- if (done)
224
+ const state = done && !isIntermediateReply(text) ? "final" : "delta";
225
+ emitChat(runId, encodedSessionKey, state, text, false);
226
+ if (state === "final")
193
227
  activeRunByEncoded.delete(encodedSessionKey);
194
228
  return;
195
229
  }
196
230
  if (msg.type === "reply") {
197
- emitChat(runId, encodedSessionKey, "final", stringValue(record.content) ?? "", false);
198
- activeRunByEncoded.delete(encodedSessionKey);
231
+ const content = stringValue(record.content) ?? "";
232
+ const permissionFallback = permissionHintActions(content, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey);
233
+ const state = isIntermediateReply(content) ? "delta" : "final";
234
+ if (permissionFallback) {
235
+ emitStructuredChat(runId, encodedSessionKey, state, {
236
+ text: content,
237
+ blocks: [textBlock(content), permissionFallback].filter(Boolean),
238
+ });
239
+ }
240
+ else {
241
+ emitChat(runId, encodedSessionKey, state, content, false);
242
+ }
243
+ if (state === "final")
244
+ activeRunByEncoded.delete(encodedSessionKey);
199
245
  return;
200
246
  }
201
247
  if (msg.type === "buttons") {
202
- emitChat(runId, encodedSessionKey, "final", stringValue(record.content) ?? "[buttons]", false);
203
- activeRunByEncoded.delete(encodedSessionKey);
248
+ const actions = normalizeBridgeButtons(record.buttons);
249
+ console.log(`[runtime:ccconnect] bridge buttons session=${rawSessionKey} actions=${actions.length}`);
250
+ emitStructuredChat(runId, encodedSessionKey, "delta", {
251
+ text: stringValue(record.content) ?? "",
252
+ blocks: [
253
+ textBlock(stringValue(record.content) ?? ""),
254
+ actionsBlock(actions, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey),
255
+ ].filter(Boolean),
256
+ });
204
257
  return;
205
258
  }
206
259
  if (msg.type === "card") {
207
- emitChat(runId, encodedSessionKey, "final", "[card message]", false);
208
- activeRunByEncoded.delete(encodedSessionKey);
260
+ console.log(`[runtime:ccconnect] bridge card session=${rawSessionKey}`);
261
+ emitStructuredChat(runId, encodedSessionKey, "delta", normalizeBridgeCard(record.card, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey));
262
+ return;
263
+ }
264
+ if (msg.type === "image" || msg.type === "file" || msg.type === "audio") {
265
+ const attachment = await uploadBridgeMedia(msg);
266
+ emitStructuredChat(runId, encodedSessionKey, "final", {
267
+ text: attachment.text,
268
+ attachments: attachment.attachments,
269
+ blocks: attachment.text ? [textBlock(attachment.text)] : [],
270
+ });
209
271
  return;
210
272
  }
211
273
  if (msg.type === "error") {
@@ -213,6 +275,37 @@ export async function runCCConnectRelayManager(opts) {
213
275
  activeRunByEncoded.delete(encodedSessionKey);
214
276
  }
215
277
  }
278
+ async function resolveEncodedBridgeSession(record) {
279
+ const rawSessionKey = stringValue(record.session_key);
280
+ if (!rawSessionKey) {
281
+ if (activeRunByEncoded.size === 1) {
282
+ return activeRunByEncoded.keys().next().value;
283
+ }
284
+ console.warn(`[runtime:ccconnect] dropped bridge ${String(record.type)} without session_key`);
285
+ return undefined;
286
+ }
287
+ const existing = rawToEncoded.get(rawSessionKey);
288
+ if (existing)
289
+ return existing;
290
+ const explicitProject = stringValue(record.project) ?? rawToProject.get(rawSessionKey);
291
+ if (explicitProject) {
292
+ const encoded = encodeCCConnectSessionKey(explicitProject, rawSessionKey, rawSessionKey);
293
+ rawToEncoded.set(rawSessionKey, encoded);
294
+ rawToProject.set(rawSessionKey, explicitProject);
295
+ console.warn(`[runtime:ccconnect] recovered bridge session mapping project=${explicitProject} rawSessionKey=${rawSessionKey}`);
296
+ return encoded;
297
+ }
298
+ if (activeRunByEncoded.size === 1) {
299
+ const encoded = activeRunByEncoded.keys().next().value;
300
+ if (encoded) {
301
+ rawToEncoded.set(rawSessionKey, encoded);
302
+ console.warn(`[runtime:ccconnect] inferred bridge session mapping rawSessionKey=${rawSessionKey}`);
303
+ return encoded;
304
+ }
305
+ }
306
+ console.warn(`[runtime:ccconnect] dropped bridge ${String(record.type)} for unmapped session_key=${rawSessionKey}`);
307
+ return undefined;
308
+ }
216
309
  function ensureRunId(encodedSessionKey, preferred) {
217
310
  const existing = activeRunByEncoded.get(encodedSessionKey);
218
311
  if (existing)
@@ -232,6 +325,37 @@ export async function runCCConnectRelayManager(opts) {
232
325
  payload.errorMessage = text ?? "cc-connect error";
233
326
  send({ type: "event", event: "chat", payload });
234
327
  }
328
+ function emitStructuredChat(runId, sessionKey, state, data) {
329
+ const blocks = data.blocks?.length ? data.blocks : data.text !== undefined ? [textBlock(data.text)] : undefined;
330
+ const payload = { runId, sessionKey, state };
331
+ if (blocks)
332
+ payload.message = { content: blocks };
333
+ if (data.attachments)
334
+ payload.attachments = data.attachments;
335
+ if (state === "error")
336
+ payload.errorMessage = data.text ?? "cc-connect error";
337
+ send({ type: "event", event: "chat", payload });
338
+ }
339
+ async function uploadBridgeMedia(msg) {
340
+ const data = stringValue(msg.data);
341
+ const fileName = stringValue(msg.file_name);
342
+ const mimeType = bridgeMediaMimeType(msg);
343
+ const label = fileName || (msg.type === "audio" ? "Audio message" : msg.type === "image" ? "Image" : "File");
344
+ if (!data) {
345
+ return { text: `[${label} missing data]`, attachments: [] };
346
+ }
347
+ const block = {
348
+ mimeType,
349
+ fileName,
350
+ data: Buffer.from(data.replace(/^data:[^;]+;base64,/, ""), "base64"),
351
+ };
352
+ const uploaded = await uploadMediaBlocks([block], opts.relayServerUrl, opts.gatewayId, opts.relaySecret);
353
+ const attachments = uploaded.map((att) => ({
354
+ ...att,
355
+ fileName: fileName ?? undefined,
356
+ }));
357
+ return { text: label, attachments };
358
+ }
235
359
  async function respondSessionsList(requestId) {
236
360
  const projects = await client.listProjects();
237
361
  const sessions = [];
@@ -248,18 +372,80 @@ export async function runCCConnectRelayManager(opts) {
248
372
  count: sessions.length,
249
373
  });
250
374
  }
375
+ async function respondProjectsList(requestId) {
376
+ const projects = await client.listProjects();
377
+ const detailed = await Promise.all(projects.map(async (project) => {
378
+ try {
379
+ const detail = await client.getProject(project.name);
380
+ return {
381
+ name: detail.name,
382
+ agentType: detail.agent_type,
383
+ workDir: detail.work_dir,
384
+ mode: detail.agent_mode || detail.mode,
385
+ sessionsCount: detail.sessions_count ?? project.sessions_count ?? 0,
386
+ platforms: detail.platforms ?? project.platforms ?? [],
387
+ heartbeatEnabled: detail.heartbeat_enabled ?? project.heartbeat_enabled ?? false,
388
+ activeSessionKeys: detail.active_session_keys ?? [],
389
+ };
390
+ }
391
+ catch {
392
+ return {
393
+ name: project.name,
394
+ agentType: project.agent_type,
395
+ sessionsCount: project.sessions_count ?? 0,
396
+ platforms: project.platforms ?? [],
397
+ heartbeatEnabled: project.heartbeat_enabled ?? false,
398
+ activeSessionKeys: [],
399
+ };
400
+ }
401
+ }));
402
+ sendResponse(requestId, { projects: detailed, count: detailed.length });
403
+ }
404
+ async function respondProjectsDiscover(requestId) {
405
+ const projects = await client.listProjects();
406
+ const detailed = await Promise.all(projects.map(async (project) => {
407
+ try {
408
+ const detail = await client.getProject(project.name);
409
+ return { name: detail.name, workDir: detail.work_dir };
410
+ }
411
+ catch {
412
+ return { name: project.name, workDir: undefined };
413
+ }
414
+ }));
415
+ const directories = discoverCCConnectProjects(detailed);
416
+ sendResponse(requestId, { directories, count: directories.length });
417
+ }
418
+ async function respondProjectCreate(requestId, params) {
419
+ const p = params;
420
+ const created = createCCConnectProject(p);
421
+ restartCCConnect(created.configPath);
422
+ sendResponse(requestId, {
423
+ project: {
424
+ name: created.name,
425
+ agentType: created.agentType,
426
+ workDir: created.workDir,
427
+ mode: created.mode,
428
+ sessionsCount: 0,
429
+ platforms: ["line", "bridge"],
430
+ heartbeatEnabled: false,
431
+ activeSessionKeys: [],
432
+ },
433
+ restarted: true,
434
+ });
435
+ }
251
436
  async function respondMessagesHistory(requestId, params) {
252
437
  const p = params;
253
438
  if (!p.sessionKey)
254
439
  throw new Error("sessionKey required");
255
440
  const decoded = decodeCCConnectSessionKey(p.sessionKey);
256
- const detail = await client.getSession(decoded.project, decoded.sessionId, Math.min(p.limit ?? 50, 200));
441
+ const sessionId = await resolveCCConnectSessionId(decoded.project, decoded.sessionKey, decoded.sessionId);
442
+ const detail = await client.getSession(decoded.project, sessionId, Math.min(p.limit ?? 50, 200));
257
443
  const history = detail.history ?? [];
258
444
  const messages = history.map((entry, index) => ({
259
445
  id: index + 1,
260
446
  role: entry.role === "user" ? "user" : "assistant",
261
447
  content: entry.content ?? "",
262
- runId: entry.role === "user" ? undefined : `${decoded.sessionId}-${index + 1}`,
448
+ runId: entry.role === "user" ? undefined : `${sessionId}-${index + 1}`,
263
449
  state: "final",
264
450
  createdAt: timestampMs(entry.timestamp),
265
451
  updatedAt: timestampMs(entry.timestamp),
@@ -292,11 +478,14 @@ export async function runCCConnectRelayManager(opts) {
292
478
  throw new Error("sessionKey required");
293
479
  if (!p.message)
294
480
  throw new Error("message required");
295
- const decoded = decodeCCConnectSessionKey(p.sessionKey);
481
+ const decoded = await resolveCCConnectSendTarget(p.sessionKey);
296
482
  rawToEncoded.set(decoded.sessionKey, p.sessionKey);
483
+ rawToProject.set(decoded.sessionKey, decoded.project);
484
+ activeRunByEncoded.delete(p.sessionKey);
297
485
  const runId = ensureRunId(p.sessionKey, p.idempotencyKey);
298
486
  emitChat(runId, p.sessionKey, "delta", undefined, true);
299
487
  if (bridgeReady && bridgeWs?.readyState === WebSocket.OPEN) {
488
+ console.log(`[runtime:ccconnect] chat.send via bridge project=${decoded.project} rawSessionKey=${decoded.sessionKey}`);
300
489
  bridgeWs.send(JSON.stringify({
301
490
  type: "message",
302
491
  msg_id: `pocketclaw-${Date.now()}`,
@@ -306,17 +495,68 @@ export async function runCCConnectRelayManager(opts) {
306
495
  content: p.message,
307
496
  reply_ctx: decoded.sessionKey,
308
497
  project: decoded.project,
498
+ ...bridgeAttachments(params),
309
499
  }));
310
500
  return;
311
501
  }
312
502
  scheduleBridgeReconnect("bridge not ready during chat.send");
503
+ console.log(`[runtime:ccconnect] chat.send via management fallback project=${decoded.project} rawSessionKey=${decoded.sessionKey}`);
313
504
  await client.send(decoded.project, decoded.sessionKey, p.message);
314
505
  }
506
+ async function handleChatAction(params) {
507
+ const p = params;
508
+ if (!p.sessionKey)
509
+ throw new Error("sessionKey required");
510
+ if (!p.action)
511
+ throw new Error("action required");
512
+ if (!bridgeReady || bridgeWs?.readyState !== WebSocket.OPEN) {
513
+ throw new Error("cc-connect bridge not ready");
514
+ }
515
+ const decoded = await resolveCCConnectSendTarget(p.sessionKey);
516
+ bridgeWs.send(JSON.stringify({
517
+ type: "card_action",
518
+ session_key: decoded.sessionKey,
519
+ action: p.action,
520
+ reply_ctx: p.replyCtx || decoded.sessionKey,
521
+ project: decoded.project,
522
+ }));
523
+ }
524
+ async function resolveCCConnectSendTarget(sessionKey) {
525
+ try {
526
+ return decodeCCConnectSessionKey(sessionKey);
527
+ }
528
+ catch {
529
+ const projects = await client.listProjects();
530
+ for (const project of projects) {
531
+ const sessions = await client.listSessions(project.name);
532
+ const preferred = sessions.find((session) => session.active || session.live) ?? sessions[0];
533
+ if (preferred?.session_key) {
534
+ console.warn(`[runtime:ccconnect] non-ccconnect sessionKey received (${sessionKey}); using ${project.name}/${preferred.session_key}`);
535
+ return {
536
+ project: project.name,
537
+ sessionKey: preferred.session_key,
538
+ sessionId: preferred.id || preferred.session_key,
539
+ };
540
+ }
541
+ }
542
+ throw new Error(`invalid cc-connect session key: ${sessionKey}`);
543
+ }
544
+ }
315
545
  function rememberSession(project, session) {
316
- const encoded = encodeCCConnectSessionKey(project, session.session_key, session.id);
317
- rawToEncoded.set(session.session_key, encoded);
546
+ const encoded = encodeCCConnectSessionKey(project, session.session_key, session.session_key);
547
+ if (!rawToEncoded.has(session.session_key)) {
548
+ rawToEncoded.set(session.session_key, encoded);
549
+ rawToProject.set(session.session_key, project);
550
+ }
318
551
  return encoded;
319
552
  }
553
+ async function resolveCCConnectSessionId(project, sessionKey, sessionId) {
554
+ if (sessionId && sessionId !== sessionKey)
555
+ return sessionId;
556
+ const sessions = await client.listSessions(project);
557
+ const matched = sessions.find((session) => session.session_key === sessionKey);
558
+ return matched?.id || sessionId || sessionKey;
559
+ }
320
560
  function toRuntimeSession(project, session, encoded) {
321
561
  const platform = session.platform || session.session_key.split(":")[0] || "cc-connect";
322
562
  const title = session.name || session.chat_name || session.user_name || session.session_key || session.id;
@@ -383,6 +623,18 @@ export async function runCCConnectRelayManager(opts) {
383
623
  }
384
624
  try {
385
625
  switch (msg.method) {
626
+ case "projects.list":
627
+ if (requestId)
628
+ await respondProjectsList(requestId);
629
+ return;
630
+ case "projects.discover":
631
+ if (requestId)
632
+ await respondProjectsDiscover(requestId);
633
+ return;
634
+ case "projects.create":
635
+ if (requestId)
636
+ await respondProjectCreate(requestId, msg.params ?? {});
637
+ return;
386
638
  case "sessions.list":
387
639
  if (requestId)
388
640
  await respondSessionsList(requestId);
@@ -400,6 +652,11 @@ export async function runCCConnectRelayManager(opts) {
400
652
  if (requestId)
401
653
  sendResponse(requestId, { ok: true });
402
654
  return;
655
+ case "chat.action":
656
+ await handleChatAction(msg.params ?? {});
657
+ if (requestId)
658
+ sendResponse(requestId, { ok: true });
659
+ return;
403
660
  case "chat.abort":
404
661
  if (requestId)
405
662
  sendResponse(requestId, { ok: true });
@@ -445,4 +702,254 @@ function buildRelayUrl(base, gatewayId, relaySecret) {
445
702
  function stringValue(value) {
446
703
  return typeof value === "string" ? value : undefined;
447
704
  }
705
+ function textBlock(text) {
706
+ return text ? { type: "text", text } : undefined;
707
+ }
708
+ function actionsBlock(buttons, sessionKey, replyCtx) {
709
+ if (buttons.length === 0)
710
+ return undefined;
711
+ return {
712
+ type: "actions",
713
+ actions: buttons,
714
+ sessionKey,
715
+ replyCtx,
716
+ };
717
+ }
718
+ function normalizeBridgeButtons(buttons) {
719
+ if (!Array.isArray(buttons))
720
+ return [];
721
+ const actions = [];
722
+ const rows = buttons.some(Array.isArray) ? buttons : [buttons];
723
+ for (const row of rows) {
724
+ const buttonRow = Array.isArray(row) ? row : [row];
725
+ for (const button of buttonRow) {
726
+ const action = normalizeBridgeAction(button);
727
+ if (action)
728
+ actions.push(action);
729
+ }
730
+ }
731
+ return actions;
732
+ }
733
+ function normalizeBridgeCard(card, sessionKey, replyCtx) {
734
+ if (!isRecord(card)) {
735
+ return { text: "[card message]", blocks: [textBlock("[card message]")].filter(Boolean) };
736
+ }
737
+ const blocks = [];
738
+ const textParts = [];
739
+ const header = isRecord(card.header) ? stringValue(card.header.title) : undefined;
740
+ if (header) {
741
+ blocks.push({ type: "text", text: `**${header}**` });
742
+ textParts.push(header);
743
+ }
744
+ const elements = Array.isArray(card.elements) ? card.elements : [];
745
+ for (const element of elements) {
746
+ if (!isRecord(element))
747
+ continue;
748
+ const type = stringValue(element.type);
749
+ if (type === "markdown") {
750
+ const text = stringValue(element.content) ?? "";
751
+ if (text) {
752
+ blocks.push({ type: "markdown", text });
753
+ textParts.push(text);
754
+ }
755
+ continue;
756
+ }
757
+ if (type === "note") {
758
+ const text = stringValue(element.text) ?? "";
759
+ if (text) {
760
+ blocks.push({ type: "text", text });
761
+ textParts.push(text);
762
+ }
763
+ continue;
764
+ }
765
+ if (type === "divider") {
766
+ blocks.push({ type: "divider" });
767
+ continue;
768
+ }
769
+ if (type === "actions") {
770
+ const actions = normalizeCardActionButtons(element.buttons);
771
+ const block = actionsBlock(actions, sessionKey, replyCtx);
772
+ if (block)
773
+ blocks.push(block);
774
+ continue;
775
+ }
776
+ if (type === "list_item") {
777
+ const text = stringValue(element.text) ?? "";
778
+ if (text)
779
+ textParts.push(text);
780
+ const btnText = stringValue(element.btn_text);
781
+ const btnValue = stringValue(element.btn_value);
782
+ blocks.push({
783
+ type: "list_item",
784
+ text,
785
+ action: btnText && btnValue ? {
786
+ id: btnValue,
787
+ label: btnText,
788
+ value: btnValue,
789
+ style: actionStyle(btnValue, stringValue(element.btn_type)),
790
+ } : undefined,
791
+ sessionKey,
792
+ replyCtx,
793
+ });
794
+ continue;
795
+ }
796
+ if (type === "select") {
797
+ const options = Array.isArray(element.options) ? element.options.filter(isRecord).map((option) => ({
798
+ id: stringValue(option.value) ?? "",
799
+ label: stringValue(option.text) ?? "",
800
+ value: stringValue(option.value) ?? "",
801
+ style: "default",
802
+ })).filter((option) => option.label && option.value) : [];
803
+ blocks.push({
804
+ type: "select",
805
+ placeholder: stringValue(element.placeholder) ?? "Select",
806
+ initValue: stringValue(element.init_value),
807
+ actions: options,
808
+ sessionKey,
809
+ replyCtx,
810
+ });
811
+ }
812
+ }
813
+ return { text: textParts.join("\n\n"), blocks };
814
+ }
815
+ function normalizeCardActionButtons(buttons) {
816
+ if (!Array.isArray(buttons))
817
+ return [];
818
+ const actions = [];
819
+ for (const button of buttons) {
820
+ const action = normalizeBridgeAction(button);
821
+ if (action)
822
+ actions.push(action);
823
+ }
824
+ return actions;
825
+ }
826
+ function normalizeBridgeAction(button) {
827
+ if (!isRecord(button))
828
+ return undefined;
829
+ const label = stringValue(button.text)
830
+ ?? stringValue(button.label)
831
+ ?? stringValue(button.title);
832
+ const value = stringValue(button.data)
833
+ ?? stringValue(button.value)
834
+ ?? stringValue(button.id)
835
+ ?? inferActionValue(label);
836
+ if (!label || !value)
837
+ return undefined;
838
+ return {
839
+ id: value,
840
+ label,
841
+ value,
842
+ style: actionStyle(value, stringValue(button.btn_type) ?? stringValue(button.style)),
843
+ };
844
+ }
845
+ function permissionHintActions(content, sessionKey, replyCtx) {
846
+ if (!isPermissionHint(content))
847
+ return undefined;
848
+ return actionsBlock([
849
+ { id: "perm:allow", label: "允许", value: "perm:allow", style: "primary" },
850
+ { id: "perm:deny", label: "拒绝", value: "perm:deny", style: "danger" },
851
+ { id: "perm:allow_all", label: "允许所有", value: "perm:allow_all", style: "default" },
852
+ ], sessionKey, replyCtx);
853
+ }
854
+ function isPermissionHint(content) {
855
+ const normalized = content.toLowerCase();
856
+ return (normalized.includes("等待权限响应") || normalized.includes("permission"))
857
+ && (normalized.includes("允许所有") || normalized.includes("allow all"))
858
+ && (normalized.includes("拒绝") || normalized.includes("deny"));
859
+ }
860
+ function isIntermediateReply(content) {
861
+ const normalized = content.toLowerCase().trim();
862
+ if (!normalized)
863
+ return false;
864
+ if (isPermissionHint(content))
865
+ return true;
866
+ return (normalized.includes("继续执行")
867
+ || normalized.includes("继续处理中")
868
+ || normalized.includes("continuing")
869
+ || normalized.includes("continue execution")
870
+ || normalized.includes("permission granted")
871
+ || normalized.includes("已允许")
872
+ || normalized.includes("已开启自动批准")
873
+ || normalized.includes("自动批准")
874
+ || normalized.includes("权限请求将自动允许"));
875
+ }
876
+ function inferActionValue(label) {
877
+ if (!label)
878
+ return undefined;
879
+ const normalized = label.toLowerCase().replace(/\s+/g, "");
880
+ if (normalized.includes("允许所有") || normalized.includes("全部允许") || normalized.includes("allowall")) {
881
+ return "perm:allow_all";
882
+ }
883
+ if (normalized.includes("拒绝") || normalized.includes("deny") || normalized.includes("reject")) {
884
+ return "perm:deny";
885
+ }
886
+ if (normalized.includes("允许") || normalized.includes("同意") || normalized.includes("allow") || normalized.includes("approve")) {
887
+ return "perm:allow";
888
+ }
889
+ return undefined;
890
+ }
891
+ function actionStyle(value, explicit) {
892
+ if (explicit === "primary" || explicit === "danger")
893
+ return explicit;
894
+ if (value.includes("deny") || value.includes("reject") || value.includes("cancel"))
895
+ return "danger";
896
+ if (value.includes("allow"))
897
+ return "primary";
898
+ return "default";
899
+ }
900
+ function bridgeMediaMimeType(msg) {
901
+ const record = msg;
902
+ const explicit = stringValue(record.mime_type);
903
+ if (explicit)
904
+ return explicit;
905
+ if (msg.type === "audio") {
906
+ const format = stringValue(record.format) ?? "mpeg";
907
+ return format.includes("/") ? format : `audio/${format}`;
908
+ }
909
+ if (msg.type === "image")
910
+ return "image/png";
911
+ return "application/octet-stream";
912
+ }
913
+ function bridgeAttachments(params) {
914
+ const attachments = isRecord(params) && Array.isArray(params.attachments) ? params.attachments : [];
915
+ const images = [];
916
+ const files = [];
917
+ let audio;
918
+ for (const attachment of attachments) {
919
+ if (!isRecord(attachment))
920
+ continue;
921
+ const content = stringValue(attachment.content);
922
+ const mimeType = stringValue(attachment.mimeType) ?? "application/octet-stream";
923
+ const fileName = stringValue(attachment.fileName);
924
+ if (!content)
925
+ continue;
926
+ const payload = {
927
+ mime_type: mimeType,
928
+ data: content.replace(/^data:[^;]+;base64,/, ""),
929
+ file_name: fileName,
930
+ };
931
+ if (mimeType.startsWith("image/")) {
932
+ images.push(payload);
933
+ }
934
+ else if (mimeType.startsWith("audio/") && !audio) {
935
+ audio = {
936
+ mime_type: mimeType,
937
+ data: payload.data,
938
+ format: mimeType.replace(/^audio\//, ""),
939
+ file_name: fileName,
940
+ };
941
+ }
942
+ else {
943
+ files.push(payload);
944
+ }
945
+ }
946
+ return {
947
+ ...(images.length > 0 ? { images } : {}),
948
+ ...(files.length > 0 ? { files } : {}),
949
+ ...(audio ? { audio } : {}),
950
+ };
951
+ }
952
+ function isRecord(value) {
953
+ return typeof value === "object" && value !== null;
954
+ }
448
955
  //# sourceMappingURL=ccconnect-relay-manager.js.map