codex-to-im 1.0.48 → 1.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/daemon.mjs
CHANGED
|
@@ -1415,7 +1415,8 @@ function buildPermissionButtonCard(text2, permissionRequestId, chatId) {
|
|
|
1415
1415
|
// src/lib/bridge/adapters/feishu-adapter.ts
|
|
1416
1416
|
var DEDUP_MAX = 1e3;
|
|
1417
1417
|
var MAX_FILE_SIZE = 20 * 1024 * 1024;
|
|
1418
|
-
var
|
|
1418
|
+
var COMPLETED_EMOJI = "DONE";
|
|
1419
|
+
var ERROR_EMOJI = "ERROR";
|
|
1419
1420
|
var CARD_THROTTLE_MS = 1e3;
|
|
1420
1421
|
var CARD_REQUEST_TIMEOUT_MS = 15e3;
|
|
1421
1422
|
var CARD_FINALIZE_FLUSH_WAIT_EXTRA_MS = 1e3;
|
|
@@ -1484,14 +1485,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1484
1485
|
botOpenId = null;
|
|
1485
1486
|
/** All known bot IDs (open_id, user_id, union_id) for mention matching. */
|
|
1486
1487
|
botIds = /* @__PURE__ */ new Set();
|
|
1487
|
-
/** Track last incoming message ID per chat for
|
|
1488
|
+
/** Track last incoming message ID per chat for replying with streaming cards. */
|
|
1488
1489
|
lastIncomingMessageId = /* @__PURE__ */ new Map();
|
|
1489
|
-
/** Track active typing reaction IDs per stream key for cleanup. */
|
|
1490
|
-
typingReactions = /* @__PURE__ */ new Map();
|
|
1491
|
-
/** Track in-flight typing reaction creates so repeated status updates stay idempotent. */
|
|
1492
|
-
typingReactionCreatePromises = /* @__PURE__ */ new Map();
|
|
1493
|
-
/** Track streams that ended before the async reaction create finished. */
|
|
1494
|
-
typingReactionCleanupRequested = /* @__PURE__ */ new Set();
|
|
1495
1490
|
/** Active streaming card state per stream key. */
|
|
1496
1491
|
activeCards = /* @__PURE__ */ new Map();
|
|
1497
1492
|
/** In-flight card creation promises per stream key — prevents duplicate creation. */
|
|
@@ -1598,9 +1593,6 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1598
1593
|
this.cardCreatePromises.clear();
|
|
1599
1594
|
this.seenMessageIds.clear();
|
|
1600
1595
|
this.lastIncomingMessageId.clear();
|
|
1601
|
-
this.typingReactions.clear();
|
|
1602
|
-
this.typingReactionCreatePromises.clear();
|
|
1603
|
-
this.typingReactionCleanupRequested.clear();
|
|
1604
1596
|
console.log("[feishu-adapter] Stopped");
|
|
1605
1597
|
}
|
|
1606
1598
|
isRunning() {
|
|
@@ -1610,67 +1602,24 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1610
1602
|
consumeOne() {
|
|
1611
1603
|
return this.consumeInboundMessage(this.running);
|
|
1612
1604
|
}
|
|
1613
|
-
// ──
|
|
1605
|
+
// ── Streaming lifecycle hooks ──────────────────────────────
|
|
1614
1606
|
/**
|
|
1615
|
-
*
|
|
1607
|
+
* Create the streaming card as early as possible.
|
|
1616
1608
|
* Called by bridge-manager via onMessageStart().
|
|
1617
1609
|
*/
|
|
1618
1610
|
onMessageStart(chatId, streamKey) {
|
|
1619
1611
|
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
1620
|
-
const reactionKey = this.resolveStreamKey(chatId, streamKey);
|
|
1621
1612
|
if (messageId && this.isStreamingEnabled()) {
|
|
1622
1613
|
this.createStreamingCard(chatId, messageId, streamKey).catch(() => {
|
|
1623
1614
|
});
|
|
1624
1615
|
}
|
|
1625
|
-
if (!messageId || !this.restClient) return;
|
|
1626
|
-
if (this.typingReactions.has(reactionKey) || this.typingReactionCreatePromises.has(reactionKey)) {
|
|
1627
|
-
return;
|
|
1628
|
-
}
|
|
1629
|
-
const createPromise = this.restClient.im.messageReaction.create({
|
|
1630
|
-
path: { message_id: messageId },
|
|
1631
|
-
data: { reaction_type: { emoji_type: TYPING_EMOJI } }
|
|
1632
|
-
}).then((res) => {
|
|
1633
|
-
const reactionId = res?.data?.reaction_id;
|
|
1634
|
-
if (reactionId) {
|
|
1635
|
-
this.typingReactions.set(reactionKey, { messageId, reactionId });
|
|
1636
|
-
if (this.typingReactionCleanupRequested.delete(reactionKey)) {
|
|
1637
|
-
this.removeTypingReaction(reactionKey);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
}).catch((err) => {
|
|
1641
|
-
const code2 = err?.code;
|
|
1642
|
-
if (code2 !== 99991400 && code2 !== 99991403) {
|
|
1643
|
-
console.warn("[feishu-adapter] Typing indicator failed:", err instanceof Error ? err.message : err);
|
|
1644
|
-
}
|
|
1645
|
-
}).finally(() => {
|
|
1646
|
-
this.typingReactionCreatePromises.delete(reactionKey);
|
|
1647
|
-
if (!this.typingReactions.has(reactionKey)) {
|
|
1648
|
-
this.typingReactionCleanupRequested.delete(reactionKey);
|
|
1649
|
-
}
|
|
1650
|
-
});
|
|
1651
|
-
this.typingReactionCreatePromises.set(reactionKey, createPromise);
|
|
1652
1616
|
}
|
|
1653
1617
|
/**
|
|
1654
|
-
*
|
|
1618
|
+
* Clean up card state.
|
|
1655
1619
|
* Called by bridge-manager via onMessageEnd().
|
|
1656
1620
|
*/
|
|
1657
1621
|
onMessageEnd(chatId, streamKey) {
|
|
1658
1622
|
this.cleanupCard(chatId, streamKey);
|
|
1659
|
-
const reactionKey = this.resolveStreamKey(chatId, streamKey);
|
|
1660
|
-
if (this.typingReactionCreatePromises.has(reactionKey) && !this.typingReactions.has(reactionKey)) {
|
|
1661
|
-
this.typingReactionCleanupRequested.add(reactionKey);
|
|
1662
|
-
return;
|
|
1663
|
-
}
|
|
1664
|
-
this.removeTypingReaction(reactionKey);
|
|
1665
|
-
}
|
|
1666
|
-
removeTypingReaction(reactionKey) {
|
|
1667
|
-
const reaction = this.typingReactions.get(reactionKey);
|
|
1668
|
-
if (!reaction || !this.restClient) return;
|
|
1669
|
-
this.typingReactions.delete(reactionKey);
|
|
1670
|
-
this.restClient.im.messageReaction.delete({
|
|
1671
|
-
path: { message_id: reaction.messageId, reaction_id: reaction.reactionId }
|
|
1672
|
-
}).catch(() => {
|
|
1673
|
-
});
|
|
1674
1623
|
}
|
|
1675
1624
|
// ── Card Action Handler ─────────────────────────────────────
|
|
1676
1625
|
/**
|
|
@@ -2055,6 +2004,10 @@ ${trimmedResponse}`;
|
|
|
2055
2004
|
sequence: state.sequence
|
|
2056
2005
|
}
|
|
2057
2006
|
}));
|
|
2007
|
+
const terminalReactionEmoji = status === "completed" ? COMPLETED_EMOJI : status === "error" ? ERROR_EMOJI : null;
|
|
2008
|
+
if (terminalReactionEmoji) {
|
|
2009
|
+
await this.addTerminalReaction(cardKey, state.messageId, terminalReactionEmoji);
|
|
2010
|
+
}
|
|
2058
2011
|
console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
2059
2012
|
return true;
|
|
2060
2013
|
} catch (err) {
|
|
@@ -2064,6 +2017,21 @@ ${trimmedResponse}`;
|
|
|
2064
2017
|
this.activeCards.delete(cardKey);
|
|
2065
2018
|
}
|
|
2066
2019
|
}
|
|
2020
|
+
async addTerminalReaction(streamKey, messageId, emojiType) {
|
|
2021
|
+
const messageReaction = this.restClient?.im?.messageReaction;
|
|
2022
|
+
if (typeof messageReaction?.create !== "function") return;
|
|
2023
|
+
try {
|
|
2024
|
+
await this.withFeishuRequestTimeout(streamKey, `im.messageReaction.create:${emojiType}`, () => messageReaction.create({
|
|
2025
|
+
path: { message_id: messageId },
|
|
2026
|
+
data: { reaction_type: { emoji_type: emojiType } }
|
|
2027
|
+
}));
|
|
2028
|
+
} catch (err) {
|
|
2029
|
+
const code2 = err?.code;
|
|
2030
|
+
if (code2 !== 99991400 && code2 !== 99991403) {
|
|
2031
|
+
console.warn("[feishu-adapter] Terminal reaction failed:", err instanceof Error ? err.message : err);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2067
2035
|
/**
|
|
2068
2036
|
* Clean up card state without finalizing (e.g. on unexpected errors).
|
|
2069
2037
|
*/
|
|
@@ -10178,6 +10146,31 @@ var IGNORED_EVENT_MSG_TYPES = /* @__PURE__ */ new Set([
|
|
|
10178
10146
|
var IGNORED_RESPONSE_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
10179
10147
|
"web_search_call"
|
|
10180
10148
|
]);
|
|
10149
|
+
var TERMINAL_COMPLETION_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
10150
|
+
"task_complete",
|
|
10151
|
+
"turn.completed",
|
|
10152
|
+
"turn_completed"
|
|
10153
|
+
]);
|
|
10154
|
+
function isTerminalCompletionEventType(value) {
|
|
10155
|
+
return typeof value === "string" && TERMINAL_COMPLETION_EVENT_TYPES.has(value.trim());
|
|
10156
|
+
}
|
|
10157
|
+
function getEventTurnId(payload) {
|
|
10158
|
+
return payload?.turn_id || payload?.turnId || "";
|
|
10159
|
+
}
|
|
10160
|
+
function extractTerminalCompletionText(payload) {
|
|
10161
|
+
if (!payload) return "";
|
|
10162
|
+
for (const value of [
|
|
10163
|
+
payload.last_agent_message,
|
|
10164
|
+
payload.message,
|
|
10165
|
+
payload.text,
|
|
10166
|
+
payload.final_response,
|
|
10167
|
+
payload.response
|
|
10168
|
+
]) {
|
|
10169
|
+
const text2 = extractNormalizedStructuredText(value);
|
|
10170
|
+
if (text2) return text2;
|
|
10171
|
+
}
|
|
10172
|
+
return "";
|
|
10173
|
+
}
|
|
10181
10174
|
function isIgnoredMirrorLineKind(line) {
|
|
10182
10175
|
if (isSessionEventLine(line)) {
|
|
10183
10176
|
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
@@ -10281,8 +10274,8 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
|
|
|
10281
10274
|
});
|
|
10282
10275
|
return;
|
|
10283
10276
|
}
|
|
10284
|
-
if (isSessionEventLine(parsed) && parsed.payload?.type
|
|
10285
|
-
const text2 =
|
|
10277
|
+
if (isSessionEventLine(parsed) && isTerminalCompletionEventType(parsed.payload?.type)) {
|
|
10278
|
+
const text2 = extractTerminalCompletionText(parsed.payload);
|
|
10286
10279
|
if (!text2) return;
|
|
10287
10280
|
const lastEvent = events[events.length - 1];
|
|
10288
10281
|
if (lastEvent?.role === "assistant" && lastEvent.content === text2) {
|
|
@@ -10329,7 +10322,7 @@ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
|
10329
10322
|
type: "task_started",
|
|
10330
10323
|
content: "",
|
|
10331
10324
|
timestamp,
|
|
10332
|
-
turnId: parsed.payload
|
|
10325
|
+
turnId: getEventTurnId(parsed.payload)
|
|
10333
10326
|
});
|
|
10334
10327
|
return true;
|
|
10335
10328
|
}
|
|
@@ -10476,14 +10469,14 @@ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
|
10476
10469
|
});
|
|
10477
10470
|
return true;
|
|
10478
10471
|
}
|
|
10479
|
-
if (parsed.payload?.type
|
|
10472
|
+
if (isTerminalCompletionEventType(parsed.payload?.type)) {
|
|
10480
10473
|
records.push({
|
|
10481
10474
|
signature,
|
|
10482
10475
|
type: "task_complete",
|
|
10483
10476
|
role: "assistant",
|
|
10484
|
-
content:
|
|
10477
|
+
content: extractTerminalCompletionText(parsed.payload),
|
|
10485
10478
|
timestamp,
|
|
10486
|
-
turnId: parsed.payload
|
|
10479
|
+
turnId: getEventTurnId(parsed.payload)
|
|
10487
10480
|
});
|
|
10488
10481
|
return true;
|
|
10489
10482
|
}
|
|
@@ -10712,16 +10705,16 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
|
|
|
10712
10705
|
}
|
|
10713
10706
|
if (isSessionEventLine(parsed) && parsed.payload?.type === "task_started") {
|
|
10714
10707
|
const eventPayload = parsed.payload;
|
|
10715
|
-
activeTurnId = eventPayload
|
|
10708
|
+
activeTurnId = getEventTurnId(eventPayload) || activeTurnId;
|
|
10716
10709
|
}
|
|
10717
10710
|
const handled = pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId, activeSpecialCallIds);
|
|
10718
10711
|
if (!handled) {
|
|
10719
10712
|
const unknownKind = describeUnhandledMirrorLineKind(parsed);
|
|
10720
10713
|
if (unknownKind) unknownKinds.add(unknownKind);
|
|
10721
10714
|
}
|
|
10722
|
-
if (isSessionEventLine(parsed) && (parsed.payload?.type
|
|
10715
|
+
if (isSessionEventLine(parsed) && (isTerminalCompletionEventType(parsed.payload?.type) || parsed.payload?.type === "turn_aborted")) {
|
|
10723
10716
|
const eventPayload = parsed.payload;
|
|
10724
|
-
const completedTurnId = eventPayload
|
|
10717
|
+
const completedTurnId = getEventTurnId(eventPayload) || activeTurnId;
|
|
10725
10718
|
if (!completedTurnId || completedTurnId === activeTurnId) {
|
|
10726
10719
|
activeTurnId = null;
|
|
10727
10720
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-to-im",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.50",
|
|
4
4
|
"description": "Installable Codex-to-IM bridge with local setup UI and background service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/zhangle1987/codex-to-im#readme",
|
|
@@ -40,11 +40,16 @@
|
|
|
40
40
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@larksuiteoapi/node-sdk": "^1.
|
|
44
|
-
"@openai/codex-sdk": "^0.
|
|
43
|
+
"@larksuiteoapi/node-sdk": "^1.65.0",
|
|
44
|
+
"@openai/codex-sdk": "^0.133.0",
|
|
45
45
|
"markdown-it": "^14.1.1",
|
|
46
46
|
"qrcode": "^1.5.4",
|
|
47
|
-
"ws": "^8.
|
|
47
|
+
"ws": "^8.20.1"
|
|
48
|
+
},
|
|
49
|
+
"overrides": {
|
|
50
|
+
"axios": "1.16.1",
|
|
51
|
+
"follow-redirects": "1.16.0",
|
|
52
|
+
"protobufjs": "7.5.9"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
55
|
"@types/markdown-it": "^14.1.2",
|
|
@@ -15,7 +15,7 @@ import path from 'node:path';
|
|
|
15
15
|
* - If upstream adds `windowsHide` natively, remove this script.
|
|
16
16
|
*/
|
|
17
17
|
const PATCH_MARKER = 'windowsHide: process.platform === "win32"';
|
|
18
|
-
const SUPPORTED_SDK_VERSION = /^0\.(11\d|12\d)\.\d+$/;
|
|
18
|
+
const SUPPORTED_SDK_VERSION = /^0\.(11\d|12\d|13[0-3])\.\d+$/;
|
|
19
19
|
|
|
20
20
|
function logSkip(message) {
|
|
21
21
|
console.warn(`[postinstall] ${message}`);
|