codex-to-im 1.0.44 → 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 +836 -401
- package/dist/ui-server.mjs +48 -10
- package/docs/dev-plan.md +653 -0
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -10473,6 +10473,18 @@ function readDesktopSessionEventStream(threadId) {
|
|
|
10473
10473
|
return readDesktopSessionEventStreamByFilePath(session.filePath);
|
|
10474
10474
|
}
|
|
10475
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
|
+
|
|
10476
10488
|
// src/session-bindings.ts
|
|
10477
10489
|
function asChannelProvider(value) {
|
|
10478
10490
|
return value === "feishu" || value === "weixin" ? value : void 0;
|
|
@@ -10518,15 +10530,31 @@ function assertBindingTargetAvailable(store, current, opts) {
|
|
|
10518
10530
|
function getSessionMode(store, session) {
|
|
10519
10531
|
return session.preferred_mode || store.getSetting("bridge_default_mode") || "code";
|
|
10520
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
|
+
}
|
|
10521
10545
|
function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
10522
10546
|
const session = store.getSession(sessionId);
|
|
10523
10547
|
if (!session) return null;
|
|
10524
10548
|
assertBindingTargetAvailable(
|
|
10525
10549
|
store,
|
|
10526
10550
|
{ channelType, chatId },
|
|
10527
|
-
{
|
|
10551
|
+
{
|
|
10552
|
+
sessionId: session.id,
|
|
10553
|
+
sdkSessionId: getCodexThreadId(session) || getExplicitDesktopThreadId(session)
|
|
10554
|
+
}
|
|
10528
10555
|
);
|
|
10529
10556
|
const meta = resolveChannelMeta(channelType);
|
|
10557
|
+
const sdkSessionId = getBindingResumeThreadId(session);
|
|
10530
10558
|
return store.upsertChannelBinding({
|
|
10531
10559
|
channelType,
|
|
10532
10560
|
channelProvider: meta.provider,
|
|
@@ -10535,7 +10563,7 @@ function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
|
10535
10563
|
chatUserId: chatMeta?.chatUserId,
|
|
10536
10564
|
chatDisplayName: chatMeta?.chatDisplayName,
|
|
10537
10565
|
codepilotSessionId: session.id,
|
|
10538
|
-
sdkSessionId
|
|
10566
|
+
sdkSessionId,
|
|
10539
10567
|
workingDirectory: session.working_directory,
|
|
10540
10568
|
model: session.model,
|
|
10541
10569
|
mode: getSessionMode(store, session)
|
|
@@ -10550,6 +10578,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
10550
10578
|
const meta = resolveChannelMeta(channelType);
|
|
10551
10579
|
const existing = store.findSessionBySdkSessionId(sdkSessionId);
|
|
10552
10580
|
if (existing) {
|
|
10581
|
+
markSessionAsDesktopBacked(store, existing.id, sdkSessionId);
|
|
10553
10582
|
return store.upsertChannelBinding({
|
|
10554
10583
|
channelType,
|
|
10555
10584
|
channelProvider: meta.provider,
|
|
@@ -10574,7 +10603,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
10574
10603
|
workingDirectory,
|
|
10575
10604
|
"code"
|
|
10576
10605
|
);
|
|
10577
|
-
store
|
|
10606
|
+
markSessionAsDesktopBacked(store, session.id, sdkSessionId);
|
|
10578
10607
|
return store.upsertChannelBinding({
|
|
10579
10608
|
channelType,
|
|
10580
10609
|
channelProvider: meta.provider,
|
|
@@ -10947,15 +10976,17 @@ async function deliver(adapter, message, opts) {
|
|
|
10947
10976
|
} catch {
|
|
10948
10977
|
}
|
|
10949
10978
|
}
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
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
|
+
}
|
|
10959
10990
|
}
|
|
10960
10991
|
return { ok: true, messageId: lastMessageId };
|
|
10961
10992
|
}
|
|
@@ -11552,7 +11583,6 @@ function buildHealthCommandResponse(title, diagnosis, markdown = false) {
|
|
|
11552
11583
|
title,
|
|
11553
11584
|
[
|
|
11554
11585
|
["Session", diagnosis.sessionId],
|
|
11555
|
-
["\u68C0\u67E5\u65F6\u95F4", formatCommandTimestamp(diagnosis.checkedAt)],
|
|
11556
11586
|
["\u8FD0\u884C\u72B6\u6001", formatRuntimeStatus({ runtime_status: diagnosis.runtimeStatus, queued_count: 0 })],
|
|
11557
11587
|
["\u5065\u5EB7\u72B6\u6001", formatHealthStatusLabel(diagnosis.healthStatus)],
|
|
11558
11588
|
["\u5F53\u524D\u9636\u6BB5", currentStage],
|
|
@@ -11573,7 +11603,6 @@ function buildHealthListResponse(diagnoses, markdown = false) {
|
|
|
11573
11603
|
diagnoses.map((diagnosis) => ({
|
|
11574
11604
|
heading: diagnosis.sessionId,
|
|
11575
11605
|
details: [
|
|
11576
|
-
`\u68C0\u67E5\u65F6\u95F4\uFF1A${formatCommandTimestamp(diagnosis.checkedAt)}`,
|
|
11577
11606
|
`\u5065\u5EB7\u72B6\u6001\uFF1A${formatHealthStatusLabel(diagnosis.healthStatus)}`,
|
|
11578
11607
|
`\u5F53\u524D\u9636\u6BB5\uFF1A${diagnosis.activeToolName ? `\u5DE5\u5177 \xB7 ${diagnosis.activeToolName}` : diagnosis.lastProgressType || "-"}`,
|
|
11579
11608
|
`\u6700\u540E\u8FDB\u5C55\uFF1A${formatCommandTimestamp(diagnosis.lastProgressAt)}`,
|
|
@@ -11681,6 +11710,8 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
|
11681
11710
|
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
11682
11711
|
startedAt: safeTimestamp,
|
|
11683
11712
|
lastActivityAt: safeTimestamp,
|
|
11713
|
+
lastContentResponseAt: null,
|
|
11714
|
+
lastResponseAt: null,
|
|
11684
11715
|
lastStatusText: null,
|
|
11685
11716
|
lastStatusAt: 0,
|
|
11686
11717
|
statusNote: null,
|
|
@@ -11727,8 +11758,14 @@ function ensureMirrorTurnState(subscription, record) {
|
|
|
11727
11758
|
}
|
|
11728
11759
|
return subscription.pendingTurn;
|
|
11729
11760
|
}
|
|
11730
|
-
function
|
|
11731
|
-
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;
|
|
11732
11769
|
}
|
|
11733
11770
|
function finalizeMirrorTurn(subscription, signature, timestamp, status, preferredText) {
|
|
11734
11771
|
const pendingTurn = subscription.pendingTurn;
|
|
@@ -11801,7 +11838,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11801
11838
|
if (text2) {
|
|
11802
11839
|
pendingTurn.lastAssistantText = text2;
|
|
11803
11840
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11804
|
-
|
|
11841
|
+
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11805
11842
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
11806
11843
|
}
|
|
11807
11844
|
} else if (record.role === "commentary") {
|
|
@@ -11809,7 +11846,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11809
11846
|
if (text2) {
|
|
11810
11847
|
pendingTurn.lastCommentaryText = text2;
|
|
11811
11848
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11812
|
-
|
|
11849
|
+
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11813
11850
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
11814
11851
|
}
|
|
11815
11852
|
}
|
|
@@ -11820,14 +11857,14 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11820
11857
|
const text2 = record.content.trim();
|
|
11821
11858
|
if (!text2) continue;
|
|
11822
11859
|
pendingTurn.statusNote = text2;
|
|
11823
|
-
|
|
11860
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11824
11861
|
hooks.onStatusProgress?.(subscription, pendingTurn);
|
|
11825
11862
|
continue;
|
|
11826
11863
|
}
|
|
11827
11864
|
if (record.type === "plan_update") {
|
|
11828
11865
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
11829
11866
|
pendingTurn.taskItems = record.tasks || [];
|
|
11830
|
-
|
|
11867
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11831
11868
|
hooks.onTaskProgress?.(subscription, pendingTurn);
|
|
11832
11869
|
continue;
|
|
11833
11870
|
}
|
|
@@ -11840,7 +11877,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11840
11877
|
name: toolName,
|
|
11841
11878
|
status: "running"
|
|
11842
11879
|
});
|
|
11843
|
-
|
|
11880
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11844
11881
|
hooks.onToolProgress?.(subscription, pendingTurn);
|
|
11845
11882
|
continue;
|
|
11846
11883
|
}
|
|
@@ -11853,7 +11890,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11853
11890
|
name: existing?.name || record.toolName || "tool",
|
|
11854
11891
|
status: record.isError ? "error" : "complete"
|
|
11855
11892
|
});
|
|
11856
|
-
|
|
11893
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11857
11894
|
hooks.onToolProgress?.(subscription, pendingTurn);
|
|
11858
11895
|
continue;
|
|
11859
11896
|
}
|
|
@@ -12022,9 +12059,6 @@ function abortMirrorSuppression(store, sessionId, config2, suppressionId, nowMs
|
|
|
12022
12059
|
}
|
|
12023
12060
|
target.until = nowMs + config2.suppressionWindowMs;
|
|
12024
12061
|
}
|
|
12025
|
-
function isMirrorSuppressed(store, sessionId, nowMs = Date.now()) {
|
|
12026
|
-
return getMirrorSuppressionStates(store, sessionId, nowMs).length > 0;
|
|
12027
|
-
}
|
|
12028
12062
|
function filterSuppressedMirrorRecords(store, sessionId, records, config2, nowMs = Date.now()) {
|
|
12029
12063
|
const suppressions = getMirrorSuppressionStates(store, sessionId, nowMs);
|
|
12030
12064
|
const ignoredTurnIds = cleanupIgnoredMirrorTurns(store, sessionId, nowMs);
|
|
@@ -12661,7 +12695,7 @@ function supportsOutboundArtifacts(provider) {
|
|
|
12661
12695
|
}
|
|
12662
12696
|
|
|
12663
12697
|
// src/lib/bridge/feedback-delivery.ts
|
|
12664
|
-
async function deliverTextResponse(adapter, address, responseText, sessionId, replyToMessageId) {
|
|
12698
|
+
async function deliverTextResponse(adapter, address, responseText, sessionId, replyToMessageId, options) {
|
|
12665
12699
|
if (!responseText.trim()) return { ok: true };
|
|
12666
12700
|
const parseMode = getFeedbackParseMode(adapter.channelType);
|
|
12667
12701
|
const renderedText = renderFeedbackText(responseText, parseMode);
|
|
@@ -12671,14 +12705,14 @@ async function deliverTextResponse(adapter, address, responseText, sessionId, re
|
|
|
12671
12705
|
text: responseText,
|
|
12672
12706
|
parseMode: "Markdown",
|
|
12673
12707
|
replyToMessageId
|
|
12674
|
-
}, { sessionId });
|
|
12708
|
+
}, { sessionId, audit: options?.audit });
|
|
12675
12709
|
}
|
|
12676
12710
|
return deliver(adapter, {
|
|
12677
12711
|
address,
|
|
12678
12712
|
text: parseMode === "Markdown" ? responseText : renderedText,
|
|
12679
12713
|
parseMode,
|
|
12680
12714
|
replyToMessageId
|
|
12681
|
-
}, { sessionId });
|
|
12715
|
+
}, { sessionId, audit: options?.audit });
|
|
12682
12716
|
}
|
|
12683
12717
|
async function deliverBridgeNotice(adapter, address, text2, options) {
|
|
12684
12718
|
return deliverTextResponse(
|
|
@@ -12686,7 +12720,8 @@ async function deliverBridgeNotice(adapter, address, text2, options) {
|
|
|
12686
12720
|
address,
|
|
12687
12721
|
text2,
|
|
12688
12722
|
options?.sessionId,
|
|
12689
|
-
options?.replyToMessageId
|
|
12723
|
+
options?.replyToMessageId,
|
|
12724
|
+
{ audit: options?.audit }
|
|
12690
12725
|
);
|
|
12691
12726
|
}
|
|
12692
12727
|
async function deliverResponse(adapter, address, responseText, sessionId, replyToMessageId, attachments = []) {
|
|
@@ -12802,6 +12837,7 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
12802
12837
|
}
|
|
12803
12838
|
let response = "";
|
|
12804
12839
|
let responseParseMode = getFeedbackParseMode(adapter.channelType);
|
|
12840
|
+
let auditResponse = true;
|
|
12805
12841
|
const currentBinding = store.getChannelBinding(msg.address.channelType, msg.address.chatId);
|
|
12806
12842
|
switch (command) {
|
|
12807
12843
|
case "/start":
|
|
@@ -13134,6 +13170,7 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13134
13170
|
break;
|
|
13135
13171
|
}
|
|
13136
13172
|
if (!args) {
|
|
13173
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13137
13174
|
const currentModel = resolveDisplayedModel(
|
|
13138
13175
|
binding,
|
|
13139
13176
|
session,
|
|
@@ -13145,14 +13182,14 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13145
13182
|
[["\u6A21\u578B", formatDisplayedModel(currentModel)]],
|
|
13146
13183
|
[
|
|
13147
13184
|
getAvailableModelChoicesText(),
|
|
13148
|
-
|
|
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",
|
|
13149
13186
|
"\u6A21\u578B\u5207\u6362\u53EA\u5F71\u54CD\u540E\u7EED\u4ECE IM \u53D1\u8D77\u7684 Codex CLI \u8BF7\u6C42\u3002"
|
|
13150
13187
|
],
|
|
13151
13188
|
responseParseMode === "Markdown"
|
|
13152
13189
|
);
|
|
13153
13190
|
break;
|
|
13154
13191
|
}
|
|
13155
|
-
if (
|
|
13192
|
+
if (getExplicitDesktopThreadId(session)) {
|
|
13156
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";
|
|
13157
13194
|
break;
|
|
13158
13195
|
}
|
|
@@ -13203,9 +13240,32 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13203
13240
|
break;
|
|
13204
13241
|
}
|
|
13205
13242
|
case "/status": {
|
|
13206
|
-
|
|
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
|
+
}
|
|
13207
13254
|
const session = store.getSession(binding.codepilotSessionId);
|
|
13208
|
-
|
|
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);
|
|
13209
13269
|
const sandboxMode = resolveEffectiveSandboxMode();
|
|
13210
13270
|
const reasoningEffort = resolveEffectiveReasoningEffort(session);
|
|
13211
13271
|
const currentModel = resolveDisplayedModel(
|
|
@@ -13229,21 +13289,25 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13229
13289
|
["\u601D\u8003\u7EA7\u522B", formatReasoningEffort(reasoningEffort)]
|
|
13230
13290
|
],
|
|
13231
13291
|
[
|
|
13232
|
-
|
|
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"
|
|
13233
13293
|
],
|
|
13234
13294
|
responseParseMode === "Markdown"
|
|
13235
13295
|
);
|
|
13236
13296
|
break;
|
|
13237
13297
|
}
|
|
13238
13298
|
case "/health": {
|
|
13299
|
+
auditResponse = false;
|
|
13239
13300
|
if (args === "all") {
|
|
13240
13301
|
const diagnoses = await deps.diagnoseAllActiveSessions();
|
|
13241
13302
|
response = diagnoses.length > 0 ? buildHealthListResponse(diagnoses, responseParseMode === "Markdown") : "\u5F53\u524D\u6CA1\u6709\u68C0\u6D4B\u5230\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u3002";
|
|
13242
13303
|
break;
|
|
13243
13304
|
}
|
|
13244
|
-
const binding = currentBinding || resolve(msg.address);
|
|
13245
13305
|
const explicitTargetSessionId = args.trim();
|
|
13246
|
-
const targetSessionId = explicitTargetSessionId ||
|
|
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
|
+
}
|
|
13247
13311
|
const diagnosis = await deps.diagnoseSessionHealth(targetSessionId);
|
|
13248
13312
|
if (!diagnosis) {
|
|
13249
13313
|
response = `\u6CA1\u6709\u627E\u5230\u4F1A\u8BDD ${targetSessionId}\u3002`;
|
|
@@ -13262,15 +13326,16 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13262
13326
|
break;
|
|
13263
13327
|
}
|
|
13264
13328
|
const limit = getHistoryMessageLimit();
|
|
13265
|
-
const
|
|
13329
|
+
const session = store.getSession(currentBinding.codepilotSessionId);
|
|
13330
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13331
|
+
const desktopMessages = desktopThreadId ? readDesktopSessionMessages(desktopThreadId, limit) : [];
|
|
13266
13332
|
const { messages: storedMessages } = store.getMessages(currentBinding.codepilotSessionId, { limit });
|
|
13267
13333
|
const messages = desktopMessages.length > 0 ? desktopMessages : storedMessages;
|
|
13268
13334
|
if (messages.length === 0) {
|
|
13269
13335
|
response = "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u5386\u53F2\u6D88\u606F\u3002";
|
|
13270
13336
|
break;
|
|
13271
13337
|
}
|
|
13272
|
-
const threadTitle = getDesktopThreadTitle(
|
|
13273
|
-
const session = store.getSession(currentBinding.codepilotSessionId);
|
|
13338
|
+
const threadTitle = getDesktopThreadTitle(desktopThreadId);
|
|
13274
13339
|
const header = buildCommandFields(
|
|
13275
13340
|
"\u6700\u8FD1\u5BF9\u8BDD\uFF08raw\uFF09",
|
|
13276
13341
|
[
|
|
@@ -13402,7 +13467,8 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
13402
13467
|
}
|
|
13403
13468
|
if (response) {
|
|
13404
13469
|
await deliverBridgeNotice(adapter, msg.address, response, {
|
|
13405
|
-
replyToMessageId: msg.messageId
|
|
13470
|
+
replyToMessageId: msg.messageId,
|
|
13471
|
+
audit: auditResponse
|
|
13406
13472
|
});
|
|
13407
13473
|
}
|
|
13408
13474
|
}
|
|
@@ -13414,6 +13480,39 @@ import path13 from "node:path";
|
|
|
13414
13480
|
import fs8 from "fs";
|
|
13415
13481
|
import path12 from "path";
|
|
13416
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
|
|
13417
13516
|
init_runtime_options();
|
|
13418
13517
|
|
|
13419
13518
|
// src/lib/bridge/sse-stream-decoder.ts
|
|
@@ -13792,8 +13891,8 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13792
13891
|
if (contentBlocks.length > 0) {
|
|
13793
13892
|
for (const block2 of contentBlocks) {
|
|
13794
13893
|
if (block2.type !== "text") continue;
|
|
13795
|
-
const parsed =
|
|
13796
|
-
block2.text = parsed.
|
|
13894
|
+
const parsed = collectFinalResponseArtifacts(block2.text);
|
|
13895
|
+
block2.text = parsed.text;
|
|
13797
13896
|
outboundAttachments.push(...parsed.attachments);
|
|
13798
13897
|
}
|
|
13799
13898
|
const hasToolBlocks = contentBlocks.some(
|
|
@@ -13807,7 +13906,7 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13807
13906
|
const responseText = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
13808
13907
|
return {
|
|
13809
13908
|
responseText,
|
|
13810
|
-
outboundAttachments,
|
|
13909
|
+
outboundAttachments: dedupeOutboundAttachments(outboundAttachments),
|
|
13811
13910
|
tokenUsage,
|
|
13812
13911
|
hasError,
|
|
13813
13912
|
errorMessage,
|
|
@@ -13840,6 +13939,42 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13840
13939
|
}
|
|
13841
13940
|
}
|
|
13842
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
|
+
|
|
13843
13978
|
// src/lib/bridge/stream-feedback-controller.ts
|
|
13844
13979
|
function pushStreamFeedbackText(target, text2) {
|
|
13845
13980
|
if (typeof target.adapter.onStreamText !== "function") return;
|
|
@@ -13868,13 +14003,15 @@ function pushStreamFeedbackTasks(target, tasks) {
|
|
|
13868
14003
|
}
|
|
13869
14004
|
}
|
|
13870
14005
|
function pushStreamFeedbackStatus(target, text2) {
|
|
13871
|
-
if (typeof target.adapter.onStreamStatus !== "function") return;
|
|
14006
|
+
if (typeof target.adapter.onStreamStatus !== "function") return false;
|
|
13872
14007
|
target.ensureStarted?.();
|
|
13873
14008
|
const rendered = renderFeedbackTextForChannel(target.channelType, text2);
|
|
13874
|
-
if (!rendered) return;
|
|
14009
|
+
if (!rendered) return false;
|
|
13875
14010
|
try {
|
|
13876
14011
|
target.adapter.onStreamStatus(target.chatId, rendered, target.streamKey);
|
|
14012
|
+
return true;
|
|
13877
14013
|
} catch {
|
|
14014
|
+
return false;
|
|
13878
14015
|
}
|
|
13879
14016
|
}
|
|
13880
14017
|
async function finalizeStreamFeedback(target, status, text2) {
|
|
@@ -13887,6 +14024,118 @@ async function finalizeStreamFeedback(target, status, text2) {
|
|
|
13887
14024
|
}
|
|
13888
14025
|
}
|
|
13889
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
|
+
|
|
13890
14139
|
// src/lib/bridge/interactive-message-runner.ts
|
|
13891
14140
|
function generateDraftId() {
|
|
13892
14141
|
return Math.floor(Math.random() * 2147483646) + 1;
|
|
@@ -13930,18 +14179,6 @@ function flushPreview(adapter, state, config2) {
|
|
|
13930
14179
|
}).catch(() => {
|
|
13931
14180
|
});
|
|
13932
14181
|
}
|
|
13933
|
-
function formatRuntimeDuration(ms) {
|
|
13934
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
13935
|
-
const seconds = totalSeconds % 60;
|
|
13936
|
-
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
13937
|
-
const minutes = totalMinutes % 60;
|
|
13938
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
13939
|
-
const parts = [];
|
|
13940
|
-
if (hours > 0) parts.push(`${hours}\u5C0F\u65F6`);
|
|
13941
|
-
if (minutes > 0) parts.push(`${minutes}\u5206`);
|
|
13942
|
-
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}\u79D2`);
|
|
13943
|
-
return parts.join("");
|
|
13944
|
-
}
|
|
13945
14182
|
function pathBaseName(value) {
|
|
13946
14183
|
return value.includes("\\") ? path13.win32.basename(value) : path13.basename(value);
|
|
13947
14184
|
}
|
|
@@ -13963,18 +14200,10 @@ function buildStaleTaskCompletionNotice(address, binding) {
|
|
|
13963
14200
|
const taskName = formatTaskDisplayName(binding);
|
|
13964
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`;
|
|
13965
14202
|
}
|
|
13966
|
-
function formatInteractiveRuntimeStatus(elapsedMs, lastResponseAgeMs, statusNote) {
|
|
13967
|
-
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
13968
|
-
if (typeof lastResponseAgeMs === "number" && lastResponseAgeMs >= 0) {
|
|
13969
|
-
parts.push(`\u4E0A\u6B21\u54CD\u5E94\u8DDD\u4ECA ${formatRuntimeDuration(lastResponseAgeMs)}`);
|
|
13970
|
-
}
|
|
13971
|
-
const runtimeText = parts.join("\uFF0C");
|
|
13972
|
-
const note = (statusNote || "").trim();
|
|
13973
|
-
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
13974
|
-
${runtimeText}` : runtimeText;
|
|
13975
|
-
}
|
|
13976
14203
|
async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
13977
14204
|
const binding = resolve(msg.address);
|
|
14205
|
+
const initialSession = getBridgeContext().store.getSession(binding.codepilotSessionId);
|
|
14206
|
+
const desktopThreadId = getExplicitDesktopThreadId(initialSession);
|
|
13978
14207
|
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
13979
14208
|
const nowMs = deps.nowMs ?? (() => Date.now());
|
|
13980
14209
|
const setIntervalFn = deps.setIntervalFn ?? ((callback, intervalMs) => setInterval(callback, intervalMs));
|
|
@@ -14000,11 +14229,14 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14000
14229
|
const taskAbort = new AbortController();
|
|
14001
14230
|
const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
14002
14231
|
const taskStartedAt = nowMs();
|
|
14232
|
+
const streamState = createStreamState(taskStartedAt);
|
|
14003
14233
|
let externalTerminalRequest = null;
|
|
14234
|
+
let desktopTerminalFinalExpected = false;
|
|
14004
14235
|
let resolveExternalTerminal = null;
|
|
14005
14236
|
const externalTerminalPromise = new Promise((resolve2) => {
|
|
14006
14237
|
resolveExternalTerminal = resolve2;
|
|
14007
14238
|
});
|
|
14239
|
+
let processResultSettled = false;
|
|
14008
14240
|
let resolveExternalTerminalCompletion = null;
|
|
14009
14241
|
let externalTerminalCompletionSettled = false;
|
|
14010
14242
|
const externalTerminalCompletion = new Promise((resolve2) => {
|
|
@@ -14028,6 +14260,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14028
14260
|
structuredStreamUiActive: false,
|
|
14029
14261
|
lastActivityAt: taskStartedAt,
|
|
14030
14262
|
lastResponseAt: null,
|
|
14263
|
+
lastContentResponseAt: null,
|
|
14031
14264
|
streamFinalized: false,
|
|
14032
14265
|
uiEnded: false,
|
|
14033
14266
|
mirrorSuppressionId: null,
|
|
@@ -14036,13 +14269,26 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14036
14269
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return false;
|
|
14037
14270
|
externalTerminalRequest = { outcome, detail, finalText };
|
|
14038
14271
|
resolveExternalTerminal?.(externalTerminalRequest);
|
|
14039
|
-
if (!taskAbort.signal.aborted) {
|
|
14272
|
+
if (!processResultSettled && !taskAbort.signal.aborted) {
|
|
14040
14273
|
taskAbort.abort();
|
|
14041
14274
|
}
|
|
14042
14275
|
return externalTerminalCompletion;
|
|
14043
14276
|
}
|
|
14044
14277
|
};
|
|
14045
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
|
+
});
|
|
14046
14292
|
deps.recordInteractiveHealthStart(binding.codepilotSessionId);
|
|
14047
14293
|
let previewState = null;
|
|
14048
14294
|
const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
|
|
@@ -14062,7 +14308,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14062
14308
|
const ps = previewState;
|
|
14063
14309
|
const cfg = streamCfg;
|
|
14064
14310
|
if (ps.degraded) return;
|
|
14065
|
-
const sanitizedText =
|
|
14311
|
+
const sanitizedText = stripFinalOnlyBlocksForStreaming(fullText);
|
|
14066
14312
|
ps.pendingText = sanitizedText.length > cfg.maxChars ? sanitizedText.slice(0, cfg.maxChars) + "..." : sanitizedText;
|
|
14067
14313
|
const delta = ps.pendingText.length - ps.lastSentText.length;
|
|
14068
14314
|
const elapsed = Date.now() - ps.lastSentAt;
|
|
@@ -14104,7 +14350,6 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14104
14350
|
};
|
|
14105
14351
|
const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
|
|
14106
14352
|
const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
|
|
14107
|
-
let latestStatusNote = null;
|
|
14108
14353
|
let latestTasks = [];
|
|
14109
14354
|
const syncStructuredStreamUiState = () => {
|
|
14110
14355
|
if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
|
|
@@ -14123,12 +14368,22 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14123
14368
|
if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
|
|
14124
14369
|
pushStreamFeedbackStatus(
|
|
14125
14370
|
streamFeedbackTarget,
|
|
14126
|
-
|
|
14371
|
+
lastResponseAgeMs == null ? buildStreamRuntimeStatus(streamState, nowMs()) : formatStreamRuntimeStatus(nowMs() - taskStartedAt, lastResponseAgeMs, streamState.statusNote)
|
|
14127
14372
|
);
|
|
14128
14373
|
syncStructuredStreamUiSnapshot();
|
|
14129
14374
|
};
|
|
14130
|
-
const
|
|
14131
|
-
|
|
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;
|
|
14132
14387
|
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14133
14388
|
};
|
|
14134
14389
|
let streamStatusHeartbeat = null;
|
|
@@ -14167,10 +14422,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14167
14422
|
endPreviewOnce();
|
|
14168
14423
|
if (hasStreamingCards && !streamUiFinalizeAttempted) {
|
|
14169
14424
|
streamUiFinalizeAttempted = true;
|
|
14170
|
-
taskState.streamFinalized = await
|
|
14425
|
+
taskState.streamFinalized = await finalizeStreamingUi(
|
|
14171
14426
|
streamFeedbackTarget,
|
|
14172
14427
|
status,
|
|
14173
|
-
responseText
|
|
14428
|
+
assembleDesktopFinalResponse({ text: responseText })
|
|
14174
14429
|
);
|
|
14175
14430
|
}
|
|
14176
14431
|
return taskState.streamFinalized;
|
|
@@ -14184,12 +14439,12 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14184
14439
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14185
14440
|
pushStreamFeedbackText(
|
|
14186
14441
|
streamFeedbackTarget,
|
|
14187
|
-
|
|
14442
|
+
stripFinalOnlyBlocksForStreaming(fullText)
|
|
14188
14443
|
);
|
|
14189
14444
|
} : void 0;
|
|
14190
14445
|
const onToolEvent = (toolId, toolName, status) => {
|
|
14191
14446
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14192
|
-
|
|
14447
|
+
markActivity();
|
|
14193
14448
|
deps.recordInteractiveHealthTool(binding.codepilotSessionId, toolId, toolName, status);
|
|
14194
14449
|
if (toolName) {
|
|
14195
14450
|
toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
|
|
@@ -14205,7 +14460,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14205
14460
|
};
|
|
14206
14461
|
const onTaskEvent = (tasks) => {
|
|
14207
14462
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14208
|
-
|
|
14463
|
+
markActivity();
|
|
14209
14464
|
latestTasks = tasks;
|
|
14210
14465
|
if (hasStreamingCards) {
|
|
14211
14466
|
pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
|
|
@@ -14215,17 +14470,15 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14215
14470
|
};
|
|
14216
14471
|
const onStatusNote = (note) => {
|
|
14217
14472
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14218
|
-
|
|
14219
|
-
if (
|
|
14220
|
-
markSuccessfulResponse();
|
|
14221
|
-
}
|
|
14473
|
+
updateStreamStatusNote(streamState, note, nowMs());
|
|
14474
|
+
if (streamState.statusNote) markActivity();
|
|
14222
14475
|
pushRunningStatus(null);
|
|
14223
14476
|
syncStructuredStreamUiSnapshot();
|
|
14224
14477
|
};
|
|
14225
14478
|
const onPartialText = (fullText) => {
|
|
14226
14479
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14227
14480
|
if (fullText.trim()) {
|
|
14228
|
-
|
|
14481
|
+
markContentResponse();
|
|
14229
14482
|
}
|
|
14230
14483
|
deps.recordInteractiveHealthProgress(binding.codepilotSessionId, "text");
|
|
14231
14484
|
previewOnPartialText?.(fullText);
|
|
@@ -14233,6 +14486,34 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14233
14486
|
pushRunningStatus(null);
|
|
14234
14487
|
syncStructuredStreamUiSnapshot();
|
|
14235
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
|
+
};
|
|
14236
14517
|
if (supportsStructuredStreamUi) {
|
|
14237
14518
|
pushRunningStatus(null);
|
|
14238
14519
|
streamStatusHeartbeat = setIntervalFn(() => {
|
|
@@ -14245,8 +14526,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14245
14526
|
return;
|
|
14246
14527
|
}
|
|
14247
14528
|
const elapsedMs = nowMs() - taskStartedAt;
|
|
14248
|
-
const
|
|
14249
|
-
|
|
14529
|
+
const showLastResponseAge = shouldShowStreamLastContentResponseAge(streamState, nowMs(), {
|
|
14530
|
+
idleStartMs: streamStatusIdleDetectionStartMs,
|
|
14531
|
+
heartbeatMs: streamStatusHeartbeatMs
|
|
14532
|
+
}) ? getStreamLastContentResponseAgeMs(streamState, nowMs()) : null;
|
|
14250
14533
|
pushRunningStatus(showLastResponseAge);
|
|
14251
14534
|
syncStructuredStreamUiSnapshot();
|
|
14252
14535
|
}, streamStatusHeartbeatMs);
|
|
@@ -14275,7 +14558,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14275
14558
|
"permission_wait",
|
|
14276
14559
|
`\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
|
|
14277
14560
|
);
|
|
14278
|
-
|
|
14561
|
+
markActivity();
|
|
14279
14562
|
pushRunningStatus(null);
|
|
14280
14563
|
syncStructuredStreamUiSnapshot();
|
|
14281
14564
|
},
|
|
@@ -14286,7 +14569,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14286
14569
|
onTaskEvent,
|
|
14287
14570
|
onStatusNote,
|
|
14288
14571
|
(preparedPrompt) => {
|
|
14289
|
-
if (
|
|
14572
|
+
if (desktopThreadId) {
|
|
14573
|
+
desktopTerminalFinalExpected = true;
|
|
14574
|
+
}
|
|
14575
|
+
if (desktopThreadId && !taskState.mirrorSuppressionId) {
|
|
14290
14576
|
taskState.mirrorSuppressionId = deps.beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
14291
14577
|
}
|
|
14292
14578
|
}
|
|
@@ -14308,73 +14594,87 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14308
14594
|
finalOutcomeDetail = raced.terminal.detail;
|
|
14309
14595
|
const streamEndStatus = raced.terminal.outcome === "completed" ? "completed" : raced.terminal.outcome === "aborted" ? "interrupted" : "error";
|
|
14310
14596
|
const staleTaskNotice2 = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14311
|
-
const
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
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({
|
|
14315
14603
|
adapter,
|
|
14316
|
-
msg.address,
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
);
|
|
14604
|
+
address: msg.address,
|
|
14605
|
+
sessionId: binding.codepilotSessionId,
|
|
14606
|
+
replyToMessageId: msg.messageId,
|
|
14607
|
+
deliverResponse: deps.deliverResponse
|
|
14608
|
+
}, terminalResponse2, { skipText: cardFinalized2 });
|
|
14322
14609
|
}
|
|
14323
14610
|
return;
|
|
14324
14611
|
}
|
|
14325
14612
|
const result = raced.result;
|
|
14613
|
+
processResultSettled = true;
|
|
14326
14614
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
14327
14615
|
shouldRecordHealthEnd = false;
|
|
14328
14616
|
return;
|
|
14329
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;
|
|
14330
14630
|
let cardFinalized = false;
|
|
14331
14631
|
const staleTaskNotice = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14632
|
+
const staleResponse = staleTaskNotice ? assembleDesktopFinalResponse({ text: staleTaskNotice }) : null;
|
|
14332
14633
|
if (hasStreamingCards) {
|
|
14333
|
-
const streamEndStatus = taskAbort.signal.aborted ? "interrupted" : result.hasError ? "error" : "completed";
|
|
14634
|
+
const streamEndStatus = terminalAfterProcess ? terminalAfterProcess.outcome === "completed" ? "completed" : terminalAfterProcess.outcome === "aborted" ? "interrupted" : "error" : taskAbort.signal.aborted ? "interrupted" : result.hasError ? "error" : "completed";
|
|
14334
14635
|
cardFinalized = await finalizeStreamUiOnce(
|
|
14335
14636
|
streamEndStatus,
|
|
14336
|
-
|
|
14637
|
+
staleResponse?.text || (streamEndStatus === "interrupted" ? "" : effectiveResponse.text)
|
|
14337
14638
|
);
|
|
14338
14639
|
}
|
|
14339
|
-
if (
|
|
14340
|
-
|
|
14341
|
-
await deps.deliverResponse(
|
|
14342
|
-
adapter,
|
|
14343
|
-
msg.address,
|
|
14344
|
-
staleTaskNotice,
|
|
14345
|
-
binding.codepilotSessionId,
|
|
14346
|
-
msg.messageId,
|
|
14347
|
-
[]
|
|
14348
|
-
);
|
|
14349
|
-
}
|
|
14350
|
-
} else if (result.responseText || result.outboundAttachments.length > 0) {
|
|
14351
|
-
const textToDeliver = cardFinalized ? "" : result.responseText;
|
|
14352
|
-
if (!cardFinalized || result.outboundAttachments.length > 0) {
|
|
14353
|
-
await deps.deliverResponse(
|
|
14354
|
-
adapter,
|
|
14355
|
-
msg.address,
|
|
14356
|
-
textToDeliver,
|
|
14357
|
-
binding.codepilotSessionId,
|
|
14358
|
-
msg.messageId,
|
|
14359
|
-
result.outboundAttachments
|
|
14360
|
-
);
|
|
14361
|
-
}
|
|
14362
|
-
} else if (result.hasError && !taskAbort.signal.aborted) {
|
|
14363
|
-
await deps.deliverResponse(
|
|
14640
|
+
if (staleResponse) {
|
|
14641
|
+
await deliverFinalResponse({
|
|
14364
14642
|
adapter,
|
|
14365
|
-
msg.address,
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
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
|
+
})
|
|
14370
14670
|
);
|
|
14371
14671
|
}
|
|
14372
14672
|
try {
|
|
14373
14673
|
deps.persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
|
|
14374
14674
|
} catch {
|
|
14375
14675
|
}
|
|
14376
|
-
finalOutcome = result.hasError ? "failed" : "completed";
|
|
14377
|
-
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);
|
|
14378
14678
|
} finally {
|
|
14379
14679
|
await finalizeStreamUiOnce(
|
|
14380
14680
|
taskAbort.signal.aborted ? "interrupted" : finalOutcome === "completed" ? "completed" : "error",
|
|
@@ -14396,6 +14696,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14396
14696
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
14397
14697
|
}
|
|
14398
14698
|
deps.releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14699
|
+
deps.releaseBridgeTurn?.(binding.codepilotSessionId, taskId);
|
|
14399
14700
|
endMessageUiOnce();
|
|
14400
14701
|
settleExternalTerminalCompletion(taskState.streamFinalized || !hasStreamingCards);
|
|
14401
14702
|
}
|
|
@@ -14410,12 +14711,6 @@ var TERMINAL_SESSION_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
|
14410
14711
|
function isTerminalSessionHealthStatus(status) {
|
|
14411
14712
|
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
14412
14713
|
}
|
|
14413
|
-
function terminalOutcomeFromHealthStatus(status) {
|
|
14414
|
-
if (status === "completed") return "completed";
|
|
14415
|
-
if (status === "failed") return "failed";
|
|
14416
|
-
if (status === "aborted") return "aborted";
|
|
14417
|
-
return null;
|
|
14418
|
-
}
|
|
14419
14714
|
function createInteractiveRuntime(getState2, deps) {
|
|
14420
14715
|
function getQueuedCount(sessionId) {
|
|
14421
14716
|
return getState2().queuedCounts.get(sessionId) || 0;
|
|
@@ -14467,14 +14762,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14467
14762
|
const store = deps.getStore();
|
|
14468
14763
|
for (const session of store.listSessions()) {
|
|
14469
14764
|
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
14470
|
-
|
|
14471
|
-
if (activeTask) {
|
|
14472
|
-
const outcome = terminalOutcomeFromHealthStatus(session.health_status);
|
|
14473
|
-
if (outcome) {
|
|
14474
|
-
await finalizeTerminalActiveTask(session.id, outcome, session.health_reason || void 0);
|
|
14475
|
-
}
|
|
14476
|
-
continue;
|
|
14477
|
-
}
|
|
14765
|
+
if (getState2().activeTasks.has(session.id)) continue;
|
|
14478
14766
|
const queuedCount = getQueuedCount(session.id);
|
|
14479
14767
|
const persistedQueuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
|
|
14480
14768
|
if (queuedCount > 0) continue;
|
|
@@ -14887,7 +15175,7 @@ function buildMirrorSubscriptionRegistryPlan(bindings, activeChannelTypes, exist
|
|
|
14887
15175
|
if (binding.active === false) return false;
|
|
14888
15176
|
if (!activeChannels.has(binding.channelType)) return false;
|
|
14889
15177
|
const session = getSession(binding.codepilotSessionId);
|
|
14890
|
-
return Boolean(
|
|
15178
|
+
return Boolean(session?.desktop_thread_id || (session?.thread_origin === "desktop" ? session.sdk_session_id : null));
|
|
14891
15179
|
});
|
|
14892
15180
|
const desiredIds = new Set(upsertBindings.map((binding) => binding.id));
|
|
14893
15181
|
const removeBindingIds = Array.from(existingBindingIds).filter((bindingId) => !desiredIds.has(bindingId));
|
|
@@ -14981,11 +15269,17 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
14981
15269
|
function clearDanglingMirrorThread(subscription, reason) {
|
|
14982
15270
|
const { store } = getBridgeContext();
|
|
14983
15271
|
const session = store.getSession(subscription.sessionId);
|
|
14984
|
-
const currentThreadId = session
|
|
15272
|
+
const currentThreadId = getExplicitDesktopThreadId(session) || subscription.threadId;
|
|
14985
15273
|
console.warn(
|
|
14986
15274
|
`[bridge-manager] Clearing dangling desktop thread ${currentThreadId} for session ${subscription.sessionId}: ${reason}`
|
|
14987
15275
|
);
|
|
14988
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
|
+
});
|
|
14989
15283
|
removeMirrorSubscription(subscription.bindingId);
|
|
14990
15284
|
}
|
|
14991
15285
|
function upsertMirrorSubscription(binding) {
|
|
@@ -14996,7 +15290,7 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
14996
15290
|
removeMirrorSubscription(binding.id);
|
|
14997
15291
|
return;
|
|
14998
15292
|
}
|
|
14999
|
-
const threadId =
|
|
15293
|
+
const threadId = getExplicitDesktopThreadId(session) || "";
|
|
15000
15294
|
if (!threadId) {
|
|
15001
15295
|
removeMirrorSubscription(binding.id);
|
|
15002
15296
|
return;
|
|
@@ -15109,11 +15403,13 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
15109
15403
|
`[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
|
|
15110
15404
|
);
|
|
15111
15405
|
}
|
|
15112
|
-
|
|
15113
|
-
|
|
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);
|
|
15114
15410
|
}
|
|
15115
|
-
const blocked = getState2().activeTasks.has(subscription.sessionId)
|
|
15116
|
-
const deliveryPlan = buildMirrorDeliveryPlan(subscription,
|
|
15411
|
+
const blocked = getState2().activeTasks.has(subscription.sessionId);
|
|
15412
|
+
const deliveryPlan = buildMirrorDeliveryPlan(subscription, mirrorRecords, {
|
|
15117
15413
|
blocked,
|
|
15118
15414
|
filterSuppressedRecords: deps.filterSuppressedMirrorRecords,
|
|
15119
15415
|
flushTimedOutTurn: (currentSubscription) => deps.flushTimedOutMirrorTurn(currentSubscription),
|
|
@@ -15210,6 +15506,241 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
15210
15506
|
};
|
|
15211
15507
|
}
|
|
15212
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
|
+
|
|
15213
15744
|
// src/lib/bridge/session-health-process.ts
|
|
15214
15745
|
import { execFile } from "node:child_process";
|
|
15215
15746
|
import { promisify } from "node:util";
|
|
@@ -15302,7 +15833,6 @@ async function probeCodexThreadProcess(threadId) {
|
|
|
15302
15833
|
var HEALTH_RECENT_PROGRESS_MS = 10 * 60 * 1e3;
|
|
15303
15834
|
var HEALTH_SLOW_OBSERVED_MS = 30 * 60 * 1e3;
|
|
15304
15835
|
var HEALTH_PROGRESS_PERSIST_THROTTLE_MS = 15 * 1e3;
|
|
15305
|
-
var HEALTH_PROCESS_PROBE_CACHE_MS = 30 * 1e3;
|
|
15306
15836
|
var HEALTH_STREAM_UI_STALL_MS = 60 * 1e3;
|
|
15307
15837
|
var RUNNING_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
15308
15838
|
"running_active",
|
|
@@ -15435,14 +15965,13 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15435
15965
|
const lastStreamUiError = trimOrNull(session.last_stream_ui_error);
|
|
15436
15966
|
const streamUiConsecutiveFailures = typeof session.stream_ui_consecutive_failures === "number" && Number.isFinite(session.stream_ui_consecutive_failures) && session.stream_ui_consecutive_failures > 0 ? session.stream_ui_consecutive_failures : 0;
|
|
15437
15967
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
15438
|
-
const checkedAt = trimOrNull(session.last_health_check_at);
|
|
15439
15968
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
15440
15969
|
const previousStatus = session.health_status || "idle";
|
|
15441
15970
|
if (!lastProgressMs) {
|
|
15442
15971
|
const fallbackStatus = isRunningRuntimeStatus(runtimeStatus) ? "running_active" : previousStatus;
|
|
15443
15972
|
return {
|
|
15444
15973
|
sessionId: session.id,
|
|
15445
|
-
checkedAt,
|
|
15974
|
+
checkedAt: null,
|
|
15446
15975
|
runtimeStatus,
|
|
15447
15976
|
healthStatus: fallbackStatus,
|
|
15448
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"),
|
|
@@ -15480,7 +16009,7 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15480
16009
|
}
|
|
15481
16010
|
return {
|
|
15482
16011
|
sessionId: session.id,
|
|
15483
|
-
checkedAt,
|
|
16012
|
+
checkedAt: null,
|
|
15484
16013
|
runtimeStatus,
|
|
15485
16014
|
healthStatus,
|
|
15486
16015
|
healthReason,
|
|
@@ -15589,7 +16118,6 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
15589
16118
|
// src/lib/bridge/session-health-runtime.ts
|
|
15590
16119
|
function createSessionHealthRuntime(deps) {
|
|
15591
16120
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
15592
|
-
const processProbeCache = /* @__PURE__ */ new Map();
|
|
15593
16121
|
function summarizePlanUpdate(tasks) {
|
|
15594
16122
|
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
15595
16123
|
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
@@ -15725,11 +16253,9 @@ function createSessionHealthRuntime(deps) {
|
|
|
15725
16253
|
last_stream_ui_update_at: void 0,
|
|
15726
16254
|
last_stream_ui_error_at: void 0,
|
|
15727
16255
|
last_stream_ui_error: void 0,
|
|
15728
|
-
stream_ui_consecutive_failures: void 0
|
|
15729
|
-
last_health_check_at: nowIso4
|
|
16256
|
+
stream_ui_consecutive_failures: void 0
|
|
15730
16257
|
}, { force: true });
|
|
15731
16258
|
lastProgressPersistAt.set(sessionId, Date.now());
|
|
15732
|
-
processProbeCache.delete(sessionId);
|
|
15733
16259
|
}
|
|
15734
16260
|
function toIso(value) {
|
|
15735
16261
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
@@ -15824,17 +16350,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
15824
16350
|
async function loadProcessProbe(session) {
|
|
15825
16351
|
const threadId = session.sdk_session_id?.trim();
|
|
15826
16352
|
if (!threadId || !deps.probeThreadProcess) return null;
|
|
15827
|
-
|
|
15828
|
-
const nowMs = Date.now();
|
|
15829
|
-
if (cached && nowMs - cached.checkedAtMs < HEALTH_PROCESS_PROBE_CACHE_MS) {
|
|
15830
|
-
return cached.result;
|
|
15831
|
-
}
|
|
15832
|
-
const result = await deps.probeThreadProcess(threadId);
|
|
15833
|
-
processProbeCache.set(session.id, {
|
|
15834
|
-
checkedAtMs: nowMs,
|
|
15835
|
-
result
|
|
15836
|
-
});
|
|
15837
|
-
return result;
|
|
16353
|
+
return deps.probeThreadProcess(threadId);
|
|
15838
16354
|
}
|
|
15839
16355
|
async function diagnoseSessionHealth(sessionId) {
|
|
15840
16356
|
const store = deps.getStore();
|
|
@@ -15842,21 +16358,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
15842
16358
|
if (!session) return null;
|
|
15843
16359
|
const base = computeBaseDiagnosis(session, Date.now());
|
|
15844
16360
|
const processProbe = await loadProcessProbe(session);
|
|
15845
|
-
|
|
15846
|
-
const diagnosis = applyStreamUiDiagnosis(
|
|
16361
|
+
return applyStreamUiDiagnosis(
|
|
15847
16362
|
applyProcessProbeDiagnosis(base, processProbe),
|
|
15848
16363
|
Date.now()
|
|
15849
16364
|
);
|
|
15850
|
-
const checkedDiagnosis = {
|
|
15851
|
-
...diagnosis,
|
|
15852
|
-
checkedAt
|
|
15853
|
-
};
|
|
15854
|
-
updateSessionHealth(sessionId, {
|
|
15855
|
-
health_status: checkedDiagnosis.healthStatus,
|
|
15856
|
-
health_reason: checkedDiagnosis.healthReason,
|
|
15857
|
-
last_health_check_at: checkedAt
|
|
15858
|
-
}, { touch: false });
|
|
15859
|
-
return checkedDiagnosis;
|
|
15860
16365
|
}
|
|
15861
16366
|
async function diagnoseAllActiveSessions() {
|
|
15862
16367
|
const store = deps.getStore();
|
|
@@ -15877,6 +16382,100 @@ function createSessionHealthRuntime(deps) {
|
|
|
15877
16382
|
};
|
|
15878
16383
|
}
|
|
15879
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
|
+
|
|
15880
16479
|
// src/lib/bridge/bridge-manager.ts
|
|
15881
16480
|
var GLOBAL_KEY = "__bridge_manager__";
|
|
15882
16481
|
var DANGLING_MIRROR_THREAD_RETRY_LIMIT = 3;
|
|
@@ -15887,6 +16486,7 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
|
|
|
15887
16486
|
var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
15888
16487
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
15889
16488
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
16489
|
+
var DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS = 3e4;
|
|
15890
16490
|
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
15891
16491
|
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
15892
16492
|
var MIRROR_TURN_BUFFER_TIMEOUT_MS = 6e5;
|
|
@@ -15974,6 +16574,23 @@ var INTERACTIVE_RUNTIME = createInteractiveRuntime(getState, {
|
|
|
15974
16574
|
getStore: () => getBridgeContext().store,
|
|
15975
16575
|
nowIso: nowIso3
|
|
15976
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
|
+
});
|
|
15977
16594
|
var SESSION_HEALTH_RUNTIME = createSessionHealthRuntime({
|
|
15978
16595
|
getStore: () => getBridgeContext().store,
|
|
15979
16596
|
nowIso: nowIso3,
|
|
@@ -16010,9 +16627,6 @@ function settleMirrorSuppression2(sessionId, suppressionId, durationMs = MIRROR_
|
|
|
16010
16627
|
durationMs
|
|
16011
16628
|
);
|
|
16012
16629
|
}
|
|
16013
|
-
function isMirrorSuppressed2(sessionId) {
|
|
16014
|
-
return isMirrorSuppressed(getMirrorSuppressionStore(), sessionId);
|
|
16015
|
-
}
|
|
16016
16630
|
function filterSuppressedMirrorRecords2(sessionId, records) {
|
|
16017
16631
|
return filterSuppressedMirrorRecords(
|
|
16018
16632
|
getMirrorSuppressionStore(),
|
|
@@ -16046,28 +16660,6 @@ function syncMirrorSessionStateSafe(sessionId, context) {
|
|
|
16046
16660
|
);
|
|
16047
16661
|
}
|
|
16048
16662
|
}
|
|
16049
|
-
function getMirrorStreamingAdapter(subscription) {
|
|
16050
|
-
const state = getState();
|
|
16051
|
-
const adapter = state.adapters.get(subscription.channelType);
|
|
16052
|
-
if (!adapter || !adapter.isRunning()) return null;
|
|
16053
|
-
if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
|
|
16054
|
-
if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
|
|
16055
|
-
return null;
|
|
16056
|
-
}
|
|
16057
|
-
return adapter;
|
|
16058
|
-
}
|
|
16059
|
-
function getMirrorStreamingText(subscription, turnState) {
|
|
16060
|
-
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
16061
|
-
const markdown = getFeedbackParseMode(subscription.channelType) === "Markdown";
|
|
16062
|
-
const rendered = formatMirrorMessage(
|
|
16063
|
-
title,
|
|
16064
|
-
turnState.userText,
|
|
16065
|
-
turnState.streamedText,
|
|
16066
|
-
markdown,
|
|
16067
|
-
true
|
|
16068
|
-
);
|
|
16069
|
-
return rendered || buildMirrorTitle(title, markdown);
|
|
16070
|
-
}
|
|
16071
16663
|
function getMirrorStructuredStreamStatusConfig() {
|
|
16072
16664
|
const { store } = getBridgeContext();
|
|
16073
16665
|
const idleStartSeconds = parseInt(store.getSetting("bridge_stream_status_idle_start_seconds") || "", 10);
|
|
@@ -16083,216 +16675,43 @@ function getMirrorStructuredStreamStatusConfig() {
|
|
|
16083
16675
|
)
|
|
16084
16676
|
};
|
|
16085
16677
|
}
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
}
|
|
16094
|
-
turnState.streamStarted = true;
|
|
16095
|
-
} catch {
|
|
16096
|
-
}
|
|
16097
|
-
}
|
|
16098
|
-
function createMirrorStreamFeedbackTarget(subscription, turnState, adapter) {
|
|
16099
|
-
return {
|
|
16100
|
-
adapter,
|
|
16101
|
-
channelType: subscription.channelType,
|
|
16102
|
-
chatId: subscription.chatId,
|
|
16103
|
-
streamKey: turnState.streamKey,
|
|
16104
|
-
ensureStarted: () => {
|
|
16105
|
-
startMirrorStreaming(subscription, turnState);
|
|
16106
|
-
}
|
|
16107
|
-
};
|
|
16108
|
-
}
|
|
16109
|
-
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
16110
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16111
|
-
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
16112
|
-
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
16113
|
-
const startedAtMs = Date.parse(turnState.startedAt);
|
|
16114
|
-
if (!Number.isFinite(startedAtMs)) return;
|
|
16115
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
16116
|
-
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
16117
|
-
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
16118
|
-
return;
|
|
16119
|
-
}
|
|
16120
|
-
const statusText = formatInteractiveRuntimeStatus(
|
|
16121
|
-
Math.max(0, nowMs - startedAtMs),
|
|
16122
|
-
options.lastResponseAgeMs,
|
|
16123
|
-
turnState.statusNote
|
|
16124
|
-
);
|
|
16125
|
-
if (turnState.lastStatusText === statusText) return;
|
|
16126
|
-
pushStreamFeedbackStatus(
|
|
16127
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16128
|
-
statusText
|
|
16129
|
-
);
|
|
16130
|
-
turnState.lastStatusText = statusText;
|
|
16131
|
-
turnState.lastStatusAt = nowMs;
|
|
16132
|
-
}
|
|
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
|
+
});
|
|
16133
16685
|
function refreshMirrorStreamingStatus(subscription, nowMs = Date.now(), config2 = getMirrorStructuredStreamStatusConfig()) {
|
|
16134
|
-
|
|
16135
|
-
if (!pendingTurn?.streamStarted) return;
|
|
16136
|
-
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
16137
|
-
if (!Number.isFinite(startedAtMs)) return;
|
|
16138
|
-
const elapsedMs = nowMs - startedAtMs;
|
|
16139
|
-
if (elapsedMs < config2.idleStartMs) return;
|
|
16140
|
-
const lastResponseAtMs = pendingTurn.lastResponseAt ? Date.parse(pendingTurn.lastResponseAt) : NaN;
|
|
16141
|
-
const lastResponseAgeMs = Number.isFinite(lastResponseAtMs) ? nowMs - lastResponseAtMs : null;
|
|
16142
|
-
if (lastResponseAgeMs != null && lastResponseAgeMs < config2.heartbeatMs) return;
|
|
16143
|
-
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
16144
|
-
nowMs,
|
|
16145
|
-
lastResponseAgeMs,
|
|
16146
|
-
minIntervalMs: config2.heartbeatMs
|
|
16147
|
-
});
|
|
16686
|
+
MIRROR_FEEDBACK.refreshMirrorStreamingStatus(subscription, nowMs, config2);
|
|
16148
16687
|
}
|
|
16149
16688
|
function refreshActiveMirrorStreamingStatuses(nowMs = Date.now()) {
|
|
16150
16689
|
for (const subscription of getState().mirrorSubscriptions.values()) {
|
|
16151
16690
|
refreshMirrorStreamingStatus(subscription, nowMs);
|
|
16152
16691
|
}
|
|
16153
16692
|
}
|
|
16154
|
-
function updateMirrorStreaming(subscription, turnState) {
|
|
16155
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16156
|
-
if (!adapter) return;
|
|
16157
|
-
pushStreamFeedbackText(
|
|
16158
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16159
|
-
getMirrorStreamingText(subscription, turnState)
|
|
16160
|
-
);
|
|
16161
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16162
|
-
}
|
|
16163
|
-
function updateMirrorToolProgress(subscription, turnState) {
|
|
16164
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16165
|
-
if (!adapter) return;
|
|
16166
|
-
pushStreamFeedbackTools(
|
|
16167
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16168
|
-
Array.from(turnState.toolCalls.values())
|
|
16169
|
-
);
|
|
16170
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16171
|
-
}
|
|
16172
|
-
function updateMirrorTaskProgress(subscription, turnState) {
|
|
16173
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16174
|
-
if (!adapter) return;
|
|
16175
|
-
pushStreamFeedbackTasks(
|
|
16176
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16177
|
-
turnState.taskItems
|
|
16178
|
-
);
|
|
16179
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16180
|
-
}
|
|
16181
|
-
function updateMirrorStatusProgress(subscription, turnState) {
|
|
16182
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16183
|
-
if (!adapter) return;
|
|
16184
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16185
|
-
}
|
|
16186
16693
|
function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
16187
|
-
|
|
16188
|
-
const pendingTurn = subscription.pendingTurn;
|
|
16189
|
-
if (!adapter || !pendingTurn?.streamStarted) return;
|
|
16190
|
-
void finalizeStreamFeedback(
|
|
16191
|
-
createMirrorStreamFeedbackTarget(subscription, pendingTurn, adapter),
|
|
16192
|
-
status,
|
|
16193
|
-
getMirrorStreamingText(subscription, pendingTurn)
|
|
16194
|
-
);
|
|
16195
|
-
}
|
|
16196
|
-
async function deliverMirrorTurn(subscription, turn) {
|
|
16197
|
-
const state = getState();
|
|
16198
|
-
const adapter = state.adapters.get(subscription.channelType);
|
|
16199
|
-
if (!adapter || !adapter.isRunning()) return;
|
|
16200
|
-
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
16201
|
-
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
16202
|
-
const markdown = responseParseMode === "Markdown";
|
|
16203
|
-
const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
|
|
16204
|
-
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
|
|
16205
|
-
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
16206
|
-
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
16207
|
-
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
16208
|
-
const streamText = renderFeedbackText(
|
|
16209
|
-
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
16210
|
-
responseParseMode
|
|
16211
|
-
);
|
|
16212
|
-
if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
16213
|
-
try {
|
|
16214
|
-
const finalized = await adapter.onStreamEnd(
|
|
16215
|
-
subscription.chatId,
|
|
16216
|
-
turn.status,
|
|
16217
|
-
streamText,
|
|
16218
|
-
turn.streamKey
|
|
16219
|
-
);
|
|
16220
|
-
if (finalized) {
|
|
16221
|
-
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
16222
|
-
return;
|
|
16223
|
-
}
|
|
16224
|
-
} catch (error) {
|
|
16225
|
-
console.warn("[bridge-manager] Mirror stream finalize failed:", error instanceof Error ? error.message : error);
|
|
16226
|
-
}
|
|
16227
|
-
}
|
|
16228
|
-
if (!text2) return;
|
|
16229
|
-
const response = await deliver(adapter, {
|
|
16230
|
-
address: {
|
|
16231
|
-
channelType: subscription.channelType,
|
|
16232
|
-
chatId: subscription.chatId
|
|
16233
|
-
},
|
|
16234
|
-
text: text2,
|
|
16235
|
-
parseMode: responseParseMode
|
|
16236
|
-
}, {
|
|
16237
|
-
sessionId: subscription.sessionId,
|
|
16238
|
-
dedupKey: `mirror:${subscription.bindingId}:${turn.signature}`
|
|
16239
|
-
});
|
|
16240
|
-
if (!response.ok) {
|
|
16241
|
-
throw new Error(response.error || "mirror delivery failed");
|
|
16242
|
-
}
|
|
16243
|
-
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
16694
|
+
MIRROR_FEEDBACK.stopMirrorStreaming(subscription, status);
|
|
16244
16695
|
}
|
|
16245
16696
|
async function deliverMirrorTurns(subscription, turns) {
|
|
16246
|
-
|
|
16247
|
-
for (const turn of turns.slice(0, MIRROR_EVENT_BATCH_LIMIT)) {
|
|
16248
|
-
try {
|
|
16249
|
-
await deliverMirrorTurn(subscription, turn);
|
|
16250
|
-
deliveredCount += 1;
|
|
16251
|
-
} catch (error) {
|
|
16252
|
-
return { deliveredCount, error };
|
|
16253
|
-
}
|
|
16254
|
-
}
|
|
16255
|
-
return { deliveredCount };
|
|
16697
|
+
return MIRROR_FEEDBACK.deliverMirrorTurns(subscription, turns);
|
|
16256
16698
|
}
|
|
16257
|
-
var MIRROR_TURN_HOOKS =
|
|
16258
|
-
onStreamText: updateMirrorStreaming,
|
|
16259
|
-
onStatusProgress: updateMirrorStatusProgress,
|
|
16260
|
-
onTaskProgress: updateMirrorTaskProgress,
|
|
16261
|
-
onToolProgress: updateMirrorToolProgress
|
|
16262
|
-
};
|
|
16699
|
+
var MIRROR_TURN_HOOKS = MIRROR_FEEDBACK.hooks;
|
|
16263
16700
|
function consumeMirrorRecords2(subscription, records) {
|
|
16264
16701
|
return consumeMirrorRecords(subscription, records, MIRROR_TURN_HOOKS);
|
|
16265
16702
|
}
|
|
16266
16703
|
function flushTimedOutMirrorTurn2(subscription, nowMs = Date.now()) {
|
|
16704
|
+
if (subscription.pendingTurn?.streamStarted) {
|
|
16705
|
+
return null;
|
|
16706
|
+
}
|
|
16267
16707
|
return flushTimedOutMirrorTurn(subscription, MIRROR_TURN_BUFFER_TIMEOUT_MS, nowMs);
|
|
16268
16708
|
}
|
|
16269
16709
|
function hasPendingMirrorWork2(subscription) {
|
|
16270
16710
|
return hasPendingMirrorWork(subscription);
|
|
16271
16711
|
}
|
|
16272
16712
|
function consumeBufferedMirrorTurns2(subscription, nowMs = Date.now()) {
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
function finalizeInteractiveTaskFromMirrorRecords(sessionId, records) {
|
|
16276
|
-
let terminalRecord = null;
|
|
16277
|
-
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
16278
|
-
const record = records[index];
|
|
16279
|
-
if (record.type === "task_complete" || record.type === "task_aborted") {
|
|
16280
|
-
terminalRecord = record;
|
|
16281
|
-
break;
|
|
16282
|
-
}
|
|
16283
|
-
}
|
|
16284
|
-
if (!terminalRecord) return Promise.resolve(false);
|
|
16285
|
-
const outcome = terminalRecord.type === "task_complete" ? "completed" : "aborted";
|
|
16286
|
-
const detail = terminalRecord.type === "task_complete" ? "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002" : "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16287
|
-
return INTERACTIVE_RUNTIME.finalizeTerminalActiveTask(
|
|
16288
|
-
sessionId,
|
|
16289
|
-
outcome,
|
|
16290
|
-
detail,
|
|
16291
|
-
terminalRecord.content
|
|
16292
|
-
).catch((error) => {
|
|
16293
|
-
console.error("[bridge-manager] Failed to finalize terminal interactive task:", describeUnknownError(error));
|
|
16294
|
-
return false;
|
|
16295
|
-
});
|
|
16713
|
+
const timeoutMs = subscription.pendingTurn?.streamStarted ? Number.POSITIVE_INFINITY : MIRROR_TURN_BUFFER_TIMEOUT_MS;
|
|
16714
|
+
return consumeBufferedMirrorTurns(subscription, timeoutMs, nowMs, MIRROR_TURN_HOOKS);
|
|
16296
16715
|
}
|
|
16297
16716
|
var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
16298
16717
|
watchDebounceMs: MIRROR_WATCH_DEBOUNCE_MS,
|
|
@@ -16304,12 +16723,16 @@ var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
|
16304
16723
|
describeUnknownError,
|
|
16305
16724
|
getDesktopSessionByThreadIdSafe,
|
|
16306
16725
|
syncMirrorSessionStateSafe,
|
|
16307
|
-
isMirrorSuppressed: isMirrorSuppressed2,
|
|
16308
16726
|
filterSuppressedMirrorRecords: filterSuppressedMirrorRecords2,
|
|
16309
16727
|
observeSessionHealthRecords: (sessionId, threadId, records) => {
|
|
16310
16728
|
SESSION_HEALTH_RUNTIME.observeDesktopMirrorRecords(sessionId, threadId, records);
|
|
16311
|
-
void finalizeInteractiveTaskFromMirrorRecords(sessionId, records);
|
|
16312
16729
|
},
|
|
16730
|
+
routeDesktopRecords: (sessionId, threadId, records) => routeDesktopRecords(
|
|
16731
|
+
sessionId,
|
|
16732
|
+
threadId,
|
|
16733
|
+
records,
|
|
16734
|
+
TURN_COORDINATOR
|
|
16735
|
+
),
|
|
16313
16736
|
consumeMirrorRecords: consumeMirrorRecords2,
|
|
16314
16737
|
flushTimedOutMirrorTurn: (subscription) => flushTimedOutMirrorTurn2(subscription),
|
|
16315
16738
|
hasPendingMirrorWork: hasPendingMirrorWork2,
|
|
@@ -16557,6 +16980,7 @@ async function handleMessage(adapter, msg) {
|
|
|
16557
16980
|
try {
|
|
16558
16981
|
await runInteractiveMessage(adapter, msg, text2, hasAttachments ? msg.attachments : void 0, {
|
|
16559
16982
|
registerInteractiveTask: (task) => INTERACTIVE_RUNTIME.registerInteractiveTask(task),
|
|
16983
|
+
registerBridgeTurn: (turn) => TURN_COORDINATOR.registerInteractiveTurn(turn),
|
|
16560
16984
|
resetMirrorSessionForInteractiveRun,
|
|
16561
16985
|
isCurrentInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.isCurrentInteractiveTask(sessionId, taskId),
|
|
16562
16986
|
touchInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.touchInteractiveTask(sessionId, taskId),
|
|
@@ -16573,8 +16997,10 @@ async function handleMessage(adapter, msg) {
|
|
|
16573
16997
|
abortMirrorSuppression: abortMirrorSuppression2,
|
|
16574
16998
|
settleMirrorSuppression: settleMirrorSuppression2,
|
|
16575
16999
|
releaseInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.releaseInteractiveTask(sessionId, taskId),
|
|
17000
|
+
releaseBridgeTurn: (sessionId, taskId) => TURN_COORDINATOR.releaseSessionTurn(sessionId, taskId),
|
|
16576
17001
|
deliverResponse,
|
|
16577
|
-
persistSdkSessionUpdate
|
|
17002
|
+
persistSdkSessionUpdate,
|
|
17003
|
+
desktopTerminalFinalizationTimeoutMs: DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS
|
|
16578
17004
|
});
|
|
16579
17005
|
} finally {
|
|
16580
17006
|
ack();
|
|
@@ -16886,7 +17312,7 @@ var JsonFileStore = class {
|
|
|
16886
17312
|
findSessionBySdkSessionId(sdkSessionId) {
|
|
16887
17313
|
this.reloadSessions();
|
|
16888
17314
|
for (const session of this.sessions.values()) {
|
|
16889
|
-
if (session.sdk_session_id === sdkSessionId) {
|
|
17315
|
+
if (session.sdk_session_id === sdkSessionId || session.codex_thread_id === sdkSessionId || session.desktop_thread_id === sdkSessionId) {
|
|
16890
17316
|
return session;
|
|
16891
17317
|
}
|
|
16892
17318
|
}
|
|
@@ -17020,6 +17446,15 @@ var JsonFileStore = class {
|
|
|
17020
17446
|
const s = this.sessions.get(sessionId);
|
|
17021
17447
|
if (s) {
|
|
17022
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
|
+
}
|
|
17023
17458
|
s.updated_at = now();
|
|
17024
17459
|
this.persistSessions();
|
|
17025
17460
|
}
|