codex-to-im 1.0.17 → 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 +136 -76
- 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) {
|
|
@@ -5118,9 +5120,27 @@ function htmlToFeishuMarkdown(html) {
|
|
|
5118
5120
|
}
|
|
5119
5121
|
function buildToolProgressMarkdown(tools) {
|
|
5120
5122
|
if (tools.length === 0) return "";
|
|
5121
|
-
const
|
|
5122
|
-
|
|
5123
|
-
|
|
5123
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5124
|
+
for (const tool of tools) {
|
|
5125
|
+
const key = tool.name || "tool";
|
|
5126
|
+
const bucket = grouped.get(key) || { running: 0, complete: 0, error: 0 };
|
|
5127
|
+
if (tool.status === "running") bucket.running += 1;
|
|
5128
|
+
else if (tool.status === "error") bucket.error += 1;
|
|
5129
|
+
else bucket.complete += 1;
|
|
5130
|
+
grouped.set(key, bucket);
|
|
5131
|
+
}
|
|
5132
|
+
const lines = Array.from(grouped.entries()).map(([name, counts]) => {
|
|
5133
|
+
const total = counts.running + counts.complete + counts.error;
|
|
5134
|
+
const icon = counts.running > 0 ? "\u{1F504}" : counts.error > 0 ? "\u274C" : "\u2705";
|
|
5135
|
+
const countSuffix = total > 1 ? ` \xD7${total}` : "";
|
|
5136
|
+
const detailParts = [];
|
|
5137
|
+
if (counts.running > 0) detailParts.push(`\u8FD0\u884C\u4E2D ${counts.running}`);
|
|
5138
|
+
if (counts.error > 0) detailParts.push(`\u5F02\u5E38 ${counts.error}`);
|
|
5139
|
+
if ((counts.running > 0 || counts.error > 0) && counts.complete > 0) {
|
|
5140
|
+
detailParts.push(`\u5B8C\u6210 ${counts.complete}`);
|
|
5141
|
+
}
|
|
5142
|
+
const detailSuffix = detailParts.length > 0 ? `\uFF08${detailParts.join(" / ")}\uFF09` : "";
|
|
5143
|
+
return `${icon} \`${name}\`${countSuffix}${detailSuffix}`;
|
|
5124
5144
|
});
|
|
5125
5145
|
return lines.join("\n");
|
|
5126
5146
|
}
|
|
@@ -5253,15 +5273,18 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5253
5273
|
lastIncomingMessageId = /* @__PURE__ */ new Map();
|
|
5254
5274
|
/** Track active typing reaction IDs per chat for cleanup. */
|
|
5255
5275
|
typingReactions = /* @__PURE__ */ new Map();
|
|
5256
|
-
/** Active streaming card state per
|
|
5276
|
+
/** Active streaming card state per stream key. */
|
|
5257
5277
|
activeCards = /* @__PURE__ */ new Map();
|
|
5258
|
-
/** In-flight card creation promises per
|
|
5278
|
+
/** In-flight card creation promises per stream key — prevents duplicate creation. */
|
|
5259
5279
|
cardCreatePromises = /* @__PURE__ */ new Map();
|
|
5260
5280
|
/** Cached tenant token for upload APIs. */
|
|
5261
5281
|
tenantTokenCache = null;
|
|
5262
5282
|
isStreamingEnabled() {
|
|
5263
5283
|
return getBridgeContext().store.getSetting("bridge_feishu_streaming_enabled") !== "false";
|
|
5264
5284
|
}
|
|
5285
|
+
resolveStreamKey(chatId, streamKey) {
|
|
5286
|
+
return streamKey?.trim() || chatId;
|
|
5287
|
+
}
|
|
5265
5288
|
// ── Lifecycle ───────────────────────────────────────────────
|
|
5266
5289
|
async start() {
|
|
5267
5290
|
if (this.running) return;
|
|
@@ -5366,10 +5389,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5366
5389
|
* Add a "Typing" emoji reaction to the user's message and create streaming card.
|
|
5367
5390
|
* Called by bridge-manager via onMessageStart().
|
|
5368
5391
|
*/
|
|
5369
|
-
onMessageStart(chatId) {
|
|
5392
|
+
onMessageStart(chatId, streamKey) {
|
|
5370
5393
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5371
5394
|
if (messageId && this.isStreamingEnabled()) {
|
|
5372
|
-
this.createStreamingCard(chatId, messageId).catch(() => {
|
|
5395
|
+
this.createStreamingCard(chatId, messageId, streamKey).catch(() => {
|
|
5373
5396
|
});
|
|
5374
5397
|
}
|
|
5375
5398
|
if (!messageId || !this.restClient) return;
|
|
@@ -5392,8 +5415,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5392
5415
|
* Remove the "Typing" emoji reaction and clean up card state.
|
|
5393
5416
|
* Called by bridge-manager via onMessageEnd().
|
|
5394
5417
|
*/
|
|
5395
|
-
onMessageEnd(chatId) {
|
|
5396
|
-
this.cleanupCard(chatId);
|
|
5418
|
+
onMessageEnd(chatId, streamKey) {
|
|
5419
|
+
this.cleanupCard(chatId, streamKey);
|
|
5397
5420
|
const reactionId = this.typingReactions.get(chatId);
|
|
5398
5421
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5399
5422
|
if (!reactionId || !messageId || !this.restClient) return;
|
|
@@ -5444,17 +5467,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5444
5467
|
* Create a new streaming card and send it as a message.
|
|
5445
5468
|
* Returns true if card was created successfully.
|
|
5446
5469
|
*/
|
|
5447
|
-
createStreamingCard(chatId, replyToMessageId) {
|
|
5448
|
-
|
|
5449
|
-
|
|
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);
|
|
5450
5474
|
if (existing) return existing;
|
|
5451
|
-
const promise = this._doCreateStreamingCard(chatId, replyToMessageId);
|
|
5452
|
-
this.cardCreatePromises.set(
|
|
5453
|
-
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));
|
|
5454
5478
|
return promise;
|
|
5455
5479
|
}
|
|
5456
|
-
async _doCreateStreamingCard(chatId, replyToMessageId) {
|
|
5480
|
+
async _doCreateStreamingCard(chatId, replyToMessageId, streamKey) {
|
|
5457
5481
|
if (!this.restClient) return false;
|
|
5482
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5458
5483
|
const cardkit = this.restClient.cardkit?.v1;
|
|
5459
5484
|
if (!cardkit?.card) {
|
|
5460
5485
|
console.warn("[feishu-adapter] CardKit v1 API is unavailable in the current Feishu SDK client");
|
|
@@ -5508,7 +5533,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5508
5533
|
console.warn("[feishu-adapter] Card message send returned no message_id");
|
|
5509
5534
|
return false;
|
|
5510
5535
|
}
|
|
5511
|
-
this.activeCards.set(
|
|
5536
|
+
this.activeCards.set(cardKey, {
|
|
5537
|
+
chatId,
|
|
5512
5538
|
cardId,
|
|
5513
5539
|
messageId,
|
|
5514
5540
|
sequence: 0,
|
|
@@ -5519,7 +5545,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5519
5545
|
lastUpdateAt: 0,
|
|
5520
5546
|
throttleTimer: null
|
|
5521
5547
|
});
|
|
5522
|
-
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}`);
|
|
5523
5549
|
return true;
|
|
5524
5550
|
} catch (err) {
|
|
5525
5551
|
console.warn("[feishu-adapter] Failed to create streaming card:", err instanceof Error ? err.message : err);
|
|
@@ -5529,8 +5555,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5529
5555
|
/**
|
|
5530
5556
|
* Update streaming card content with throttling.
|
|
5531
5557
|
*/
|
|
5532
|
-
updateCardContent(chatId, text2) {
|
|
5533
|
-
const
|
|
5558
|
+
updateCardContent(chatId, text2, streamKey) {
|
|
5559
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5560
|
+
const state = this.activeCards.get(cardKey);
|
|
5534
5561
|
if (!state || !this.restClient) return;
|
|
5535
5562
|
if (state.thinking && text2.trim()) {
|
|
5536
5563
|
state.thinking = false;
|
|
@@ -5541,7 +5568,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5541
5568
|
if (!state.throttleTimer) {
|
|
5542
5569
|
state.throttleTimer = setTimeout(() => {
|
|
5543
5570
|
state.throttleTimer = null;
|
|
5544
|
-
this.flushCardUpdate(
|
|
5571
|
+
this.flushCardUpdate(cardKey);
|
|
5545
5572
|
}, CARD_THROTTLE_MS - elapsed);
|
|
5546
5573
|
}
|
|
5547
5574
|
return;
|
|
@@ -5550,13 +5577,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5550
5577
|
clearTimeout(state.throttleTimer);
|
|
5551
5578
|
state.throttleTimer = null;
|
|
5552
5579
|
}
|
|
5553
|
-
this.flushCardUpdate(
|
|
5580
|
+
this.flushCardUpdate(cardKey);
|
|
5554
5581
|
}
|
|
5555
5582
|
/**
|
|
5556
5583
|
* Flush pending card update to Feishu API.
|
|
5557
5584
|
*/
|
|
5558
|
-
flushCardUpdate(
|
|
5559
|
-
const state = this.activeCards.get(
|
|
5585
|
+
flushCardUpdate(streamKey) {
|
|
5586
|
+
const state = this.activeCards.get(streamKey);
|
|
5560
5587
|
if (!state || !this.restClient) return;
|
|
5561
5588
|
const cardkit = this.restClient.cardkit?.v1;
|
|
5562
5589
|
if (!cardkit?.cardElement?.content) return;
|
|
@@ -5576,24 +5603,26 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5576
5603
|
/**
|
|
5577
5604
|
* Update tool progress in the streaming card.
|
|
5578
5605
|
*/
|
|
5579
|
-
updateToolProgress(chatId, tools) {
|
|
5580
|
-
const
|
|
5606
|
+
updateToolProgress(chatId, tools, streamKey) {
|
|
5607
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5608
|
+
const state = this.activeCards.get(cardKey);
|
|
5581
5609
|
if (!state) return;
|
|
5582
5610
|
state.toolCalls = tools;
|
|
5583
|
-
this.updateCardContent(chatId, state.pendingText || "");
|
|
5611
|
+
this.updateCardContent(chatId, state.pendingText || "", cardKey);
|
|
5584
5612
|
}
|
|
5585
5613
|
/**
|
|
5586
5614
|
* Finalize the streaming card: close streaming mode, update with final content + footer.
|
|
5587
5615
|
*/
|
|
5588
|
-
async finalizeCard(chatId, status, responseText) {
|
|
5589
|
-
const
|
|
5616
|
+
async finalizeCard(chatId, status, responseText, streamKey) {
|
|
5617
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5618
|
+
const pending = this.cardCreatePromises.get(cardKey);
|
|
5590
5619
|
if (pending) {
|
|
5591
5620
|
try {
|
|
5592
5621
|
await pending;
|
|
5593
5622
|
} catch {
|
|
5594
5623
|
}
|
|
5595
5624
|
}
|
|
5596
|
-
const state = this.activeCards.get(
|
|
5625
|
+
const state = this.activeCards.get(cardKey);
|
|
5597
5626
|
if (!state || !this.restClient) return false;
|
|
5598
5627
|
const cardkit = this.restClient.cardkit?.v1;
|
|
5599
5628
|
if (!cardkit?.card?.settings || !cardkit?.card?.update) return false;
|
|
@@ -5629,63 +5658,66 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5629
5658
|
sequence: state.sequence
|
|
5630
5659
|
}
|
|
5631
5660
|
});
|
|
5632
|
-
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)}`);
|
|
5633
5662
|
return true;
|
|
5634
5663
|
} catch (err) {
|
|
5635
5664
|
console.warn("[feishu-adapter] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
5636
5665
|
return false;
|
|
5637
5666
|
} finally {
|
|
5638
|
-
this.activeCards.delete(
|
|
5667
|
+
this.activeCards.delete(cardKey);
|
|
5639
5668
|
}
|
|
5640
5669
|
}
|
|
5641
5670
|
/**
|
|
5642
5671
|
* Clean up card state without finalizing (e.g. on unexpected errors).
|
|
5643
5672
|
*/
|
|
5644
|
-
cleanupCard(chatId) {
|
|
5645
|
-
this.
|
|
5646
|
-
|
|
5673
|
+
cleanupCard(chatId, streamKey) {
|
|
5674
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5675
|
+
this.cardCreatePromises.delete(cardKey);
|
|
5676
|
+
const state = this.activeCards.get(cardKey);
|
|
5647
5677
|
if (!state) return;
|
|
5648
5678
|
if (state.throttleTimer) {
|
|
5649
5679
|
clearTimeout(state.throttleTimer);
|
|
5650
5680
|
}
|
|
5651
|
-
this.activeCards.delete(
|
|
5681
|
+
this.activeCards.delete(cardKey);
|
|
5652
5682
|
}
|
|
5653
5683
|
/**
|
|
5654
5684
|
* Check if there is an active streaming card for a given chat.
|
|
5655
5685
|
*/
|
|
5656
|
-
hasActiveCard(chatId) {
|
|
5657
|
-
return this.activeCards.has(chatId);
|
|
5686
|
+
hasActiveCard(chatId, streamKey) {
|
|
5687
|
+
return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
|
|
5658
5688
|
}
|
|
5659
5689
|
// ── Streaming adapter interface ────────────────────────────────
|
|
5660
5690
|
/**
|
|
5661
5691
|
* Called by bridge-manager on each text SSE event.
|
|
5662
5692
|
* Creates streaming card on first call, then updates content.
|
|
5663
5693
|
*/
|
|
5664
|
-
onStreamText(chatId, fullText) {
|
|
5694
|
+
onStreamText(chatId, fullText, streamKey) {
|
|
5665
5695
|
if (!this.isStreamingEnabled()) return;
|
|
5666
|
-
|
|
5696
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5697
|
+
if (!this.activeCards.has(cardKey)) {
|
|
5667
5698
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
5668
|
-
this.createStreamingCard(chatId, messageId).then((ok) => {
|
|
5669
|
-
if (ok) this.updateCardContent(chatId, fullText);
|
|
5699
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
5700
|
+
if (ok) this.updateCardContent(chatId, fullText, cardKey);
|
|
5670
5701
|
}).catch(() => {
|
|
5671
5702
|
});
|
|
5672
5703
|
return;
|
|
5673
5704
|
}
|
|
5674
|
-
this.updateCardContent(chatId, fullText);
|
|
5705
|
+
this.updateCardContent(chatId, fullText, cardKey);
|
|
5675
5706
|
}
|
|
5676
|
-
onMirrorStreamStart(chatId) {
|
|
5707
|
+
onMirrorStreamStart(chatId, streamKey) {
|
|
5677
5708
|
if (!this.isStreamingEnabled()) return;
|
|
5678
|
-
|
|
5679
|
-
this.
|
|
5709
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
5710
|
+
if (this.activeCards.has(cardKey)) return;
|
|
5711
|
+
this.createStreamingCard(chatId, void 0, cardKey).catch(() => {
|
|
5680
5712
|
});
|
|
5681
5713
|
}
|
|
5682
|
-
onToolEvent(chatId, tools) {
|
|
5714
|
+
onToolEvent(chatId, tools, streamKey) {
|
|
5683
5715
|
if (!this.isStreamingEnabled()) return;
|
|
5684
|
-
this.updateToolProgress(chatId, tools);
|
|
5716
|
+
this.updateToolProgress(chatId, tools, streamKey);
|
|
5685
5717
|
}
|
|
5686
|
-
async onStreamEnd(chatId, status, responseText) {
|
|
5718
|
+
async onStreamEnd(chatId, status, responseText, streamKey) {
|
|
5687
5719
|
if (!this.isStreamingEnabled()) return false;
|
|
5688
|
-
return this.finalizeCard(chatId, status, responseText);
|
|
5720
|
+
return this.finalizeCard(chatId, status, responseText, streamKey);
|
|
5689
5721
|
}
|
|
5690
5722
|
// ── Send ────────────────────────────────────────────────────
|
|
5691
5723
|
async send(message) {
|
|
@@ -16800,8 +16832,6 @@ function resolveCommandAlias(rawCommand, args) {
|
|
|
16800
16832
|
return "/help";
|
|
16801
16833
|
case "/t":
|
|
16802
16834
|
return !args ? "/threads" : /^(all|n\b)/i.test(args.trim()) ? "/threads" : "/thread";
|
|
16803
|
-
case "/s":
|
|
16804
|
-
return args ? "/use" : "/sessions";
|
|
16805
16835
|
case "/n":
|
|
16806
16836
|
return "/new";
|
|
16807
16837
|
case "/m":
|
|
@@ -16872,6 +16902,12 @@ function getSessionDisplayName(session, fallbackDirectory) {
|
|
|
16872
16902
|
function nowIso() {
|
|
16873
16903
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
16874
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
|
+
}
|
|
16875
16911
|
function getWorkspaceRoot() {
|
|
16876
16912
|
const { store } = getBridgeContext();
|
|
16877
16913
|
return store.getSetting("bridge_default_workspace_root") || DEFAULT_WORKSPACE_ROOT;
|
|
@@ -17811,6 +17847,16 @@ function formatMirrorMessage(threadTitle, userText, assistantText, markdown = fa
|
|
|
17811
17847
|
sections.unshift(buildMirrorTitle(threadTitle, markdown));
|
|
17812
17848
|
return sections.join("\n\n").trim();
|
|
17813
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
|
+
}
|
|
17814
17860
|
function getMirrorStreamingAdapter(subscription) {
|
|
17815
17861
|
const state = getState();
|
|
17816
17862
|
const adapter = state.adapters.get(subscription.channelType);
|
|
@@ -17837,9 +17883,9 @@ function startMirrorStreaming(subscription, turnState) {
|
|
|
17837
17883
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
17838
17884
|
if (!adapter || turnState.streamStarted) return;
|
|
17839
17885
|
try {
|
|
17840
|
-
adapter.onMirrorStreamStart?.(subscription.chatId);
|
|
17886
|
+
adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
|
|
17841
17887
|
if (!adapter.onMirrorStreamStart) {
|
|
17842
|
-
adapter.onStreamText?.(subscription.chatId, "");
|
|
17888
|
+
adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
|
|
17843
17889
|
}
|
|
17844
17890
|
turnState.streamStarted = true;
|
|
17845
17891
|
} catch {
|
|
@@ -17855,7 +17901,7 @@ function updateMirrorStreaming(subscription, turnState) {
|
|
|
17855
17901
|
);
|
|
17856
17902
|
if (!text2) return;
|
|
17857
17903
|
try {
|
|
17858
|
-
adapter.onStreamText?.(subscription.chatId, text2);
|
|
17904
|
+
adapter.onStreamText?.(subscription.chatId, text2, turnState.streamKey);
|
|
17859
17905
|
} catch {
|
|
17860
17906
|
}
|
|
17861
17907
|
}
|
|
@@ -17864,7 +17910,7 @@ function updateMirrorToolProgress(subscription, turnState) {
|
|
|
17864
17910
|
if (!adapter || typeof adapter.onToolEvent !== "function") return;
|
|
17865
17911
|
startMirrorStreaming(subscription, turnState);
|
|
17866
17912
|
try {
|
|
17867
|
-
adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()));
|
|
17913
|
+
adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()), turnState.streamKey);
|
|
17868
17914
|
} catch {
|
|
17869
17915
|
}
|
|
17870
17916
|
}
|
|
@@ -17876,7 +17922,7 @@ function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
|
17876
17922
|
subscription.channelType,
|
|
17877
17923
|
getMirrorStreamingText(subscription, pendingTurn)
|
|
17878
17924
|
);
|
|
17879
|
-
void adapter.onStreamEnd(subscription.chatId, status, text2).catch(() => {
|
|
17925
|
+
void adapter.onStreamEnd(subscription.chatId, status, text2, pendingTurn.streamKey).catch(() => {
|
|
17880
17926
|
});
|
|
17881
17927
|
}
|
|
17882
17928
|
async function deliverMirrorTurn(subscription, turn) {
|
|
@@ -17886,8 +17932,10 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17886
17932
|
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
17887
17933
|
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
17888
17934
|
const markdown = responseParseMode === "Markdown";
|
|
17889
|
-
const
|
|
17890
|
-
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;
|
|
17891
17939
|
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
17892
17940
|
const streamText = renderFeedbackText(
|
|
17893
17941
|
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
@@ -17898,7 +17946,8 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17898
17946
|
const finalized = await adapter.onStreamEnd(
|
|
17899
17947
|
subscription.chatId,
|
|
17900
17948
|
turn.status,
|
|
17901
|
-
streamText
|
|
17949
|
+
streamText,
|
|
17950
|
+
turn.streamKey
|
|
17902
17951
|
);
|
|
17903
17952
|
if (finalized) {
|
|
17904
17953
|
subscription.lastDeliveredAt = turn.timestamp || nowIso();
|
|
@@ -17930,10 +17979,11 @@ async function deliverMirrorTurns(subscription, turns) {
|
|
|
17930
17979
|
await deliverMirrorTurn(subscription, turn);
|
|
17931
17980
|
}
|
|
17932
17981
|
}
|
|
17933
|
-
function createMirrorTurnState(timestamp, turnId) {
|
|
17982
|
+
function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
17934
17983
|
const safeTimestamp = timestamp || nowIso();
|
|
17935
17984
|
return {
|
|
17936
17985
|
turnId: turnId || null,
|
|
17986
|
+
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
17937
17987
|
startedAt: safeTimestamp,
|
|
17938
17988
|
lastActivityAt: safeTimestamp,
|
|
17939
17989
|
userText: null,
|
|
@@ -17967,7 +18017,7 @@ ${normalized}` : normalized;
|
|
|
17967
18017
|
}
|
|
17968
18018
|
function ensureMirrorTurnState(subscription, record) {
|
|
17969
18019
|
if (!subscription.pendingTurn) {
|
|
17970
|
-
subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
|
|
18020
|
+
subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
|
|
17971
18021
|
return subscription.pendingTurn;
|
|
17972
18022
|
}
|
|
17973
18023
|
if (!subscription.pendingTurn.turnId && record.turnId) {
|
|
@@ -17990,11 +18040,13 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
|
|
|
17990
18040
|
const userText = pendingTurn.userText?.trim() || null;
|
|
17991
18041
|
if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
|
|
17992
18042
|
return {
|
|
18043
|
+
streamKey: pendingTurn.streamKey,
|
|
17993
18044
|
userText,
|
|
17994
18045
|
text: text2,
|
|
17995
18046
|
signature,
|
|
17996
18047
|
timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
|
|
17997
|
-
status
|
|
18048
|
+
status,
|
|
18049
|
+
...signature.startsWith("timeout:") ? { timedOut: true } : {}
|
|
17998
18050
|
};
|
|
17999
18051
|
}
|
|
18000
18052
|
function consumeMirrorRecords(subscription, records) {
|
|
@@ -18008,7 +18060,7 @@ function consumeMirrorRecords(subscription, records) {
|
|
|
18008
18060
|
if (superseded) finalized.push(superseded);
|
|
18009
18061
|
}
|
|
18010
18062
|
if (!subscription.pendingTurn) {
|
|
18011
|
-
subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
|
|
18063
|
+
subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
|
|
18012
18064
|
} else {
|
|
18013
18065
|
if (!subscription.pendingTurn.turnId && record.turnId) {
|
|
18014
18066
|
subscription.pendingTurn.turnId = record.turnId;
|
|
@@ -18287,11 +18339,21 @@ async function reconcileMirrorSubscription(subscription) {
|
|
|
18287
18339
|
subscription.bufferedRecords.push(...filteredRecords);
|
|
18288
18340
|
}
|
|
18289
18341
|
}
|
|
18342
|
+
const timedOutTurn = flushTimedOutMirrorTurn(subscription);
|
|
18290
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
|
+
}
|
|
18291
18352
|
syncMirrorSessionState(subscription.sessionId);
|
|
18292
18353
|
return;
|
|
18293
18354
|
}
|
|
18294
|
-
const finalizedTurns =
|
|
18355
|
+
const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
|
|
18356
|
+
finalizedTurns.push(...consumeBufferedMirrorTurns(subscription));
|
|
18295
18357
|
if (finalizedTurns.length === 0) {
|
|
18296
18358
|
syncMirrorSessionState(subscription.sessionId);
|
|
18297
18359
|
return;
|
|
@@ -18694,7 +18756,8 @@ async function handleMessage(adapter, msg) {
|
|
|
18694
18756
|
return;
|
|
18695
18757
|
}
|
|
18696
18758
|
const binding = resolve(msg.address);
|
|
18697
|
-
|
|
18759
|
+
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
18760
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
18698
18761
|
const taskAbort = new AbortController();
|
|
18699
18762
|
const state = getState();
|
|
18700
18763
|
resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
|
|
@@ -18755,7 +18818,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18755
18818
|
stripOutboundArtifactBlocksForStreaming(fullText)
|
|
18756
18819
|
);
|
|
18757
18820
|
try {
|
|
18758
|
-
adapter.onStreamText(msg.address.chatId, rendered);
|
|
18821
|
+
adapter.onStreamText(msg.address.chatId, rendered, streamKey);
|
|
18759
18822
|
} catch {
|
|
18760
18823
|
}
|
|
18761
18824
|
} : void 0;
|
|
@@ -18767,7 +18830,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18767
18830
|
if (existing) existing.status = status;
|
|
18768
18831
|
}
|
|
18769
18832
|
try {
|
|
18770
|
-
adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()));
|
|
18833
|
+
adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()), streamKey);
|
|
18771
18834
|
} catch {
|
|
18772
18835
|
}
|
|
18773
18836
|
} : void 0;
|
|
@@ -18800,7 +18863,8 @@ async function handleMessage(adapter, msg) {
|
|
|
18800
18863
|
cardFinalized = await adapter.onStreamEnd(
|
|
18801
18864
|
msg.address.chatId,
|
|
18802
18865
|
status,
|
|
18803
|
-
renderFeedbackTextForChannel(adapter.channelType, result.responseText)
|
|
18866
|
+
renderFeedbackTextForChannel(adapter.channelType, result.responseText),
|
|
18867
|
+
streamKey
|
|
18804
18868
|
);
|
|
18805
18869
|
} catch (err) {
|
|
18806
18870
|
console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
@@ -18845,7 +18909,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18845
18909
|
}
|
|
18846
18910
|
if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
|
|
18847
18911
|
try {
|
|
18848
|
-
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "");
|
|
18912
|
+
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
|
|
18849
18913
|
} catch {
|
|
18850
18914
|
}
|
|
18851
18915
|
}
|
|
@@ -18854,7 +18918,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18854
18918
|
}
|
|
18855
18919
|
state.activeTasks.delete(binding.codepilotSessionId);
|
|
18856
18920
|
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
18857
|
-
adapter.onMessageEnd?.(msg.address.chatId);
|
|
18921
|
+
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
18858
18922
|
ack();
|
|
18859
18923
|
}
|
|
18860
18924
|
}
|
|
@@ -18912,8 +18976,6 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
18912
18976
|
const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
|
|
18913
18977
|
if (oldTask) {
|
|
18914
18978
|
oldTask.abort();
|
|
18915
|
-
st.activeTasks.delete(currentBinding.codepilotSessionId);
|
|
18916
|
-
syncSessionRuntimeState(currentBinding.codepilotSessionId);
|
|
18917
18979
|
}
|
|
18918
18980
|
}
|
|
18919
18981
|
const workDir = resolved.workDir;
|
|
@@ -19359,8 +19421,6 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19359
19421
|
const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
|
|
19360
19422
|
if (taskAbort) {
|
|
19361
19423
|
taskAbort.abort();
|
|
19362
|
-
st.activeTasks.delete(binding.codepilotSessionId);
|
|
19363
|
-
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
19364
19424
|
response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
|
|
19365
19425
|
} else {
|
|
19366
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