@rethinkingstudio/clawpilot 2.1.7-internal.8 → 2.1.7
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/commands/local-handlers.js +46 -0
- package/dist/commands/local-handlers.js.map +1 -1
- package/dist/commands/pair.js +15 -8
- package/dist/commands/pair.js.map +1 -1
- package/dist/config/config.js +2 -1
- package/dist/config/config.js.map +1 -1
- package/dist/generated/build-config.d.ts +1 -1
- package/dist/generated/build-config.js +1 -1
- package/dist/generated/build-config.js.map +1 -1
- package/dist/hermes/agent-bridge.js +5 -0
- package/dist/hermes/agent-bridge.js.map +1 -1
- package/dist/hermes/home.d.ts +23 -0
- package/dist/hermes/home.js +250 -0
- package/dist/hermes/home.js.map +1 -0
- package/dist/hermes/model-catalog.js +10 -9
- package/dist/hermes/model-catalog.js.map +1 -1
- package/dist/platform/service-manager.js +9 -0
- package/dist/platform/service-manager.js.map +1 -1
- package/dist/relay/agent-status.d.ts +55 -0
- package/dist/relay/agent-status.js +294 -0
- package/dist/relay/agent-status.js.map +1 -0
- package/dist/relay/ccconnect-relay-manager.js +316 -65
- package/dist/relay/ccconnect-relay-manager.js.map +1 -1
- package/dist/relay/hermes-relay-manager.js +225 -53
- package/dist/relay/hermes-relay-manager.js.map +1 -1
- package/dist/relay/relay-command-dispatcher.d.ts +14 -0
- package/dist/relay/relay-command-dispatcher.js +25 -0
- package/dist/relay/relay-command-dispatcher.js.map +1 -0
- package/dist/relay/relay-manager.js +60 -29
- package/dist/relay/relay-manager.js.map +1 -1
- package/dist/relay/relay-method-registry.d.ts +9 -0
- package/dist/relay/relay-method-registry.js +61 -0
- package/dist/relay/relay-method-registry.js.map +1 -0
- 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 +14 -2
- package/scripts/verify-build-config.mjs +1 -3
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
|
-
import { handleLocalCommand } from "../commands/local-handlers.js";
|
|
4
|
-
import { handleProviderCommand } from "../commands/provider-handlers.js";
|
|
5
3
|
import { getServicePlatform } from "../platform/service-manager.js";
|
|
6
4
|
import { CCConnectManagementClient, decodeCCConnectSessionKey, encodeCCConnectSessionKey, timestampMs, } from "../ccconnect/api-client.js";
|
|
7
5
|
import { createCCConnectProject, discoverCCConnectProjects, restartCCConnect } from "../ccconnect/project-config.js";
|
|
8
6
|
import { uploadMediaBlocks } from "../media/assistant-attachments.js";
|
|
7
|
+
import { installRelayWatchdog } from "./relay-watchdog.js";
|
|
8
|
+
import { dispatchCommonRelayCommand } from "./relay-command-dispatcher.js";
|
|
9
|
+
import { relayMethodSupport } from "./relay-method-registry.js";
|
|
9
10
|
const DEFAULT_CONTEXT_TOKENS = 200_000;
|
|
10
11
|
export async function runCCConnectRelayManager(opts) {
|
|
11
12
|
const wsUrl = buildRelayUrl(opts.relayServerUrl, opts.gatewayId, opts.relaySecret);
|
|
@@ -21,7 +22,10 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
21
22
|
let bridgePingTimer = null;
|
|
22
23
|
let bridgeReconnectAttempts = 0;
|
|
23
24
|
const rawToEncoded = new Map();
|
|
25
|
+
const rawToProject = new Map();
|
|
24
26
|
const activeRunByEncoded = new Map();
|
|
27
|
+
const previewHandleToRun = new Map();
|
|
28
|
+
const pendingInteractiveReplyByEncoded = new Set();
|
|
25
29
|
try {
|
|
26
30
|
relayWs = new WebSocket(wsUrl);
|
|
27
31
|
}
|
|
@@ -30,6 +34,7 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
30
34
|
resolve(true);
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
37
|
+
const uninstallWatchdog = installRelayWatchdog(relayWs, `ccconnect:${opts.gatewayId}`);
|
|
33
38
|
function send(msg) {
|
|
34
39
|
if (relayWs.readyState === WebSocket.OPEN) {
|
|
35
40
|
relayWs.send(JSON.stringify(msg));
|
|
@@ -121,7 +126,12 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
121
126
|
"file",
|
|
122
127
|
"audio",
|
|
123
128
|
],
|
|
124
|
-
metadata: {
|
|
129
|
+
metadata: {
|
|
130
|
+
version: "1.0.0",
|
|
131
|
+
description: "PocketClaw relay adapter",
|
|
132
|
+
progress_style: "card",
|
|
133
|
+
supports_progress_card_payload: true,
|
|
134
|
+
},
|
|
125
135
|
}));
|
|
126
136
|
});
|
|
127
137
|
ws.on("message", (raw) => {
|
|
@@ -177,10 +187,12 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
177
187
|
if (msg.type === "pong")
|
|
178
188
|
return;
|
|
179
189
|
const rawSessionKey = stringValue(record.session_key) ?? "";
|
|
180
|
-
const encodedSessionKey =
|
|
190
|
+
const encodedSessionKey = await resolveEncodedBridgeSession(record);
|
|
181
191
|
if (!encodedSessionKey)
|
|
182
192
|
return;
|
|
183
|
-
const
|
|
193
|
+
const previewHandle = stringValue(record.preview_handle);
|
|
194
|
+
const runInfo = resolveRunForBridgeMessage(encodedSessionKey, previewHandle);
|
|
195
|
+
let runId = runInfo.runId;
|
|
184
196
|
if (msg.type === "typing_start") {
|
|
185
197
|
emitChat(runId, encodedSessionKey, "delta", undefined, true);
|
|
186
198
|
return;
|
|
@@ -189,57 +201,93 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
189
201
|
return;
|
|
190
202
|
}
|
|
191
203
|
if (msg.type === "preview_start") {
|
|
192
|
-
|
|
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);
|
|
193
210
|
const refId = stringValue(record.ref_id);
|
|
211
|
+
const handle = `pocketclaw-${runId}`;
|
|
212
|
+
previewHandleToRun.set(handle, { encodedSessionKey, runId, sideChannel });
|
|
194
213
|
if (refId) {
|
|
195
214
|
bridgeWs?.send(JSON.stringify({
|
|
196
215
|
type: "preview_ack",
|
|
197
216
|
ref_id: refId,
|
|
198
|
-
preview_handle:
|
|
217
|
+
preview_handle: handle,
|
|
199
218
|
}));
|
|
200
219
|
}
|
|
201
220
|
return;
|
|
202
221
|
}
|
|
203
222
|
if (msg.type === "update_message") {
|
|
204
|
-
|
|
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);
|
|
205
229
|
return;
|
|
206
230
|
}
|
|
207
231
|
if (msg.type === "delete_message") {
|
|
232
|
+
if (previewHandle)
|
|
233
|
+
previewHandleToRun.delete(previewHandle);
|
|
208
234
|
send({
|
|
209
235
|
type: "event",
|
|
210
236
|
event: "message.delete",
|
|
211
237
|
payload: {
|
|
212
238
|
runId,
|
|
213
239
|
sessionKey: encodedSessionKey,
|
|
214
|
-
previewHandle
|
|
240
|
+
previewHandle,
|
|
241
|
+
keepTurn: true,
|
|
242
|
+
sideChannel: runInfo.sideChannel,
|
|
215
243
|
},
|
|
216
244
|
});
|
|
217
|
-
activeRunByEncoded.delete(encodedSessionKey);
|
|
218
245
|
return;
|
|
219
246
|
}
|
|
220
247
|
if (msg.type === "reply_stream") {
|
|
221
248
|
const text = stringValue(record.full_text) ?? stringValue(record.delta) ?? "";
|
|
222
249
|
const done = Boolean(record.done);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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);
|
|
226
255
|
return;
|
|
227
256
|
}
|
|
228
257
|
if (msg.type === "reply") {
|
|
229
|
-
|
|
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);
|
|
230
275
|
return;
|
|
231
276
|
}
|
|
232
277
|
if (msg.type === "buttons") {
|
|
278
|
+
const actions = normalizeBridgeButtons(record.buttons);
|
|
279
|
+
console.log(`[runtime:ccconnect] bridge buttons session=${rawSessionKey} actions=${actions.length}`);
|
|
233
280
|
emitStructuredChat(runId, encodedSessionKey, "delta", {
|
|
234
281
|
text: stringValue(record.content) ?? "",
|
|
235
282
|
blocks: [
|
|
236
283
|
textBlock(stringValue(record.content) ?? ""),
|
|
237
|
-
actionsBlock(
|
|
284
|
+
actionsBlock(actions, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey),
|
|
238
285
|
].filter(Boolean),
|
|
239
286
|
});
|
|
240
287
|
return;
|
|
241
288
|
}
|
|
242
289
|
if (msg.type === "card") {
|
|
290
|
+
console.log(`[runtime:ccconnect] bridge card session=${rawSessionKey}`);
|
|
243
291
|
emitStructuredChat(runId, encodedSessionKey, "delta", normalizeBridgeCard(record.card, rawSessionKey, stringValue(record.reply_ctx) ?? rawSessionKey));
|
|
244
292
|
return;
|
|
245
293
|
}
|
|
@@ -250,13 +298,45 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
250
298
|
attachments: attachment.attachments,
|
|
251
299
|
blocks: attachment.text ? [textBlock(attachment.text)] : [],
|
|
252
300
|
});
|
|
301
|
+
clearActiveBridgeRun(encodedSessionKey, runId);
|
|
253
302
|
return;
|
|
254
303
|
}
|
|
255
304
|
if (msg.type === "error") {
|
|
256
305
|
emitChat(runId, encodedSessionKey, "error", stringValue(record.message) ?? "cc-connect bridge error", false);
|
|
257
|
-
|
|
306
|
+
clearActiveBridgeRun(encodedSessionKey, runId);
|
|
258
307
|
}
|
|
259
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;
|
|
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;
|
|
339
|
+
}
|
|
260
340
|
function ensureRunId(encodedSessionKey, preferred) {
|
|
261
341
|
const existing = activeRunByEncoded.get(encodedSessionKey);
|
|
262
342
|
if (existing)
|
|
@@ -265,6 +345,39 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
265
345
|
activeRunByEncoded.set(encodedSessionKey, runId);
|
|
266
346
|
return runId;
|
|
267
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
|
+
}
|
|
268
381
|
function emitChat(runId, sessionKey, state, text, loading) {
|
|
269
382
|
const payload = { runId, sessionKey, state };
|
|
270
383
|
if (loading)
|
|
@@ -276,6 +389,17 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
276
389
|
payload.errorMessage = text ?? "cc-connect error";
|
|
277
390
|
send({ type: "event", event: "chat", payload });
|
|
278
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
|
+
}
|
|
279
403
|
function emitStructuredChat(runId, sessionKey, state, data) {
|
|
280
404
|
const blocks = data.blocks?.length ? data.blocks : data.text !== undefined ? [textBlock(data.text)] : undefined;
|
|
281
405
|
const payload = { runId, sessionKey, state };
|
|
@@ -410,7 +534,7 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
410
534
|
if (!project)
|
|
411
535
|
throw new Error("cc-connect has no configured projects");
|
|
412
536
|
const title = p.title?.trim() || "New Session";
|
|
413
|
-
const rawSessionKey = `pocketclaw:${opts.gatewayId}
|
|
537
|
+
const rawSessionKey = `pocketclaw:${opts.gatewayId}:${randomUUID()}`;
|
|
414
538
|
const created = await client.createSession(project, rawSessionKey, title);
|
|
415
539
|
const session = {
|
|
416
540
|
...created,
|
|
@@ -431,7 +555,9 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
431
555
|
throw new Error("message required");
|
|
432
556
|
const decoded = await resolveCCConnectSendTarget(p.sessionKey);
|
|
433
557
|
rawToEncoded.set(decoded.sessionKey, p.sessionKey);
|
|
558
|
+
rawToProject.set(decoded.sessionKey, decoded.project);
|
|
434
559
|
activeRunByEncoded.delete(p.sessionKey);
|
|
560
|
+
pendingInteractiveReplyByEncoded.delete(p.sessionKey);
|
|
435
561
|
const runId = ensureRunId(p.sessionKey, p.idempotencyKey);
|
|
436
562
|
emitChat(runId, p.sessionKey, "delta", undefined, true);
|
|
437
563
|
if (bridgeReady && bridgeWs?.readyState === WebSocket.OPEN) {
|
|
@@ -463,6 +589,9 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
463
589
|
throw new Error("cc-connect bridge not ready");
|
|
464
590
|
}
|
|
465
591
|
const decoded = await resolveCCConnectSendTarget(p.sessionKey);
|
|
592
|
+
if (isInteractiveAction(p.action)) {
|
|
593
|
+
pendingInteractiveReplyByEncoded.add(p.sessionKey);
|
|
594
|
+
}
|
|
466
595
|
bridgeWs.send(JSON.stringify({
|
|
467
596
|
type: "card_action",
|
|
468
597
|
session_key: decoded.sessionKey,
|
|
@@ -493,8 +622,12 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
493
622
|
}
|
|
494
623
|
}
|
|
495
624
|
function rememberSession(project, session) {
|
|
496
|
-
const
|
|
497
|
-
|
|
625
|
+
const sessionId = session.id || session.session_key;
|
|
626
|
+
const encoded = encodeCCConnectSessionKey(project, session.session_key, sessionId);
|
|
627
|
+
if (!rawToEncoded.has(session.session_key)) {
|
|
628
|
+
rawToEncoded.set(session.session_key, encoded);
|
|
629
|
+
rawToProject.set(session.session_key, project);
|
|
630
|
+
}
|
|
498
631
|
return encoded;
|
|
499
632
|
}
|
|
500
633
|
async function resolveCCConnectSessionId(project, sessionKey, sessionId) {
|
|
@@ -548,24 +681,9 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
548
681
|
if (msg.type !== "cmd" || !msg.method)
|
|
549
682
|
return;
|
|
550
683
|
const requestId = msg.id;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
const result = await providerPromise;
|
|
555
|
-
if (requestId) {
|
|
556
|
-
send(result.ok
|
|
557
|
-
? { type: "res", id: requestId, ok: true, payload: result.payload }
|
|
558
|
-
: { type: "res", id: requestId, ok: false, error: { message: result.error } });
|
|
559
|
-
}
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
const localResult = handleLocalCommand(msg.method, msg.params);
|
|
563
|
-
if (localResult !== null) {
|
|
564
|
-
if (requestId) {
|
|
565
|
-
send(localResult.ok
|
|
566
|
-
? { type: "res", id: requestId, ok: true, payload: localResult.payload }
|
|
567
|
-
: { type: "res", id: requestId, ok: false, error: { message: localResult.error } });
|
|
568
|
-
}
|
|
684
|
+
const support = relayMethodSupport(msg.method, "ccconnect") ?? "unsupported";
|
|
685
|
+
console.log(`[relay:ccconnect] cmd received method=${msg.method} support=${support} id=${requestId ?? "(no-id)"}`);
|
|
686
|
+
if (await dispatchCommonRelayCommand(msg.method, msg.params, requestId, send)) {
|
|
569
687
|
return;
|
|
570
688
|
}
|
|
571
689
|
try {
|
|
@@ -630,6 +748,7 @@ export async function runCCConnectRelayManager(opts) {
|
|
|
630
748
|
}
|
|
631
749
|
});
|
|
632
750
|
relayWs.on("close", (code, reason) => {
|
|
751
|
+
uninstallWatchdog();
|
|
633
752
|
console.log(`cc-connect relay connection closed: ${code} ${reason.toString()}`);
|
|
634
753
|
opts.onDisconnected?.();
|
|
635
754
|
clearBridgeTimers();
|
|
@@ -652,6 +771,83 @@ function stringValue(value) {
|
|
|
652
771
|
function textBlock(text) {
|
|
653
772
|
return text ? { type: "text", text } : undefined;
|
|
654
773
|
}
|
|
774
|
+
const PROGRESS_CARD_PREFIX = "__cc_connect_progress_card_v1__:";
|
|
775
|
+
function isProgressContent(content) {
|
|
776
|
+
return content.startsWith(PROGRESS_CARD_PREFIX) || content.trimStart().startsWith("⏳ **Progress**");
|
|
777
|
+
}
|
|
778
|
+
function progressContentIsCompleted(content) {
|
|
779
|
+
const payload = parseProgressPayload(content);
|
|
780
|
+
return payload?.state === "completed" || payload?.state === "failed";
|
|
781
|
+
}
|
|
782
|
+
function normalizeBridgeContent(content) {
|
|
783
|
+
const progress = parseProgressPayload(content);
|
|
784
|
+
if (!progress)
|
|
785
|
+
return { text: content };
|
|
786
|
+
const text = renderProgressPayload(progress);
|
|
787
|
+
return {
|
|
788
|
+
text,
|
|
789
|
+
blocks: [
|
|
790
|
+
{
|
|
791
|
+
type: "markdown",
|
|
792
|
+
text,
|
|
793
|
+
},
|
|
794
|
+
],
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function parseProgressPayload(content) {
|
|
798
|
+
if (!content.startsWith(PROGRESS_CARD_PREFIX))
|
|
799
|
+
return undefined;
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(content.slice(PROGRESS_CARD_PREFIX.length));
|
|
802
|
+
const items = Array.isArray(parsed.items)
|
|
803
|
+
? parsed.items.map((item) => ({
|
|
804
|
+
kind: stringValue(item.kind),
|
|
805
|
+
text: stringValue(item.text)?.trim() ?? "",
|
|
806
|
+
tool: stringValue(item.tool),
|
|
807
|
+
status: stringValue(item.status),
|
|
808
|
+
})).filter((item) => item.text)
|
|
809
|
+
: [];
|
|
810
|
+
const entries = items.length > 0
|
|
811
|
+
? items
|
|
812
|
+
: (Array.isArray(parsed.entries) ? parsed.entries.map((entry) => ({
|
|
813
|
+
kind: undefined,
|
|
814
|
+
text: typeof entry === "string" ? entry.trim() : "",
|
|
815
|
+
})).filter((entry) => entry.text) : []);
|
|
816
|
+
if (entries.length === 0)
|
|
817
|
+
return undefined;
|
|
818
|
+
return {
|
|
819
|
+
state: stringValue(parsed.state),
|
|
820
|
+
truncated: Boolean(parsed.truncated),
|
|
821
|
+
entries,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
catch {
|
|
825
|
+
return undefined;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
function renderProgressPayload(payload) {
|
|
829
|
+
const icon = payload.state === "completed" ? "✅" : payload.state === "failed" ? "❌" : "⏳";
|
|
830
|
+
const lines = [`${icon} **Progress**`];
|
|
831
|
+
if (payload.truncated)
|
|
832
|
+
lines.push("_Showing latest updates only._");
|
|
833
|
+
for (const [index, entry] of payload.entries.entries()) {
|
|
834
|
+
const prefix = progressEntryPrefix(entry.kind, entry.tool);
|
|
835
|
+
const text = entry.text.replace(/\n/g, "\n ");
|
|
836
|
+
lines.push(`\n${index + 1}. ${prefix}${text}`);
|
|
837
|
+
}
|
|
838
|
+
return lines.join("\n");
|
|
839
|
+
}
|
|
840
|
+
function progressEntryPrefix(kind, tool) {
|
|
841
|
+
if (kind === "thinking")
|
|
842
|
+
return "💭 ";
|
|
843
|
+
if (kind === "tool_use")
|
|
844
|
+
return tool ? `🔧 **工具: ${tool}**\n ` : "🔧 ";
|
|
845
|
+
if (kind === "tool_result")
|
|
846
|
+
return tool ? `🧾 **结果: ${tool}**\n ` : "🧾 ";
|
|
847
|
+
if (kind === "error")
|
|
848
|
+
return "❌ ";
|
|
849
|
+
return "";
|
|
850
|
+
}
|
|
655
851
|
function actionsBlock(buttons, sessionKey, replyCtx) {
|
|
656
852
|
if (buttons.length === 0)
|
|
657
853
|
return undefined;
|
|
@@ -666,22 +862,13 @@ function normalizeBridgeButtons(buttons) {
|
|
|
666
862
|
if (!Array.isArray(buttons))
|
|
667
863
|
return [];
|
|
668
864
|
const actions = [];
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
for (const button of
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const value = stringValue(button.data) ?? stringValue(button.value);
|
|
677
|
-
if (!label || !value)
|
|
678
|
-
continue;
|
|
679
|
-
actions.push({
|
|
680
|
-
id: value,
|
|
681
|
-
label,
|
|
682
|
-
value,
|
|
683
|
-
style: actionStyle(value, stringValue(button.btn_type)),
|
|
684
|
-
});
|
|
865
|
+
const rows = buttons.some(Array.isArray) ? buttons : [buttons];
|
|
866
|
+
for (const row of rows) {
|
|
867
|
+
const buttonRow = Array.isArray(row) ? row : [row];
|
|
868
|
+
for (const button of buttonRow) {
|
|
869
|
+
const action = normalizeBridgeAction(button);
|
|
870
|
+
if (action)
|
|
871
|
+
actions.push(action);
|
|
685
872
|
}
|
|
686
873
|
}
|
|
687
874
|
return actions;
|
|
@@ -768,26 +955,90 @@ function normalizeBridgeCard(card, sessionKey, replyCtx) {
|
|
|
768
955
|
}
|
|
769
956
|
return { text: textParts.join("\n\n"), blocks };
|
|
770
957
|
}
|
|
958
|
+
function isInteractiveAction(action) {
|
|
959
|
+
const normalized = action.toLowerCase();
|
|
960
|
+
return (normalized.startsWith("perm:")
|
|
961
|
+
|| normalized.startsWith("askq:")
|
|
962
|
+
|| normalized === "allow"
|
|
963
|
+
|| normalized === "deny"
|
|
964
|
+
|| normalized === "allow all");
|
|
965
|
+
}
|
|
771
966
|
function normalizeCardActionButtons(buttons) {
|
|
772
967
|
if (!Array.isArray(buttons))
|
|
773
968
|
return [];
|
|
774
969
|
const actions = [];
|
|
775
970
|
for (const button of buttons) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
const value = stringValue(button.value) ?? stringValue(button.data);
|
|
780
|
-
if (!label || !value)
|
|
781
|
-
continue;
|
|
782
|
-
actions.push({
|
|
783
|
-
id: value,
|
|
784
|
-
label,
|
|
785
|
-
value,
|
|
786
|
-
style: actionStyle(value, stringValue(button.btn_type)),
|
|
787
|
-
});
|
|
971
|
+
const action = normalizeBridgeAction(button);
|
|
972
|
+
if (action)
|
|
973
|
+
actions.push(action);
|
|
788
974
|
}
|
|
789
975
|
return actions;
|
|
790
976
|
}
|
|
977
|
+
function normalizeBridgeAction(button) {
|
|
978
|
+
if (!isRecord(button))
|
|
979
|
+
return undefined;
|
|
980
|
+
const label = stringValue(button.text)
|
|
981
|
+
?? stringValue(button.label)
|
|
982
|
+
?? stringValue(button.title);
|
|
983
|
+
const value = stringValue(button.data)
|
|
984
|
+
?? stringValue(button.value)
|
|
985
|
+
?? stringValue(button.id)
|
|
986
|
+
?? inferActionValue(label);
|
|
987
|
+
if (!label || !value)
|
|
988
|
+
return undefined;
|
|
989
|
+
return {
|
|
990
|
+
id: value,
|
|
991
|
+
label,
|
|
992
|
+
value,
|
|
993
|
+
style: actionStyle(value, stringValue(button.btn_type) ?? stringValue(button.style)),
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function permissionHintActions(content, sessionKey, replyCtx) {
|
|
997
|
+
if (!isPermissionHint(content))
|
|
998
|
+
return undefined;
|
|
999
|
+
return actionsBlock([
|
|
1000
|
+
{ id: "perm:allow", label: "允许", value: "perm:allow", style: "primary" },
|
|
1001
|
+
{ id: "perm:deny", label: "拒绝", value: "perm:deny", style: "danger" },
|
|
1002
|
+
{ id: "perm:allow_all", label: "允许所有", value: "perm:allow_all", style: "default" },
|
|
1003
|
+
], sessionKey, replyCtx);
|
|
1004
|
+
}
|
|
1005
|
+
function isPermissionHint(content) {
|
|
1006
|
+
const normalized = content.toLowerCase();
|
|
1007
|
+
return (normalized.includes("等待权限响应") || normalized.includes("permission"))
|
|
1008
|
+
&& (normalized.includes("允许所有") || normalized.includes("allow all"))
|
|
1009
|
+
&& (normalized.includes("拒绝") || normalized.includes("deny"));
|
|
1010
|
+
}
|
|
1011
|
+
function isIntermediateReply(content) {
|
|
1012
|
+
const normalized = content.toLowerCase().trim();
|
|
1013
|
+
if (!normalized)
|
|
1014
|
+
return false;
|
|
1015
|
+
if (isPermissionHint(content))
|
|
1016
|
+
return true;
|
|
1017
|
+
return (normalized.includes("继续执行")
|
|
1018
|
+
|| normalized.includes("继续处理中")
|
|
1019
|
+
|| normalized.includes("continuing")
|
|
1020
|
+
|| normalized.includes("continue execution")
|
|
1021
|
+
|| normalized.includes("permission granted")
|
|
1022
|
+
|| normalized.includes("已允许")
|
|
1023
|
+
|| normalized.includes("已开启自动批准")
|
|
1024
|
+
|| normalized.includes("自动批准")
|
|
1025
|
+
|| normalized.includes("权限请求将自动允许"));
|
|
1026
|
+
}
|
|
1027
|
+
function inferActionValue(label) {
|
|
1028
|
+
if (!label)
|
|
1029
|
+
return undefined;
|
|
1030
|
+
const normalized = label.toLowerCase().replace(/\s+/g, "");
|
|
1031
|
+
if (normalized.includes("允许所有") || normalized.includes("全部允许") || normalized.includes("allowall")) {
|
|
1032
|
+
return "perm:allow_all";
|
|
1033
|
+
}
|
|
1034
|
+
if (normalized.includes("拒绝") || normalized.includes("deny") || normalized.includes("reject")) {
|
|
1035
|
+
return "perm:deny";
|
|
1036
|
+
}
|
|
1037
|
+
if (normalized.includes("允许") || normalized.includes("同意") || normalized.includes("allow") || normalized.includes("approve")) {
|
|
1038
|
+
return "perm:allow";
|
|
1039
|
+
}
|
|
1040
|
+
return undefined;
|
|
1041
|
+
}
|
|
791
1042
|
function actionStyle(value, explicit) {
|
|
792
1043
|
if (explicit === "primary" || explicit === "danger")
|
|
793
1044
|
return explicit;
|