codex-to-im 1.0.18 → 1.0.20
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/cli.mjs +78 -10
- package/dist/daemon.mjs +230 -102
- package/dist/ui-server.mjs +3 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -22,6 +22,29 @@ function resolveDefaultCtiHome() {
|
|
|
22
22
|
}
|
|
23
23
|
var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
|
|
24
24
|
var CONFIG_PATH = path.join(CTI_HOME, "config.env");
|
|
25
|
+
function parseEnvFile(content) {
|
|
26
|
+
const entries = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const line of content.split("\n")) {
|
|
28
|
+
const trimmed = line.trim();
|
|
29
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
30
|
+
const eqIdx = trimmed.indexOf("=");
|
|
31
|
+
if (eqIdx === -1) continue;
|
|
32
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
33
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
34
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
35
|
+
value = value.slice(1, -1);
|
|
36
|
+
}
|
|
37
|
+
entries.set(key, value);
|
|
38
|
+
}
|
|
39
|
+
return entries;
|
|
40
|
+
}
|
|
41
|
+
function loadRawConfigEnv() {
|
|
42
|
+
try {
|
|
43
|
+
return parseEnvFile(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
44
|
+
} catch {
|
|
45
|
+
return /* @__PURE__ */ new Map();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
25
48
|
|
|
26
49
|
// src/service-manager.ts
|
|
27
50
|
var moduleDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
@@ -104,6 +127,24 @@ function getUiServerStatus() {
|
|
|
104
127
|
port: status.port ?? uiPort
|
|
105
128
|
};
|
|
106
129
|
}
|
|
130
|
+
function buildDaemonEnv() {
|
|
131
|
+
const env = { ...process.env };
|
|
132
|
+
env.CTI_HOME = CTI_HOME;
|
|
133
|
+
for (const [key, value] of loadRawConfigEnv()) {
|
|
134
|
+
env[key] = value;
|
|
135
|
+
}
|
|
136
|
+
delete env.CLAUDECODE;
|
|
137
|
+
return env;
|
|
138
|
+
}
|
|
139
|
+
async function waitForBridgeRunning(timeoutMs = 2e4) {
|
|
140
|
+
const startedAt = Date.now();
|
|
141
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
142
|
+
const status = getBridgeStatus();
|
|
143
|
+
if (status.running) return status;
|
|
144
|
+
await sleep(500);
|
|
145
|
+
}
|
|
146
|
+
return getBridgeStatus();
|
|
147
|
+
}
|
|
107
148
|
async function waitForUiServer(timeoutMs = 15e3) {
|
|
108
149
|
const startedAt = Date.now();
|
|
109
150
|
while (Date.now() - startedAt < timeoutMs) {
|
|
@@ -119,6 +160,30 @@ async function waitForUiServer(timeoutMs = 15e3) {
|
|
|
119
160
|
}
|
|
120
161
|
return getUiServerStatus();
|
|
121
162
|
}
|
|
163
|
+
async function startBridge() {
|
|
164
|
+
ensureDirs();
|
|
165
|
+
const current = getBridgeStatus();
|
|
166
|
+
if (current.running) return current;
|
|
167
|
+
const daemonEntry = path2.join(packageRoot, "dist", "daemon.mjs");
|
|
168
|
+
if (!fs2.existsSync(daemonEntry)) {
|
|
169
|
+
throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
|
|
170
|
+
}
|
|
171
|
+
const stdoutFd = fs2.openSync(path2.join(logsDir, "bridge-launcher.out.log"), "a");
|
|
172
|
+
const stderrFd = fs2.openSync(path2.join(logsDir, "bridge-launcher.err.log"), "a");
|
|
173
|
+
const child = spawn(process.execPath, [daemonEntry], {
|
|
174
|
+
cwd: packageRoot,
|
|
175
|
+
detached: true,
|
|
176
|
+
env: buildDaemonEnv(),
|
|
177
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
178
|
+
...WINDOWS_HIDE
|
|
179
|
+
});
|
|
180
|
+
child.unref();
|
|
181
|
+
const status = await waitForBridgeRunning();
|
|
182
|
+
if (!status.running) {
|
|
183
|
+
throw new Error(status.lastExitReason || "Bridge failed to report running=true.");
|
|
184
|
+
}
|
|
185
|
+
return status;
|
|
186
|
+
}
|
|
122
187
|
async function stopBridge() {
|
|
123
188
|
const status = getBridgeStatus();
|
|
124
189
|
if (!status.pid || !isProcessAlive(status.pid)) {
|
|
@@ -238,8 +303,19 @@ async function main() {
|
|
|
238
303
|
const status = await ensureUiServerRunning();
|
|
239
304
|
const url = getUiServerUrl(status.port);
|
|
240
305
|
openBrowser(url);
|
|
241
|
-
|
|
306
|
+
try {
|
|
307
|
+
await startBridge();
|
|
308
|
+
process.stdout.write(`Codex to IM is available at ${url}
|
|
242
309
|
`);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
process.stdout.write(`Codex to IM UI is available at ${url}
|
|
312
|
+
`);
|
|
313
|
+
process.stderr.write(
|
|
314
|
+
`Bridge failed to start. Open the UI and check logs/config first: ${error instanceof Error ? error.message : String(error)}
|
|
315
|
+
`
|
|
316
|
+
);
|
|
317
|
+
process.exitCode = 1;
|
|
318
|
+
}
|
|
243
319
|
return;
|
|
244
320
|
}
|
|
245
321
|
case "url": {
|
|
@@ -260,14 +336,6 @@ async function main() {
|
|
|
260
336
|
process.exitCode = 1;
|
|
261
337
|
return;
|
|
262
338
|
}
|
|
263
|
-
case "share-feishu": {
|
|
264
|
-
const status = await ensureUiServerRunning();
|
|
265
|
-
const url = `${getUiServerUrl(status.port)}/#desktop`;
|
|
266
|
-
openBrowser(url);
|
|
267
|
-
process.stdout.write(`Opened Feishu handoff entry at ${url}
|
|
268
|
-
`);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
339
|
case "stop": {
|
|
272
340
|
const bridge = await stopBridge();
|
|
273
341
|
const ui = await stopUiServer();
|
|
@@ -290,7 +358,7 @@ async function main() {
|
|
|
290
358
|
return;
|
|
291
359
|
}
|
|
292
360
|
default:
|
|
293
|
-
process.stdout.write("Usage: codex-to-im [open|url|
|
|
361
|
+
process.stdout.write("Usage: codex-to-im [open|url|stop|status]\n");
|
|
294
362
|
}
|
|
295
363
|
}
|
|
296
364
|
main().catch((error) => {
|
package/dist/daemon.mjs
CHANGED
|
@@ -4047,7 +4047,9 @@ var init_codex_provider = __esm({
|
|
|
4047
4047
|
}
|
|
4048
4048
|
let sawAnyEvent = false;
|
|
4049
4049
|
try {
|
|
4050
|
-
const { events } = await thread.runStreamed(input
|
|
4050
|
+
const { events } = await thread.runStreamed(input, {
|
|
4051
|
+
signal: params.abortController?.signal
|
|
4052
|
+
});
|
|
4051
4053
|
for await (const event of events) {
|
|
4052
4054
|
sawAnyEvent = true;
|
|
4053
4055
|
if (params.abortController?.signal.aborted) {
|
|
@@ -5271,15 +5273,18 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5271
5273
|
lastIncomingMessageId = /* @__PURE__ */ new Map();
|
|
5272
5274
|
/** Track active typing reaction IDs per chat for cleanup. */
|
|
5273
5275
|
typingReactions = /* @__PURE__ */ new Map();
|
|
5274
|
-
/** Active streaming card state per
|
|
5276
|
+
/** Active streaming card state per stream key. */
|
|
5275
5277
|
activeCards = /* @__PURE__ */ new Map();
|
|
5276
|
-
/** In-flight card creation promises per
|
|
5278
|
+
/** In-flight card creation promises per stream key — prevents duplicate creation. */
|
|
5277
5279
|
cardCreatePromises = /* @__PURE__ */ new Map();
|
|
5278
5280
|
/** Cached tenant token for upload APIs. */
|
|
5279
5281
|
tenantTokenCache = null;
|
|
5280
5282
|
isStreamingEnabled() {
|
|
5281
5283
|
return getBridgeContext().store.getSetting("bridge_feishu_streaming_enabled") !== "false";
|
|
5282
5284
|
}
|
|
5285
|
+
resolveStreamKey(chatId, streamKey) {
|
|
5286
|
+
return streamKey?.trim() || chatId;
|
|
5287
|
+
}
|
|
5283
5288
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
5284
5289
|
async start() {
|
|
5285
5290
|
if (this.running) return;
|
|
@@ -5384,10 +5389,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5384
5389
|
* Add a "Typing" emoji reaction to the user's message and create streaming card.
|
|
5385
5390
|
* Called by bridge-manager via onMessageStart().
|
|
5386
5391
|
*/
|
|
5387
|
-
onMessageStart(chatId) {
|
|
5392
|
+
onMessageStart(chatId, streamKey) {
|
|
5388
5393
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5389
5394
|
if (messageId && this.isStreamingEnabled()) {
|
|
5390
|
-
this.createStreamingCard(chatId, messageId).catch(() => {
|
|
5395
|
+
this.createStreamingCard(chatId, messageId, streamKey).catch(() => {
|
|
5391
5396
|
});
|
|
5392
5397
|
}
|
|
5393
5398
|
if (!messageId || !this.restClient) return;
|
|
@@ -5410,8 +5415,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5410
5415
|
* Remove the "Typing" emoji reaction and clean up card state.
|
|
5411
5416
|
* Called by bridge-manager via onMessageEnd().
|
|
5412
5417
|
*/
|
|
5413
|
-
onMessageEnd(chatId) {
|
|
5414
|
-
this.cleanupCard(chatId);
|
|
5418
|
+
onMessageEnd(chatId, streamKey) {
|
|
5419
|
+
this.cleanupCard(chatId, streamKey);
|
|
5415
5420
|
const reactionId = this.typingReactions.get(chatId);
|
|
5416
5421
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5417
5422
|
if (!reactionId || !messageId || !this.restClient) return;
|
|
@@ -5462,17 +5467,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5462
5467
|
* Create a new streaming card and send it as a message.
|
|
5463
5468
|
* Returns true if card was created successfully.
|
|
5464
5469
|
*/
|
|
5465
|
-
createStreamingCard(chatId, replyToMessageId) {
|
|
5466
|
-
|
|
5467
|
-
|
|
5470
|
+
createStreamingCard(chatId, replyToMessageId, streamKey) {
|
|
5471
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5472
|
+
if (!this.restClient || this.activeCards.has(cardKey)) return Promise.resolve(false);
|
|
5473
|
+
const existing = this.cardCreatePromises.get(cardKey);
|
|
5468
5474
|
if (existing) return existing;
|
|
5469
|
-
const promise = this._doCreateStreamingCard(chatId, replyToMessageId);
|
|
5470
|
-
this.cardCreatePromises.set(
|
|
5471
|
-
promise.finally(() => this.cardCreatePromises.delete(
|
|
5475
|
+
const promise = this._doCreateStreamingCard(chatId, replyToMessageId, cardKey);
|
|
5476
|
+
this.cardCreatePromises.set(cardKey, promise);
|
|
5477
|
+
promise.finally(() => this.cardCreatePromises.delete(cardKey));
|
|
5472
5478
|
return promise;
|
|
5473
5479
|
}
|
|
5474
|
-
async _doCreateStreamingCard(chatId, replyToMessageId) {
|
|
5480
|
+
async _doCreateStreamingCard(chatId, replyToMessageId, streamKey) {
|
|
5475
5481
|
if (!this.restClient) return false;
|
|
5482
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5476
5483
|
const cardkit = this.restClient.cardkit?.v1;
|
|
5477
5484
|
if (!cardkit?.card) {
|
|
5478
5485
|
console.warn("[feishu-adapter] CardKit v1 API is unavailable in the current Feishu SDK client");
|
|
@@ -5526,7 +5533,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5526
5533
|
console.warn("[feishu-adapter] Card message send returned no message_id");
|
|
5527
5534
|
return false;
|
|
5528
5535
|
}
|
|
5529
|
-
this.activeCards.set(
|
|
5536
|
+
this.activeCards.set(cardKey, {
|
|
5537
|
+
chatId,
|
|
5530
5538
|
cardId,
|
|
5531
5539
|
messageId,
|
|
5532
5540
|
sequence: 0,
|
|
@@ -5537,7 +5545,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5537
5545
|
lastUpdateAt: 0,
|
|
5538
5546
|
throttleTimer: null
|
|
5539
5547
|
});
|
|
5540
|
-
console.log(`[feishu-adapter] Streaming card created: cardId=${cardId}, msgId=${messageId}`);
|
|
5548
|
+
console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
|
|
5541
5549
|
return true;
|
|
5542
5550
|
} catch (err) {
|
|
5543
5551
|
console.warn("[feishu-adapter] Failed to create streaming card:", err instanceof Error ? err.message : err);
|
|
@@ -5547,8 +5555,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5547
5555
|
/**
|
|
5548
5556
|
* Update streaming card content with throttling.
|
|
5549
5557
|
*/
|
|
5550
|
-
updateCardContent(chatId, text2) {
|
|
5551
|
-
const
|
|
5558
|
+
updateCardContent(chatId, text2, streamKey) {
|
|
5559
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5560
|
+
const state = this.activeCards.get(cardKey);
|
|
5552
5561
|
if (!state || !this.restClient) return;
|
|
5553
5562
|
if (state.thinking && text2.trim()) {
|
|
5554
5563
|
state.thinking = false;
|
|
@@ -5559,7 +5568,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5559
5568
|
if (!state.throttleTimer) {
|
|
5560
5569
|
state.throttleTimer = setTimeout(() => {
|
|
5561
5570
|
state.throttleTimer = null;
|
|
5562
|
-
this.flushCardUpdate(
|
|
5571
|
+
this.flushCardUpdate(cardKey);
|
|
5563
5572
|
}, CARD_THROTTLE_MS - elapsed);
|
|
5564
5573
|
}
|
|
5565
5574
|
return;
|
|
@@ -5568,13 +5577,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5568
5577
|
clearTimeout(state.throttleTimer);
|
|
5569
5578
|
state.throttleTimer = null;
|
|
5570
5579
|
}
|
|
5571
|
-
this.flushCardUpdate(
|
|
5580
|
+
this.flushCardUpdate(cardKey);
|
|
5572
5581
|
}
|
|
5573
5582
|
/**
|
|
5574
5583
|
* Flush pending card update to Feishu API.
|
|
5575
5584
|
*/
|
|
5576
|
-
flushCardUpdate(
|
|
5577
|
-
const state = this.activeCards.get(
|
|
5585
|
+
flushCardUpdate(streamKey) {
|
|
5586
|
+
const state = this.activeCards.get(streamKey);
|
|
5578
5587
|
if (!state || !this.restClient) return;
|
|
5579
5588
|
const cardkit = this.restClient.cardkit?.v1;
|
|
5580
5589
|
if (!cardkit?.cardElement?.content) return;
|
|
@@ -5594,24 +5603,26 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5594
5603
|
/**
|
|
5595
5604
|
* Update tool progress in the streaming card.
|
|
5596
5605
|
*/
|
|
5597
|
-
updateToolProgress(chatId, tools) {
|
|
5598
|
-
const
|
|
5606
|
+
updateToolProgress(chatId, tools, streamKey) {
|
|
5607
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5608
|
+
const state = this.activeCards.get(cardKey);
|
|
5599
5609
|
if (!state) return;
|
|
5600
5610
|
state.toolCalls = tools;
|
|
5601
|
-
this.updateCardContent(chatId, state.pendingText || "");
|
|
5611
|
+
this.updateCardContent(chatId, state.pendingText || "", cardKey);
|
|
5602
5612
|
}
|
|
5603
5613
|
/**
|
|
5604
5614
|
* Finalize the streaming card: close streaming mode, update with final content + footer.
|
|
5605
5615
|
*/
|
|
5606
|
-
async finalizeCard(chatId, status, responseText) {
|
|
5607
|
-
const
|
|
5616
|
+
async finalizeCard(chatId, status, responseText, streamKey) {
|
|
5617
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5618
|
+
const pending = this.cardCreatePromises.get(cardKey);
|
|
5608
5619
|
if (pending) {
|
|
5609
5620
|
try {
|
|
5610
5621
|
await pending;
|
|
5611
5622
|
} catch {
|
|
5612
5623
|
}
|
|
5613
5624
|
}
|
|
5614
|
-
const state = this.activeCards.get(
|
|
5625
|
+
const state = this.activeCards.get(cardKey);
|
|
5615
5626
|
if (!state || !this.restClient) return false;
|
|
5616
5627
|
const cardkit = this.restClient.cardkit?.v1;
|
|
5617
5628
|
if (!cardkit?.card?.settings || !cardkit?.card?.update) return false;
|
|
@@ -5638,7 +5649,16 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5638
5649
|
status: statusLabels[status] || status,
|
|
5639
5650
|
elapsed: formatElapsed(elapsedMs)
|
|
5640
5651
|
};
|
|
5641
|
-
const
|
|
5652
|
+
const existingText = state.pendingText || "";
|
|
5653
|
+
const trimmedExisting = existingText.trim();
|
|
5654
|
+
const trimmedResponse = responseText.trim();
|
|
5655
|
+
let finalText = trimmedResponse || trimmedExisting;
|
|
5656
|
+
if (status === "interrupted" && trimmedExisting && trimmedResponse && trimmedResponse !== trimmedExisting && !trimmedExisting.includes(trimmedResponse)) {
|
|
5657
|
+
finalText = `${trimmedExisting}
|
|
5658
|
+
|
|
5659
|
+
${trimmedResponse}`;
|
|
5660
|
+
}
|
|
5661
|
+
const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
|
|
5642
5662
|
state.sequence++;
|
|
5643
5663
|
await cardkit.card.update({
|
|
5644
5664
|
path: { card_id: state.cardId },
|
|
@@ -5647,63 +5667,66 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5647
5667
|
sequence: state.sequence
|
|
5648
5668
|
}
|
|
5649
5669
|
});
|
|
5650
|
-
console.log(`[feishu-adapter] Card finalized: cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
5670
|
+
console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
5651
5671
|
return true;
|
|
5652
5672
|
} catch (err) {
|
|
5653
5673
|
console.warn("[feishu-adapter] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
5654
5674
|
return false;
|
|
5655
5675
|
} finally {
|
|
5656
|
-
this.activeCards.delete(
|
|
5676
|
+
this.activeCards.delete(cardKey);
|
|
5657
5677
|
}
|
|
5658
5678
|
}
|
|
5659
5679
|
/**
|
|
5660
5680
|
* Clean up card state without finalizing (e.g. on unexpected errors).
|
|
5661
5681
|
*/
|
|
5662
|
-
cleanupCard(chatId) {
|
|
5663
|
-
this.
|
|
5664
|
-
|
|
5682
|
+
cleanupCard(chatId, streamKey) {
|
|
5683
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5684
|
+
this.cardCreatePromises.delete(cardKey);
|
|
5685
|
+
const state = this.activeCards.get(cardKey);
|
|
5665
5686
|
if (!state) return;
|
|
5666
5687
|
if (state.throttleTimer) {
|
|
5667
5688
|
clearTimeout(state.throttleTimer);
|
|
5668
5689
|
}
|
|
5669
|
-
this.activeCards.delete(
|
|
5690
|
+
this.activeCards.delete(cardKey);
|
|
5670
5691
|
}
|
|
5671
5692
|
/**
|
|
5672
5693
|
* Check if there is an active streaming card for a given chat.
|
|
5673
5694
|
*/
|
|
5674
|
-
hasActiveCard(chatId) {
|
|
5675
|
-
return this.activeCards.has(chatId);
|
|
5695
|
+
hasActiveCard(chatId, streamKey) {
|
|
5696
|
+
return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
|
|
5676
5697
|
}
|
|
5677
5698
|
// ── Streaming adapter interface ────────────────────────────────
|
|
5678
5699
|
/**
|
|
5679
5700
|
* Called by bridge-manager on each text SSE event.
|
|
5680
5701
|
* Creates streaming card on first call, then updates content.
|
|
5681
5702
|
*/
|
|
5682
|
-
onStreamText(chatId, fullText) {
|
|
5703
|
+
onStreamText(chatId, fullText, streamKey) {
|
|
5683
5704
|
if (!this.isStreamingEnabled()) return;
|
|
5684
|
-
|
|
5705
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5706
|
+
if (!this.activeCards.has(cardKey)) {
|
|
5685
5707
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5686
|
-
this.createStreamingCard(chatId, messageId).then((ok) => {
|
|
5687
|
-
if (ok) this.updateCardContent(chatId, fullText);
|
|
5708
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
5709
|
+
if (ok) this.updateCardContent(chatId, fullText, cardKey);
|
|
5688
5710
|
}).catch(() => {
|
|
5689
5711
|
});
|
|
5690
5712
|
return;
|
|
5691
5713
|
}
|
|
5692
|
-
this.updateCardContent(chatId, fullText);
|
|
5714
|
+
this.updateCardContent(chatId, fullText, cardKey);
|
|
5693
5715
|
}
|
|
5694
|
-
onMirrorStreamStart(chatId) {
|
|
5716
|
+
onMirrorStreamStart(chatId, streamKey) {
|
|
5695
5717
|
if (!this.isStreamingEnabled()) return;
|
|
5696
|
-
|
|
5697
|
-
this.
|
|
5718
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5719
|
+
if (this.activeCards.has(cardKey)) return;
|
|
5720
|
+
this.createStreamingCard(chatId, void 0, cardKey).catch(() => {
|
|
5698
5721
|
});
|
|
5699
5722
|
}
|
|
5700
|
-
onToolEvent(chatId, tools) {
|
|
5723
|
+
onToolEvent(chatId, tools, streamKey) {
|
|
5701
5724
|
if (!this.isStreamingEnabled()) return;
|
|
5702
|
-
this.updateToolProgress(chatId, tools);
|
|
5725
|
+
this.updateToolProgress(chatId, tools, streamKey);
|
|
5703
5726
|
}
|
|
5704
|
-
async onStreamEnd(chatId, status, responseText) {
|
|
5727
|
+
async onStreamEnd(chatId, status, responseText, streamKey) {
|
|
5705
5728
|
if (!this.isStreamingEnabled()) return false;
|
|
5706
|
-
return this.finalizeCard(chatId, status, responseText);
|
|
5729
|
+
return this.finalizeCard(chatId, status, responseText, streamKey);
|
|
5707
5730
|
}
|
|
5708
5731
|
// ── Send ────────────────────────────────────────────────────
|
|
5709
5732
|
async send(message) {
|
|
@@ -16784,7 +16807,8 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
|
|
|
16784
16807
|
var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
16785
16808
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
16786
16809
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
16787
|
-
var
|
|
16810
|
+
var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
|
|
16811
|
+
var MIRROR_IDLE_TIMEOUT_MS = 6e5;
|
|
16788
16812
|
var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
|
|
16789
16813
|
var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
|
|
16790
16814
|
function generateDraftId() {
|
|
@@ -16818,8 +16842,6 @@ function resolveCommandAlias(rawCommand, args) {
|
|
|
16818
16842
|
return "/help";
|
|
16819
16843
|
case "/t":
|
|
16820
16844
|
return !args ? "/threads" : /^(all|n\b)/i.test(args.trim()) ? "/threads" : "/thread";
|
|
16821
|
-
case "/s":
|
|
16822
|
-
return args ? "/use" : "/sessions";
|
|
16823
16845
|
case "/n":
|
|
16824
16846
|
return "/new";
|
|
16825
16847
|
case "/m":
|
|
@@ -16890,6 +16912,12 @@ function getSessionDisplayName(session, fallbackDirectory) {
|
|
|
16890
16912
|
function nowIso() {
|
|
16891
16913
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
16892
16914
|
}
|
|
16915
|
+
function buildInteractiveStreamKey(sessionId, messageId) {
|
|
16916
|
+
return `im:${sessionId}:${messageId}`;
|
|
16917
|
+
}
|
|
16918
|
+
function buildMirrorStreamKey(sessionId, turnId, startedAt) {
|
|
16919
|
+
return `mirror:${sessionId}:${turnId || startedAt}`;
|
|
16920
|
+
}
|
|
16893
16921
|
function getWorkspaceRoot() {
|
|
16894
16922
|
const { store } = getBridgeContext();
|
|
16895
16923
|
return store.getSetting("bridge_default_workspace_root") || DEFAULT_WORKSPACE_ROOT;
|
|
@@ -17468,6 +17496,53 @@ function getQueuedCount(sessionId) {
|
|
|
17468
17496
|
const state = getState();
|
|
17469
17497
|
return state.queuedCounts.get(sessionId) || 0;
|
|
17470
17498
|
}
|
|
17499
|
+
function buildInteractiveIdleReminderNotice() {
|
|
17500
|
+
return [
|
|
17501
|
+
"\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
|
|
17502
|
+
"\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u7EC8\u6B62\u5B83\uFF1B\u5982\u679C\u4F60\u4ECD\u5728\u5BF9\u5E94\u7EBF\u7A0B\uFF0C\u53EF\u53D1\u9001 `/stop` \u4E3B\u52A8\u505C\u6B62\uFF1B\u5982\u679C\u5DF2\u7ECF\u5207\u5230\u522B\u7684\u7EBF\u7A0B\uFF0C\u9700\u8981\u5148\u5207\u56DE\u5BF9\u5E94\u7EBF\u7A0B\u3002"
|
|
17503
|
+
].join("\n");
|
|
17504
|
+
}
|
|
17505
|
+
function isCurrentInteractiveTask(sessionId, taskId) {
|
|
17506
|
+
return getState().activeTasks.get(sessionId)?.id === taskId;
|
|
17507
|
+
}
|
|
17508
|
+
function touchInteractiveTask(sessionId, taskId) {
|
|
17509
|
+
const task = getState().activeTasks.get(sessionId);
|
|
17510
|
+
if (task?.id !== taskId) return;
|
|
17511
|
+
task.lastActivityAt = Date.now();
|
|
17512
|
+
task.idleReminderSent = false;
|
|
17513
|
+
}
|
|
17514
|
+
function releaseInteractiveTask(sessionId, taskId) {
|
|
17515
|
+
const state = getState();
|
|
17516
|
+
const current = state.activeTasks.get(sessionId);
|
|
17517
|
+
if (current?.id !== taskId) return;
|
|
17518
|
+
state.activeTasks.delete(sessionId);
|
|
17519
|
+
syncSessionRuntimeState(sessionId);
|
|
17520
|
+
}
|
|
17521
|
+
async function remindIdleInteractiveTask(task) {
|
|
17522
|
+
if (!isCurrentInteractiveTask(task.sessionId, task.id) || task.idleReminderSent) return;
|
|
17523
|
+
task.idleReminderSent = true;
|
|
17524
|
+
try {
|
|
17525
|
+
await deliver(task.adapter, {
|
|
17526
|
+
address: task.address,
|
|
17527
|
+
text: renderFeedbackTextForChannel(
|
|
17528
|
+
task.adapter.channelType,
|
|
17529
|
+
buildInteractiveIdleReminderNotice()
|
|
17530
|
+
),
|
|
17531
|
+
parseMode: getFeedbackParseMode(task.adapter.channelType),
|
|
17532
|
+
replyToMessageId: task.requestMessageId
|
|
17533
|
+
});
|
|
17534
|
+
} catch {
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
async function reconcileIdleInteractiveTasks() {
|
|
17538
|
+
const now2 = Date.now();
|
|
17539
|
+
const tasks = Array.from(getState().activeTasks.values());
|
|
17540
|
+
for (const task of tasks) {
|
|
17541
|
+
if (task.idleReminderSent) continue;
|
|
17542
|
+
if (now2 - task.lastActivityAt < INTERACTIVE_IDLE_REMINDER_MS) continue;
|
|
17543
|
+
await remindIdleInteractiveTask(task);
|
|
17544
|
+
}
|
|
17545
|
+
}
|
|
17471
17546
|
function syncSessionRuntimeState(sessionId) {
|
|
17472
17547
|
const { store } = getBridgeContext();
|
|
17473
17548
|
const session = store.getSession(sessionId);
|
|
@@ -17829,6 +17904,16 @@ function formatMirrorMessage(threadTitle, userText, assistantText, markdown = fa
|
|
|
17829
17904
|
sections.unshift(buildMirrorTitle(threadTitle, markdown));
|
|
17830
17905
|
return sections.join("\n\n").trim();
|
|
17831
17906
|
}
|
|
17907
|
+
function buildMirrorTimeoutNotice(markdown = false) {
|
|
17908
|
+
return markdown ? "> \u8D85\u65F6\u63D0\u9192\uFF1A\u957F\u65F6\u95F4\u6CA1\u6709\u6536\u5230\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u8F93\u51FA\uFF0C\u672C\u6B21\u6D41\u5F0F\u540C\u6B65\u5DF2\u5148\u7ED3\u675F\uFF1B\u5982\u679C\u684C\u9762\u540E\u7EED\u7EE7\u7EED\u4EA7\u51FA\u5185\u5BB9\uFF0C\u4F1A\u91CD\u65B0\u5F00\u59CB\u65B0\u4E00\u8F6E\u540C\u6B65\u3002" : "\u8D85\u65F6\u63D0\u9192\uFF1A\u957F\u65F6\u95F4\u6CA1\u6709\u6536\u5230\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u8F93\u51FA\uFF0C\u672C\u6B21\u6D41\u5F0F\u540C\u6B65\u5DF2\u5148\u7ED3\u675F\uFF1B\u5982\u679C\u684C\u9762\u540E\u7EED\u7EE7\u7EED\u4EA7\u51FA\u5185\u5BB9\uFF0C\u4F1A\u91CD\u65B0\u5F00\u59CB\u65B0\u4E00\u8F6E\u540C\u6B65\u3002";
|
|
17909
|
+
}
|
|
17910
|
+
function appendMirrorTimeoutNotice(text2, markdown = false) {
|
|
17911
|
+
const notice = buildMirrorTimeoutNotice(markdown);
|
|
17912
|
+
const normalized = text2.trim();
|
|
17913
|
+
return normalized ? `${normalized}
|
|
17914
|
+
|
|
17915
|
+
${notice}` : notice;
|
|
17916
|
+
}
|
|
17832
17917
|
function getMirrorStreamingAdapter(subscription) {
|
|
17833
17918
|
const state = getState();
|
|
17834
17919
|
const adapter = state.adapters.get(subscription.channelType);
|
|
@@ -17855,9 +17940,9 @@ function startMirrorStreaming(subscription, turnState) {
|
|
|
17855
17940
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
17856
17941
|
if (!adapter || turnState.streamStarted) return;
|
|
17857
17942
|
try {
|
|
17858
|
-
adapter.onMirrorStreamStart?.(subscription.chatId);
|
|
17943
|
+
adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
|
|
17859
17944
|
if (!adapter.onMirrorStreamStart) {
|
|
17860
|
-
adapter.onStreamText?.(subscription.chatId, "");
|
|
17945
|
+
adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
|
|
17861
17946
|
}
|
|
17862
17947
|
turnState.streamStarted = true;
|
|
17863
17948
|
} catch {
|
|
@@ -17873,7 +17958,7 @@ function updateMirrorStreaming(subscription, turnState) {
|
|
|
17873
17958
|
);
|
|
17874
17959
|
if (!text2) return;
|
|
17875
17960
|
try {
|
|
17876
|
-
adapter.onStreamText?.(subscription.chatId, text2);
|
|
17961
|
+
adapter.onStreamText?.(subscription.chatId, text2, turnState.streamKey);
|
|
17877
17962
|
} catch {
|
|
17878
17963
|
}
|
|
17879
17964
|
}
|
|
@@ -17882,7 +17967,7 @@ function updateMirrorToolProgress(subscription, turnState) {
|
|
|
17882
17967
|
if (!adapter || typeof adapter.onToolEvent !== "function") return;
|
|
17883
17968
|
startMirrorStreaming(subscription, turnState);
|
|
17884
17969
|
try {
|
|
17885
|
-
adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()));
|
|
17970
|
+
adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()), turnState.streamKey);
|
|
17886
17971
|
} catch {
|
|
17887
17972
|
}
|
|
17888
17973
|
}
|
|
@@ -17894,7 +17979,7 @@ function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
|
17894
17979
|
subscription.channelType,
|
|
17895
17980
|
getMirrorStreamingText(subscription, pendingTurn)
|
|
17896
17981
|
);
|
|
17897
|
-
void adapter.onStreamEnd(subscription.chatId, status, text2).catch(() => {
|
|
17982
|
+
void adapter.onStreamEnd(subscription.chatId, status, text2, pendingTurn.streamKey).catch(() => {
|
|
17898
17983
|
});
|
|
17899
17984
|
}
|
|
17900
17985
|
async function deliverMirrorTurn(subscription, turn) {
|
|
@@ -17904,8 +17989,10 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17904
17989
|
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
17905
17990
|
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
17906
17991
|
const markdown = responseParseMode === "Markdown";
|
|
17907
|
-
const
|
|
17908
|
-
const
|
|
17992
|
+
const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
|
|
17993
|
+
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
|
|
17994
|
+
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
17995
|
+
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
17909
17996
|
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
17910
17997
|
const streamText = renderFeedbackText(
|
|
17911
17998
|
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
@@ -17916,7 +18003,8 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17916
18003
|
const finalized = await adapter.onStreamEnd(
|
|
17917
18004
|
subscription.chatId,
|
|
17918
18005
|
turn.status,
|
|
17919
|
-
streamText
|
|
18006
|
+
streamText,
|
|
18007
|
+
turn.streamKey
|
|
17920
18008
|
);
|
|
17921
18009
|
if (finalized) {
|
|
17922
18010
|
subscription.lastDeliveredAt = turn.timestamp || nowIso();
|
|
@@ -17948,10 +18036,11 @@ async function deliverMirrorTurns(subscription, turns) {
|
|
|
17948
18036
|
await deliverMirrorTurn(subscription, turn);
|
|
17949
18037
|
}
|
|
17950
18038
|
}
|
|
17951
|
-
function createMirrorTurnState(timestamp, turnId) {
|
|
18039
|
+
function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
17952
18040
|
const safeTimestamp = timestamp || nowIso();
|
|
17953
18041
|
return {
|
|
17954
18042
|
turnId: turnId || null,
|
|
18043
|
+
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
17955
18044
|
startedAt: safeTimestamp,
|
|
17956
18045
|
lastActivityAt: safeTimestamp,
|
|
17957
18046
|
userText: null,
|
|
@@ -17985,7 +18074,7 @@ ${normalized}` : normalized;
|
|
|
17985
18074
|
}
|
|
17986
18075
|
function ensureMirrorTurnState(subscription, record) {
|
|
17987
18076
|
if (!subscription.pendingTurn) {
|
|
17988
|
-
subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
|
|
18077
|
+
subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
|
|
17989
18078
|
return subscription.pendingTurn;
|
|
17990
18079
|
}
|
|
17991
18080
|
if (!subscription.pendingTurn.turnId && record.turnId) {
|
|
@@ -18008,11 +18097,13 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
|
|
|
18008
18097
|
const userText = pendingTurn.userText?.trim() || null;
|
|
18009
18098
|
if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
|
|
18010
18099
|
return {
|
|
18100
|
+
streamKey: pendingTurn.streamKey,
|
|
18011
18101
|
userText,
|
|
18012
18102
|
text: text2,
|
|
18013
18103
|
signature,
|
|
18014
18104
|
timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
|
|
18015
|
-
status
|
|
18105
|
+
status,
|
|
18106
|
+
...signature.startsWith("timeout:") ? { timedOut: true } : {}
|
|
18016
18107
|
};
|
|
18017
18108
|
}
|
|
18018
18109
|
function consumeMirrorRecords(subscription, records) {
|
|
@@ -18026,7 +18117,7 @@ function consumeMirrorRecords(subscription, records) {
|
|
|
18026
18117
|
if (superseded) finalized.push(superseded);
|
|
18027
18118
|
}
|
|
18028
18119
|
if (!subscription.pendingTurn) {
|
|
18029
|
-
subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
|
|
18120
|
+
subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
|
|
18030
18121
|
} else {
|
|
18031
18122
|
if (!subscription.pendingTurn.turnId && record.turnId) {
|
|
18032
18123
|
subscription.pendingTurn.turnId = record.turnId;
|
|
@@ -18305,11 +18396,21 @@ async function reconcileMirrorSubscription(subscription) {
|
|
|
18305
18396
|
subscription.bufferedRecords.push(...filteredRecords);
|
|
18306
18397
|
}
|
|
18307
18398
|
}
|
|
18399
|
+
const timedOutTurn = flushTimedOutMirrorTurn(subscription);
|
|
18308
18400
|
if (getState().activeTasks.has(subscription.sessionId) || isMirrorSuppressed(subscription.sessionId)) {
|
|
18401
|
+
if (timedOutTurn) {
|
|
18402
|
+
try {
|
|
18403
|
+
await deliverMirrorTurns(subscription, [timedOutTurn]);
|
|
18404
|
+
} catch (error) {
|
|
18405
|
+
subscription.dirty = true;
|
|
18406
|
+
console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
|
|
18407
|
+
}
|
|
18408
|
+
}
|
|
18309
18409
|
syncMirrorSessionState(subscription.sessionId);
|
|
18310
18410
|
return;
|
|
18311
18411
|
}
|
|
18312
|
-
const finalizedTurns =
|
|
18412
|
+
const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
|
|
18413
|
+
finalizedTurns.push(...consumeBufferedMirrorTurns(subscription));
|
|
18313
18414
|
if (finalizedTurns.length === 0) {
|
|
18314
18415
|
syncMirrorSessionState(subscription.sessionId);
|
|
18315
18416
|
return;
|
|
@@ -18473,6 +18574,9 @@ async function start() {
|
|
|
18473
18574
|
void syncConfiguredAdapters({ startLoops: true }).catch((err) => {
|
|
18474
18575
|
console.error("[bridge-manager] Adapter reconcile failed:", err);
|
|
18475
18576
|
});
|
|
18577
|
+
void reconcileIdleInteractiveTasks().catch((err) => {
|
|
18578
|
+
console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
|
|
18579
|
+
});
|
|
18476
18580
|
}, 5e3);
|
|
18477
18581
|
state.mirrorPollTimer = setInterval(() => {
|
|
18478
18582
|
void reconcileMirrorSubscriptions().catch((err) => {
|
|
@@ -18506,8 +18610,8 @@ async function stop() {
|
|
|
18506
18610
|
}
|
|
18507
18611
|
state.loopAborts.clear();
|
|
18508
18612
|
const activeSessionIds = Array.from(state.activeTasks.keys());
|
|
18509
|
-
for (const
|
|
18510
|
-
|
|
18613
|
+
for (const task of state.activeTasks.values()) {
|
|
18614
|
+
task.abortController.abort();
|
|
18511
18615
|
}
|
|
18512
18616
|
state.activeTasks.clear();
|
|
18513
18617
|
state.mirrorSuppressUntil.clear();
|
|
@@ -18712,12 +18816,28 @@ async function handleMessage(adapter, msg) {
|
|
|
18712
18816
|
return;
|
|
18713
18817
|
}
|
|
18714
18818
|
const binding = resolve(msg.address);
|
|
18715
|
-
|
|
18819
|
+
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
18820
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
18716
18821
|
const taskAbort = new AbortController();
|
|
18822
|
+
const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
18717
18823
|
const state = getState();
|
|
18718
18824
|
resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
|
|
18719
|
-
|
|
18720
|
-
|
|
18825
|
+
const taskState = {
|
|
18826
|
+
id: taskId,
|
|
18827
|
+
abortController: taskAbort,
|
|
18828
|
+
adapter,
|
|
18829
|
+
address: msg.address,
|
|
18830
|
+
requestMessageId: msg.messageId,
|
|
18831
|
+
streamKey,
|
|
18832
|
+
sessionId: binding.codepilotSessionId,
|
|
18833
|
+
hasStreamingCards: false,
|
|
18834
|
+
lastActivityAt: Date.now(),
|
|
18835
|
+
idleReminderSent: false,
|
|
18836
|
+
streamFinalized: false,
|
|
18837
|
+
uiEnded: false,
|
|
18838
|
+
mirrorSuppressionId: null
|
|
18839
|
+
};
|
|
18840
|
+
state.activeTasks.set(binding.codepilotSessionId, taskState);
|
|
18721
18841
|
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
18722
18842
|
let previewState = null;
|
|
18723
18843
|
const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
|
|
@@ -18766,18 +18886,22 @@ async function handleMessage(adapter, msg) {
|
|
|
18766
18886
|
flushPreview(adapter, ps, cfg);
|
|
18767
18887
|
} : void 0;
|
|
18768
18888
|
const hasStreamingCards = typeof adapter.onStreamText === "function";
|
|
18889
|
+
taskState.hasStreamingCards = hasStreamingCards;
|
|
18769
18890
|
const toolCallTracker = /* @__PURE__ */ new Map();
|
|
18770
18891
|
const onStreamCardText = hasStreamingCards ? (fullText) => {
|
|
18892
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
18771
18893
|
const rendered = renderFeedbackTextForChannel(
|
|
18772
18894
|
adapter.channelType,
|
|
18773
18895
|
stripOutboundArtifactBlocksForStreaming(fullText)
|
|
18774
18896
|
);
|
|
18775
18897
|
try {
|
|
18776
|
-
adapter.onStreamText(msg.address.chatId, rendered);
|
|
18898
|
+
adapter.onStreamText(msg.address.chatId, rendered, streamKey);
|
|
18777
18899
|
} catch {
|
|
18778
18900
|
}
|
|
18779
18901
|
} : void 0;
|
|
18780
18902
|
const onToolEvent = hasStreamingCards ? (toolId, toolName, status) => {
|
|
18903
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
18904
|
+
touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
18781
18905
|
if (toolName) {
|
|
18782
18906
|
toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
|
|
18783
18907
|
} else {
|
|
@@ -18785,11 +18909,13 @@ async function handleMessage(adapter, msg) {
|
|
|
18785
18909
|
if (existing) existing.status = status;
|
|
18786
18910
|
}
|
|
18787
18911
|
try {
|
|
18788
|
-
adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()));
|
|
18912
|
+
adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()), streamKey);
|
|
18789
18913
|
} catch {
|
|
18790
18914
|
}
|
|
18791
18915
|
} : void 0;
|
|
18792
18916
|
const onPartialText = previewOnPartialText || onStreamCardText ? (fullText) => {
|
|
18917
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
18918
|
+
touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
18793
18919
|
if (previewOnPartialText) previewOnPartialText(fullText);
|
|
18794
18920
|
if (onStreamCardText) onStreamCardText(fullText);
|
|
18795
18921
|
} : void 0;
|
|
@@ -18807,10 +18933,13 @@ async function handleMessage(adapter, msg) {
|
|
|
18807
18933
|
msg.messageId
|
|
18808
18934
|
);
|
|
18809
18935
|
}, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent, (preparedPrompt) => {
|
|
18810
|
-
if (!mirrorSuppressionId) {
|
|
18811
|
-
mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
18936
|
+
if (!taskState.mirrorSuppressionId) {
|
|
18937
|
+
taskState.mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
18812
18938
|
}
|
|
18813
18939
|
});
|
|
18940
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
18941
|
+
return;
|
|
18942
|
+
}
|
|
18814
18943
|
let cardFinalized = false;
|
|
18815
18944
|
if (hasStreamingCards && adapter.onStreamEnd) {
|
|
18816
18945
|
try {
|
|
@@ -18818,8 +18947,10 @@ async function handleMessage(adapter, msg) {
|
|
|
18818
18947
|
cardFinalized = await adapter.onStreamEnd(
|
|
18819
18948
|
msg.address.chatId,
|
|
18820
18949
|
status,
|
|
18821
|
-
renderFeedbackTextForChannel(adapter.channelType, result.responseText)
|
|
18950
|
+
renderFeedbackTextForChannel(adapter.channelType, result.responseText),
|
|
18951
|
+
streamKey
|
|
18822
18952
|
);
|
|
18953
|
+
taskState.streamFinalized = cardFinalized;
|
|
18823
18954
|
} catch (err) {
|
|
18824
18955
|
console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
18825
18956
|
}
|
|
@@ -18844,14 +18975,9 @@ async function handleMessage(adapter, msg) {
|
|
|
18844
18975
|
[]
|
|
18845
18976
|
);
|
|
18846
18977
|
}
|
|
18847
|
-
|
|
18848
|
-
|
|
18849
|
-
|
|
18850
|
-
if (update !== null) {
|
|
18851
|
-
store.updateChannelBinding(binding.id, { sdkSessionId: update });
|
|
18852
|
-
}
|
|
18853
|
-
} catch {
|
|
18854
|
-
}
|
|
18978
|
+
try {
|
|
18979
|
+
persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
|
|
18980
|
+
} catch {
|
|
18855
18981
|
}
|
|
18856
18982
|
} finally {
|
|
18857
18983
|
if (previewState) {
|
|
@@ -18861,18 +18987,22 @@ async function handleMessage(adapter, msg) {
|
|
|
18861
18987
|
}
|
|
18862
18988
|
adapter.endPreview?.(msg.address.chatId, previewState.draftId);
|
|
18863
18989
|
}
|
|
18864
|
-
if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
|
|
18990
|
+
if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted && !taskState.streamFinalized) {
|
|
18865
18991
|
try {
|
|
18866
|
-
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "");
|
|
18992
|
+
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
|
|
18993
|
+
taskState.streamFinalized = true;
|
|
18867
18994
|
} catch {
|
|
18868
18995
|
}
|
|
18869
18996
|
}
|
|
18870
|
-
if (mirrorSuppressionId) {
|
|
18871
|
-
settleMirrorSuppression(binding.codepilotSessionId, mirrorSuppressionId);
|
|
18997
|
+
if (taskState.mirrorSuppressionId) {
|
|
18998
|
+
settleMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
|
|
18999
|
+
taskState.mirrorSuppressionId = null;
|
|
19000
|
+
}
|
|
19001
|
+
releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19002
|
+
if (!taskState.uiEnded) {
|
|
19003
|
+
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
19004
|
+
taskState.uiEnded = true;
|
|
18872
19005
|
}
|
|
18873
|
-
state.activeTasks.delete(binding.codepilotSessionId);
|
|
18874
|
-
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
18875
|
-
adapter.onMessageEnd?.(msg.address.chatId);
|
|
18876
19006
|
ack();
|
|
18877
19007
|
}
|
|
18878
19008
|
}
|
|
@@ -18925,15 +19055,6 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
18925
19055
|
response = resolved.message;
|
|
18926
19056
|
break;
|
|
18927
19057
|
}
|
|
18928
|
-
if (currentBinding) {
|
|
18929
|
-
const st = getState();
|
|
18930
|
-
const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
|
|
18931
|
-
if (oldTask) {
|
|
18932
|
-
oldTask.abort();
|
|
18933
|
-
st.activeTasks.delete(currentBinding.codepilotSessionId);
|
|
18934
|
-
syncSessionRuntimeState(currentBinding.codepilotSessionId);
|
|
18935
|
-
}
|
|
18936
|
-
}
|
|
18937
19058
|
const workDir = resolved.workDir;
|
|
18938
19059
|
ensureWorkingDirectoryExists(workDir);
|
|
18939
19060
|
const binding = createBinding(msg.address, workDir);
|
|
@@ -18947,6 +19068,7 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
18947
19068
|
],
|
|
18948
19069
|
[
|
|
18949
19070
|
args.trim() ? "\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002" : "\u5DF2\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u65B0\u5EFA\u4E00\u4E2A\u7EBF\u7A0B\u3002\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002",
|
|
19071
|
+
"\u5982\u679C\u5F53\u524D\u804A\u5929\u91CC\u5DF2\u6709\u65E7\u4EFB\u52A1\u5728\u8FD0\u884C\uFF0C\u5B83\u4E0D\u4F1A\u88AB\u7EC8\u6B62\uFF0C\u4ECD\u4F1A\u5728\u540E\u53F0\u7EE7\u7EED\u6267\u884C\u5E76\u53EF\u80FD\u7A0D\u540E\u56DE\u6D88\u606F\u3002",
|
|
18950
19072
|
"\u8FD9\u662F IM \u4FA7\u7EBF\u7A0B\uFF0C\u5F53\u524D\u53EA\u4FDD\u8BC1\u5728 IM \u4E2D\u53EF\u7EE7\u7EED\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 Codex Desktop \u4F1A\u8BDD\u5217\u8868\u4E2D\u3002"
|
|
18951
19073
|
],
|
|
18952
19074
|
responseParseMode === "Markdown"
|
|
@@ -19358,6 +19480,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19358
19480
|
return {
|
|
19359
19481
|
heading: `${getSessionDisplayName(session, session.working_directory)}${session.id === currentBinding?.codepilotSessionId ? " [\u5F53\u524D]" : ""}`,
|
|
19360
19482
|
details: [
|
|
19483
|
+
`\u72B6\u6001\uFF1A${formatRuntimeStatus(session)}`,
|
|
19361
19484
|
`\u76EE\u5F55\uFF1A${formatCommandPath(session.working_directory)}`
|
|
19362
19485
|
]
|
|
19363
19486
|
};
|
|
@@ -19376,9 +19499,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19376
19499
|
const st = getState();
|
|
19377
19500
|
const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
|
|
19378
19501
|
if (taskAbort) {
|
|
19379
|
-
taskAbort.abort();
|
|
19380
|
-
st.activeTasks.delete(binding.codepilotSessionId);
|
|
19381
|
-
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
19502
|
+
taskAbort.abortController.abort();
|
|
19382
19503
|
response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
|
|
19383
19504
|
} else {
|
|
19384
19505
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
@@ -19483,6 +19604,13 @@ function computeSdkSessionUpdate(sdkSessionId, hasError) {
|
|
|
19483
19604
|
}
|
|
19484
19605
|
return null;
|
|
19485
19606
|
}
|
|
19607
|
+
function persistSdkSessionUpdate(sessionId, sdkSessionId, hasError) {
|
|
19608
|
+
const update = computeSdkSessionUpdate(sdkSessionId, hasError);
|
|
19609
|
+
if (update === null) {
|
|
19610
|
+
return;
|
|
19611
|
+
}
|
|
19612
|
+
getBridgeContext().store.updateSdkSessionId(sessionId, update);
|
|
19613
|
+
}
|
|
19486
19614
|
|
|
19487
19615
|
// src/store.ts
|
|
19488
19616
|
import fs9 from "node:fs";
|
package/dist/ui-server.mjs
CHANGED
|
@@ -5045,7 +5045,9 @@ var CodexProvider = class {
|
|
|
5045
5045
|
}
|
|
5046
5046
|
let sawAnyEvent = false;
|
|
5047
5047
|
try {
|
|
5048
|
-
const { events } = await thread.runStreamed(input
|
|
5048
|
+
const { events } = await thread.runStreamed(input, {
|
|
5049
|
+
signal: params.abortController?.signal
|
|
5050
|
+
});
|
|
5049
5051
|
for await (const event of events) {
|
|
5050
5052
|
sawAnyEvent = true;
|
|
5051
5053
|
if (params.abortController?.signal.aborted) {
|
package/package.json
CHANGED