codex-to-im 1.0.18 → 1.0.19
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 +115 -73
- 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;
|
|
@@ -5647,63 +5658,66 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5647
5658
|
sequence: state.sequence
|
|
5648
5659
|
}
|
|
5649
5660
|
});
|
|
5650
|
-
console.log(`[feishu-adapter] Card finalized: cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
5661
|
+
console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
5651
5662
|
return true;
|
|
5652
5663
|
} catch (err) {
|
|
5653
5664
|
console.warn("[feishu-adapter] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
5654
5665
|
return false;
|
|
5655
5666
|
} finally {
|
|
5656
|
-
this.activeCards.delete(
|
|
5667
|
+
this.activeCards.delete(cardKey);
|
|
5657
5668
|
}
|
|
5658
5669
|
}
|
|
5659
5670
|
/**
|
|
5660
5671
|
* Clean up card state without finalizing (e.g. on unexpected errors).
|
|
5661
5672
|
*/
|
|
5662
|
-
cleanupCard(chatId) {
|
|
5663
|
-
this.
|
|
5664
|
-
|
|
5673
|
+
cleanupCard(chatId, streamKey) {
|
|
5674
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5675
|
+
this.cardCreatePromises.delete(cardKey);
|
|
5676
|
+
const state = this.activeCards.get(cardKey);
|
|
5665
5677
|
if (!state) return;
|
|
5666
5678
|
if (state.throttleTimer) {
|
|
5667
5679
|
clearTimeout(state.throttleTimer);
|
|
5668
5680
|
}
|
|
5669
|
-
this.activeCards.delete(
|
|
5681
|
+
this.activeCards.delete(cardKey);
|
|
5670
5682
|
}
|
|
5671
5683
|
/**
|
|
5672
5684
|
* Check if there is an active streaming card for a given chat.
|
|
5673
5685
|
*/
|
|
5674
|
-
hasActiveCard(chatId) {
|
|
5675
|
-
return this.activeCards.has(chatId);
|
|
5686
|
+
hasActiveCard(chatId, streamKey) {
|
|
5687
|
+
return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
|
|
5676
5688
|
}
|
|
5677
5689
|
// ── Streaming adapter interface ────────────────────────────────
|
|
5678
5690
|
/**
|
|
5679
5691
|
* Called by bridge-manager on each text SSE event.
|
|
5680
5692
|
* Creates streaming card on first call, then updates content.
|
|
5681
5693
|
*/
|
|
5682
|
-
onStreamText(chatId, fullText) {
|
|
5694
|
+
onStreamText(chatId, fullText, streamKey) {
|
|
5683
5695
|
if (!this.isStreamingEnabled()) return;
|
|
5684
|
-
|
|
5696
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5697
|
+
if (!this.activeCards.has(cardKey)) {
|
|
5685
5698
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5686
|
-
this.createStreamingCard(chatId, messageId).then((ok) => {
|
|
5687
|
-
if (ok) this.updateCardContent(chatId, fullText);
|
|
5699
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
5700
|
+
if (ok) this.updateCardContent(chatId, fullText, cardKey);
|
|
5688
5701
|
}).catch(() => {
|
|
5689
5702
|
});
|
|
5690
5703
|
return;
|
|
5691
5704
|
}
|
|
5692
|
-
this.updateCardContent(chatId, fullText);
|
|
5705
|
+
this.updateCardContent(chatId, fullText, cardKey);
|
|
5693
5706
|
}
|
|
5694
|
-
onMirrorStreamStart(chatId) {
|
|
5707
|
+
onMirrorStreamStart(chatId, streamKey) {
|
|
5695
5708
|
if (!this.isStreamingEnabled()) return;
|
|
5696
|
-
|
|
5697
|
-
this.
|
|
5709
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5710
|
+
if (this.activeCards.has(cardKey)) return;
|
|
5711
|
+
this.createStreamingCard(chatId, void 0, cardKey).catch(() => {
|
|
5698
5712
|
});
|
|
5699
5713
|
}
|
|
5700
|
-
onToolEvent(chatId, tools) {
|
|
5714
|
+
onToolEvent(chatId, tools, streamKey) {
|
|
5701
5715
|
if (!this.isStreamingEnabled()) return;
|
|
5702
|
-
this.updateToolProgress(chatId, tools);
|
|
5716
|
+
this.updateToolProgress(chatId, tools, streamKey);
|
|
5703
5717
|
}
|
|
5704
|
-
async onStreamEnd(chatId, status, responseText) {
|
|
5718
|
+
async onStreamEnd(chatId, status, responseText, streamKey) {
|
|
5705
5719
|
if (!this.isStreamingEnabled()) return false;
|
|
5706
|
-
return this.finalizeCard(chatId, status, responseText);
|
|
5720
|
+
return this.finalizeCard(chatId, status, responseText, streamKey);
|
|
5707
5721
|
}
|
|
5708
5722
|
// ── Send ────────────────────────────────────────────────────
|
|
5709
5723
|
async send(message) {
|
|
@@ -16818,8 +16832,6 @@ function resolveCommandAlias(rawCommand, args) {
|
|
|
16818
16832
|
return "/help";
|
|
16819
16833
|
case "/t":
|
|
16820
16834
|
return !args ? "/threads" : /^(all|n\b)/i.test(args.trim()) ? "/threads" : "/thread";
|
|
16821
|
-
case "/s":
|
|
16822
|
-
return args ? "/use" : "/sessions";
|
|
16823
16835
|
case "/n":
|
|
16824
16836
|
return "/new";
|
|
16825
16837
|
case "/m":
|
|
@@ -16890,6 +16902,12 @@ function getSessionDisplayName(session, fallbackDirectory) {
|
|
|
16890
16902
|
function nowIso() {
|
|
16891
16903
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
16892
16904
|
}
|
|
16905
|
+
function buildInteractiveStreamKey(sessionId, messageId) {
|
|
16906
|
+
return `im:${sessionId}:${messageId}`;
|
|
16907
|
+
}
|
|
16908
|
+
function buildMirrorStreamKey(sessionId, turnId, startedAt) {
|
|
16909
|
+
return `mirror:${sessionId}:${turnId || startedAt}`;
|
|
16910
|
+
}
|
|
16893
16911
|
function getWorkspaceRoot() {
|
|
16894
16912
|
const { store } = getBridgeContext();
|
|
16895
16913
|
return store.getSetting("bridge_default_workspace_root") || DEFAULT_WORKSPACE_ROOT;
|
|
@@ -17829,6 +17847,16 @@ function formatMirrorMessage(threadTitle, userText, assistantText, markdown = fa
|
|
|
17829
17847
|
sections.unshift(buildMirrorTitle(threadTitle, markdown));
|
|
17830
17848
|
return sections.join("\n\n").trim();
|
|
17831
17849
|
}
|
|
17850
|
+
function buildMirrorTimeoutNotice(markdown = false) {
|
|
17851
|
+
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";
|
|
17852
|
+
}
|
|
17853
|
+
function appendMirrorTimeoutNotice(text2, markdown = false) {
|
|
17854
|
+
const notice = buildMirrorTimeoutNotice(markdown);
|
|
17855
|
+
const normalized = text2.trim();
|
|
17856
|
+
return normalized ? `${normalized}
|
|
17857
|
+
|
|
17858
|
+
${notice}` : notice;
|
|
17859
|
+
}
|
|
17832
17860
|
function getMirrorStreamingAdapter(subscription) {
|
|
17833
17861
|
const state = getState();
|
|
17834
17862
|
const adapter = state.adapters.get(subscription.channelType);
|
|
@@ -17855,9 +17883,9 @@ function startMirrorStreaming(subscription, turnState) {
|
|
|
17855
17883
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
17856
17884
|
if (!adapter || turnState.streamStarted) return;
|
|
17857
17885
|
try {
|
|
17858
|
-
adapter.onMirrorStreamStart?.(subscription.chatId);
|
|
17886
|
+
adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
|
|
17859
17887
|
if (!adapter.onMirrorStreamStart) {
|
|
17860
|
-
adapter.onStreamText?.(subscription.chatId, "");
|
|
17888
|
+
adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
|
|
17861
17889
|
}
|
|
17862
17890
|
turnState.streamStarted = true;
|
|
17863
17891
|
} catch {
|
|
@@ -17873,7 +17901,7 @@ function updateMirrorStreaming(subscription, turnState) {
|
|
|
17873
17901
|
);
|
|
17874
17902
|
if (!text2) return;
|
|
17875
17903
|
try {
|
|
17876
|
-
adapter.onStreamText?.(subscription.chatId, text2);
|
|
17904
|
+
adapter.onStreamText?.(subscription.chatId, text2, turnState.streamKey);
|
|
17877
17905
|
} catch {
|
|
17878
17906
|
}
|
|
17879
17907
|
}
|
|
@@ -17882,7 +17910,7 @@ function updateMirrorToolProgress(subscription, turnState) {
|
|
|
17882
17910
|
if (!adapter || typeof adapter.onToolEvent !== "function") return;
|
|
17883
17911
|
startMirrorStreaming(subscription, turnState);
|
|
17884
17912
|
try {
|
|
17885
|
-
adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()));
|
|
17913
|
+
adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()), turnState.streamKey);
|
|
17886
17914
|
} catch {
|
|
17887
17915
|
}
|
|
17888
17916
|
}
|
|
@@ -17894,7 +17922,7 @@ function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
|
17894
17922
|
subscription.channelType,
|
|
17895
17923
|
getMirrorStreamingText(subscription, pendingTurn)
|
|
17896
17924
|
);
|
|
17897
|
-
void adapter.onStreamEnd(subscription.chatId, status, text2).catch(() => {
|
|
17925
|
+
void adapter.onStreamEnd(subscription.chatId, status, text2, pendingTurn.streamKey).catch(() => {
|
|
17898
17926
|
});
|
|
17899
17927
|
}
|
|
17900
17928
|
async function deliverMirrorTurn(subscription, turn) {
|
|
@@ -17904,8 +17932,10 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17904
17932
|
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
17905
17933
|
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
17906
17934
|
const markdown = responseParseMode === "Markdown";
|
|
17907
|
-
const
|
|
17908
|
-
const
|
|
17935
|
+
const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
|
|
17936
|
+
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
|
|
17937
|
+
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
17938
|
+
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
17909
17939
|
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
17910
17940
|
const streamText = renderFeedbackText(
|
|
17911
17941
|
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
@@ -17916,7 +17946,8 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17916
17946
|
const finalized = await adapter.onStreamEnd(
|
|
17917
17947
|
subscription.chatId,
|
|
17918
17948
|
turn.status,
|
|
17919
|
-
streamText
|
|
17949
|
+
streamText,
|
|
17950
|
+
turn.streamKey
|
|
17920
17951
|
);
|
|
17921
17952
|
if (finalized) {
|
|
17922
17953
|
subscription.lastDeliveredAt = turn.timestamp || nowIso();
|
|
@@ -17948,10 +17979,11 @@ async function deliverMirrorTurns(subscription, turns) {
|
|
|
17948
17979
|
await deliverMirrorTurn(subscription, turn);
|
|
17949
17980
|
}
|
|
17950
17981
|
}
|
|
17951
|
-
function createMirrorTurnState(timestamp, turnId) {
|
|
17982
|
+
function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
17952
17983
|
const safeTimestamp = timestamp || nowIso();
|
|
17953
17984
|
return {
|
|
17954
17985
|
turnId: turnId || null,
|
|
17986
|
+
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
17955
17987
|
startedAt: safeTimestamp,
|
|
17956
17988
|
lastActivityAt: safeTimestamp,
|
|
17957
17989
|
userText: null,
|
|
@@ -17985,7 +18017,7 @@ ${normalized}` : normalized;
|
|
|
17985
18017
|
}
|
|
17986
18018
|
function ensureMirrorTurnState(subscription, record) {
|
|
17987
18019
|
if (!subscription.pendingTurn) {
|
|
17988
|
-
subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
|
|
18020
|
+
subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
|
|
17989
18021
|
return subscription.pendingTurn;
|
|
17990
18022
|
}
|
|
17991
18023
|
if (!subscription.pendingTurn.turnId && record.turnId) {
|
|
@@ -18008,11 +18040,13 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
|
|
|
18008
18040
|
const userText = pendingTurn.userText?.trim() || null;
|
|
18009
18041
|
if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
|
|
18010
18042
|
return {
|
|
18043
|
+
streamKey: pendingTurn.streamKey,
|
|
18011
18044
|
userText,
|
|
18012
18045
|
text: text2,
|
|
18013
18046
|
signature,
|
|
18014
18047
|
timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
|
|
18015
|
-
status
|
|
18048
|
+
status,
|
|
18049
|
+
...signature.startsWith("timeout:") ? { timedOut: true } : {}
|
|
18016
18050
|
};
|
|
18017
18051
|
}
|
|
18018
18052
|
function consumeMirrorRecords(subscription, records) {
|
|
@@ -18026,7 +18060,7 @@ function consumeMirrorRecords(subscription, records) {
|
|
|
18026
18060
|
if (superseded) finalized.push(superseded);
|
|
18027
18061
|
}
|
|
18028
18062
|
if (!subscription.pendingTurn) {
|
|
18029
|
-
subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
|
|
18063
|
+
subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
|
|
18030
18064
|
} else {
|
|
18031
18065
|
if (!subscription.pendingTurn.turnId && record.turnId) {
|
|
18032
18066
|
subscription.pendingTurn.turnId = record.turnId;
|
|
@@ -18305,11 +18339,21 @@ async function reconcileMirrorSubscription(subscription) {
|
|
|
18305
18339
|
subscription.bufferedRecords.push(...filteredRecords);
|
|
18306
18340
|
}
|
|
18307
18341
|
}
|
|
18342
|
+
const timedOutTurn = flushTimedOutMirrorTurn(subscription);
|
|
18308
18343
|
if (getState().activeTasks.has(subscription.sessionId) || isMirrorSuppressed(subscription.sessionId)) {
|
|
18344
|
+
if (timedOutTurn) {
|
|
18345
|
+
try {
|
|
18346
|
+
await deliverMirrorTurns(subscription, [timedOutTurn]);
|
|
18347
|
+
} catch (error) {
|
|
18348
|
+
subscription.dirty = true;
|
|
18349
|
+
console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
|
|
18350
|
+
}
|
|
18351
|
+
}
|
|
18309
18352
|
syncMirrorSessionState(subscription.sessionId);
|
|
18310
18353
|
return;
|
|
18311
18354
|
}
|
|
18312
|
-
const finalizedTurns =
|
|
18355
|
+
const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
|
|
18356
|
+
finalizedTurns.push(...consumeBufferedMirrorTurns(subscription));
|
|
18313
18357
|
if (finalizedTurns.length === 0) {
|
|
18314
18358
|
syncMirrorSessionState(subscription.sessionId);
|
|
18315
18359
|
return;
|
|
@@ -18712,7 +18756,8 @@ async function handleMessage(adapter, msg) {
|
|
|
18712
18756
|
return;
|
|
18713
18757
|
}
|
|
18714
18758
|
const binding = resolve(msg.address);
|
|
18715
|
-
|
|
18759
|
+
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
18760
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
18716
18761
|
const taskAbort = new AbortController();
|
|
18717
18762
|
const state = getState();
|
|
18718
18763
|
resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
|
|
@@ -18773,7 +18818,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18773
18818
|
stripOutboundArtifactBlocksForStreaming(fullText)
|
|
18774
18819
|
);
|
|
18775
18820
|
try {
|
|
18776
|
-
adapter.onStreamText(msg.address.chatId, rendered);
|
|
18821
|
+
adapter.onStreamText(msg.address.chatId, rendered, streamKey);
|
|
18777
18822
|
} catch {
|
|
18778
18823
|
}
|
|
18779
18824
|
} : void 0;
|
|
@@ -18785,7 +18830,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18785
18830
|
if (existing) existing.status = status;
|
|
18786
18831
|
}
|
|
18787
18832
|
try {
|
|
18788
|
-
adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()));
|
|
18833
|
+
adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()), streamKey);
|
|
18789
18834
|
} catch {
|
|
18790
18835
|
}
|
|
18791
18836
|
} : void 0;
|
|
@@ -18818,7 +18863,8 @@ async function handleMessage(adapter, msg) {
|
|
|
18818
18863
|
cardFinalized = await adapter.onStreamEnd(
|
|
18819
18864
|
msg.address.chatId,
|
|
18820
18865
|
status,
|
|
18821
|
-
renderFeedbackTextForChannel(adapter.channelType, result.responseText)
|
|
18866
|
+
renderFeedbackTextForChannel(adapter.channelType, result.responseText),
|
|
18867
|
+
streamKey
|
|
18822
18868
|
);
|
|
18823
18869
|
} catch (err) {
|
|
18824
18870
|
console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
@@ -18863,7 +18909,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18863
18909
|
}
|
|
18864
18910
|
if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
|
|
18865
18911
|
try {
|
|
18866
|
-
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "");
|
|
18912
|
+
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
|
|
18867
18913
|
} catch {
|
|
18868
18914
|
}
|
|
18869
18915
|
}
|
|
@@ -18872,7 +18918,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18872
18918
|
}
|
|
18873
18919
|
state.activeTasks.delete(binding.codepilotSessionId);
|
|
18874
18920
|
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
18875
|
-
adapter.onMessageEnd?.(msg.address.chatId);
|
|
18921
|
+
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
18876
18922
|
ack();
|
|
18877
18923
|
}
|
|
18878
18924
|
}
|
|
@@ -18930,8 +18976,6 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
18930
18976
|
const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
|
|
18931
18977
|
if (oldTask) {
|
|
18932
18978
|
oldTask.abort();
|
|
18933
|
-
st.activeTasks.delete(currentBinding.codepilotSessionId);
|
|
18934
|
-
syncSessionRuntimeState(currentBinding.codepilotSessionId);
|
|
18935
18979
|
}
|
|
18936
18980
|
}
|
|
18937
18981
|
const workDir = resolved.workDir;
|
|
@@ -19377,8 +19421,6 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19377
19421
|
const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
|
|
19378
19422
|
if (taskAbort) {
|
|
19379
19423
|
taskAbort.abort();
|
|
19380
|
-
st.activeTasks.delete(binding.codepilotSessionId);
|
|
19381
|
-
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
19382
19424
|
response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
|
|
19383
19425
|
} else {
|
|
19384
19426
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
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