codex-to-im 1.0.43 → 1.0.44
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 +232 -95
- package/dist/ui-server.mjs +2 -2
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -1408,8 +1408,12 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1408
1408
|
botIds = /* @__PURE__ */ new Set();
|
|
1409
1409
|
/** Track last incoming message ID per chat for typing indicator. */
|
|
1410
1410
|
lastIncomingMessageId = /* @__PURE__ */ new Map();
|
|
1411
|
-
/** Track active typing reaction IDs per
|
|
1411
|
+
/** Track active typing reaction IDs per stream key for cleanup. */
|
|
1412
1412
|
typingReactions = /* @__PURE__ */ new Map();
|
|
1413
|
+
/** Track in-flight typing reaction creates so repeated status updates stay idempotent. */
|
|
1414
|
+
typingReactionCreatePromises = /* @__PURE__ */ new Map();
|
|
1415
|
+
/** Track streams that ended before the async reaction create finished. */
|
|
1416
|
+
typingReactionCleanupRequested = /* @__PURE__ */ new Set();
|
|
1413
1417
|
/** Active streaming card state per stream key. */
|
|
1414
1418
|
activeCards = /* @__PURE__ */ new Map();
|
|
1415
1419
|
/** In-flight card creation promises per stream key — prevents duplicate creation. */
|
|
@@ -1515,6 +1519,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1515
1519
|
this.seenMessageIds.clear();
|
|
1516
1520
|
this.lastIncomingMessageId.clear();
|
|
1517
1521
|
this.typingReactions.clear();
|
|
1522
|
+
this.typingReactionCreatePromises.clear();
|
|
1523
|
+
this.typingReactionCleanupRequested.clear();
|
|
1518
1524
|
console.log("[feishu-adapter] Stopped");
|
|
1519
1525
|
}
|
|
1520
1526
|
isRunning() {
|
|
@@ -1531,25 +1537,38 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1531
1537
|
*/
|
|
1532
1538
|
onMessageStart(chatId, streamKey) {
|
|
1533
1539
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
1540
|
+
const reactionKey = this.resolveStreamKey(chatId, streamKey);
|
|
1534
1541
|
if (messageId && this.isStreamingEnabled()) {
|
|
1535
1542
|
this.createStreamingCard(chatId, messageId, streamKey).catch(() => {
|
|
1536
1543
|
});
|
|
1537
1544
|
}
|
|
1538
1545
|
if (!messageId || !this.restClient) return;
|
|
1539
|
-
this.
|
|
1546
|
+
if (this.typingReactions.has(reactionKey) || this.typingReactionCreatePromises.has(reactionKey)) {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const createPromise = this.restClient.im.messageReaction.create({
|
|
1540
1550
|
path: { message_id: messageId },
|
|
1541
1551
|
data: { reaction_type: { emoji_type: TYPING_EMOJI } }
|
|
1542
1552
|
}).then((res) => {
|
|
1543
1553
|
const reactionId = res?.data?.reaction_id;
|
|
1544
1554
|
if (reactionId) {
|
|
1545
|
-
this.typingReactions.set(
|
|
1555
|
+
this.typingReactions.set(reactionKey, { messageId, reactionId });
|
|
1556
|
+
if (this.typingReactionCleanupRequested.delete(reactionKey)) {
|
|
1557
|
+
this.removeTypingReaction(reactionKey);
|
|
1558
|
+
}
|
|
1546
1559
|
}
|
|
1547
1560
|
}).catch((err) => {
|
|
1548
1561
|
const code2 = err?.code;
|
|
1549
1562
|
if (code2 !== 99991400 && code2 !== 99991403) {
|
|
1550
1563
|
console.warn("[feishu-adapter] Typing indicator failed:", err instanceof Error ? err.message : err);
|
|
1551
1564
|
}
|
|
1565
|
+
}).finally(() => {
|
|
1566
|
+
this.typingReactionCreatePromises.delete(reactionKey);
|
|
1567
|
+
if (!this.typingReactions.has(reactionKey)) {
|
|
1568
|
+
this.typingReactionCleanupRequested.delete(reactionKey);
|
|
1569
|
+
}
|
|
1552
1570
|
});
|
|
1571
|
+
this.typingReactionCreatePromises.set(reactionKey, createPromise);
|
|
1553
1572
|
}
|
|
1554
1573
|
/**
|
|
1555
1574
|
* Remove the "Typing" emoji reaction and clean up card state.
|
|
@@ -1557,12 +1576,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1557
1576
|
*/
|
|
1558
1577
|
onMessageEnd(chatId, streamKey) {
|
|
1559
1578
|
this.cleanupCard(chatId, streamKey);
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1579
|
+
const reactionKey = this.resolveStreamKey(chatId, streamKey);
|
|
1580
|
+
if (this.typingReactionCreatePromises.has(reactionKey) && !this.typingReactions.has(reactionKey)) {
|
|
1581
|
+
this.typingReactionCleanupRequested.add(reactionKey);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
this.removeTypingReaction(reactionKey);
|
|
1585
|
+
}
|
|
1586
|
+
removeTypingReaction(reactionKey) {
|
|
1587
|
+
const reaction = this.typingReactions.get(reactionKey);
|
|
1588
|
+
if (!reaction || !this.restClient) return;
|
|
1589
|
+
this.typingReactions.delete(reactionKey);
|
|
1564
1590
|
this.restClient.im.messageReaction.delete({
|
|
1565
|
-
path: { message_id: messageId, reaction_id: reactionId }
|
|
1591
|
+
path: { message_id: reaction.messageId, reaction_id: reaction.reactionId }
|
|
1566
1592
|
}).catch(() => {
|
|
1567
1593
|
});
|
|
1568
1594
|
}
|
|
@@ -11526,6 +11552,7 @@ function buildHealthCommandResponse(title, diagnosis, markdown = false) {
|
|
|
11526
11552
|
title,
|
|
11527
11553
|
[
|
|
11528
11554
|
["Session", diagnosis.sessionId],
|
|
11555
|
+
["\u68C0\u67E5\u65F6\u95F4", formatCommandTimestamp(diagnosis.checkedAt)],
|
|
11529
11556
|
["\u8FD0\u884C\u72B6\u6001", formatRuntimeStatus({ runtime_status: diagnosis.runtimeStatus, queued_count: 0 })],
|
|
11530
11557
|
["\u5065\u5EB7\u72B6\u6001", formatHealthStatusLabel(diagnosis.healthStatus)],
|
|
11531
11558
|
["\u5F53\u524D\u9636\u6BB5", currentStage],
|
|
@@ -11546,6 +11573,7 @@ function buildHealthListResponse(diagnoses, markdown = false) {
|
|
|
11546
11573
|
diagnoses.map((diagnosis) => ({
|
|
11547
11574
|
heading: diagnosis.sessionId,
|
|
11548
11575
|
details: [
|
|
11576
|
+
`\u68C0\u67E5\u65F6\u95F4\uFF1A${formatCommandTimestamp(diagnosis.checkedAt)}`,
|
|
11549
11577
|
`\u5065\u5EB7\u72B6\u6001\uFF1A${formatHealthStatusLabel(diagnosis.healthStatus)}`,
|
|
11550
11578
|
`\u5F53\u524D\u9636\u6BB5\uFF1A${diagnosis.activeToolName ? `\u5DE5\u5177 \xB7 ${diagnosis.activeToolName}` : diagnosis.lastProgressType || "-"}`,
|
|
11551
11579
|
`\u6700\u540E\u8FDB\u5C55\uFF1A${formatCommandTimestamp(diagnosis.lastProgressAt)}`,
|
|
@@ -13214,14 +13242,15 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13214
13242
|
break;
|
|
13215
13243
|
}
|
|
13216
13244
|
const binding = currentBinding || resolve(msg.address);
|
|
13217
|
-
const
|
|
13245
|
+
const explicitTargetSessionId = args.trim();
|
|
13246
|
+
const targetSessionId = explicitTargetSessionId || binding.codepilotSessionId;
|
|
13218
13247
|
const diagnosis = await deps.diagnoseSessionHealth(targetSessionId);
|
|
13219
13248
|
if (!diagnosis) {
|
|
13220
13249
|
response = `\u6CA1\u6709\u627E\u5230\u4F1A\u8BDD ${targetSessionId}\u3002`;
|
|
13221
13250
|
break;
|
|
13222
13251
|
}
|
|
13223
13252
|
response = buildHealthCommandResponse(
|
|
13224
|
-
"\u5F53\u524D\u4F1A\u8BDD\u5065\u5EB7\u68C0\u67E5",
|
|
13253
|
+
explicitTargetSessionId ? "\u6307\u5B9A\u4F1A\u8BDD\u5065\u5EB7\u68C0\u67E5" : "\u5F53\u524D\u4F1A\u8BDD\u5065\u5EB7\u68C0\u67E5",
|
|
13225
13254
|
diagnosis,
|
|
13226
13255
|
responseParseMode === "Markdown"
|
|
13227
13256
|
);
|
|
@@ -13961,10 +13990,31 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
13961
13990
|
1e3,
|
|
13962
13991
|
deps.streamStatusHeartbeatMs ?? structuredStreamStatusConfig.heartbeatMs
|
|
13963
13992
|
);
|
|
13964
|
-
|
|
13993
|
+
let messageStartCalled = false;
|
|
13994
|
+
const ensureMessageStarted = () => {
|
|
13995
|
+
if (messageStartCalled) return;
|
|
13996
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
13997
|
+
messageStartCalled = true;
|
|
13998
|
+
};
|
|
13999
|
+
ensureMessageStarted();
|
|
13965
14000
|
const taskAbort = new AbortController();
|
|
13966
14001
|
const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
13967
14002
|
const taskStartedAt = nowMs();
|
|
14003
|
+
let externalTerminalRequest = null;
|
|
14004
|
+
let resolveExternalTerminal = null;
|
|
14005
|
+
const externalTerminalPromise = new Promise((resolve2) => {
|
|
14006
|
+
resolveExternalTerminal = resolve2;
|
|
14007
|
+
});
|
|
14008
|
+
let resolveExternalTerminalCompletion = null;
|
|
14009
|
+
let externalTerminalCompletionSettled = false;
|
|
14010
|
+
const externalTerminalCompletion = new Promise((resolve2) => {
|
|
14011
|
+
resolveExternalTerminalCompletion = resolve2;
|
|
14012
|
+
});
|
|
14013
|
+
const settleExternalTerminalCompletion = (finalized) => {
|
|
14014
|
+
if (!externalTerminalRequest || externalTerminalCompletionSettled) return;
|
|
14015
|
+
externalTerminalCompletionSettled = true;
|
|
14016
|
+
resolveExternalTerminalCompletion?.(finalized);
|
|
14017
|
+
};
|
|
13968
14018
|
deps.resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
|
|
13969
14019
|
const taskState = {
|
|
13970
14020
|
id: taskId,
|
|
@@ -13978,10 +14028,19 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
13978
14028
|
structuredStreamUiActive: false,
|
|
13979
14029
|
lastActivityAt: taskStartedAt,
|
|
13980
14030
|
lastResponseAt: null,
|
|
13981
|
-
idleReminderSent: false,
|
|
13982
14031
|
streamFinalized: false,
|
|
13983
14032
|
uiEnded: false,
|
|
13984
|
-
mirrorSuppressionId: null
|
|
14033
|
+
mirrorSuppressionId: null,
|
|
14034
|
+
finalizeFromExternalTerminal: async (outcome, detail, finalText) => {
|
|
14035
|
+
if (externalTerminalRequest) return externalTerminalCompletion;
|
|
14036
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return false;
|
|
14037
|
+
externalTerminalRequest = { outcome, detail, finalText };
|
|
14038
|
+
resolveExternalTerminal?.(externalTerminalRequest);
|
|
14039
|
+
if (!taskAbort.signal.aborted) {
|
|
14040
|
+
taskAbort.abort();
|
|
14041
|
+
}
|
|
14042
|
+
return externalTerminalCompletion;
|
|
14043
|
+
}
|
|
13985
14044
|
};
|
|
13986
14045
|
deps.registerInteractiveTask(taskState);
|
|
13987
14046
|
deps.recordInteractiveHealthStart(binding.codepilotSessionId);
|
|
@@ -14040,7 +14099,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14040
14099
|
chatId: msg.address.chatId,
|
|
14041
14100
|
streamKey,
|
|
14042
14101
|
ensureStarted: () => {
|
|
14043
|
-
|
|
14102
|
+
ensureMessageStarted();
|
|
14044
14103
|
}
|
|
14045
14104
|
};
|
|
14046
14105
|
const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
|
|
@@ -14083,6 +14142,44 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14083
14142
|
streamStatusUpdatesClosed = true;
|
|
14084
14143
|
clearStreamStatusHeartbeat();
|
|
14085
14144
|
};
|
|
14145
|
+
let structuredStreamInactiveRecorded = false;
|
|
14146
|
+
const recordStructuredStreamInactiveOnce = () => {
|
|
14147
|
+
if (structuredStreamInactiveRecorded) return;
|
|
14148
|
+
structuredStreamInactiveRecorded = true;
|
|
14149
|
+
taskState.structuredStreamUiActive = false;
|
|
14150
|
+
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, { active: false });
|
|
14151
|
+
};
|
|
14152
|
+
let previewEnded = false;
|
|
14153
|
+
const endPreviewOnce = () => {
|
|
14154
|
+
if (previewEnded) return;
|
|
14155
|
+
previewEnded = true;
|
|
14156
|
+
if (!previewState) return;
|
|
14157
|
+
if (previewState.throttleTimer) {
|
|
14158
|
+
clearTimeout(previewState.throttleTimer);
|
|
14159
|
+
previewState.throttleTimer = null;
|
|
14160
|
+
}
|
|
14161
|
+
adapter.endPreview?.(msg.address.chatId, previewState.draftId);
|
|
14162
|
+
};
|
|
14163
|
+
let streamUiFinalizeAttempted = false;
|
|
14164
|
+
const finalizeStreamUiOnce = async (status, responseText) => {
|
|
14165
|
+
stopStructuredStreamStatusUpdates();
|
|
14166
|
+
recordStructuredStreamInactiveOnce();
|
|
14167
|
+
endPreviewOnce();
|
|
14168
|
+
if (hasStreamingCards && !streamUiFinalizeAttempted) {
|
|
14169
|
+
streamUiFinalizeAttempted = true;
|
|
14170
|
+
taskState.streamFinalized = await finalizeStreamFeedback(
|
|
14171
|
+
streamFeedbackTarget,
|
|
14172
|
+
status,
|
|
14173
|
+
responseText
|
|
14174
|
+
);
|
|
14175
|
+
}
|
|
14176
|
+
return taskState.streamFinalized;
|
|
14177
|
+
};
|
|
14178
|
+
const endMessageUiOnce = () => {
|
|
14179
|
+
if (taskState.uiEnded) return;
|
|
14180
|
+
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
14181
|
+
taskState.uiEnded = true;
|
|
14182
|
+
};
|
|
14086
14183
|
const onStreamCardText = hasStreamingCards ? (fullText) => {
|
|
14087
14184
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14088
14185
|
pushStreamFeedbackText(
|
|
@@ -14159,7 +14256,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14159
14256
|
let shouldRecordHealthEnd = true;
|
|
14160
14257
|
try {
|
|
14161
14258
|
const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
|
|
14162
|
-
const
|
|
14259
|
+
const processPromise = processMessageImpl(
|
|
14163
14260
|
binding,
|
|
14164
14261
|
promptText,
|
|
14165
14262
|
async (perm) => {
|
|
@@ -14194,6 +14291,38 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14194
14291
|
}
|
|
14195
14292
|
}
|
|
14196
14293
|
);
|
|
14294
|
+
let raced;
|
|
14295
|
+
try {
|
|
14296
|
+
raced = await Promise.race([
|
|
14297
|
+
processPromise.then((result2) => ({ kind: "process", result: result2 })),
|
|
14298
|
+
externalTerminalPromise.then((terminal) => ({ kind: "external", terminal }))
|
|
14299
|
+
]);
|
|
14300
|
+
} catch (error) {
|
|
14301
|
+
if (!externalTerminalRequest) throw error;
|
|
14302
|
+
raced = { kind: "external", terminal: externalTerminalRequest };
|
|
14303
|
+
}
|
|
14304
|
+
if (raced.kind === "external") {
|
|
14305
|
+
processPromise.catch(() => {
|
|
14306
|
+
});
|
|
14307
|
+
finalOutcome = raced.terminal.outcome;
|
|
14308
|
+
finalOutcomeDetail = raced.terminal.detail;
|
|
14309
|
+
const streamEndStatus = raced.terminal.outcome === "completed" ? "completed" : raced.terminal.outcome === "aborted" ? "interrupted" : "error";
|
|
14310
|
+
const staleTaskNotice2 = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14311
|
+
const terminalText = staleTaskNotice2 || raced.terminal.finalText || "";
|
|
14312
|
+
const cardFinalized2 = await finalizeStreamUiOnce(streamEndStatus, terminalText);
|
|
14313
|
+
if (!cardFinalized2 && terminalText) {
|
|
14314
|
+
await deps.deliverResponse(
|
|
14315
|
+
adapter,
|
|
14316
|
+
msg.address,
|
|
14317
|
+
terminalText,
|
|
14318
|
+
binding.codepilotSessionId,
|
|
14319
|
+
msg.messageId,
|
|
14320
|
+
[]
|
|
14321
|
+
);
|
|
14322
|
+
}
|
|
14323
|
+
return;
|
|
14324
|
+
}
|
|
14325
|
+
const result = raced.result;
|
|
14197
14326
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
14198
14327
|
shouldRecordHealthEnd = false;
|
|
14199
14328
|
return;
|
|
@@ -14201,14 +14330,11 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14201
14330
|
let cardFinalized = false;
|
|
14202
14331
|
const staleTaskNotice = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14203
14332
|
if (hasStreamingCards) {
|
|
14204
|
-
stopStructuredStreamStatusUpdates();
|
|
14205
14333
|
const streamEndStatus = taskAbort.signal.aborted ? "interrupted" : result.hasError ? "error" : "completed";
|
|
14206
|
-
cardFinalized = await
|
|
14207
|
-
streamFeedbackTarget,
|
|
14334
|
+
cardFinalized = await finalizeStreamUiOnce(
|
|
14208
14335
|
streamEndStatus,
|
|
14209
14336
|
staleTaskNotice || (streamEndStatus === "interrupted" ? "" : result.responseText)
|
|
14210
14337
|
);
|
|
14211
|
-
taskState.streamFinalized = cardFinalized;
|
|
14212
14338
|
}
|
|
14213
14339
|
if (staleTaskNotice) {
|
|
14214
14340
|
if (!cardFinalized) {
|
|
@@ -14250,24 +14376,12 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14250
14376
|
finalOutcome = result.hasError ? "failed" : "completed";
|
|
14251
14377
|
finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
|
|
14252
14378
|
} finally {
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
clearTimeout(previewState.throttleTimer);
|
|
14258
|
-
previewState.throttleTimer = null;
|
|
14259
|
-
}
|
|
14260
|
-
adapter.endPreview?.(msg.address.chatId, previewState.draftId);
|
|
14261
|
-
}
|
|
14262
|
-
if (hasStreamingCards && taskAbort.signal.aborted && !taskState.streamFinalized) {
|
|
14263
|
-
taskState.streamFinalized = await finalizeStreamFeedback(
|
|
14264
|
-
streamFeedbackTarget,
|
|
14265
|
-
"interrupted",
|
|
14266
|
-
""
|
|
14267
|
-
);
|
|
14268
|
-
}
|
|
14379
|
+
await finalizeStreamUiOnce(
|
|
14380
|
+
taskAbort.signal.aborted ? "interrupted" : finalOutcome === "completed" ? "completed" : "error",
|
|
14381
|
+
""
|
|
14382
|
+
);
|
|
14269
14383
|
if (taskState.mirrorSuppressionId) {
|
|
14270
|
-
if (
|
|
14384
|
+
if (finalOutcome === "aborted") {
|
|
14271
14385
|
deps.abortMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
|
|
14272
14386
|
} else {
|
|
14273
14387
|
deps.settleMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
|
|
@@ -14275,17 +14389,15 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14275
14389
|
taskState.mirrorSuppressionId = null;
|
|
14276
14390
|
}
|
|
14277
14391
|
if (shouldRecordHealthEnd) {
|
|
14278
|
-
if (taskAbort.signal.aborted) {
|
|
14392
|
+
if (taskAbort.signal.aborted && !externalTerminalRequest) {
|
|
14279
14393
|
finalOutcome = "aborted";
|
|
14280
14394
|
finalOutcomeDetail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14281
14395
|
}
|
|
14282
14396
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
14283
14397
|
}
|
|
14284
14398
|
deps.releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
taskState.uiEnded = true;
|
|
14288
|
-
}
|
|
14399
|
+
endMessageUiOnce();
|
|
14400
|
+
settleExternalTerminalCompletion(taskState.streamFinalized || !hasStreamingCards);
|
|
14289
14401
|
}
|
|
14290
14402
|
}
|
|
14291
14403
|
|
|
@@ -14298,16 +14410,13 @@ var TERMINAL_SESSION_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
|
14298
14410
|
function isTerminalSessionHealthStatus(status) {
|
|
14299
14411
|
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
14300
14412
|
}
|
|
14301
|
-
function
|
|
14302
|
-
return
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
}
|
|
14307
|
-
function shouldSkipIdleReminder(task) {
|
|
14308
|
-
return task.adapter.provider === "feishu" && task.structuredStreamUiActive;
|
|
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;
|
|
14309
14418
|
}
|
|
14310
|
-
function createInteractiveRuntime(getState2,
|
|
14419
|
+
function createInteractiveRuntime(getState2, deps) {
|
|
14311
14420
|
function getQueuedCount(sessionId) {
|
|
14312
14421
|
return getState2().queuedCounts.get(sessionId) || 0;
|
|
14313
14422
|
}
|
|
@@ -14341,7 +14450,6 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
14341
14450
|
const task = getState2().activeTasks.get(sessionId);
|
|
14342
14451
|
if (task?.id !== taskId) return;
|
|
14343
14452
|
task.lastActivityAt = Date.now();
|
|
14344
|
-
task.idleReminderSent = false;
|
|
14345
14453
|
}
|
|
14346
14454
|
function releaseInteractiveTask(sessionId, taskId) {
|
|
14347
14455
|
const state = getState2();
|
|
@@ -14350,35 +14458,26 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
14350
14458
|
state.activeTasks.delete(sessionId);
|
|
14351
14459
|
syncSessionRuntimeState(sessionId);
|
|
14352
14460
|
}
|
|
14353
|
-
async function
|
|
14354
|
-
|
|
14355
|
-
task
|
|
14356
|
-
|
|
14357
|
-
await deliverBridgeNotice(task.adapter, task.address, buildInteractiveIdleReminderNotice(), {
|
|
14358
|
-
sessionId: task.sessionId,
|
|
14359
|
-
replyToMessageId: task.requestMessageId
|
|
14360
|
-
});
|
|
14361
|
-
} catch {
|
|
14362
|
-
}
|
|
14363
|
-
}
|
|
14364
|
-
async function reconcileIdleInteractiveTasks() {
|
|
14365
|
-
const now2 = Date.now();
|
|
14366
|
-
const tasks = Array.from(getState2().activeTasks.values());
|
|
14367
|
-
for (const task of tasks) {
|
|
14368
|
-
if (shouldSkipIdleReminder(task)) continue;
|
|
14369
|
-
if (task.idleReminderSent) continue;
|
|
14370
|
-
if (now2 - task.lastActivityAt < options.idleReminderMs) continue;
|
|
14371
|
-
await remindIdleInteractiveTask(task);
|
|
14372
|
-
}
|
|
14461
|
+
async function finalizeTerminalActiveTask(sessionId, outcome, detail, finalText) {
|
|
14462
|
+
const task = getState2().activeTasks.get(sessionId);
|
|
14463
|
+
if (!task?.finalizeFromExternalTerminal) return false;
|
|
14464
|
+
return task.finalizeFromExternalTerminal(outcome, detail, finalText);
|
|
14373
14465
|
}
|
|
14374
|
-
function reconcileTerminalSessionRuntimeState() {
|
|
14466
|
+
async function reconcileTerminalSessionRuntimeState() {
|
|
14375
14467
|
const store = deps.getStore();
|
|
14376
14468
|
for (const session of store.listSessions()) {
|
|
14469
|
+
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
14470
|
+
const activeTask = getState2().activeTasks.get(session.id);
|
|
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
|
+
}
|
|
14377
14478
|
const queuedCount = getQueuedCount(session.id);
|
|
14378
14479
|
const persistedQueuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
|
|
14379
|
-
|
|
14380
|
-
if (hasActiveTask || queuedCount > 0) continue;
|
|
14381
|
-
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
14480
|
+
if (queuedCount > 0) continue;
|
|
14382
14481
|
if (persistedQueuedCount === 0 && session.runtime_status !== "running" && session.runtime_status !== "queued") {
|
|
14383
14482
|
continue;
|
|
14384
14483
|
}
|
|
@@ -14449,7 +14548,7 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
14449
14548
|
touchInteractiveTask,
|
|
14450
14549
|
releaseInteractiveTask,
|
|
14451
14550
|
syncSessionRuntimeState,
|
|
14452
|
-
|
|
14551
|
+
finalizeTerminalActiveTask,
|
|
14453
14552
|
reconcileTerminalSessionRuntimeState,
|
|
14454
14553
|
resetPersistedInteractiveRuntimeState,
|
|
14455
14554
|
processWithSessionLock
|
|
@@ -15336,12 +15435,14 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15336
15435
|
const lastStreamUiError = trimOrNull(session.last_stream_ui_error);
|
|
15337
15436
|
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;
|
|
15338
15437
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
15438
|
+
const checkedAt = trimOrNull(session.last_health_check_at);
|
|
15339
15439
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
15340
15440
|
const previousStatus = session.health_status || "idle";
|
|
15341
15441
|
if (!lastProgressMs) {
|
|
15342
15442
|
const fallbackStatus = isRunningRuntimeStatus(runtimeStatus) ? "running_active" : previousStatus;
|
|
15343
15443
|
return {
|
|
15344
15444
|
sessionId: session.id,
|
|
15445
|
+
checkedAt,
|
|
15345
15446
|
runtimeStatus,
|
|
15346
15447
|
healthStatus: fallbackStatus,
|
|
15347
15448
|
healthReason: session.health_reason?.trim() || (fallbackStatus === "idle" ? "\u5F53\u524D\u6CA1\u6709\u8BB0\u5F55\u5230\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002" : "\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\uFF0C\u4F46\u8FD8\u6CA1\u6709\u8BB0\u5F55\u5230\u8BE6\u7EC6\u8FDB\u5C55\u3002"),
|
|
@@ -15379,6 +15480,7 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15379
15480
|
}
|
|
15380
15481
|
return {
|
|
15381
15482
|
sessionId: session.id,
|
|
15483
|
+
checkedAt,
|
|
15382
15484
|
runtimeStatus,
|
|
15383
15485
|
healthStatus,
|
|
15384
15486
|
healthReason,
|
|
@@ -15516,7 +15618,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
15516
15618
|
}
|
|
15517
15619
|
}
|
|
15518
15620
|
if (!changed) return;
|
|
15519
|
-
store.updateSession(sessionId, next);
|
|
15621
|
+
store.updateSession(sessionId, next, { touch: options?.touch });
|
|
15520
15622
|
}
|
|
15521
15623
|
function maybePersistProgress(sessionId, updates, progressType) {
|
|
15522
15624
|
const nowMs = Date.now();
|
|
@@ -15619,6 +15721,11 @@ function createSessionHealthRuntime(deps) {
|
|
|
15619
15721
|
active_tool_name: void 0,
|
|
15620
15722
|
active_tool_started_at: void 0,
|
|
15621
15723
|
stream_ui_flush_started_at: void 0,
|
|
15724
|
+
last_stream_ui_attempt_at: void 0,
|
|
15725
|
+
last_stream_ui_update_at: void 0,
|
|
15726
|
+
last_stream_ui_error_at: void 0,
|
|
15727
|
+
last_stream_ui_error: void 0,
|
|
15728
|
+
stream_ui_consecutive_failures: void 0,
|
|
15622
15729
|
last_health_check_at: nowIso4
|
|
15623
15730
|
}, { force: true });
|
|
15624
15731
|
lastProgressPersistAt.set(sessionId, Date.now());
|
|
@@ -15638,6 +15745,12 @@ function createSessionHealthRuntime(deps) {
|
|
|
15638
15745
|
updates.last_stream_ui_error_at = toIso(snapshot.lastErrorAt);
|
|
15639
15746
|
updates.last_stream_ui_error = snapshot.lastError?.trim() || void 0;
|
|
15640
15747
|
updates.stream_ui_consecutive_failures = snapshot.consecutiveFailures && snapshot.consecutiveFailures > 0 ? snapshot.consecutiveFailures : void 0;
|
|
15748
|
+
} else {
|
|
15749
|
+
updates.last_stream_ui_attempt_at = void 0;
|
|
15750
|
+
updates.last_stream_ui_update_at = void 0;
|
|
15751
|
+
updates.last_stream_ui_error_at = void 0;
|
|
15752
|
+
updates.last_stream_ui_error = void 0;
|
|
15753
|
+
updates.stream_ui_consecutive_failures = void 0;
|
|
15641
15754
|
}
|
|
15642
15755
|
updateSessionHealth(sessionId, updates);
|
|
15643
15756
|
}
|
|
@@ -15705,7 +15818,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
15705
15818
|
updateSessionHealth(session.id, {
|
|
15706
15819
|
health_status: diagnosis.healthStatus,
|
|
15707
15820
|
health_reason: diagnosis.healthReason
|
|
15708
|
-
});
|
|
15821
|
+
}, { touch: false });
|
|
15709
15822
|
}
|
|
15710
15823
|
}
|
|
15711
15824
|
async function loadProcessProbe(session) {
|
|
@@ -15729,16 +15842,21 @@ function createSessionHealthRuntime(deps) {
|
|
|
15729
15842
|
if (!session) return null;
|
|
15730
15843
|
const base = computeBaseDiagnosis(session, Date.now());
|
|
15731
15844
|
const processProbe = await loadProcessProbe(session);
|
|
15845
|
+
const checkedAt = deps.nowIso();
|
|
15732
15846
|
const diagnosis = applyStreamUiDiagnosis(
|
|
15733
15847
|
applyProcessProbeDiagnosis(base, processProbe),
|
|
15734
15848
|
Date.now()
|
|
15735
15849
|
);
|
|
15850
|
+
const checkedDiagnosis = {
|
|
15851
|
+
...diagnosis,
|
|
15852
|
+
checkedAt
|
|
15853
|
+
};
|
|
15736
15854
|
updateSessionHealth(sessionId, {
|
|
15737
|
-
health_status:
|
|
15738
|
-
health_reason:
|
|
15739
|
-
last_health_check_at:
|
|
15740
|
-
});
|
|
15741
|
-
return
|
|
15855
|
+
health_status: checkedDiagnosis.healthStatus,
|
|
15856
|
+
health_reason: checkedDiagnosis.healthReason,
|
|
15857
|
+
last_health_check_at: checkedAt
|
|
15858
|
+
}, { touch: false });
|
|
15859
|
+
return checkedDiagnosis;
|
|
15742
15860
|
}
|
|
15743
15861
|
async function diagnoseAllActiveSessions() {
|
|
15744
15862
|
const store = deps.getStore();
|
|
@@ -15769,10 +15887,9 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
|
|
|
15769
15887
|
var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
15770
15888
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
15771
15889
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
15772
|
-
var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
|
|
15773
15890
|
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
15774
15891
|
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
15775
|
-
var
|
|
15892
|
+
var MIRROR_TURN_BUFFER_TIMEOUT_MS = 6e5;
|
|
15776
15893
|
function describeUnknownError(error) {
|
|
15777
15894
|
if (error instanceof Error) {
|
|
15778
15895
|
return error.stack || `${error.name}: ${error.message}`;
|
|
@@ -15854,8 +15971,6 @@ function getState() {
|
|
|
15854
15971
|
return g[GLOBAL_KEY];
|
|
15855
15972
|
}
|
|
15856
15973
|
var INTERACTIVE_RUNTIME = createInteractiveRuntime(getState, {
|
|
15857
|
-
idleReminderMs: INTERACTIVE_IDLE_REMINDER_MS
|
|
15858
|
-
}, {
|
|
15859
15974
|
getStore: () => getBridgeContext().store,
|
|
15860
15975
|
nowIso: nowIso3
|
|
15861
15976
|
});
|
|
@@ -16149,13 +16264,35 @@ function consumeMirrorRecords2(subscription, records) {
|
|
|
16149
16264
|
return consumeMirrorRecords(subscription, records, MIRROR_TURN_HOOKS);
|
|
16150
16265
|
}
|
|
16151
16266
|
function flushTimedOutMirrorTurn2(subscription, nowMs = Date.now()) {
|
|
16152
|
-
return flushTimedOutMirrorTurn(subscription,
|
|
16267
|
+
return flushTimedOutMirrorTurn(subscription, MIRROR_TURN_BUFFER_TIMEOUT_MS, nowMs);
|
|
16153
16268
|
}
|
|
16154
16269
|
function hasPendingMirrorWork2(subscription) {
|
|
16155
16270
|
return hasPendingMirrorWork(subscription);
|
|
16156
16271
|
}
|
|
16157
16272
|
function consumeBufferedMirrorTurns2(subscription, nowMs = Date.now()) {
|
|
16158
|
-
return consumeBufferedMirrorTurns(subscription,
|
|
16273
|
+
return consumeBufferedMirrorTurns(subscription, MIRROR_TURN_BUFFER_TIMEOUT_MS, nowMs, MIRROR_TURN_HOOKS);
|
|
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
|
+
});
|
|
16159
16296
|
}
|
|
16160
16297
|
var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
16161
16298
|
watchDebounceMs: MIRROR_WATCH_DEBOUNCE_MS,
|
|
@@ -16171,6 +16308,7 @@ var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
|
16171
16308
|
filterSuppressedMirrorRecords: filterSuppressedMirrorRecords2,
|
|
16172
16309
|
observeSessionHealthRecords: (sessionId, threadId, records) => {
|
|
16173
16310
|
SESSION_HEALTH_RUNTIME.observeDesktopMirrorRecords(sessionId, threadId, records);
|
|
16311
|
+
void finalizeInteractiveTaskFromMirrorRecords(sessionId, records);
|
|
16174
16312
|
},
|
|
16175
16313
|
consumeMirrorRecords: consumeMirrorRecords2,
|
|
16176
16314
|
flushTimedOutMirrorTurn: (subscription) => flushTimedOutMirrorTurn2(subscription),
|
|
@@ -16229,15 +16367,14 @@ async function start() {
|
|
|
16229
16367
|
void ADAPTER_RUNTIME.syncConfiguredAdapters({ startLoops: true }).catch((err) => {
|
|
16230
16368
|
console.error("[bridge-manager] Adapter reconcile failed:", err);
|
|
16231
16369
|
});
|
|
16232
|
-
void INTERACTIVE_RUNTIME.reconcileIdleInteractiveTasks().catch((err) => {
|
|
16233
|
-
console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
|
|
16234
|
-
});
|
|
16235
16370
|
try {
|
|
16236
16371
|
SESSION_HEALTH_RUNTIME.reconcileSessionHealth();
|
|
16237
|
-
INTERACTIVE_RUNTIME.reconcileTerminalSessionRuntimeState();
|
|
16238
16372
|
} catch (err) {
|
|
16239
16373
|
console.error("[bridge-manager] Session health reconcile failed:", describeUnknownError(err));
|
|
16240
16374
|
}
|
|
16375
|
+
void INTERACTIVE_RUNTIME.reconcileTerminalSessionRuntimeState().catch((err) => {
|
|
16376
|
+
console.error("[bridge-manager] Terminal interactive reconcile failed:", describeUnknownError(err));
|
|
16377
|
+
});
|
|
16241
16378
|
}, 5e3);
|
|
16242
16379
|
state.mirrorPollTimer = setInterval(() => {
|
|
16243
16380
|
void reconcileMirrorSubscriptions().catch((err) => {
|
|
@@ -16786,7 +16923,7 @@ var JsonFileStore = class {
|
|
|
16786
16923
|
this.persistSessions();
|
|
16787
16924
|
}
|
|
16788
16925
|
}
|
|
16789
|
-
updateSession(sessionId, updates) {
|
|
16926
|
+
updateSession(sessionId, updates, options) {
|
|
16790
16927
|
this.reloadSessions();
|
|
16791
16928
|
const session = this.sessions.get(sessionId);
|
|
16792
16929
|
if (!session) return;
|
|
@@ -16794,7 +16931,7 @@ var JsonFileStore = class {
|
|
|
16794
16931
|
...session,
|
|
16795
16932
|
...updates,
|
|
16796
16933
|
id: session.id,
|
|
16797
|
-
updated_at: now()
|
|
16934
|
+
updated_at: options?.touch === false ? session.updated_at : now()
|
|
16798
16935
|
};
|
|
16799
16936
|
this.sessions.set(sessionId, next);
|
|
16800
16937
|
this.persistSessions();
|
package/dist/ui-server.mjs
CHANGED
|
@@ -6532,7 +6532,7 @@ var JsonFileStore = class {
|
|
|
6532
6532
|
this.persistSessions();
|
|
6533
6533
|
}
|
|
6534
6534
|
}
|
|
6535
|
-
updateSession(sessionId, updates) {
|
|
6535
|
+
updateSession(sessionId, updates, options) {
|
|
6536
6536
|
this.reloadSessions();
|
|
6537
6537
|
const session = this.sessions.get(sessionId);
|
|
6538
6538
|
if (!session) return;
|
|
@@ -6540,7 +6540,7 @@ var JsonFileStore = class {
|
|
|
6540
6540
|
...session,
|
|
6541
6541
|
...updates,
|
|
6542
6542
|
id: session.id,
|
|
6543
|
-
updated_at: now()
|
|
6543
|
+
updated_at: options?.touch === false ? session.updated_at : now()
|
|
6544
6544
|
};
|
|
6545
6545
|
this.sessions.set(sessionId, next);
|
|
6546
6546
|
this.persistSessions();
|
package/package.json
CHANGED