codex-to-im 1.0.43 → 1.0.45
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/daemon.mjs +1001 -429
- package/dist/ui-server.mjs +50 -12
- package/docs/dev-plan.md +653 -0
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -1408,8 +1408,12 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1408
1408
|
botIds = /* @__PURE__ */ new Set();
|
|
1409
1409
|
/** Track last incoming message ID per chat for typing indicator. */
|
|
1410
1410
|
lastIncomingMessageId = /* @__PURE__ */ new Map();
|
|
1411
|
-
/** Track active typing reaction IDs per
|
|
1411
|
+
/** Track active typing reaction IDs per stream key for cleanup. */
|
|
1412
1412
|
typingReactions = /* @__PURE__ */ new Map();
|
|
1413
|
+
/** Track in-flight typing reaction creates so repeated status updates stay idempotent. */
|
|
1414
|
+
typingReactionCreatePromises = /* @__PURE__ */ new Map();
|
|
1415
|
+
/** Track streams that ended before the async reaction create finished. */
|
|
1416
|
+
typingReactionCleanupRequested = /* @__PURE__ */ new Set();
|
|
1413
1417
|
/** Active streaming card state per stream key. */
|
|
1414
1418
|
activeCards = /* @__PURE__ */ new Map();
|
|
1415
1419
|
/** In-flight card creation promises per stream key — prevents duplicate creation. */
|
|
@@ -1515,6 +1519,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1515
1519
|
this.seenMessageIds.clear();
|
|
1516
1520
|
this.lastIncomingMessageId.clear();
|
|
1517
1521
|
this.typingReactions.clear();
|
|
1522
|
+
this.typingReactionCreatePromises.clear();
|
|
1523
|
+
this.typingReactionCleanupRequested.clear();
|
|
1518
1524
|
console.log("[feishu-adapter] Stopped");
|
|
1519
1525
|
}
|
|
1520
1526
|
isRunning() {
|
|
@@ -1531,25 +1537,38 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1531
1537
|
*/
|
|
1532
1538
|
onMessageStart(chatId, streamKey) {
|
|
1533
1539
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
1540
|
+
const reactionKey = this.resolveStreamKey(chatId, streamKey);
|
|
1534
1541
|
if (messageId && this.isStreamingEnabled()) {
|
|
1535
1542
|
this.createStreamingCard(chatId, messageId, streamKey).catch(() => {
|
|
1536
1543
|
});
|
|
1537
1544
|
}
|
|
1538
1545
|
if (!messageId || !this.restClient) return;
|
|
1539
|
-
this.
|
|
1546
|
+
if (this.typingReactions.has(reactionKey) || this.typingReactionCreatePromises.has(reactionKey)) {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const createPromise = this.restClient.im.messageReaction.create({
|
|
1540
1550
|
path: { message_id: messageId },
|
|
1541
1551
|
data: { reaction_type: { emoji_type: TYPING_EMOJI } }
|
|
1542
1552
|
}).then((res) => {
|
|
1543
1553
|
const reactionId = res?.data?.reaction_id;
|
|
1544
1554
|
if (reactionId) {
|
|
1545
|
-
this.typingReactions.set(
|
|
1555
|
+
this.typingReactions.set(reactionKey, { messageId, reactionId });
|
|
1556
|
+
if (this.typingReactionCleanupRequested.delete(reactionKey)) {
|
|
1557
|
+
this.removeTypingReaction(reactionKey);
|
|
1558
|
+
}
|
|
1546
1559
|
}
|
|
1547
1560
|
}).catch((err) => {
|
|
1548
1561
|
const code2 = err?.code;
|
|
1549
1562
|
if (code2 !== 99991400 && code2 !== 99991403) {
|
|
1550
1563
|
console.warn("[feishu-adapter] Typing indicator failed:", err instanceof Error ? err.message : err);
|
|
1551
1564
|
}
|
|
1565
|
+
}).finally(() => {
|
|
1566
|
+
this.typingReactionCreatePromises.delete(reactionKey);
|
|
1567
|
+
if (!this.typingReactions.has(reactionKey)) {
|
|
1568
|
+
this.typingReactionCleanupRequested.delete(reactionKey);
|
|
1569
|
+
}
|
|
1552
1570
|
});
|
|
1571
|
+
this.typingReactionCreatePromises.set(reactionKey, createPromise);
|
|
1553
1572
|
}
|
|
1554
1573
|
/**
|
|
1555
1574
|
* Remove the "Typing" emoji reaction and clean up card state.
|
|
@@ -1557,12 +1576,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1557
1576
|
*/
|
|
1558
1577
|
onMessageEnd(chatId, streamKey) {
|
|
1559
1578
|
this.cleanupCard(chatId, streamKey);
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1579
|
+
const reactionKey = this.resolveStreamKey(chatId, streamKey);
|
|
1580
|
+
if (this.typingReactionCreatePromises.has(reactionKey) && !this.typingReactions.has(reactionKey)) {
|
|
1581
|
+
this.typingReactionCleanupRequested.add(reactionKey);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
this.removeTypingReaction(reactionKey);
|
|
1585
|
+
}
|
|
1586
|
+
removeTypingReaction(reactionKey) {
|
|
1587
|
+
const reaction = this.typingReactions.get(reactionKey);
|
|
1588
|
+
if (!reaction || !this.restClient) return;
|
|
1589
|
+
this.typingReactions.delete(reactionKey);
|
|
1564
1590
|
this.restClient.im.messageReaction.delete({
|
|
1565
|
-
path: { message_id: messageId, reaction_id: reactionId }
|
|
1591
|
+
path: { message_id: reaction.messageId, reaction_id: reaction.reactionId }
|
|
1566
1592
|
}).catch(() => {
|
|
1567
1593
|
});
|
|
1568
1594
|
}
|
|
@@ -10447,6 +10473,18 @@ function readDesktopSessionEventStream(threadId) {
|
|
|
10447
10473
|
return readDesktopSessionEventStreamByFilePath(session.filePath);
|
|
10448
10474
|
}
|
|
10449
10475
|
|
|
10476
|
+
// src/lib/bridge/turns/turn-classifier.ts
|
|
10477
|
+
function normalizeThreadId(value) {
|
|
10478
|
+
const normalized = value?.trim();
|
|
10479
|
+
return normalized || void 0;
|
|
10480
|
+
}
|
|
10481
|
+
function getCodexThreadId(session, binding) {
|
|
10482
|
+
return normalizeThreadId(session?.codex_thread_id) || normalizeThreadId(binding?.sdkSessionId) || normalizeThreadId(session?.sdk_session_id);
|
|
10483
|
+
}
|
|
10484
|
+
function getExplicitDesktopThreadId(session) {
|
|
10485
|
+
return normalizeThreadId(session?.desktop_thread_id) || (session?.thread_origin === "desktop" ? normalizeThreadId(session.sdk_session_id) : void 0);
|
|
10486
|
+
}
|
|
10487
|
+
|
|
10450
10488
|
// src/session-bindings.ts
|
|
10451
10489
|
function asChannelProvider(value) {
|
|
10452
10490
|
return value === "feishu" || value === "weixin" ? value : void 0;
|
|
@@ -10492,15 +10530,31 @@ function assertBindingTargetAvailable(store, current, opts) {
|
|
|
10492
10530
|
function getSessionMode(store, session) {
|
|
10493
10531
|
return session.preferred_mode || store.getSetting("bridge_default_mode") || "code";
|
|
10494
10532
|
}
|
|
10533
|
+
function getBindingResumeThreadId(session) {
|
|
10534
|
+
return getCodexThreadId(session) || "";
|
|
10535
|
+
}
|
|
10536
|
+
function markSessionAsDesktopBacked(store, sessionId, desktopThreadId) {
|
|
10537
|
+
store.updateSdkSessionId(sessionId, desktopThreadId);
|
|
10538
|
+
store.updateSession(sessionId, {
|
|
10539
|
+
sdk_session_id: desktopThreadId,
|
|
10540
|
+
codex_thread_id: desktopThreadId,
|
|
10541
|
+
desktop_thread_id: desktopThreadId,
|
|
10542
|
+
thread_origin: "desktop"
|
|
10543
|
+
});
|
|
10544
|
+
}
|
|
10495
10545
|
function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
10496
10546
|
const session = store.getSession(sessionId);
|
|
10497
10547
|
if (!session) return null;
|
|
10498
10548
|
assertBindingTargetAvailable(
|
|
10499
10549
|
store,
|
|
10500
10550
|
{ channelType, chatId },
|
|
10501
|
-
{
|
|
10551
|
+
{
|
|
10552
|
+
sessionId: session.id,
|
|
10553
|
+
sdkSessionId: getCodexThreadId(session) || getExplicitDesktopThreadId(session)
|
|
10554
|
+
}
|
|
10502
10555
|
);
|
|
10503
10556
|
const meta = resolveChannelMeta(channelType);
|
|
10557
|
+
const sdkSessionId = getBindingResumeThreadId(session);
|
|
10504
10558
|
return store.upsertChannelBinding({
|
|
10505
10559
|
channelType,
|
|
10506
10560
|
channelProvider: meta.provider,
|
|
@@ -10509,7 +10563,7 @@ function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
|
10509
10563
|
chatUserId: chatMeta?.chatUserId,
|
|
10510
10564
|
chatDisplayName: chatMeta?.chatDisplayName,
|
|
10511
10565
|
codepilotSessionId: session.id,
|
|
10512
|
-
sdkSessionId
|
|
10566
|
+
sdkSessionId,
|
|
10513
10567
|
workingDirectory: session.working_directory,
|
|
10514
10568
|
model: session.model,
|
|
10515
10569
|
mode: getSessionMode(store, session)
|
|
@@ -10524,6 +10578,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
10524
10578
|
const meta = resolveChannelMeta(channelType);
|
|
10525
10579
|
const existing = store.findSessionBySdkSessionId(sdkSessionId);
|
|
10526
10580
|
if (existing) {
|
|
10581
|
+
markSessionAsDesktopBacked(store, existing.id, sdkSessionId);
|
|
10527
10582
|
return store.upsertChannelBinding({
|
|
10528
10583
|
channelType,
|
|
10529
10584
|
channelProvider: meta.provider,
|
|
@@ -10548,7 +10603,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
10548
10603
|
workingDirectory,
|
|
10549
10604
|
"code"
|
|
10550
10605
|
);
|
|
10551
|
-
store
|
|
10606
|
+
markSessionAsDesktopBacked(store, session.id, sdkSessionId);
|
|
10552
10607
|
return store.upsertChannelBinding({
|
|
10553
10608
|
channelType,
|
|
10554
10609
|
channelProvider: meta.provider,
|
|
@@ -10921,15 +10976,17 @@ async function deliver(adapter, message, opts) {
|
|
|
10921
10976
|
} catch {
|
|
10922
10977
|
}
|
|
10923
10978
|
}
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
|
|
10979
|
+
if (opts?.audit !== false) {
|
|
10980
|
+
try {
|
|
10981
|
+
store.insertAuditLog({
|
|
10982
|
+
channelType: adapter.channelType,
|
|
10983
|
+
chatId: message.address.chatId,
|
|
10984
|
+
direction: "outbound",
|
|
10985
|
+
messageId: lastMessageId || "",
|
|
10986
|
+
summary: message.text.slice(0, 200)
|
|
10987
|
+
});
|
|
10988
|
+
} catch {
|
|
10989
|
+
}
|
|
10933
10990
|
}
|
|
10934
10991
|
return { ok: true, messageId: lastMessageId };
|
|
10935
10992
|
}
|
|
@@ -11653,6 +11710,8 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
|
11653
11710
|
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
11654
11711
|
startedAt: safeTimestamp,
|
|
11655
11712
|
lastActivityAt: safeTimestamp,
|
|
11713
|
+
lastContentResponseAt: null,
|
|
11714
|
+
lastResponseAt: null,
|
|
11656
11715
|
lastStatusText: null,
|
|
11657
11716
|
lastStatusAt: 0,
|
|
11658
11717
|
statusNote: null,
|
|
@@ -11699,8 +11758,14 @@ function ensureMirrorTurnState(subscription, record) {
|
|
|
11699
11758
|
}
|
|
11700
11759
|
return subscription.pendingTurn;
|
|
11701
11760
|
}
|
|
11702
|
-
function
|
|
11703
|
-
turnState.
|
|
11761
|
+
function markMirrorActivity(turnState, timestamp) {
|
|
11762
|
+
turnState.lastActivityAt = timestamp || nowIso2();
|
|
11763
|
+
}
|
|
11764
|
+
function markMirrorContentResponse(turnState, timestamp) {
|
|
11765
|
+
const responseAt = timestamp || nowIso2();
|
|
11766
|
+
markMirrorActivity(turnState, responseAt);
|
|
11767
|
+
turnState.lastContentResponseAt = responseAt;
|
|
11768
|
+
turnState.lastResponseAt = responseAt;
|
|
11704
11769
|
}
|
|
11705
11770
|
function finalizeMirrorTurn(subscription, signature, timestamp, status, preferredText) {
|
|
11706
11771
|
const pendingTurn = subscription.pendingTurn;
|
|
@@ -11773,7 +11838,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11773
11838
|
if (text2) {
|
|
11774
11839
|
pendingTurn.lastAssistantText = text2;
|
|
11775
11840
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11776
|
-
|
|
11841
|
+
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11777
11842
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
11778
11843
|
}
|
|
11779
11844
|
} else if (record.role === "commentary") {
|
|
@@ -11781,7 +11846,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11781
11846
|
if (text2) {
|
|
11782
11847
|
pendingTurn.lastCommentaryText = text2;
|
|
11783
11848
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11784
|
-
|
|
11849
|
+
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11785
11850
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
11786
11851
|
}
|
|
11787
11852
|
}
|
|
@@ -11792,14 +11857,14 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11792
11857
|
const text2 = record.content.trim();
|
|
11793
11858
|
if (!text2) continue;
|
|
11794
11859
|
pendingTurn.statusNote = text2;
|
|
11795
|
-
|
|
11860
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11796
11861
|
hooks.onStatusProgress?.(subscription, pendingTurn);
|
|
11797
11862
|
continue;
|
|
11798
11863
|
}
|
|
11799
11864
|
if (record.type === "plan_update") {
|
|
11800
11865
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
11801
11866
|
pendingTurn.taskItems = record.tasks || [];
|
|
11802
|
-
|
|
11867
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11803
11868
|
hooks.onTaskProgress?.(subscription, pendingTurn);
|
|
11804
11869
|
continue;
|
|
11805
11870
|
}
|
|
@@ -11812,7 +11877,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11812
11877
|
name: toolName,
|
|
11813
11878
|
status: "running"
|
|
11814
11879
|
});
|
|
11815
|
-
|
|
11880
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11816
11881
|
hooks.onToolProgress?.(subscription, pendingTurn);
|
|
11817
11882
|
continue;
|
|
11818
11883
|
}
|
|
@@ -11825,7 +11890,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11825
11890
|
name: existing?.name || record.toolName || "tool",
|
|
11826
11891
|
status: record.isError ? "error" : "complete"
|
|
11827
11892
|
});
|
|
11828
|
-
|
|
11893
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11829
11894
|
hooks.onToolProgress?.(subscription, pendingTurn);
|
|
11830
11895
|
continue;
|
|
11831
11896
|
}
|
|
@@ -11994,9 +12059,6 @@ function abortMirrorSuppression(store, sessionId, config2, suppressionId, nowMs
|
|
|
11994
12059
|
}
|
|
11995
12060
|
target.until = nowMs + config2.suppressionWindowMs;
|
|
11996
12061
|
}
|
|
11997
|
-
function isMirrorSuppressed(store, sessionId, nowMs = Date.now()) {
|
|
11998
|
-
return getMirrorSuppressionStates(store, sessionId, nowMs).length > 0;
|
|
11999
|
-
}
|
|
12000
12062
|
function filterSuppressedMirrorRecords(store, sessionId, records, config2, nowMs = Date.now()) {
|
|
12001
12063
|
const suppressions = getMirrorSuppressionStates(store, sessionId, nowMs);
|
|
12002
12064
|
const ignoredTurnIds = cleanupIgnoredMirrorTurns(store, sessionId, nowMs);
|
|
@@ -12633,7 +12695,7 @@ function supportsOutboundArtifacts(provider) {
|
|
|
12633
12695
|
}
|
|
12634
12696
|
|
|
12635
12697
|
// src/lib/bridge/feedback-delivery.ts
|
|
12636
|
-
async function deliverTextResponse(adapter, address, responseText, sessionId, replyToMessageId) {
|
|
12698
|
+
async function deliverTextResponse(adapter, address, responseText, sessionId, replyToMessageId, options) {
|
|
12637
12699
|
if (!responseText.trim()) return { ok: true };
|
|
12638
12700
|
const parseMode = getFeedbackParseMode(adapter.channelType);
|
|
12639
12701
|
const renderedText = renderFeedbackText(responseText, parseMode);
|
|
@@ -12643,14 +12705,14 @@ async function deliverTextResponse(adapter, address, responseText, sessionId, re
|
|
|
12643
12705
|
text: responseText,
|
|
12644
12706
|
parseMode: "Markdown",
|
|
12645
12707
|
replyToMessageId
|
|
12646
|
-
}, { sessionId });
|
|
12708
|
+
}, { sessionId, audit: options?.audit });
|
|
12647
12709
|
}
|
|
12648
12710
|
return deliver(adapter, {
|
|
12649
12711
|
address,
|
|
12650
12712
|
text: parseMode === "Markdown" ? responseText : renderedText,
|
|
12651
12713
|
parseMode,
|
|
12652
12714
|
replyToMessageId
|
|
12653
|
-
}, { sessionId });
|
|
12715
|
+
}, { sessionId, audit: options?.audit });
|
|
12654
12716
|
}
|
|
12655
12717
|
async function deliverBridgeNotice(adapter, address, text2, options) {
|
|
12656
12718
|
return deliverTextResponse(
|
|
@@ -12658,7 +12720,8 @@ async function deliverBridgeNotice(adapter, address, text2, options) {
|
|
|
12658
12720
|
address,
|
|
12659
12721
|
text2,
|
|
12660
12722
|
options?.sessionId,
|
|
12661
|
-
options?.replyToMessageId
|
|
12723
|
+
options?.replyToMessageId,
|
|
12724
|
+
{ audit: options?.audit }
|
|
12662
12725
|
);
|
|
12663
12726
|
}
|
|
12664
12727
|
async function deliverResponse(adapter, address, responseText, sessionId, replyToMessageId, attachments = []) {
|
|
@@ -12774,6 +12837,7 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
12774
12837
|
}
|
|
12775
12838
|
let response = "";
|
|
12776
12839
|
let responseParseMode = getFeedbackParseMode(adapter.channelType);
|
|
12840
|
+
let auditResponse = true;
|
|
12777
12841
|
const currentBinding = store.getChannelBinding(msg.address.channelType, msg.address.chatId);
|
|
12778
12842
|
switch (command) {
|
|
12779
12843
|
case "/start":
|
|
@@ -13106,6 +13170,7 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13106
13170
|
break;
|
|
13107
13171
|
}
|
|
13108
13172
|
if (!args) {
|
|
13173
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13109
13174
|
const currentModel = resolveDisplayedModel(
|
|
13110
13175
|
binding,
|
|
13111
13176
|
session,
|
|
@@ -13117,14 +13182,14 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13117
13182
|
[["\u6A21\u578B", formatDisplayedModel(currentModel)]],
|
|
13118
13183
|
[
|
|
13119
13184
|
getAvailableModelChoicesText(),
|
|
13120
|
-
|
|
13185
|
+
desktopThreadId ? "\u5F53\u524D\u662F\u5171\u4EAB\u684C\u9762\u7EBF\u7A0B\uFF0C\u53EA\u652F\u6301\u67E5\u770B\u6A21\u578B\uFF1B\u5982\u9700\u5207\u6362\uFF0C\u8BF7\u5148\u7528 `/new` \u65B0\u5EFA\u4E00\u4E2A IM \u4F1A\u8BDD\u7EBF\u7A0B\u3002" : "\u53D1\u9001 `/model gpt-5.4` \u53EF\u5207\u6362\uFF1B\u53D1\u9001 `/model default` \u53EF\u56DE\u9000\u5230\u9ED8\u8BA4\u6A21\u578B\u3002",
|
|
13121
13186
|
"\u6A21\u578B\u5207\u6362\u53EA\u5F71\u54CD\u540E\u7EED\u4ECE IM \u53D1\u8D77\u7684 Codex CLI \u8BF7\u6C42\u3002"
|
|
13122
13187
|
],
|
|
13123
13188
|
responseParseMode === "Markdown"
|
|
13124
13189
|
);
|
|
13125
13190
|
break;
|
|
13126
13191
|
}
|
|
13127
|
-
if (
|
|
13192
|
+
if (getExplicitDesktopThreadId(session)) {
|
|
13128
13193
|
response = "\u5F53\u524D\u662F\u5171\u4EAB\u684C\u9762\u7EBF\u7A0B\uFF0C\u4E0D\u652F\u6301\u76F4\u63A5\u5207\u6362\u6A21\u578B\u3002\u8BF7\u5148\u7528 `/new` \u65B0\u5EFA\u4E00\u4E2A\u7EBF\u7A0B\uFF0C\u518D\u6267\u884C `/model ...`\u3002";
|
|
13129
13194
|
break;
|
|
13130
13195
|
}
|
|
@@ -13175,9 +13240,32 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13175
13240
|
break;
|
|
13176
13241
|
}
|
|
13177
13242
|
case "/status": {
|
|
13178
|
-
|
|
13243
|
+
auditResponse = false;
|
|
13244
|
+
const binding = currentBinding;
|
|
13245
|
+
if (!binding) {
|
|
13246
|
+
response = buildCommandFields(
|
|
13247
|
+
"\u5F53\u524D\u4F1A\u8BDD",
|
|
13248
|
+
[],
|
|
13249
|
+
["\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u4F1A\u8BDD\u3002\u53EF\u5148\u53D1\u9001 `/t` \u67E5\u770B\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\uFF0C\u518D\u7528 `/t 1` \u63A5\u7BA1\uFF1B\u6216\u53D1\u9001 `/new proj1` / `/new \u7EDD\u5BF9\u8DEF\u5F84` \u521B\u5EFA\u9879\u76EE\u4F1A\u8BDD\u3002"],
|
|
13250
|
+
responseParseMode === "Markdown"
|
|
13251
|
+
);
|
|
13252
|
+
break;
|
|
13253
|
+
}
|
|
13179
13254
|
const session = store.getSession(binding.codepilotSessionId);
|
|
13180
|
-
|
|
13255
|
+
if (!session) {
|
|
13256
|
+
response = buildCommandFields(
|
|
13257
|
+
"\u5F53\u524D\u4F1A\u8BDD",
|
|
13258
|
+
[
|
|
13259
|
+
["Session", binding.codepilotSessionId],
|
|
13260
|
+
["\u76EE\u5F55", formatCommandPath(binding.workingDirectory)]
|
|
13261
|
+
],
|
|
13262
|
+
["\u5F53\u524D\u804A\u5929\u7ED1\u5B9A\u7684\u4F1A\u8BDD\u5DF2\u7ECF\u4E0D\u5B58\u5728\u3002\u53EF\u7528 `/t` \u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\uFF0C\u6216\u7528 `/new proj1` / `/new \u7EDD\u5BF9\u8DEF\u5F84` \u521B\u5EFA\u65B0\u4F1A\u8BDD\u3002"],
|
|
13263
|
+
responseParseMode === "Markdown"
|
|
13264
|
+
);
|
|
13265
|
+
break;
|
|
13266
|
+
}
|
|
13267
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13268
|
+
const threadTitle = getDesktopThreadTitle(desktopThreadId);
|
|
13181
13269
|
const sandboxMode = resolveEffectiveSandboxMode();
|
|
13182
13270
|
const reasoningEffort = resolveEffectiveReasoningEffort(session);
|
|
13183
13271
|
const currentModel = resolveDisplayedModel(
|
|
@@ -13201,27 +13289,32 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13201
13289
|
["\u601D\u8003\u7EA7\u522B", formatReasoningEffort(reasoningEffort)]
|
|
13202
13290
|
],
|
|
13203
13291
|
[
|
|
13204
|
-
|
|
13292
|
+
desktopThreadId ? "\u5F53\u524D\u804A\u5929\u5DF2\u7ED1\u5B9A\u5230\u4E00\u6761\u5171\u4EAB\u4F1A\u8BDD\uFF0C\u76F4\u63A5\u53D1\u9001\u6D88\u606F\u5373\u53EF\u7EE7\u7EED\u3002" : session?.session_type === "draft" ? "\u5F53\u524D\u804A\u5929\u6B63\u5728\u4F7F\u7528\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\uFF08\u7B49\u540C `/t 0`\uFF09\u3002\u53EF\u76F4\u63A5\u53D1\u9001\u6D88\u606F\uFF0C\u6216\u7528 `/t` / `/new proj1` / `/new \u7EDD\u5BF9\u8DEF\u5F84` \u5207\u6362\u5230\u6B63\u5F0F\u4F1A\u8BDD\u3002" : "\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u684C\u9762\u4F1A\u8BDD\u3002\u53EF\u5148\u53D1\u9001 `/t`\uFF0C\u518D\u7528 `/t 1` \u63A5\u7BA1\u3002"
|
|
13205
13293
|
],
|
|
13206
13294
|
responseParseMode === "Markdown"
|
|
13207
13295
|
);
|
|
13208
13296
|
break;
|
|
13209
13297
|
}
|
|
13210
13298
|
case "/health": {
|
|
13299
|
+
auditResponse = false;
|
|
13211
13300
|
if (args === "all") {
|
|
13212
13301
|
const diagnoses = await deps.diagnoseAllActiveSessions();
|
|
13213
13302
|
response = diagnoses.length > 0 ? buildHealthListResponse(diagnoses, responseParseMode === "Markdown") : "\u5F53\u524D\u6CA1\u6709\u68C0\u6D4B\u5230\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u3002";
|
|
13214
13303
|
break;
|
|
13215
13304
|
}
|
|
13216
|
-
const
|
|
13217
|
-
const targetSessionId =
|
|
13305
|
+
const explicitTargetSessionId = args.trim();
|
|
13306
|
+
const targetSessionId = explicitTargetSessionId || currentBinding?.codepilotSessionId;
|
|
13307
|
+
if (!targetSessionId) {
|
|
13308
|
+
response = "\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u4F1A\u8BDD\u3002\u5148\u53D1\u9001\u6D88\u606F\u521B\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u5148\u7528 `/t 1` \u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\u3002";
|
|
13309
|
+
break;
|
|
13310
|
+
}
|
|
13218
13311
|
const diagnosis = await deps.diagnoseSessionHealth(targetSessionId);
|
|
13219
13312
|
if (!diagnosis) {
|
|
13220
13313
|
response = `\u6CA1\u6709\u627E\u5230\u4F1A\u8BDD ${targetSessionId}\u3002`;
|
|
13221
13314
|
break;
|
|
13222
13315
|
}
|
|
13223
13316
|
response = buildHealthCommandResponse(
|
|
13224
|
-
"\u5F53\u524D\u4F1A\u8BDD\u5065\u5EB7\u68C0\u67E5",
|
|
13317
|
+
explicitTargetSessionId ? "\u6307\u5B9A\u4F1A\u8BDD\u5065\u5EB7\u68C0\u67E5" : "\u5F53\u524D\u4F1A\u8BDD\u5065\u5EB7\u68C0\u67E5",
|
|
13225
13318
|
diagnosis,
|
|
13226
13319
|
responseParseMode === "Markdown"
|
|
13227
13320
|
);
|
|
@@ -13233,15 +13326,16 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13233
13326
|
break;
|
|
13234
13327
|
}
|
|
13235
13328
|
const limit = getHistoryMessageLimit();
|
|
13236
|
-
const
|
|
13329
|
+
const session = store.getSession(currentBinding.codepilotSessionId);
|
|
13330
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13331
|
+
const desktopMessages = desktopThreadId ? readDesktopSessionMessages(desktopThreadId, limit) : [];
|
|
13237
13332
|
const { messages: storedMessages } = store.getMessages(currentBinding.codepilotSessionId, { limit });
|
|
13238
13333
|
const messages = desktopMessages.length > 0 ? desktopMessages : storedMessages;
|
|
13239
13334
|
if (messages.length === 0) {
|
|
13240
13335
|
response = "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u5386\u53F2\u6D88\u606F\u3002";
|
|
13241
13336
|
break;
|
|
13242
13337
|
}
|
|
13243
|
-
const threadTitle = getDesktopThreadTitle(
|
|
13244
|
-
const session = store.getSession(currentBinding.codepilotSessionId);
|
|
13338
|
+
const threadTitle = getDesktopThreadTitle(desktopThreadId);
|
|
13245
13339
|
const header = buildCommandFields(
|
|
13246
13340
|
"\u6700\u8FD1\u5BF9\u8BDD\uFF08raw\uFF09",
|
|
13247
13341
|
[
|
|
@@ -13373,7 +13467,8 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
13373
13467
|
}
|
|
13374
13468
|
if (response) {
|
|
13375
13469
|
await deliverBridgeNotice(adapter, msg.address, response, {
|
|
13376
|
-
replyToMessageId: msg.messageId
|
|
13470
|
+
replyToMessageId: msg.messageId,
|
|
13471
|
+
audit: auditResponse
|
|
13377
13472
|
});
|
|
13378
13473
|
}
|
|
13379
13474
|
}
|
|
@@ -13385,6 +13480,39 @@ import path13 from "node:path";
|
|
|
13385
13480
|
import fs8 from "fs";
|
|
13386
13481
|
import path12 from "path";
|
|
13387
13482
|
import crypto5 from "crypto";
|
|
13483
|
+
|
|
13484
|
+
// src/lib/bridge/turns/final-response-artifacts.ts
|
|
13485
|
+
function attachmentKey(attachment) {
|
|
13486
|
+
return [
|
|
13487
|
+
attachment.kind,
|
|
13488
|
+
attachment.path,
|
|
13489
|
+
attachment.caption || "",
|
|
13490
|
+
attachment.name || ""
|
|
13491
|
+
].join("\0");
|
|
13492
|
+
}
|
|
13493
|
+
function dedupeOutboundAttachments(attachments) {
|
|
13494
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13495
|
+
const deduped = [];
|
|
13496
|
+
for (const attachment of attachments) {
|
|
13497
|
+
const key = attachmentKey(attachment);
|
|
13498
|
+
if (seen.has(key)) continue;
|
|
13499
|
+
seen.add(key);
|
|
13500
|
+
deduped.push(attachment);
|
|
13501
|
+
}
|
|
13502
|
+
return deduped;
|
|
13503
|
+
}
|
|
13504
|
+
function collectFinalResponseArtifacts(text2, attachments = []) {
|
|
13505
|
+
const parsed = parseOutboundArtifacts(text2 || "");
|
|
13506
|
+
return {
|
|
13507
|
+
text: parsed.cleanText,
|
|
13508
|
+
attachments: dedupeOutboundAttachments([
|
|
13509
|
+
...attachments,
|
|
13510
|
+
...parsed.attachments
|
|
13511
|
+
])
|
|
13512
|
+
};
|
|
13513
|
+
}
|
|
13514
|
+
|
|
13515
|
+
// src/lib/bridge/conversation-engine.ts
|
|
13388
13516
|
init_runtime_options();
|
|
13389
13517
|
|
|
13390
13518
|
// src/lib/bridge/sse-stream-decoder.ts
|
|
@@ -13763,8 +13891,8 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13763
13891
|
if (contentBlocks.length > 0) {
|
|
13764
13892
|
for (const block2 of contentBlocks) {
|
|
13765
13893
|
if (block2.type !== "text") continue;
|
|
13766
|
-
const parsed =
|
|
13767
|
-
block2.text = parsed.
|
|
13894
|
+
const parsed = collectFinalResponseArtifacts(block2.text);
|
|
13895
|
+
block2.text = parsed.text;
|
|
13768
13896
|
outboundAttachments.push(...parsed.attachments);
|
|
13769
13897
|
}
|
|
13770
13898
|
const hasToolBlocks = contentBlocks.some(
|
|
@@ -13778,7 +13906,7 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13778
13906
|
const responseText = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
13779
13907
|
return {
|
|
13780
13908
|
responseText,
|
|
13781
|
-
outboundAttachments,
|
|
13909
|
+
outboundAttachments: dedupeOutboundAttachments(outboundAttachments),
|
|
13782
13910
|
tokenUsage,
|
|
13783
13911
|
hasError,
|
|
13784
13912
|
errorMessage,
|
|
@@ -13811,6 +13939,42 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13811
13939
|
}
|
|
13812
13940
|
}
|
|
13813
13941
|
|
|
13942
|
+
// src/lib/bridge/turns/response-assembler.ts
|
|
13943
|
+
function assembleFinalResponse(source, input) {
|
|
13944
|
+
const parsed = collectFinalResponseArtifacts(input.text, input.attachments);
|
|
13945
|
+
return {
|
|
13946
|
+
text: parsed.text,
|
|
13947
|
+
attachments: parsed.attachments,
|
|
13948
|
+
hasError: input.hasError,
|
|
13949
|
+
errorMessage: input.errorMessage,
|
|
13950
|
+
source
|
|
13951
|
+
};
|
|
13952
|
+
}
|
|
13953
|
+
function assembleSdkFinalResponse(input) {
|
|
13954
|
+
return assembleFinalResponse("sdk_result", input);
|
|
13955
|
+
}
|
|
13956
|
+
function assembleDesktopFinalResponse(input) {
|
|
13957
|
+
return assembleFinalResponse("desktop_task_complete", input);
|
|
13958
|
+
}
|
|
13959
|
+
function hasFinalResponsePayload(response) {
|
|
13960
|
+
return Boolean(response.text || response.attachments.length > 0);
|
|
13961
|
+
}
|
|
13962
|
+
function mergeFinalResponses(primary, fallback) {
|
|
13963
|
+
return {
|
|
13964
|
+
text: primary.text || fallback.text,
|
|
13965
|
+
attachments: dedupeOutboundAttachments([
|
|
13966
|
+
...fallback.attachments,
|
|
13967
|
+
...primary.attachments
|
|
13968
|
+
]),
|
|
13969
|
+
hasError: primary.hasError ?? fallback.hasError,
|
|
13970
|
+
errorMessage: primary.errorMessage || fallback.errorMessage,
|
|
13971
|
+
source: primary.source
|
|
13972
|
+
};
|
|
13973
|
+
}
|
|
13974
|
+
function stripFinalOnlyBlocksForStreaming(text2) {
|
|
13975
|
+
return stripOutboundArtifactBlocksForStreaming(text2);
|
|
13976
|
+
}
|
|
13977
|
+
|
|
13814
13978
|
// src/lib/bridge/stream-feedback-controller.ts
|
|
13815
13979
|
function pushStreamFeedbackText(target, text2) {
|
|
13816
13980
|
if (typeof target.adapter.onStreamText !== "function") return;
|
|
@@ -13839,13 +14003,15 @@ function pushStreamFeedbackTasks(target, tasks) {
|
|
|
13839
14003
|
}
|
|
13840
14004
|
}
|
|
13841
14005
|
function pushStreamFeedbackStatus(target, text2) {
|
|
13842
|
-
if (typeof target.adapter.onStreamStatus !== "function") return;
|
|
14006
|
+
if (typeof target.adapter.onStreamStatus !== "function") return false;
|
|
13843
14007
|
target.ensureStarted?.();
|
|
13844
14008
|
const rendered = renderFeedbackTextForChannel(target.channelType, text2);
|
|
13845
|
-
if (!rendered) return;
|
|
14009
|
+
if (!rendered) return false;
|
|
13846
14010
|
try {
|
|
13847
14011
|
target.adapter.onStreamStatus(target.chatId, rendered, target.streamKey);
|
|
14012
|
+
return true;
|
|
13848
14013
|
} catch {
|
|
14014
|
+
return false;
|
|
13849
14015
|
}
|
|
13850
14016
|
}
|
|
13851
14017
|
async function finalizeStreamFeedback(target, status, text2) {
|
|
@@ -13858,6 +14024,118 @@ async function finalizeStreamFeedback(target, status, text2) {
|
|
|
13858
14024
|
}
|
|
13859
14025
|
}
|
|
13860
14026
|
|
|
14027
|
+
// src/lib/bridge/turns/delivery-pipeline.ts
|
|
14028
|
+
function normalizeUnknownSendResult(result) {
|
|
14029
|
+
if (result && typeof result === "object" && "ok" in result) {
|
|
14030
|
+
return result;
|
|
14031
|
+
}
|
|
14032
|
+
return { ok: true };
|
|
14033
|
+
}
|
|
14034
|
+
async function deliverFinalResponse(context, response, options = {}) {
|
|
14035
|
+
let lastResult = { ok: true };
|
|
14036
|
+
const deliverResponse2 = context.deliverResponse || deliverResponse;
|
|
14037
|
+
if (!options.skipText && response.text.trim()) {
|
|
14038
|
+
if (context.deliverText) {
|
|
14039
|
+
lastResult = await context.deliverText(response.text);
|
|
14040
|
+
} else {
|
|
14041
|
+
lastResult = normalizeUnknownSendResult(await deliverResponse2(
|
|
14042
|
+
context.adapter,
|
|
14043
|
+
context.address,
|
|
14044
|
+
response.text,
|
|
14045
|
+
context.sessionId,
|
|
14046
|
+
context.replyToMessageId,
|
|
14047
|
+
[]
|
|
14048
|
+
));
|
|
14049
|
+
}
|
|
14050
|
+
if (!lastResult.ok) return lastResult;
|
|
14051
|
+
}
|
|
14052
|
+
if (response.attachments.length > 0) {
|
|
14053
|
+
lastResult = normalizeUnknownSendResult(await deliverResponse2(
|
|
14054
|
+
context.adapter,
|
|
14055
|
+
context.address,
|
|
14056
|
+
"",
|
|
14057
|
+
context.sessionId,
|
|
14058
|
+
context.replyToMessageId,
|
|
14059
|
+
response.attachments
|
|
14060
|
+
));
|
|
14061
|
+
if (!lastResult.ok) return lastResult;
|
|
14062
|
+
}
|
|
14063
|
+
return lastResult;
|
|
14064
|
+
}
|
|
14065
|
+
async function finalizeStreamingUi(target, status, response) {
|
|
14066
|
+
return finalizeStreamFeedback(target, status, response.text);
|
|
14067
|
+
}
|
|
14068
|
+
|
|
14069
|
+
// src/lib/bridge/turns/stream-state.ts
|
|
14070
|
+
function createStreamState(startedAtMs) {
|
|
14071
|
+
const safeStartedAtMs = Number.isFinite(startedAtMs) ? startedAtMs : Date.now();
|
|
14072
|
+
return {
|
|
14073
|
+
startedAtMs: safeStartedAtMs,
|
|
14074
|
+
lastActivityAtMs: safeStartedAtMs,
|
|
14075
|
+
lastContentResponseAtMs: null,
|
|
14076
|
+
statusNote: null,
|
|
14077
|
+
lastStatusText: null,
|
|
14078
|
+
lastStatusAtMs: 0
|
|
14079
|
+
};
|
|
14080
|
+
}
|
|
14081
|
+
function recordStreamActivity(state, nowMs) {
|
|
14082
|
+
if (!Number.isFinite(nowMs)) return;
|
|
14083
|
+
state.lastActivityAtMs = Math.max(state.lastActivityAtMs, nowMs);
|
|
14084
|
+
}
|
|
14085
|
+
function recordStreamContentResponse(state, nowMs) {
|
|
14086
|
+
if (!Number.isFinite(nowMs)) return;
|
|
14087
|
+
recordStreamActivity(state, nowMs);
|
|
14088
|
+
state.lastContentResponseAtMs = nowMs;
|
|
14089
|
+
}
|
|
14090
|
+
function updateStreamStatusNote(state, note, nowMs) {
|
|
14091
|
+
state.statusNote = (note || "").trim() || null;
|
|
14092
|
+
if (state.statusNote) {
|
|
14093
|
+
recordStreamActivity(state, nowMs);
|
|
14094
|
+
}
|
|
14095
|
+
}
|
|
14096
|
+
function formatRuntimeDuration(ms) {
|
|
14097
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
14098
|
+
const seconds = totalSeconds % 60;
|
|
14099
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
14100
|
+
const minutes = totalMinutes % 60;
|
|
14101
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
14102
|
+
const parts = [];
|
|
14103
|
+
if (hours > 0) parts.push(`${hours}\u5C0F\u65F6`);
|
|
14104
|
+
if (minutes > 0) parts.push(`${minutes}\u5206`);
|
|
14105
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}\u79D2`);
|
|
14106
|
+
return parts.join("");
|
|
14107
|
+
}
|
|
14108
|
+
function formatStreamRuntimeStatus(elapsedMs, lastContentResponseAgeMs, statusNote) {
|
|
14109
|
+
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
14110
|
+
if (typeof lastContentResponseAgeMs === "number" && lastContentResponseAgeMs >= 0) {
|
|
14111
|
+
parts.push(`\u4E0A\u6B21\u54CD\u5E94\u8DDD\u4ECA ${formatRuntimeDuration(lastContentResponseAgeMs)}`);
|
|
14112
|
+
}
|
|
14113
|
+
const runtimeText = parts.join("\uFF0C");
|
|
14114
|
+
const note = (statusNote || "").trim();
|
|
14115
|
+
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
14116
|
+
${runtimeText}` : runtimeText;
|
|
14117
|
+
}
|
|
14118
|
+
function getStreamLastContentResponseAgeMs(state, nowMs, options = {}) {
|
|
14119
|
+
const fallbackToStart = options.fallbackToStart !== false;
|
|
14120
|
+
const base = state.lastContentResponseAtMs ?? (fallbackToStart ? state.startedAtMs : null);
|
|
14121
|
+
if (base == null || !Number.isFinite(base) || !Number.isFinite(nowMs)) return null;
|
|
14122
|
+
return Math.max(0, nowMs - base);
|
|
14123
|
+
}
|
|
14124
|
+
function shouldShowStreamLastContentResponseAge(state, nowMs, config2) {
|
|
14125
|
+
if (!Number.isFinite(nowMs)) return false;
|
|
14126
|
+
const elapsedMs = nowMs - state.startedAtMs;
|
|
14127
|
+
if (elapsedMs < Math.max(0, config2.idleStartMs)) return false;
|
|
14128
|
+
const ageMs = getStreamLastContentResponseAgeMs(state, nowMs);
|
|
14129
|
+
return ageMs != null && ageMs >= Math.max(1e3, config2.heartbeatMs);
|
|
14130
|
+
}
|
|
14131
|
+
function buildStreamRuntimeStatus(state, nowMs, options = {}) {
|
|
14132
|
+
return formatStreamRuntimeStatus(
|
|
14133
|
+
Math.max(0, nowMs - state.startedAtMs),
|
|
14134
|
+
options.includeLastContentResponseAge ? getStreamLastContentResponseAgeMs(state, nowMs) : null,
|
|
14135
|
+
state.statusNote
|
|
14136
|
+
);
|
|
14137
|
+
}
|
|
14138
|
+
|
|
13861
14139
|
// src/lib/bridge/interactive-message-runner.ts
|
|
13862
14140
|
function generateDraftId() {
|
|
13863
14141
|
return Math.floor(Math.random() * 2147483646) + 1;
|
|
@@ -13901,18 +14179,6 @@ function flushPreview(adapter, state, config2) {
|
|
|
13901
14179
|
}).catch(() => {
|
|
13902
14180
|
});
|
|
13903
14181
|
}
|
|
13904
|
-
function formatRuntimeDuration(ms) {
|
|
13905
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
13906
|
-
const seconds = totalSeconds % 60;
|
|
13907
|
-
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
13908
|
-
const minutes = totalMinutes % 60;
|
|
13909
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
13910
|
-
const parts = [];
|
|
13911
|
-
if (hours > 0) parts.push(`${hours}\u5C0F\u65F6`);
|
|
13912
|
-
if (minutes > 0) parts.push(`${minutes}\u5206`);
|
|
13913
|
-
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}\u79D2`);
|
|
13914
|
-
return parts.join("");
|
|
13915
|
-
}
|
|
13916
14182
|
function pathBaseName(value) {
|
|
13917
14183
|
return value.includes("\\") ? path13.win32.basename(value) : path13.basename(value);
|
|
13918
14184
|
}
|
|
@@ -13934,18 +14200,10 @@ function buildStaleTaskCompletionNotice(address, binding) {
|
|
|
13934
14200
|
const taskName = formatTaskDisplayName(binding);
|
|
13935
14201
|
return `\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u4EFB\u52A1\u5DF2\u7ED3\u675F\uFF0C\u4F46\u5F53\u524D\u804A\u5929\u5DF2\u5207\u6362\u5230\u5176\u4ED6\u4F1A\u8BDD\uFF0C\u56DE\u590D\u5DF2\u8DF3\u8FC7\u3002`;
|
|
13936
14202
|
}
|
|
13937
|
-
function formatInteractiveRuntimeStatus(elapsedMs, lastResponseAgeMs, statusNote) {
|
|
13938
|
-
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
13939
|
-
if (typeof lastResponseAgeMs === "number" && lastResponseAgeMs >= 0) {
|
|
13940
|
-
parts.push(`\u4E0A\u6B21\u54CD\u5E94\u8DDD\u4ECA ${formatRuntimeDuration(lastResponseAgeMs)}`);
|
|
13941
|
-
}
|
|
13942
|
-
const runtimeText = parts.join("\uFF0C");
|
|
13943
|
-
const note = (statusNote || "").trim();
|
|
13944
|
-
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
13945
|
-
${runtimeText}` : runtimeText;
|
|
13946
|
-
}
|
|
13947
14203
|
async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
13948
14204
|
const binding = resolve(msg.address);
|
|
14205
|
+
const initialSession = getBridgeContext().store.getSession(binding.codepilotSessionId);
|
|
14206
|
+
const desktopThreadId = getExplicitDesktopThreadId(initialSession);
|
|
13949
14207
|
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
13950
14208
|
const nowMs = deps.nowMs ?? (() => Date.now());
|
|
13951
14209
|
const setIntervalFn = deps.setIntervalFn ?? ((callback, intervalMs) => setInterval(callback, intervalMs));
|
|
@@ -13961,10 +14219,34 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
13961
14219
|
1e3,
|
|
13962
14220
|
deps.streamStatusHeartbeatMs ?? structuredStreamStatusConfig.heartbeatMs
|
|
13963
14221
|
);
|
|
13964
|
-
|
|
14222
|
+
let messageStartCalled = false;
|
|
14223
|
+
const ensureMessageStarted = () => {
|
|
14224
|
+
if (messageStartCalled) return;
|
|
14225
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
14226
|
+
messageStartCalled = true;
|
|
14227
|
+
};
|
|
14228
|
+
ensureMessageStarted();
|
|
13965
14229
|
const taskAbort = new AbortController();
|
|
13966
14230
|
const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
13967
14231
|
const taskStartedAt = nowMs();
|
|
14232
|
+
const streamState = createStreamState(taskStartedAt);
|
|
14233
|
+
let externalTerminalRequest = null;
|
|
14234
|
+
let desktopTerminalFinalExpected = false;
|
|
14235
|
+
let resolveExternalTerminal = null;
|
|
14236
|
+
const externalTerminalPromise = new Promise((resolve2) => {
|
|
14237
|
+
resolveExternalTerminal = resolve2;
|
|
14238
|
+
});
|
|
14239
|
+
let processResultSettled = false;
|
|
14240
|
+
let resolveExternalTerminalCompletion = null;
|
|
14241
|
+
let externalTerminalCompletionSettled = false;
|
|
14242
|
+
const externalTerminalCompletion = new Promise((resolve2) => {
|
|
14243
|
+
resolveExternalTerminalCompletion = resolve2;
|
|
14244
|
+
});
|
|
14245
|
+
const settleExternalTerminalCompletion = (finalized) => {
|
|
14246
|
+
if (!externalTerminalRequest || externalTerminalCompletionSettled) return;
|
|
14247
|
+
externalTerminalCompletionSettled = true;
|
|
14248
|
+
resolveExternalTerminalCompletion?.(finalized);
|
|
14249
|
+
};
|
|
13968
14250
|
deps.resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
|
|
13969
14251
|
const taskState = {
|
|
13970
14252
|
id: taskId,
|
|
@@ -13978,12 +14260,35 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
13978
14260
|
structuredStreamUiActive: false,
|
|
13979
14261
|
lastActivityAt: taskStartedAt,
|
|
13980
14262
|
lastResponseAt: null,
|
|
13981
|
-
|
|
14263
|
+
lastContentResponseAt: null,
|
|
13982
14264
|
streamFinalized: false,
|
|
13983
14265
|
uiEnded: false,
|
|
13984
|
-
mirrorSuppressionId: null
|
|
14266
|
+
mirrorSuppressionId: null,
|
|
14267
|
+
finalizeFromExternalTerminal: async (outcome, detail, finalText) => {
|
|
14268
|
+
if (externalTerminalRequest) return externalTerminalCompletion;
|
|
14269
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return false;
|
|
14270
|
+
externalTerminalRequest = { outcome, detail, finalText };
|
|
14271
|
+
resolveExternalTerminal?.(externalTerminalRequest);
|
|
14272
|
+
if (!processResultSettled && !taskAbort.signal.aborted) {
|
|
14273
|
+
taskAbort.abort();
|
|
14274
|
+
}
|
|
14275
|
+
return externalTerminalCompletion;
|
|
14276
|
+
}
|
|
13985
14277
|
};
|
|
13986
14278
|
deps.registerInteractiveTask(taskState);
|
|
14279
|
+
deps.registerBridgeTurn?.({
|
|
14280
|
+
id: taskId,
|
|
14281
|
+
sessionId: binding.codepilotSessionId,
|
|
14282
|
+
kind: desktopThreadId ? "im_desktop_reuse" : "im_sdk",
|
|
14283
|
+
origin: "im",
|
|
14284
|
+
progressSource: "sdk_stream",
|
|
14285
|
+
finalSource: desktopThreadId ? "desktop_task_complete" : "sdk_result",
|
|
14286
|
+
codexThreadId: binding.sdkSessionId || initialSession?.codex_thread_id || initialSession?.sdk_session_id || void 0,
|
|
14287
|
+
desktopThreadId,
|
|
14288
|
+
requestMessageId: msg.messageId,
|
|
14289
|
+
streamKey,
|
|
14290
|
+
startedAt: taskStartedAt
|
|
14291
|
+
});
|
|
13987
14292
|
deps.recordInteractiveHealthStart(binding.codepilotSessionId);
|
|
13988
14293
|
let previewState = null;
|
|
13989
14294
|
const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
|
|
@@ -14003,7 +14308,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14003
14308
|
const ps = previewState;
|
|
14004
14309
|
const cfg = streamCfg;
|
|
14005
14310
|
if (ps.degraded) return;
|
|
14006
|
-
const sanitizedText =
|
|
14311
|
+
const sanitizedText = stripFinalOnlyBlocksForStreaming(fullText);
|
|
14007
14312
|
ps.pendingText = sanitizedText.length > cfg.maxChars ? sanitizedText.slice(0, cfg.maxChars) + "..." : sanitizedText;
|
|
14008
14313
|
const delta = ps.pendingText.length - ps.lastSentText.length;
|
|
14009
14314
|
const elapsed = Date.now() - ps.lastSentAt;
|
|
@@ -14040,12 +14345,11 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14040
14345
|
chatId: msg.address.chatId,
|
|
14041
14346
|
streamKey,
|
|
14042
14347
|
ensureStarted: () => {
|
|
14043
|
-
|
|
14348
|
+
ensureMessageStarted();
|
|
14044
14349
|
}
|
|
14045
14350
|
};
|
|
14046
14351
|
const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
|
|
14047
14352
|
const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
|
|
14048
|
-
let latestStatusNote = null;
|
|
14049
14353
|
let latestTasks = [];
|
|
14050
14354
|
const syncStructuredStreamUiState = () => {
|
|
14051
14355
|
if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
|
|
@@ -14064,12 +14368,22 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14064
14368
|
if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
|
|
14065
14369
|
pushStreamFeedbackStatus(
|
|
14066
14370
|
streamFeedbackTarget,
|
|
14067
|
-
|
|
14371
|
+
lastResponseAgeMs == null ? buildStreamRuntimeStatus(streamState, nowMs()) : formatStreamRuntimeStatus(nowMs() - taskStartedAt, lastResponseAgeMs, streamState.statusNote)
|
|
14068
14372
|
);
|
|
14069
14373
|
syncStructuredStreamUiSnapshot();
|
|
14070
14374
|
};
|
|
14071
|
-
const
|
|
14072
|
-
|
|
14375
|
+
const markActivity = () => {
|
|
14376
|
+
const now2 = nowMs();
|
|
14377
|
+
recordStreamActivity(streamState, now2);
|
|
14378
|
+
taskState.lastActivityAt = streamState.lastActivityAtMs;
|
|
14379
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14380
|
+
};
|
|
14381
|
+
const markContentResponse = () => {
|
|
14382
|
+
const now2 = nowMs();
|
|
14383
|
+
recordStreamContentResponse(streamState, now2);
|
|
14384
|
+
taskState.lastActivityAt = streamState.lastActivityAtMs;
|
|
14385
|
+
taskState.lastResponseAt = streamState.lastContentResponseAtMs;
|
|
14386
|
+
taskState.lastContentResponseAt = streamState.lastContentResponseAtMs;
|
|
14073
14387
|
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14074
14388
|
};
|
|
14075
14389
|
let streamStatusHeartbeat = null;
|
|
@@ -14083,16 +14397,54 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14083
14397
|
streamStatusUpdatesClosed = true;
|
|
14084
14398
|
clearStreamStatusHeartbeat();
|
|
14085
14399
|
};
|
|
14400
|
+
let structuredStreamInactiveRecorded = false;
|
|
14401
|
+
const recordStructuredStreamInactiveOnce = () => {
|
|
14402
|
+
if (structuredStreamInactiveRecorded) return;
|
|
14403
|
+
structuredStreamInactiveRecorded = true;
|
|
14404
|
+
taskState.structuredStreamUiActive = false;
|
|
14405
|
+
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, { active: false });
|
|
14406
|
+
};
|
|
14407
|
+
let previewEnded = false;
|
|
14408
|
+
const endPreviewOnce = () => {
|
|
14409
|
+
if (previewEnded) return;
|
|
14410
|
+
previewEnded = true;
|
|
14411
|
+
if (!previewState) return;
|
|
14412
|
+
if (previewState.throttleTimer) {
|
|
14413
|
+
clearTimeout(previewState.throttleTimer);
|
|
14414
|
+
previewState.throttleTimer = null;
|
|
14415
|
+
}
|
|
14416
|
+
adapter.endPreview?.(msg.address.chatId, previewState.draftId);
|
|
14417
|
+
};
|
|
14418
|
+
let streamUiFinalizeAttempted = false;
|
|
14419
|
+
const finalizeStreamUiOnce = async (status, responseText) => {
|
|
14420
|
+
stopStructuredStreamStatusUpdates();
|
|
14421
|
+
recordStructuredStreamInactiveOnce();
|
|
14422
|
+
endPreviewOnce();
|
|
14423
|
+
if (hasStreamingCards && !streamUiFinalizeAttempted) {
|
|
14424
|
+
streamUiFinalizeAttempted = true;
|
|
14425
|
+
taskState.streamFinalized = await finalizeStreamingUi(
|
|
14426
|
+
streamFeedbackTarget,
|
|
14427
|
+
status,
|
|
14428
|
+
assembleDesktopFinalResponse({ text: responseText })
|
|
14429
|
+
);
|
|
14430
|
+
}
|
|
14431
|
+
return taskState.streamFinalized;
|
|
14432
|
+
};
|
|
14433
|
+
const endMessageUiOnce = () => {
|
|
14434
|
+
if (taskState.uiEnded) return;
|
|
14435
|
+
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
14436
|
+
taskState.uiEnded = true;
|
|
14437
|
+
};
|
|
14086
14438
|
const onStreamCardText = hasStreamingCards ? (fullText) => {
|
|
14087
14439
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14088
14440
|
pushStreamFeedbackText(
|
|
14089
14441
|
streamFeedbackTarget,
|
|
14090
|
-
|
|
14442
|
+
stripFinalOnlyBlocksForStreaming(fullText)
|
|
14091
14443
|
);
|
|
14092
14444
|
} : void 0;
|
|
14093
14445
|
const onToolEvent = (toolId, toolName, status) => {
|
|
14094
14446
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14095
|
-
|
|
14447
|
+
markActivity();
|
|
14096
14448
|
deps.recordInteractiveHealthTool(binding.codepilotSessionId, toolId, toolName, status);
|
|
14097
14449
|
if (toolName) {
|
|
14098
14450
|
toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
|
|
@@ -14108,7 +14460,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14108
14460
|
};
|
|
14109
14461
|
const onTaskEvent = (tasks) => {
|
|
14110
14462
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14111
|
-
|
|
14463
|
+
markActivity();
|
|
14112
14464
|
latestTasks = tasks;
|
|
14113
14465
|
if (hasStreamingCards) {
|
|
14114
14466
|
pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
|
|
@@ -14118,17 +14470,15 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14118
14470
|
};
|
|
14119
14471
|
const onStatusNote = (note) => {
|
|
14120
14472
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14121
|
-
|
|
14122
|
-
if (
|
|
14123
|
-
markSuccessfulResponse();
|
|
14124
|
-
}
|
|
14473
|
+
updateStreamStatusNote(streamState, note, nowMs());
|
|
14474
|
+
if (streamState.statusNote) markActivity();
|
|
14125
14475
|
pushRunningStatus(null);
|
|
14126
14476
|
syncStructuredStreamUiSnapshot();
|
|
14127
14477
|
};
|
|
14128
14478
|
const onPartialText = (fullText) => {
|
|
14129
14479
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14130
14480
|
if (fullText.trim()) {
|
|
14131
|
-
|
|
14481
|
+
markContentResponse();
|
|
14132
14482
|
}
|
|
14133
14483
|
deps.recordInteractiveHealthProgress(binding.codepilotSessionId, "text");
|
|
14134
14484
|
previewOnPartialText?.(fullText);
|
|
@@ -14136,6 +14486,34 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14136
14486
|
pushRunningStatus(null);
|
|
14137
14487
|
syncStructuredStreamUiSnapshot();
|
|
14138
14488
|
};
|
|
14489
|
+
const waitForDesktopTerminalFinalization = async () => {
|
|
14490
|
+
if (externalTerminalRequest) return externalTerminalRequest;
|
|
14491
|
+
const timeoutMs = Math.max(0, deps.desktopTerminalFinalizationTimeoutMs ?? 0);
|
|
14492
|
+
if (!desktopThreadId || !desktopTerminalFinalExpected || timeoutMs <= 0) return null;
|
|
14493
|
+
if (taskAbort.signal.aborted) return null;
|
|
14494
|
+
return new Promise((resolve2) => {
|
|
14495
|
+
let settled = false;
|
|
14496
|
+
let timer = null;
|
|
14497
|
+
const finish = (terminal) => {
|
|
14498
|
+
if (settled) return;
|
|
14499
|
+
settled = true;
|
|
14500
|
+
if (timer) {
|
|
14501
|
+
clearTimeout(timer);
|
|
14502
|
+
timer = null;
|
|
14503
|
+
}
|
|
14504
|
+
taskAbort.signal.removeEventListener("abort", onAbort);
|
|
14505
|
+
resolve2(terminal);
|
|
14506
|
+
};
|
|
14507
|
+
const onAbort = () => finish(null);
|
|
14508
|
+
timer = setTimeout(() => finish(null), timeoutMs);
|
|
14509
|
+
taskAbort.signal.addEventListener("abort", onAbort, { once: true });
|
|
14510
|
+
externalTerminalPromise.then((terminal) => {
|
|
14511
|
+
finish(terminal);
|
|
14512
|
+
}, () => {
|
|
14513
|
+
finish(null);
|
|
14514
|
+
});
|
|
14515
|
+
});
|
|
14516
|
+
};
|
|
14139
14517
|
if (supportsStructuredStreamUi) {
|
|
14140
14518
|
pushRunningStatus(null);
|
|
14141
14519
|
streamStatusHeartbeat = setIntervalFn(() => {
|
|
@@ -14148,8 +14526,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14148
14526
|
return;
|
|
14149
14527
|
}
|
|
14150
14528
|
const elapsedMs = nowMs() - taskStartedAt;
|
|
14151
|
-
const
|
|
14152
|
-
|
|
14529
|
+
const showLastResponseAge = shouldShowStreamLastContentResponseAge(streamState, nowMs(), {
|
|
14530
|
+
idleStartMs: streamStatusIdleDetectionStartMs,
|
|
14531
|
+
heartbeatMs: streamStatusHeartbeatMs
|
|
14532
|
+
}) ? getStreamLastContentResponseAgeMs(streamState, nowMs()) : null;
|
|
14153
14533
|
pushRunningStatus(showLastResponseAge);
|
|
14154
14534
|
syncStructuredStreamUiSnapshot();
|
|
14155
14535
|
}, streamStatusHeartbeatMs);
|
|
@@ -14159,7 +14539,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14159
14539
|
let shouldRecordHealthEnd = true;
|
|
14160
14540
|
try {
|
|
14161
14541
|
const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
|
|
14162
|
-
const
|
|
14542
|
+
const processPromise = processMessageImpl(
|
|
14163
14543
|
binding,
|
|
14164
14544
|
promptText,
|
|
14165
14545
|
async (perm) => {
|
|
@@ -14178,7 +14558,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14178
14558
|
"permission_wait",
|
|
14179
14559
|
`\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
|
|
14180
14560
|
);
|
|
14181
|
-
|
|
14561
|
+
markActivity();
|
|
14182
14562
|
pushRunningStatus(null);
|
|
14183
14563
|
syncStructuredStreamUiSnapshot();
|
|
14184
14564
|
},
|
|
@@ -14189,85 +14569,119 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14189
14569
|
onTaskEvent,
|
|
14190
14570
|
onStatusNote,
|
|
14191
14571
|
(preparedPrompt) => {
|
|
14192
|
-
if (
|
|
14572
|
+
if (desktopThreadId) {
|
|
14573
|
+
desktopTerminalFinalExpected = true;
|
|
14574
|
+
}
|
|
14575
|
+
if (desktopThreadId && !taskState.mirrorSuppressionId) {
|
|
14193
14576
|
taskState.mirrorSuppressionId = deps.beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
14194
14577
|
}
|
|
14195
14578
|
}
|
|
14196
14579
|
);
|
|
14580
|
+
let raced;
|
|
14581
|
+
try {
|
|
14582
|
+
raced = await Promise.race([
|
|
14583
|
+
processPromise.then((result2) => ({ kind: "process", result: result2 })),
|
|
14584
|
+
externalTerminalPromise.then((terminal) => ({ kind: "external", terminal }))
|
|
14585
|
+
]);
|
|
14586
|
+
} catch (error) {
|
|
14587
|
+
if (!externalTerminalRequest) throw error;
|
|
14588
|
+
raced = { kind: "external", terminal: externalTerminalRequest };
|
|
14589
|
+
}
|
|
14590
|
+
if (raced.kind === "external") {
|
|
14591
|
+
processPromise.catch(() => {
|
|
14592
|
+
});
|
|
14593
|
+
finalOutcome = raced.terminal.outcome;
|
|
14594
|
+
finalOutcomeDetail = raced.terminal.detail;
|
|
14595
|
+
const streamEndStatus = raced.terminal.outcome === "completed" ? "completed" : raced.terminal.outcome === "aborted" ? "interrupted" : "error";
|
|
14596
|
+
const staleTaskNotice2 = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14597
|
+
const terminalResponse2 = assembleDesktopFinalResponse({
|
|
14598
|
+
text: staleTaskNotice2 || raced.terminal.finalText || ""
|
|
14599
|
+
});
|
|
14600
|
+
const cardFinalized2 = await finalizeStreamUiOnce(streamEndStatus, terminalResponse2.text);
|
|
14601
|
+
if (hasFinalResponsePayload(terminalResponse2)) {
|
|
14602
|
+
await deliverFinalResponse({
|
|
14603
|
+
adapter,
|
|
14604
|
+
address: msg.address,
|
|
14605
|
+
sessionId: binding.codepilotSessionId,
|
|
14606
|
+
replyToMessageId: msg.messageId,
|
|
14607
|
+
deliverResponse: deps.deliverResponse
|
|
14608
|
+
}, terminalResponse2, { skipText: cardFinalized2 });
|
|
14609
|
+
}
|
|
14610
|
+
return;
|
|
14611
|
+
}
|
|
14612
|
+
const result = raced.result;
|
|
14613
|
+
processResultSettled = true;
|
|
14197
14614
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
14198
14615
|
shouldRecordHealthEnd = false;
|
|
14199
14616
|
return;
|
|
14200
14617
|
}
|
|
14618
|
+
const terminalAfterProcess = await waitForDesktopTerminalFinalization();
|
|
14619
|
+
const terminalResponse = terminalAfterProcess?.outcome === "completed" ? assembleDesktopFinalResponse({ text: terminalAfterProcess.finalText || "" }) : null;
|
|
14620
|
+
const sdkResponse = assembleSdkFinalResponse({
|
|
14621
|
+
text: result.responseText,
|
|
14622
|
+
attachments: result.outboundAttachments,
|
|
14623
|
+
hasError: result.hasError,
|
|
14624
|
+
errorMessage: result.errorMessage
|
|
14625
|
+
});
|
|
14626
|
+
const terminalHasFinalPayload = Boolean(
|
|
14627
|
+
terminalResponse && hasFinalResponsePayload(terminalResponse)
|
|
14628
|
+
);
|
|
14629
|
+
const effectiveResponse = terminalResponse && terminalHasFinalPayload ? mergeFinalResponses(terminalResponse, sdkResponse) : sdkResponse;
|
|
14201
14630
|
let cardFinalized = false;
|
|
14202
14631
|
const staleTaskNotice = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14632
|
+
const staleResponse = staleTaskNotice ? assembleDesktopFinalResponse({ text: staleTaskNotice }) : null;
|
|
14203
14633
|
if (hasStreamingCards) {
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
cardFinalized = await finalizeStreamFeedback(
|
|
14207
|
-
streamFeedbackTarget,
|
|
14634
|
+
const streamEndStatus = terminalAfterProcess ? terminalAfterProcess.outcome === "completed" ? "completed" : terminalAfterProcess.outcome === "aborted" ? "interrupted" : "error" : taskAbort.signal.aborted ? "interrupted" : result.hasError ? "error" : "completed";
|
|
14635
|
+
cardFinalized = await finalizeStreamUiOnce(
|
|
14208
14636
|
streamEndStatus,
|
|
14209
|
-
|
|
14637
|
+
staleResponse?.text || (streamEndStatus === "interrupted" ? "" : effectiveResponse.text)
|
|
14210
14638
|
);
|
|
14211
|
-
taskState.streamFinalized = cardFinalized;
|
|
14212
14639
|
}
|
|
14213
|
-
if (
|
|
14214
|
-
|
|
14215
|
-
await deps.deliverResponse(
|
|
14216
|
-
adapter,
|
|
14217
|
-
msg.address,
|
|
14218
|
-
staleTaskNotice,
|
|
14219
|
-
binding.codepilotSessionId,
|
|
14220
|
-
msg.messageId,
|
|
14221
|
-
[]
|
|
14222
|
-
);
|
|
14223
|
-
}
|
|
14224
|
-
} else if (result.responseText || result.outboundAttachments.length > 0) {
|
|
14225
|
-
const textToDeliver = cardFinalized ? "" : result.responseText;
|
|
14226
|
-
if (!cardFinalized || result.outboundAttachments.length > 0) {
|
|
14227
|
-
await deps.deliverResponse(
|
|
14228
|
-
adapter,
|
|
14229
|
-
msg.address,
|
|
14230
|
-
textToDeliver,
|
|
14231
|
-
binding.codepilotSessionId,
|
|
14232
|
-
msg.messageId,
|
|
14233
|
-
result.outboundAttachments
|
|
14234
|
-
);
|
|
14235
|
-
}
|
|
14236
|
-
} else if (result.hasError && !taskAbort.signal.aborted) {
|
|
14237
|
-
await deps.deliverResponse(
|
|
14640
|
+
if (staleResponse) {
|
|
14641
|
+
await deliverFinalResponse({
|
|
14238
14642
|
adapter,
|
|
14239
|
-
msg.address,
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14643
|
+
address: msg.address,
|
|
14644
|
+
sessionId: binding.codepilotSessionId,
|
|
14645
|
+
replyToMessageId: msg.messageId,
|
|
14646
|
+
deliverResponse: deps.deliverResponse
|
|
14647
|
+
}, staleResponse, { skipText: cardFinalized });
|
|
14648
|
+
} else if (hasFinalResponsePayload(effectiveResponse)) {
|
|
14649
|
+
await deliverFinalResponse({
|
|
14650
|
+
adapter,
|
|
14651
|
+
address: msg.address,
|
|
14652
|
+
sessionId: binding.codepilotSessionId,
|
|
14653
|
+
replyToMessageId: msg.messageId,
|
|
14654
|
+
deliverResponse: deps.deliverResponse
|
|
14655
|
+
}, effectiveResponse, { skipText: cardFinalized });
|
|
14656
|
+
} else if (result.hasError && !taskAbort.signal.aborted) {
|
|
14657
|
+
await deliverFinalResponse(
|
|
14658
|
+
{
|
|
14659
|
+
adapter,
|
|
14660
|
+
address: msg.address,
|
|
14661
|
+
sessionId: binding.codepilotSessionId,
|
|
14662
|
+
replyToMessageId: msg.messageId,
|
|
14663
|
+
deliverResponse: deps.deliverResponse
|
|
14664
|
+
},
|
|
14665
|
+
assembleSdkFinalResponse({
|
|
14666
|
+
text: `**Error:** ${result.errorMessage}`,
|
|
14667
|
+
hasError: true,
|
|
14668
|
+
errorMessage: result.errorMessage
|
|
14669
|
+
})
|
|
14244
14670
|
);
|
|
14245
14671
|
}
|
|
14246
14672
|
try {
|
|
14247
14673
|
deps.persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
|
|
14248
14674
|
} catch {
|
|
14249
14675
|
}
|
|
14250
|
-
finalOutcome = result.hasError ? "failed" : "completed";
|
|
14251
|
-
finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
|
|
14676
|
+
finalOutcome = terminalAfterProcess?.outcome || (result.hasError ? "failed" : "completed");
|
|
14677
|
+
finalOutcomeDetail = terminalAfterProcess?.detail || (result.hasError ? result.errorMessage?.trim() || void 0 : void 0);
|
|
14252
14678
|
} finally {
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
clearTimeout(previewState.throttleTimer);
|
|
14258
|
-
previewState.throttleTimer = null;
|
|
14259
|
-
}
|
|
14260
|
-
adapter.endPreview?.(msg.address.chatId, previewState.draftId);
|
|
14261
|
-
}
|
|
14262
|
-
if (hasStreamingCards && taskAbort.signal.aborted && !taskState.streamFinalized) {
|
|
14263
|
-
taskState.streamFinalized = await finalizeStreamFeedback(
|
|
14264
|
-
streamFeedbackTarget,
|
|
14265
|
-
"interrupted",
|
|
14266
|
-
""
|
|
14267
|
-
);
|
|
14268
|
-
}
|
|
14679
|
+
await finalizeStreamUiOnce(
|
|
14680
|
+
taskAbort.signal.aborted ? "interrupted" : finalOutcome === "completed" ? "completed" : "error",
|
|
14681
|
+
""
|
|
14682
|
+
);
|
|
14269
14683
|
if (taskState.mirrorSuppressionId) {
|
|
14270
|
-
if (
|
|
14684
|
+
if (finalOutcome === "aborted") {
|
|
14271
14685
|
deps.abortMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
|
|
14272
14686
|
} else {
|
|
14273
14687
|
deps.settleMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
|
|
@@ -14275,17 +14689,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14275
14689
|
taskState.mirrorSuppressionId = null;
|
|
14276
14690
|
}
|
|
14277
14691
|
if (shouldRecordHealthEnd) {
|
|
14278
|
-
if (taskAbort.signal.aborted) {
|
|
14692
|
+
if (taskAbort.signal.aborted && !externalTerminalRequest) {
|
|
14279
14693
|
finalOutcome = "aborted";
|
|
14280
14694
|
finalOutcomeDetail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14281
14695
|
}
|
|
14282
14696
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
14283
14697
|
}
|
|
14284
14698
|
deps.releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
}
|
|
14699
|
+
deps.releaseBridgeTurn?.(binding.codepilotSessionId, taskId);
|
|
14700
|
+
endMessageUiOnce();
|
|
14701
|
+
settleExternalTerminalCompletion(taskState.streamFinalized || !hasStreamingCards);
|
|
14289
14702
|
}
|
|
14290
14703
|
}
|
|
14291
14704
|
|
|
@@ -14298,16 +14711,7 @@ var TERMINAL_SESSION_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
|
14298
14711
|
function isTerminalSessionHealthStatus(status) {
|
|
14299
14712
|
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
14300
14713
|
}
|
|
14301
|
-
function
|
|
14302
|
-
return [
|
|
14303
|
-
"\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
|
|
14304
|
-
"\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u7EC8\u6B62\u5B83\uFF1B\u5982\u679C\u4F60\u4ECD\u5728\u5BF9\u5E94\u7EBF\u7A0B\uFF0C\u53EF\u53D1\u9001 `/stop` \u4E3B\u52A8\u505C\u6B62\uFF1B\u5982\u679C\u5DF2\u7ECF\u5207\u5230\u522B\u7684\u7EBF\u7A0B\uFF0C\u9700\u8981\u5148\u5207\u56DE\u5BF9\u5E94\u7EBF\u7A0B\u3002"
|
|
14305
|
-
].join("\n");
|
|
14306
|
-
}
|
|
14307
|
-
function shouldSkipIdleReminder(task) {
|
|
14308
|
-
return task.adapter.provider === "feishu" && task.structuredStreamUiActive;
|
|
14309
|
-
}
|
|
14310
|
-
function createInteractiveRuntime(getState2, options, deps) {
|
|
14714
|
+
function createInteractiveRuntime(getState2, deps) {
|
|
14311
14715
|
function getQueuedCount(sessionId) {
|
|
14312
14716
|
return getState2().queuedCounts.get(sessionId) || 0;
|
|
14313
14717
|
}
|
|
@@ -14341,7 +14745,6 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
14341
14745
|
const task = getState2().activeTasks.get(sessionId);
|
|
14342
14746
|
if (task?.id !== taskId) return;
|
|
14343
14747
|
task.lastActivityAt = Date.now();
|
|
14344
|
-
task.idleReminderSent = false;
|
|
14345
14748
|
}
|
|
14346
14749
|
function releaseInteractiveTask(sessionId, taskId) {
|
|
14347
14750
|
const state = getState2();
|
|
@@ -14350,35 +14753,19 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
14350
14753
|
state.activeTasks.delete(sessionId);
|
|
14351
14754
|
syncSessionRuntimeState(sessionId);
|
|
14352
14755
|
}
|
|
14353
|
-
async function
|
|
14354
|
-
|
|
14355
|
-
task
|
|
14356
|
-
|
|
14357
|
-
await deliverBridgeNotice(task.adapter, task.address, buildInteractiveIdleReminderNotice(), {
|
|
14358
|
-
sessionId: task.sessionId,
|
|
14359
|
-
replyToMessageId: task.requestMessageId
|
|
14360
|
-
});
|
|
14361
|
-
} catch {
|
|
14362
|
-
}
|
|
14363
|
-
}
|
|
14364
|
-
async function reconcileIdleInteractiveTasks() {
|
|
14365
|
-
const now2 = Date.now();
|
|
14366
|
-
const tasks = Array.from(getState2().activeTasks.values());
|
|
14367
|
-
for (const task of tasks) {
|
|
14368
|
-
if (shouldSkipIdleReminder(task)) continue;
|
|
14369
|
-
if (task.idleReminderSent) continue;
|
|
14370
|
-
if (now2 - task.lastActivityAt < options.idleReminderMs) continue;
|
|
14371
|
-
await remindIdleInteractiveTask(task);
|
|
14372
|
-
}
|
|
14756
|
+
async function finalizeTerminalActiveTask(sessionId, outcome, detail, finalText) {
|
|
14757
|
+
const task = getState2().activeTasks.get(sessionId);
|
|
14758
|
+
if (!task?.finalizeFromExternalTerminal) return false;
|
|
14759
|
+
return task.finalizeFromExternalTerminal(outcome, detail, finalText);
|
|
14373
14760
|
}
|
|
14374
|
-
function reconcileTerminalSessionRuntimeState() {
|
|
14761
|
+
async function reconcileTerminalSessionRuntimeState() {
|
|
14375
14762
|
const store = deps.getStore();
|
|
14376
14763
|
for (const session of store.listSessions()) {
|
|
14764
|
+
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
14765
|
+
if (getState2().activeTasks.has(session.id)) continue;
|
|
14377
14766
|
const queuedCount = getQueuedCount(session.id);
|
|
14378
14767
|
const persistedQueuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
|
|
14379
|
-
|
|
14380
|
-
if (hasActiveTask || queuedCount > 0) continue;
|
|
14381
|
-
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
14768
|
+
if (queuedCount > 0) continue;
|
|
14382
14769
|
if (persistedQueuedCount === 0 && session.runtime_status !== "running" && session.runtime_status !== "queued") {
|
|
14383
14770
|
continue;
|
|
14384
14771
|
}
|
|
@@ -14449,7 +14836,7 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
14449
14836
|
touchInteractiveTask,
|
|
14450
14837
|
releaseInteractiveTask,
|
|
14451
14838
|
syncSessionRuntimeState,
|
|
14452
|
-
|
|
14839
|
+
finalizeTerminalActiveTask,
|
|
14453
14840
|
reconcileTerminalSessionRuntimeState,
|
|
14454
14841
|
resetPersistedInteractiveRuntimeState,
|
|
14455
14842
|
processWithSessionLock
|
|
@@ -14788,7 +15175,7 @@ function buildMirrorSubscriptionRegistryPlan(bindings, activeChannelTypes, exist
|
|
|
14788
15175
|
if (binding.active === false) return false;
|
|
14789
15176
|
if (!activeChannels.has(binding.channelType)) return false;
|
|
14790
15177
|
const session = getSession(binding.codepilotSessionId);
|
|
14791
|
-
return Boolean(
|
|
15178
|
+
return Boolean(session?.desktop_thread_id || (session?.thread_origin === "desktop" ? session.sdk_session_id : null));
|
|
14792
15179
|
});
|
|
14793
15180
|
const desiredIds = new Set(upsertBindings.map((binding) => binding.id));
|
|
14794
15181
|
const removeBindingIds = Array.from(existingBindingIds).filter((bindingId) => !desiredIds.has(bindingId));
|
|
@@ -14882,11 +15269,17 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
14882
15269
|
function clearDanglingMirrorThread(subscription, reason) {
|
|
14883
15270
|
const { store } = getBridgeContext();
|
|
14884
15271
|
const session = store.getSession(subscription.sessionId);
|
|
14885
|
-
const currentThreadId = session
|
|
15272
|
+
const currentThreadId = getExplicitDesktopThreadId(session) || subscription.threadId;
|
|
14886
15273
|
console.warn(
|
|
14887
15274
|
`[bridge-manager] Clearing dangling desktop thread ${currentThreadId} for session ${subscription.sessionId}: ${reason}`
|
|
14888
15275
|
);
|
|
14889
15276
|
store.updateSdkSessionId(subscription.sessionId, "");
|
|
15277
|
+
store.updateSession(subscription.sessionId, {
|
|
15278
|
+
sdk_session_id: "",
|
|
15279
|
+
codex_thread_id: void 0,
|
|
15280
|
+
desktop_thread_id: void 0,
|
|
15281
|
+
thread_origin: void 0
|
|
15282
|
+
});
|
|
14890
15283
|
removeMirrorSubscription(subscription.bindingId);
|
|
14891
15284
|
}
|
|
14892
15285
|
function upsertMirrorSubscription(binding) {
|
|
@@ -14897,7 +15290,7 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
14897
15290
|
removeMirrorSubscription(binding.id);
|
|
14898
15291
|
return;
|
|
14899
15292
|
}
|
|
14900
|
-
const threadId =
|
|
15293
|
+
const threadId = getExplicitDesktopThreadId(session) || "";
|
|
14901
15294
|
if (!threadId) {
|
|
14902
15295
|
removeMirrorSubscription(binding.id);
|
|
14903
15296
|
return;
|
|
@@ -15010,11 +15403,13 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
15010
15403
|
`[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
|
|
15011
15404
|
);
|
|
15012
15405
|
}
|
|
15013
|
-
|
|
15014
|
-
|
|
15406
|
+
const routeResult = deliverableRecords.length > 0 && deps.routeDesktopRecords ? await deps.routeDesktopRecords(subscription.sessionId, subscription.threadId, deliverableRecords) : { claimed: [], unclaimed: deliverableRecords, terminalClaimed: false };
|
|
15407
|
+
const mirrorRecords = routeResult.unclaimed;
|
|
15408
|
+
if (mirrorRecords.length > 0) {
|
|
15409
|
+
deps.observeSessionHealthRecords(subscription.sessionId, subscription.threadId, mirrorRecords);
|
|
15015
15410
|
}
|
|
15016
|
-
const blocked = getState2().activeTasks.has(subscription.sessionId)
|
|
15017
|
-
const deliveryPlan = buildMirrorDeliveryPlan(subscription,
|
|
15411
|
+
const blocked = getState2().activeTasks.has(subscription.sessionId);
|
|
15412
|
+
const deliveryPlan = buildMirrorDeliveryPlan(subscription, mirrorRecords, {
|
|
15018
15413
|
blocked,
|
|
15019
15414
|
filterSuppressedRecords: deps.filterSuppressedMirrorRecords,
|
|
15020
15415
|
flushTimedOutTurn: (currentSubscription) => deps.flushTimedOutMirrorTurn(currentSubscription),
|
|
@@ -15111,6 +15506,241 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
15111
15506
|
};
|
|
15112
15507
|
}
|
|
15113
15508
|
|
|
15509
|
+
// src/lib/bridge/mirror-feedback-controller.ts
|
|
15510
|
+
function createMirrorStreamFeedbackTarget(subscription, turnState, adapter, startMirrorStreaming) {
|
|
15511
|
+
return {
|
|
15512
|
+
adapter,
|
|
15513
|
+
channelType: subscription.channelType,
|
|
15514
|
+
chatId: subscription.chatId,
|
|
15515
|
+
streamKey: turnState.streamKey,
|
|
15516
|
+
ensureStarted: () => {
|
|
15517
|
+
startMirrorStreaming(subscription, turnState);
|
|
15518
|
+
}
|
|
15519
|
+
};
|
|
15520
|
+
}
|
|
15521
|
+
function createMirrorFeedbackController(deps) {
|
|
15522
|
+
function getMirrorStreamingAdapter(subscription) {
|
|
15523
|
+
const adapter = deps.getAdapter(subscription.channelType);
|
|
15524
|
+
if (!adapter || !adapter.isRunning()) return null;
|
|
15525
|
+
if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
|
|
15526
|
+
if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
|
|
15527
|
+
return null;
|
|
15528
|
+
}
|
|
15529
|
+
return adapter;
|
|
15530
|
+
}
|
|
15531
|
+
function getMirrorStreamingText(subscription, turnState) {
|
|
15532
|
+
const title = deps.getThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
15533
|
+
const markdown = getFeedbackParseMode(subscription.channelType) === "Markdown";
|
|
15534
|
+
const rendered = formatMirrorMessage(
|
|
15535
|
+
title,
|
|
15536
|
+
turnState.userText,
|
|
15537
|
+
stripOutboundArtifactBlocksForStreaming(turnState.streamedText),
|
|
15538
|
+
markdown,
|
|
15539
|
+
true
|
|
15540
|
+
);
|
|
15541
|
+
return rendered || buildMirrorTitle(title, markdown);
|
|
15542
|
+
}
|
|
15543
|
+
function startMirrorStreaming(subscription, turnState) {
|
|
15544
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15545
|
+
if (!adapter || turnState.streamStarted) return;
|
|
15546
|
+
try {
|
|
15547
|
+
adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
|
|
15548
|
+
if (!adapter.onMirrorStreamStart) {
|
|
15549
|
+
adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
|
|
15550
|
+
}
|
|
15551
|
+
turnState.streamStarted = true;
|
|
15552
|
+
} catch {
|
|
15553
|
+
}
|
|
15554
|
+
}
|
|
15555
|
+
function createStreamTarget(subscription, turnState, adapter) {
|
|
15556
|
+
return createMirrorStreamFeedbackTarget(subscription, turnState, adapter, startMirrorStreaming);
|
|
15557
|
+
}
|
|
15558
|
+
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
15559
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15560
|
+
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
15561
|
+
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
15562
|
+
const startedAtMs = Date.parse(turnState.startedAt);
|
|
15563
|
+
if (!Number.isFinite(startedAtMs)) return;
|
|
15564
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
15565
|
+
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
15566
|
+
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
15567
|
+
return;
|
|
15568
|
+
}
|
|
15569
|
+
const statusText = formatStreamRuntimeStatus(
|
|
15570
|
+
Math.max(0, nowMs - startedAtMs),
|
|
15571
|
+
options.lastResponseAgeMs,
|
|
15572
|
+
turnState.statusNote
|
|
15573
|
+
);
|
|
15574
|
+
if (turnState.lastStatusText === statusText) return;
|
|
15575
|
+
const pushed = pushStreamFeedbackStatus(
|
|
15576
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15577
|
+
statusText
|
|
15578
|
+
);
|
|
15579
|
+
if (!pushed) return;
|
|
15580
|
+
turnState.lastStatusText = statusText;
|
|
15581
|
+
turnState.lastStatusAt = nowMs;
|
|
15582
|
+
}
|
|
15583
|
+
function refreshMirrorStreamingStatus2(subscription, nowMs = Date.now(), config2) {
|
|
15584
|
+
const pendingTurn = subscription.pendingTurn;
|
|
15585
|
+
if (!pendingTurn?.streamStarted) return;
|
|
15586
|
+
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
15587
|
+
if (!Number.isFinite(startedAtMs)) return;
|
|
15588
|
+
const lastContentResponseAtMs = pendingTurn.lastContentResponseAt ? Date.parse(pendingTurn.lastContentResponseAt) : pendingTurn.lastResponseAt ? Date.parse(pendingTurn.lastResponseAt) : null;
|
|
15589
|
+
const streamState = {
|
|
15590
|
+
startedAtMs,
|
|
15591
|
+
lastContentResponseAtMs: Number.isFinite(lastContentResponseAtMs) ? lastContentResponseAtMs : null
|
|
15592
|
+
};
|
|
15593
|
+
if (!shouldShowStreamLastContentResponseAge(streamState, nowMs, config2)) return;
|
|
15594
|
+
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
15595
|
+
nowMs,
|
|
15596
|
+
lastResponseAgeMs: getStreamLastContentResponseAgeMs(streamState, nowMs),
|
|
15597
|
+
minIntervalMs: config2.heartbeatMs
|
|
15598
|
+
});
|
|
15599
|
+
}
|
|
15600
|
+
function updateMirrorStreaming(subscription, turnState) {
|
|
15601
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15602
|
+
if (!adapter) return;
|
|
15603
|
+
pushStreamFeedbackText(
|
|
15604
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15605
|
+
getMirrorStreamingText(subscription, turnState)
|
|
15606
|
+
);
|
|
15607
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15608
|
+
}
|
|
15609
|
+
function updateMirrorToolProgress(subscription, turnState) {
|
|
15610
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15611
|
+
if (!adapter) return;
|
|
15612
|
+
pushStreamFeedbackTools(
|
|
15613
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15614
|
+
Array.from(turnState.toolCalls.values())
|
|
15615
|
+
);
|
|
15616
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15617
|
+
}
|
|
15618
|
+
function updateMirrorTaskProgress(subscription, turnState) {
|
|
15619
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15620
|
+
if (!adapter) return;
|
|
15621
|
+
pushStreamFeedbackTasks(
|
|
15622
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15623
|
+
turnState.taskItems
|
|
15624
|
+
);
|
|
15625
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15626
|
+
}
|
|
15627
|
+
function updateMirrorStatusProgress(subscription, turnState) {
|
|
15628
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15629
|
+
if (!adapter) return;
|
|
15630
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15631
|
+
}
|
|
15632
|
+
function stopMirrorStreaming2(subscription, status = "interrupted") {
|
|
15633
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15634
|
+
const pendingTurn = subscription.pendingTurn;
|
|
15635
|
+
if (!adapter || !pendingTurn?.streamStarted) return;
|
|
15636
|
+
void finalizeStreamFeedback(
|
|
15637
|
+
createStreamTarget(subscription, pendingTurn, adapter),
|
|
15638
|
+
status,
|
|
15639
|
+
getMirrorStreamingText(subscription, pendingTurn)
|
|
15640
|
+
);
|
|
15641
|
+
}
|
|
15642
|
+
async function deliverMirrorTurn(subscription, turn) {
|
|
15643
|
+
const adapter = deps.getAdapter(subscription.channelType);
|
|
15644
|
+
if (!adapter || !adapter.isRunning()) return;
|
|
15645
|
+
const title = deps.getThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
15646
|
+
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
15647
|
+
const markdown = responseParseMode === "Markdown";
|
|
15648
|
+
const rawFinalResponse = assembleDesktopFinalResponse({ text: turn.text });
|
|
15649
|
+
const attachments = rawFinalResponse.attachments;
|
|
15650
|
+
const cleanTurnText = rawFinalResponse.text;
|
|
15651
|
+
const renderedTextBase = formatMirrorMessage(title, turn.userText, cleanTurnText, markdown);
|
|
15652
|
+
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, cleanTurnText, markdown, true);
|
|
15653
|
+
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
15654
|
+
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
15655
|
+
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
15656
|
+
const streamText = renderFeedbackText(
|
|
15657
|
+
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
15658
|
+
responseParseMode
|
|
15659
|
+
);
|
|
15660
|
+
const address = {
|
|
15661
|
+
channelType: subscription.channelType,
|
|
15662
|
+
chatId: subscription.chatId
|
|
15663
|
+
};
|
|
15664
|
+
if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
15665
|
+
try {
|
|
15666
|
+
const finalized = await adapter.onStreamEnd(
|
|
15667
|
+
subscription.chatId,
|
|
15668
|
+
turn.status,
|
|
15669
|
+
streamText,
|
|
15670
|
+
turn.streamKey
|
|
15671
|
+
);
|
|
15672
|
+
if (finalized) {
|
|
15673
|
+
if (attachments.length > 0) {
|
|
15674
|
+
const attachmentResult = await deliverFinalResponse(
|
|
15675
|
+
{
|
|
15676
|
+
adapter,
|
|
15677
|
+
address,
|
|
15678
|
+
sessionId: subscription.sessionId,
|
|
15679
|
+
deliverResponse: deps.deliverResponse
|
|
15680
|
+
},
|
|
15681
|
+
assembleDesktopFinalResponse({ attachments }),
|
|
15682
|
+
{ skipText: true }
|
|
15683
|
+
);
|
|
15684
|
+
if (!attachmentResult.ok) {
|
|
15685
|
+
throw new Error(attachmentResult.error || "mirror attachment delivery failed");
|
|
15686
|
+
}
|
|
15687
|
+
}
|
|
15688
|
+
subscription.lastDeliveredAt = turn.timestamp || deps.nowIso();
|
|
15689
|
+
return;
|
|
15690
|
+
}
|
|
15691
|
+
} catch (error) {
|
|
15692
|
+
console.warn("[bridge-manager] Mirror stream finalize failed:", error instanceof Error ? error.message : error);
|
|
15693
|
+
}
|
|
15694
|
+
}
|
|
15695
|
+
const finalResponse = assembleDesktopFinalResponse({
|
|
15696
|
+
text: text2,
|
|
15697
|
+
attachments
|
|
15698
|
+
});
|
|
15699
|
+
if (!finalResponse.text && finalResponse.attachments.length === 0) return;
|
|
15700
|
+
const response = await deliverFinalResponse({
|
|
15701
|
+
adapter,
|
|
15702
|
+
address,
|
|
15703
|
+
sessionId: subscription.sessionId,
|
|
15704
|
+
deliverResponse: deps.deliverResponse,
|
|
15705
|
+
deliverText: async (messageText) => deliver(adapter, {
|
|
15706
|
+
address,
|
|
15707
|
+
text: messageText,
|
|
15708
|
+
parseMode: responseParseMode
|
|
15709
|
+
}, {
|
|
15710
|
+
sessionId: subscription.sessionId,
|
|
15711
|
+
dedupKey: `mirror:${subscription.bindingId}:${turn.signature}`
|
|
15712
|
+
})
|
|
15713
|
+
}, finalResponse);
|
|
15714
|
+
if (!response.ok) {
|
|
15715
|
+
throw new Error(response.error || "mirror delivery failed");
|
|
15716
|
+
}
|
|
15717
|
+
subscription.lastDeliveredAt = turn.timestamp || deps.nowIso();
|
|
15718
|
+
}
|
|
15719
|
+
async function deliverMirrorTurns2(subscription, turns) {
|
|
15720
|
+
let deliveredCount = 0;
|
|
15721
|
+
for (const turn of turns.slice(0, deps.eventBatchLimit)) {
|
|
15722
|
+
try {
|
|
15723
|
+
await deliverMirrorTurn(subscription, turn);
|
|
15724
|
+
deliveredCount += 1;
|
|
15725
|
+
} catch (error) {
|
|
15726
|
+
return { deliveredCount, error };
|
|
15727
|
+
}
|
|
15728
|
+
}
|
|
15729
|
+
return { deliveredCount };
|
|
15730
|
+
}
|
|
15731
|
+
return {
|
|
15732
|
+
hooks: {
|
|
15733
|
+
onStreamText: updateMirrorStreaming,
|
|
15734
|
+
onStatusProgress: updateMirrorStatusProgress,
|
|
15735
|
+
onTaskProgress: updateMirrorTaskProgress,
|
|
15736
|
+
onToolProgress: updateMirrorToolProgress
|
|
15737
|
+
},
|
|
15738
|
+
refreshMirrorStreamingStatus: refreshMirrorStreamingStatus2,
|
|
15739
|
+
stopMirrorStreaming: stopMirrorStreaming2,
|
|
15740
|
+
deliverMirrorTurns: deliverMirrorTurns2
|
|
15741
|
+
};
|
|
15742
|
+
}
|
|
15743
|
+
|
|
15114
15744
|
// src/lib/bridge/session-health-process.ts
|
|
15115
15745
|
import { execFile } from "node:child_process";
|
|
15116
15746
|
import { promisify } from "node:util";
|
|
@@ -15203,7 +15833,6 @@ async function probeCodexThreadProcess(threadId) {
|
|
|
15203
15833
|
var HEALTH_RECENT_PROGRESS_MS = 10 * 60 * 1e3;
|
|
15204
15834
|
var HEALTH_SLOW_OBSERVED_MS = 30 * 60 * 1e3;
|
|
15205
15835
|
var HEALTH_PROGRESS_PERSIST_THROTTLE_MS = 15 * 1e3;
|
|
15206
|
-
var HEALTH_PROCESS_PROBE_CACHE_MS = 30 * 1e3;
|
|
15207
15836
|
var HEALTH_STREAM_UI_STALL_MS = 60 * 1e3;
|
|
15208
15837
|
var RUNNING_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
15209
15838
|
"running_active",
|
|
@@ -15342,6 +15971,7 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15342
15971
|
const fallbackStatus = isRunningRuntimeStatus(runtimeStatus) ? "running_active" : previousStatus;
|
|
15343
15972
|
return {
|
|
15344
15973
|
sessionId: session.id,
|
|
15974
|
+
checkedAt: null,
|
|
15345
15975
|
runtimeStatus,
|
|
15346
15976
|
healthStatus: fallbackStatus,
|
|
15347
15977
|
healthReason: session.health_reason?.trim() || (fallbackStatus === "idle" ? "\u5F53\u524D\u6CA1\u6709\u8BB0\u5F55\u5230\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002" : "\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\uFF0C\u4F46\u8FD8\u6CA1\u6709\u8BB0\u5F55\u5230\u8BE6\u7EC6\u8FDB\u5C55\u3002"),
|
|
@@ -15379,6 +16009,7 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15379
16009
|
}
|
|
15380
16010
|
return {
|
|
15381
16011
|
sessionId: session.id,
|
|
16012
|
+
checkedAt: null,
|
|
15382
16013
|
runtimeStatus,
|
|
15383
16014
|
healthStatus,
|
|
15384
16015
|
healthReason,
|
|
@@ -15487,7 +16118,6 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
15487
16118
|
// src/lib/bridge/session-health-runtime.ts
|
|
15488
16119
|
function createSessionHealthRuntime(deps) {
|
|
15489
16120
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
15490
|
-
const processProbeCache = /* @__PURE__ */ new Map();
|
|
15491
16121
|
function summarizePlanUpdate(tasks) {
|
|
15492
16122
|
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
15493
16123
|
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
@@ -15516,7 +16146,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
15516
16146
|
}
|
|
15517
16147
|
}
|
|
15518
16148
|
if (!changed) return;
|
|
15519
|
-
store.updateSession(sessionId, next);
|
|
16149
|
+
store.updateSession(sessionId, next, { touch: options?.touch });
|
|
15520
16150
|
}
|
|
15521
16151
|
function maybePersistProgress(sessionId, updates, progressType) {
|
|
15522
16152
|
const nowMs = Date.now();
|
|
@@ -15619,10 +16249,13 @@ function createSessionHealthRuntime(deps) {
|
|
|
15619
16249
|
active_tool_name: void 0,
|
|
15620
16250
|
active_tool_started_at: void 0,
|
|
15621
16251
|
stream_ui_flush_started_at: void 0,
|
|
15622
|
-
|
|
16252
|
+
last_stream_ui_attempt_at: void 0,
|
|
16253
|
+
last_stream_ui_update_at: void 0,
|
|
16254
|
+
last_stream_ui_error_at: void 0,
|
|
16255
|
+
last_stream_ui_error: void 0,
|
|
16256
|
+
stream_ui_consecutive_failures: void 0
|
|
15623
16257
|
}, { force: true });
|
|
15624
16258
|
lastProgressPersistAt.set(sessionId, Date.now());
|
|
15625
|
-
processProbeCache.delete(sessionId);
|
|
15626
16259
|
}
|
|
15627
16260
|
function toIso(value) {
|
|
15628
16261
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
@@ -15638,6 +16271,12 @@ function createSessionHealthRuntime(deps) {
|
|
|
15638
16271
|
updates.last_stream_ui_error_at = toIso(snapshot.lastErrorAt);
|
|
15639
16272
|
updates.last_stream_ui_error = snapshot.lastError?.trim() || void 0;
|
|
15640
16273
|
updates.stream_ui_consecutive_failures = snapshot.consecutiveFailures && snapshot.consecutiveFailures > 0 ? snapshot.consecutiveFailures : void 0;
|
|
16274
|
+
} else {
|
|
16275
|
+
updates.last_stream_ui_attempt_at = void 0;
|
|
16276
|
+
updates.last_stream_ui_update_at = void 0;
|
|
16277
|
+
updates.last_stream_ui_error_at = void 0;
|
|
16278
|
+
updates.last_stream_ui_error = void 0;
|
|
16279
|
+
updates.stream_ui_consecutive_failures = void 0;
|
|
15641
16280
|
}
|
|
15642
16281
|
updateSessionHealth(sessionId, updates);
|
|
15643
16282
|
}
|
|
@@ -15705,23 +16344,13 @@ function createSessionHealthRuntime(deps) {
|
|
|
15705
16344
|
updateSessionHealth(session.id, {
|
|
15706
16345
|
health_status: diagnosis.healthStatus,
|
|
15707
16346
|
health_reason: diagnosis.healthReason
|
|
15708
|
-
});
|
|
16347
|
+
}, { touch: false });
|
|
15709
16348
|
}
|
|
15710
16349
|
}
|
|
15711
16350
|
async function loadProcessProbe(session) {
|
|
15712
16351
|
const threadId = session.sdk_session_id?.trim();
|
|
15713
16352
|
if (!threadId || !deps.probeThreadProcess) return null;
|
|
15714
|
-
|
|
15715
|
-
const nowMs = Date.now();
|
|
15716
|
-
if (cached && nowMs - cached.checkedAtMs < HEALTH_PROCESS_PROBE_CACHE_MS) {
|
|
15717
|
-
return cached.result;
|
|
15718
|
-
}
|
|
15719
|
-
const result = await deps.probeThreadProcess(threadId);
|
|
15720
|
-
processProbeCache.set(session.id, {
|
|
15721
|
-
checkedAtMs: nowMs,
|
|
15722
|
-
result
|
|
15723
|
-
});
|
|
15724
|
-
return result;
|
|
16353
|
+
return deps.probeThreadProcess(threadId);
|
|
15725
16354
|
}
|
|
15726
16355
|
async function diagnoseSessionHealth(sessionId) {
|
|
15727
16356
|
const store = deps.getStore();
|
|
@@ -15729,16 +16358,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
15729
16358
|
if (!session) return null;
|
|
15730
16359
|
const base = computeBaseDiagnosis(session, Date.now());
|
|
15731
16360
|
const processProbe = await loadProcessProbe(session);
|
|
15732
|
-
|
|
16361
|
+
return applyStreamUiDiagnosis(
|
|
15733
16362
|
applyProcessProbeDiagnosis(base, processProbe),
|
|
15734
16363
|
Date.now()
|
|
15735
16364
|
);
|
|
15736
|
-
updateSessionHealth(sessionId, {
|
|
15737
|
-
health_status: diagnosis.healthStatus,
|
|
15738
|
-
health_reason: diagnosis.healthReason,
|
|
15739
|
-
last_health_check_at: deps.nowIso()
|
|
15740
|
-
});
|
|
15741
|
-
return diagnosis;
|
|
15742
16365
|
}
|
|
15743
16366
|
async function diagnoseAllActiveSessions() {
|
|
15744
16367
|
const store = deps.getStore();
|
|
@@ -15759,6 +16382,100 @@ function createSessionHealthRuntime(deps) {
|
|
|
15759
16382
|
};
|
|
15760
16383
|
}
|
|
15761
16384
|
|
|
16385
|
+
// src/lib/bridge/turns/desktop-terminal-router.ts
|
|
16386
|
+
function isTerminalRecord(record) {
|
|
16387
|
+
return record.type === "task_complete" || record.type === "task_aborted";
|
|
16388
|
+
}
|
|
16389
|
+
function toTerminalRecord(sessionId, desktopThreadId, record) {
|
|
16390
|
+
return {
|
|
16391
|
+
sessionId,
|
|
16392
|
+
desktopThreadId,
|
|
16393
|
+
turnId: record.turnId,
|
|
16394
|
+
text: record.content,
|
|
16395
|
+
outcome: record.type === "task_aborted" ? "aborted" : "completed",
|
|
16396
|
+
timestamp: record.timestamp
|
|
16397
|
+
};
|
|
16398
|
+
}
|
|
16399
|
+
async function routeDesktopRecords(sessionId, desktopThreadId, records, coordinator) {
|
|
16400
|
+
let terminalRecord = null;
|
|
16401
|
+
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
16402
|
+
if (!isTerminalRecord(records[index])) continue;
|
|
16403
|
+
terminalRecord = records[index];
|
|
16404
|
+
break;
|
|
16405
|
+
}
|
|
16406
|
+
if (!terminalRecord) {
|
|
16407
|
+
return {
|
|
16408
|
+
claimed: [],
|
|
16409
|
+
unclaimed: records,
|
|
16410
|
+
terminalClaimed: false
|
|
16411
|
+
};
|
|
16412
|
+
}
|
|
16413
|
+
const claim = await coordinator.claimDesktopTerminal(
|
|
16414
|
+
toTerminalRecord(sessionId, desktopThreadId, terminalRecord)
|
|
16415
|
+
);
|
|
16416
|
+
if (!claim.claimed) {
|
|
16417
|
+
return {
|
|
16418
|
+
claimed: [],
|
|
16419
|
+
unclaimed: records,
|
|
16420
|
+
terminalClaimed: false
|
|
16421
|
+
};
|
|
16422
|
+
}
|
|
16423
|
+
const claimedTurnId = terminalRecord.turnId;
|
|
16424
|
+
const claimed = claimedTurnId ? records.filter((record) => record.turnId === claimedTurnId) : [terminalRecord];
|
|
16425
|
+
const claimedSet = new Set(claimed.map((record) => record.signature));
|
|
16426
|
+
return {
|
|
16427
|
+
claimed,
|
|
16428
|
+
unclaimed: records.filter((record) => !claimedSet.has(record.signature)),
|
|
16429
|
+
terminalClaimed: true
|
|
16430
|
+
};
|
|
16431
|
+
}
|
|
16432
|
+
|
|
16433
|
+
// src/lib/bridge/turns/turn-coordinator.ts
|
|
16434
|
+
function createTurnCoordinator(deps = {}) {
|
|
16435
|
+
const activeTurnsBySession = /* @__PURE__ */ new Map();
|
|
16436
|
+
function registerInteractiveTurn(turn) {
|
|
16437
|
+
activeTurnsBySession.set(turn.sessionId, turn);
|
|
16438
|
+
}
|
|
16439
|
+
function getActiveTurn(sessionId) {
|
|
16440
|
+
return activeTurnsBySession.get(sessionId);
|
|
16441
|
+
}
|
|
16442
|
+
async function claimDesktopTerminal(terminal) {
|
|
16443
|
+
const turn = activeTurnsBySession.get(terminal.sessionId);
|
|
16444
|
+
if (!turn || turn.kind !== "im_desktop_reuse") {
|
|
16445
|
+
return { claimed: false };
|
|
16446
|
+
}
|
|
16447
|
+
if (turn.desktopThreadId && turn.desktopThreadId !== terminal.desktopThreadId) {
|
|
16448
|
+
return { claimed: false };
|
|
16449
|
+
}
|
|
16450
|
+
const finalized = await deps.finalizeTerminalTurn?.(turn, terminal);
|
|
16451
|
+
return finalized ? { claimed: true, turn } : { claimed: false, turn };
|
|
16452
|
+
}
|
|
16453
|
+
function releaseTurn(turnId) {
|
|
16454
|
+
for (const [sessionId, turn] of activeTurnsBySession) {
|
|
16455
|
+
if (turn.id !== turnId) continue;
|
|
16456
|
+
activeTurnsBySession.delete(sessionId);
|
|
16457
|
+
return;
|
|
16458
|
+
}
|
|
16459
|
+
}
|
|
16460
|
+
function releaseSessionTurn(sessionId, turnId) {
|
|
16461
|
+
const turn = activeTurnsBySession.get(sessionId);
|
|
16462
|
+
if (!turn) return;
|
|
16463
|
+
if (turnId && turn.id !== turnId) return;
|
|
16464
|
+
activeTurnsBySession.delete(sessionId);
|
|
16465
|
+
}
|
|
16466
|
+
function clear() {
|
|
16467
|
+
activeTurnsBySession.clear();
|
|
16468
|
+
}
|
|
16469
|
+
return {
|
|
16470
|
+
registerInteractiveTurn,
|
|
16471
|
+
getActiveTurn,
|
|
16472
|
+
claimDesktopTerminal,
|
|
16473
|
+
releaseTurn,
|
|
16474
|
+
releaseSessionTurn,
|
|
16475
|
+
clear
|
|
16476
|
+
};
|
|
16477
|
+
}
|
|
16478
|
+
|
|
15762
16479
|
// src/lib/bridge/bridge-manager.ts
|
|
15763
16480
|
var GLOBAL_KEY = "__bridge_manager__";
|
|
15764
16481
|
var DANGLING_MIRROR_THREAD_RETRY_LIMIT = 3;
|
|
@@ -15769,10 +16486,10 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
|
|
|
15769
16486
|
var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
15770
16487
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
15771
16488
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
15772
|
-
var
|
|
16489
|
+
var DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS = 3e4;
|
|
15773
16490
|
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
15774
16491
|
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
15775
|
-
var
|
|
16492
|
+
var MIRROR_TURN_BUFFER_TIMEOUT_MS = 6e5;
|
|
15776
16493
|
function describeUnknownError(error) {
|
|
15777
16494
|
if (error instanceof Error) {
|
|
15778
16495
|
return error.stack || `${error.name}: ${error.message}`;
|
|
@@ -15854,11 +16571,26 @@ function getState() {
|
|
|
15854
16571
|
return g[GLOBAL_KEY];
|
|
15855
16572
|
}
|
|
15856
16573
|
var INTERACTIVE_RUNTIME = createInteractiveRuntime(getState, {
|
|
15857
|
-
idleReminderMs: INTERACTIVE_IDLE_REMINDER_MS
|
|
15858
|
-
}, {
|
|
15859
16574
|
getStore: () => getBridgeContext().store,
|
|
15860
16575
|
nowIso: nowIso3
|
|
15861
16576
|
});
|
|
16577
|
+
function formatDesktopTerminalDetail(terminal) {
|
|
16578
|
+
if (terminal.outcome === "aborted") {
|
|
16579
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16580
|
+
}
|
|
16581
|
+
if (terminal.outcome === "failed") {
|
|
16582
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5F53\u524D\u4EFB\u52A1\u6267\u884C\u5931\u8D25\u3002";
|
|
16583
|
+
}
|
|
16584
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16585
|
+
}
|
|
16586
|
+
var TURN_COORDINATOR = createTurnCoordinator({
|
|
16587
|
+
finalizeTerminalTurn: (turn, terminal) => INTERACTIVE_RUNTIME.finalizeTerminalActiveTask(
|
|
16588
|
+
turn.sessionId,
|
|
16589
|
+
terminal.outcome,
|
|
16590
|
+
formatDesktopTerminalDetail(terminal),
|
|
16591
|
+
terminal.text
|
|
16592
|
+
)
|
|
16593
|
+
});
|
|
15862
16594
|
var SESSION_HEALTH_RUNTIME = createSessionHealthRuntime({
|
|
15863
16595
|
getStore: () => getBridgeContext().store,
|
|
15864
16596
|
nowIso: nowIso3,
|
|
@@ -15895,9 +16627,6 @@ function settleMirrorSuppression2(sessionId, suppressionId, durationMs = MIRROR_
|
|
|
15895
16627
|
durationMs
|
|
15896
16628
|
);
|
|
15897
16629
|
}
|
|
15898
|
-
function isMirrorSuppressed2(sessionId) {
|
|
15899
|
-
return isMirrorSuppressed(getMirrorSuppressionStore(), sessionId);
|
|
15900
|
-
}
|
|
15901
16630
|
function filterSuppressedMirrorRecords2(sessionId, records) {
|
|
15902
16631
|
return filterSuppressedMirrorRecords(
|
|
15903
16632
|
getMirrorSuppressionStore(),
|
|
@@ -15931,28 +16660,6 @@ function syncMirrorSessionStateSafe(sessionId, context) {
|
|
|
15931
16660
|
);
|
|
15932
16661
|
}
|
|
15933
16662
|
}
|
|
15934
|
-
function getMirrorStreamingAdapter(subscription) {
|
|
15935
|
-
const state = getState();
|
|
15936
|
-
const adapter = state.adapters.get(subscription.channelType);
|
|
15937
|
-
if (!adapter || !adapter.isRunning()) return null;
|
|
15938
|
-
if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
|
|
15939
|
-
if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
|
|
15940
|
-
return null;
|
|
15941
|
-
}
|
|
15942
|
-
return adapter;
|
|
15943
|
-
}
|
|
15944
|
-
function getMirrorStreamingText(subscription, turnState) {
|
|
15945
|
-
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
15946
|
-
const markdown = getFeedbackParseMode(subscription.channelType) === "Markdown";
|
|
15947
|
-
const rendered = formatMirrorMessage(
|
|
15948
|
-
title,
|
|
15949
|
-
turnState.userText,
|
|
15950
|
-
turnState.streamedText,
|
|
15951
|
-
markdown,
|
|
15952
|
-
true
|
|
15953
|
-
);
|
|
15954
|
-
return rendered || buildMirrorTitle(title, markdown);
|
|
15955
|
-
}
|
|
15956
16663
|
function getMirrorStructuredStreamStatusConfig() {
|
|
15957
16664
|
const { store } = getBridgeContext();
|
|
15958
16665
|
const idleStartSeconds = parseInt(store.getSetting("bridge_stream_status_idle_start_seconds") || "", 10);
|
|
@@ -15968,194 +16675,43 @@ function getMirrorStructuredStreamStatusConfig() {
|
|
|
15968
16675
|
)
|
|
15969
16676
|
};
|
|
15970
16677
|
}
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
}
|
|
15979
|
-
turnState.streamStarted = true;
|
|
15980
|
-
} catch {
|
|
15981
|
-
}
|
|
15982
|
-
}
|
|
15983
|
-
function createMirrorStreamFeedbackTarget(subscription, turnState, adapter) {
|
|
15984
|
-
return {
|
|
15985
|
-
adapter,
|
|
15986
|
-
channelType: subscription.channelType,
|
|
15987
|
-
chatId: subscription.chatId,
|
|
15988
|
-
streamKey: turnState.streamKey,
|
|
15989
|
-
ensureStarted: () => {
|
|
15990
|
-
startMirrorStreaming(subscription, turnState);
|
|
15991
|
-
}
|
|
15992
|
-
};
|
|
15993
|
-
}
|
|
15994
|
-
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
15995
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15996
|
-
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
15997
|
-
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
15998
|
-
const startedAtMs = Date.parse(turnState.startedAt);
|
|
15999
|
-
if (!Number.isFinite(startedAtMs)) return;
|
|
16000
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
16001
|
-
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
16002
|
-
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
16003
|
-
return;
|
|
16004
|
-
}
|
|
16005
|
-
const statusText = formatInteractiveRuntimeStatus(
|
|
16006
|
-
Math.max(0, nowMs - startedAtMs),
|
|
16007
|
-
options.lastResponseAgeMs,
|
|
16008
|
-
turnState.statusNote
|
|
16009
|
-
);
|
|
16010
|
-
if (turnState.lastStatusText === statusText) return;
|
|
16011
|
-
pushStreamFeedbackStatus(
|
|
16012
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16013
|
-
statusText
|
|
16014
|
-
);
|
|
16015
|
-
turnState.lastStatusText = statusText;
|
|
16016
|
-
turnState.lastStatusAt = nowMs;
|
|
16017
|
-
}
|
|
16678
|
+
var MIRROR_FEEDBACK = createMirrorFeedbackController({
|
|
16679
|
+
getAdapter: (channelType) => getState().adapters.get(channelType) || null,
|
|
16680
|
+
getThreadTitle: (threadId) => getDesktopThreadTitle(threadId),
|
|
16681
|
+
nowIso: nowIso3,
|
|
16682
|
+
eventBatchLimit: MIRROR_EVENT_BATCH_LIMIT,
|
|
16683
|
+
deliverResponse
|
|
16684
|
+
});
|
|
16018
16685
|
function refreshMirrorStreamingStatus(subscription, nowMs = Date.now(), config2 = getMirrorStructuredStreamStatusConfig()) {
|
|
16019
|
-
|
|
16020
|
-
if (!pendingTurn?.streamStarted) return;
|
|
16021
|
-
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
16022
|
-
if (!Number.isFinite(startedAtMs)) return;
|
|
16023
|
-
const elapsedMs = nowMs - startedAtMs;
|
|
16024
|
-
if (elapsedMs < config2.idleStartMs) return;
|
|
16025
|
-
const lastResponseAtMs = pendingTurn.lastResponseAt ? Date.parse(pendingTurn.lastResponseAt) : NaN;
|
|
16026
|
-
const lastResponseAgeMs = Number.isFinite(lastResponseAtMs) ? nowMs - lastResponseAtMs : null;
|
|
16027
|
-
if (lastResponseAgeMs != null && lastResponseAgeMs < config2.heartbeatMs) return;
|
|
16028
|
-
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
16029
|
-
nowMs,
|
|
16030
|
-
lastResponseAgeMs,
|
|
16031
|
-
minIntervalMs: config2.heartbeatMs
|
|
16032
|
-
});
|
|
16686
|
+
MIRROR_FEEDBACK.refreshMirrorStreamingStatus(subscription, nowMs, config2);
|
|
16033
16687
|
}
|
|
16034
16688
|
function refreshActiveMirrorStreamingStatuses(nowMs = Date.now()) {
|
|
16035
16689
|
for (const subscription of getState().mirrorSubscriptions.values()) {
|
|
16036
16690
|
refreshMirrorStreamingStatus(subscription, nowMs);
|
|
16037
16691
|
}
|
|
16038
16692
|
}
|
|
16039
|
-
function updateMirrorStreaming(subscription, turnState) {
|
|
16040
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16041
|
-
if (!adapter) return;
|
|
16042
|
-
pushStreamFeedbackText(
|
|
16043
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16044
|
-
getMirrorStreamingText(subscription, turnState)
|
|
16045
|
-
);
|
|
16046
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16047
|
-
}
|
|
16048
|
-
function updateMirrorToolProgress(subscription, turnState) {
|
|
16049
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16050
|
-
if (!adapter) return;
|
|
16051
|
-
pushStreamFeedbackTools(
|
|
16052
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16053
|
-
Array.from(turnState.toolCalls.values())
|
|
16054
|
-
);
|
|
16055
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16056
|
-
}
|
|
16057
|
-
function updateMirrorTaskProgress(subscription, turnState) {
|
|
16058
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16059
|
-
if (!adapter) return;
|
|
16060
|
-
pushStreamFeedbackTasks(
|
|
16061
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16062
|
-
turnState.taskItems
|
|
16063
|
-
);
|
|
16064
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16065
|
-
}
|
|
16066
|
-
function updateMirrorStatusProgress(subscription, turnState) {
|
|
16067
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16068
|
-
if (!adapter) return;
|
|
16069
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16070
|
-
}
|
|
16071
16693
|
function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
16072
|
-
|
|
16073
|
-
const pendingTurn = subscription.pendingTurn;
|
|
16074
|
-
if (!adapter || !pendingTurn?.streamStarted) return;
|
|
16075
|
-
void finalizeStreamFeedback(
|
|
16076
|
-
createMirrorStreamFeedbackTarget(subscription, pendingTurn, adapter),
|
|
16077
|
-
status,
|
|
16078
|
-
getMirrorStreamingText(subscription, pendingTurn)
|
|
16079
|
-
);
|
|
16080
|
-
}
|
|
16081
|
-
async function deliverMirrorTurn(subscription, turn) {
|
|
16082
|
-
const state = getState();
|
|
16083
|
-
const adapter = state.adapters.get(subscription.channelType);
|
|
16084
|
-
if (!adapter || !adapter.isRunning()) return;
|
|
16085
|
-
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
16086
|
-
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
16087
|
-
const markdown = responseParseMode === "Markdown";
|
|
16088
|
-
const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
|
|
16089
|
-
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
|
|
16090
|
-
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
16091
|
-
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
16092
|
-
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
16093
|
-
const streamText = renderFeedbackText(
|
|
16094
|
-
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
16095
|
-
responseParseMode
|
|
16096
|
-
);
|
|
16097
|
-
if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
16098
|
-
try {
|
|
16099
|
-
const finalized = await adapter.onStreamEnd(
|
|
16100
|
-
subscription.chatId,
|
|
16101
|
-
turn.status,
|
|
16102
|
-
streamText,
|
|
16103
|
-
turn.streamKey
|
|
16104
|
-
);
|
|
16105
|
-
if (finalized) {
|
|
16106
|
-
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
16107
|
-
return;
|
|
16108
|
-
}
|
|
16109
|
-
} catch (error) {
|
|
16110
|
-
console.warn("[bridge-manager] Mirror stream finalize failed:", error instanceof Error ? error.message : error);
|
|
16111
|
-
}
|
|
16112
|
-
}
|
|
16113
|
-
if (!text2) return;
|
|
16114
|
-
const response = await deliver(adapter, {
|
|
16115
|
-
address: {
|
|
16116
|
-
channelType: subscription.channelType,
|
|
16117
|
-
chatId: subscription.chatId
|
|
16118
|
-
},
|
|
16119
|
-
text: text2,
|
|
16120
|
-
parseMode: responseParseMode
|
|
16121
|
-
}, {
|
|
16122
|
-
sessionId: subscription.sessionId,
|
|
16123
|
-
dedupKey: `mirror:${subscription.bindingId}:${turn.signature}`
|
|
16124
|
-
});
|
|
16125
|
-
if (!response.ok) {
|
|
16126
|
-
throw new Error(response.error || "mirror delivery failed");
|
|
16127
|
-
}
|
|
16128
|
-
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
16694
|
+
MIRROR_FEEDBACK.stopMirrorStreaming(subscription, status);
|
|
16129
16695
|
}
|
|
16130
16696
|
async function deliverMirrorTurns(subscription, turns) {
|
|
16131
|
-
|
|
16132
|
-
for (const turn of turns.slice(0, MIRROR_EVENT_BATCH_LIMIT)) {
|
|
16133
|
-
try {
|
|
16134
|
-
await deliverMirrorTurn(subscription, turn);
|
|
16135
|
-
deliveredCount += 1;
|
|
16136
|
-
} catch (error) {
|
|
16137
|
-
return { deliveredCount, error };
|
|
16138
|
-
}
|
|
16139
|
-
}
|
|
16140
|
-
return { deliveredCount };
|
|
16697
|
+
return MIRROR_FEEDBACK.deliverMirrorTurns(subscription, turns);
|
|
16141
16698
|
}
|
|
16142
|
-
var MIRROR_TURN_HOOKS =
|
|
16143
|
-
onStreamText: updateMirrorStreaming,
|
|
16144
|
-
onStatusProgress: updateMirrorStatusProgress,
|
|
16145
|
-
onTaskProgress: updateMirrorTaskProgress,
|
|
16146
|
-
onToolProgress: updateMirrorToolProgress
|
|
16147
|
-
};
|
|
16699
|
+
var MIRROR_TURN_HOOKS = MIRROR_FEEDBACK.hooks;
|
|
16148
16700
|
function consumeMirrorRecords2(subscription, records) {
|
|
16149
16701
|
return consumeMirrorRecords(subscription, records, MIRROR_TURN_HOOKS);
|
|
16150
16702
|
}
|
|
16151
16703
|
function flushTimedOutMirrorTurn2(subscription, nowMs = Date.now()) {
|
|
16152
|
-
|
|
16704
|
+
if (subscription.pendingTurn?.streamStarted) {
|
|
16705
|
+
return null;
|
|
16706
|
+
}
|
|
16707
|
+
return flushTimedOutMirrorTurn(subscription, MIRROR_TURN_BUFFER_TIMEOUT_MS, nowMs);
|
|
16153
16708
|
}
|
|
16154
16709
|
function hasPendingMirrorWork2(subscription) {
|
|
16155
16710
|
return hasPendingMirrorWork(subscription);
|
|
16156
16711
|
}
|
|
16157
16712
|
function consumeBufferedMirrorTurns2(subscription, nowMs = Date.now()) {
|
|
16158
|
-
|
|
16713
|
+
const timeoutMs = subscription.pendingTurn?.streamStarted ? Number.POSITIVE_INFINITY : MIRROR_TURN_BUFFER_TIMEOUT_MS;
|
|
16714
|
+
return consumeBufferedMirrorTurns(subscription, timeoutMs, nowMs, MIRROR_TURN_HOOKS);
|
|
16159
16715
|
}
|
|
16160
16716
|
var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
16161
16717
|
watchDebounceMs: MIRROR_WATCH_DEBOUNCE_MS,
|
|
@@ -16167,11 +16723,16 @@ var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
|
16167
16723
|
describeUnknownError,
|
|
16168
16724
|
getDesktopSessionByThreadIdSafe,
|
|
16169
16725
|
syncMirrorSessionStateSafe,
|
|
16170
|
-
isMirrorSuppressed: isMirrorSuppressed2,
|
|
16171
16726
|
filterSuppressedMirrorRecords: filterSuppressedMirrorRecords2,
|
|
16172
16727
|
observeSessionHealthRecords: (sessionId, threadId, records) => {
|
|
16173
16728
|
SESSION_HEALTH_RUNTIME.observeDesktopMirrorRecords(sessionId, threadId, records);
|
|
16174
16729
|
},
|
|
16730
|
+
routeDesktopRecords: (sessionId, threadId, records) => routeDesktopRecords(
|
|
16731
|
+
sessionId,
|
|
16732
|
+
threadId,
|
|
16733
|
+
records,
|
|
16734
|
+
TURN_COORDINATOR
|
|
16735
|
+
),
|
|
16175
16736
|
consumeMirrorRecords: consumeMirrorRecords2,
|
|
16176
16737
|
flushTimedOutMirrorTurn: (subscription) => flushTimedOutMirrorTurn2(subscription),
|
|
16177
16738
|
hasPendingMirrorWork: hasPendingMirrorWork2,
|
|
@@ -16229,15 +16790,14 @@ async function start() {
|
|
|
16229
16790
|
void ADAPTER_RUNTIME.syncConfiguredAdapters({ startLoops: true }).catch((err) => {
|
|
16230
16791
|
console.error("[bridge-manager] Adapter reconcile failed:", err);
|
|
16231
16792
|
});
|
|
16232
|
-
void INTERACTIVE_RUNTIME.reconcileIdleInteractiveTasks().catch((err) => {
|
|
16233
|
-
console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
|
|
16234
|
-
});
|
|
16235
16793
|
try {
|
|
16236
16794
|
SESSION_HEALTH_RUNTIME.reconcileSessionHealth();
|
|
16237
|
-
INTERACTIVE_RUNTIME.reconcileTerminalSessionRuntimeState();
|
|
16238
16795
|
} catch (err) {
|
|
16239
16796
|
console.error("[bridge-manager] Session health reconcile failed:", describeUnknownError(err));
|
|
16240
16797
|
}
|
|
16798
|
+
void INTERACTIVE_RUNTIME.reconcileTerminalSessionRuntimeState().catch((err) => {
|
|
16799
|
+
console.error("[bridge-manager] Terminal interactive reconcile failed:", describeUnknownError(err));
|
|
16800
|
+
});
|
|
16241
16801
|
}, 5e3);
|
|
16242
16802
|
state.mirrorPollTimer = setInterval(() => {
|
|
16243
16803
|
void reconcileMirrorSubscriptions().catch((err) => {
|
|
@@ -16420,6 +16980,7 @@ async function handleMessage(adapter, msg) {
|
|
|
16420
16980
|
try {
|
|
16421
16981
|
await runInteractiveMessage(adapter, msg, text2, hasAttachments ? msg.attachments : void 0, {
|
|
16422
16982
|
registerInteractiveTask: (task) => INTERACTIVE_RUNTIME.registerInteractiveTask(task),
|
|
16983
|
+
registerBridgeTurn: (turn) => TURN_COORDINATOR.registerInteractiveTurn(turn),
|
|
16423
16984
|
resetMirrorSessionForInteractiveRun,
|
|
16424
16985
|
isCurrentInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.isCurrentInteractiveTask(sessionId, taskId),
|
|
16425
16986
|
touchInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.touchInteractiveTask(sessionId, taskId),
|
|
@@ -16436,8 +16997,10 @@ async function handleMessage(adapter, msg) {
|
|
|
16436
16997
|
abortMirrorSuppression: abortMirrorSuppression2,
|
|
16437
16998
|
settleMirrorSuppression: settleMirrorSuppression2,
|
|
16438
16999
|
releaseInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.releaseInteractiveTask(sessionId, taskId),
|
|
17000
|
+
releaseBridgeTurn: (sessionId, taskId) => TURN_COORDINATOR.releaseSessionTurn(sessionId, taskId),
|
|
16439
17001
|
deliverResponse,
|
|
16440
|
-
persistSdkSessionUpdate
|
|
17002
|
+
persistSdkSessionUpdate,
|
|
17003
|
+
desktopTerminalFinalizationTimeoutMs: DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS
|
|
16441
17004
|
});
|
|
16442
17005
|
} finally {
|
|
16443
17006
|
ack();
|
|
@@ -16749,7 +17312,7 @@ var JsonFileStore = class {
|
|
|
16749
17312
|
findSessionBySdkSessionId(sdkSessionId) {
|
|
16750
17313
|
this.reloadSessions();
|
|
16751
17314
|
for (const session of this.sessions.values()) {
|
|
16752
|
-
if (session.sdk_session_id === sdkSessionId) {
|
|
17315
|
+
if (session.sdk_session_id === sdkSessionId || session.codex_thread_id === sdkSessionId || session.desktop_thread_id === sdkSessionId) {
|
|
16753
17316
|
return session;
|
|
16754
17317
|
}
|
|
16755
17318
|
}
|
|
@@ -16786,7 +17349,7 @@ var JsonFileStore = class {
|
|
|
16786
17349
|
this.persistSessions();
|
|
16787
17350
|
}
|
|
16788
17351
|
}
|
|
16789
|
-
updateSession(sessionId, updates) {
|
|
17352
|
+
updateSession(sessionId, updates, options) {
|
|
16790
17353
|
this.reloadSessions();
|
|
16791
17354
|
const session = this.sessions.get(sessionId);
|
|
16792
17355
|
if (!session) return;
|
|
@@ -16794,7 +17357,7 @@ var JsonFileStore = class {
|
|
|
16794
17357
|
...session,
|
|
16795
17358
|
...updates,
|
|
16796
17359
|
id: session.id,
|
|
16797
|
-
updated_at: now()
|
|
17360
|
+
updated_at: options?.touch === false ? session.updated_at : now()
|
|
16798
17361
|
};
|
|
16799
17362
|
this.sessions.set(sessionId, next);
|
|
16800
17363
|
this.persistSessions();
|
|
@@ -16883,6 +17446,15 @@ var JsonFileStore = class {
|
|
|
16883
17446
|
const s = this.sessions.get(sessionId);
|
|
16884
17447
|
if (s) {
|
|
16885
17448
|
s.sdk_session_id = sdkSessionId;
|
|
17449
|
+
if (sdkSessionId) {
|
|
17450
|
+
s.codex_thread_id = sdkSessionId;
|
|
17451
|
+
s.thread_origin = s.thread_origin || "bridge";
|
|
17452
|
+
} else {
|
|
17453
|
+
delete s.codex_thread_id;
|
|
17454
|
+
if (s.thread_origin !== "desktop") {
|
|
17455
|
+
delete s.thread_origin;
|
|
17456
|
+
}
|
|
17457
|
+
}
|
|
16886
17458
|
s.updated_at = now();
|
|
16887
17459
|
this.persistSessions();
|
|
16888
17460
|
}
|