@wu529778790/open-im 1.11.8-beta.3 → 1.11.8-beta.4

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.
@@ -122,6 +122,17 @@ function startPolling() {
122
122
  const extracted = extractTextContent(msg);
123
123
  if (!extracted)
124
124
  continue;
125
+ // Skip echoed bot messages (iLink API echoes bot's own messages as USER type)
126
+ // 1) Messages sent by this bot have client_id starting with "open-im:"
127
+ if (msg.client_id?.startsWith('open-im:')) {
128
+ log.debug(`Skipping echoed bot message: client_id=${msg.client_id}`);
129
+ continue;
130
+ }
131
+ // 2) Bot lifecycle notifications and tool call notifications echoed back
132
+ if (isBotNotificationEcho(extracted)) {
133
+ log.debug(`Skipping bot notification echo: content="${extracted.substring(0, 80)}"`);
134
+ continue;
135
+ }
125
136
  const chatId = msg.from_user_id ?? '';
126
137
  const msgId = String(msg.message_id ?? msg.seq ?? '');
127
138
  if (!chatId) {
@@ -313,6 +324,23 @@ async function extractImages(msg) {
313
324
  }
314
325
  return paths;
315
326
  }
327
+ /**
328
+ * Check if extracted text is a bot notification being echoed back by the iLink API.
329
+ * Bot notifications have distinctive emoji prefixes that are unlikely in real user messages.
330
+ */
331
+ function isBotNotificationEcho(text) {
332
+ const firstLine = text.split('\n')[0]?.trim() ?? '';
333
+ // Lifecycle/AI info notifications
334
+ if (firstLine.startsWith('🤖 AI:'))
335
+ return true;
336
+ // Service status notifications
337
+ if (firstLine.startsWith('✅ ') || firstLine.startsWith('🔄 ') || firstLine.startsWith('ℹ️ '))
338
+ return true;
339
+ // Tool call notifications (sent by streamUpdate in event-handler)
340
+ if (firstLine.startsWith('⚙️ '))
341
+ return true;
342
+ return false;
343
+ }
316
344
  /**
317
345
  * Extract text content from an iLink message's item_list.
318
346
  * Returns the first text item found, or a placeholder for media types.
@@ -46,9 +46,15 @@ export async function runOpenCodeSdk(prompt, sessionId, workDir, callbacks, opti
46
46
  let accumulatedThinking = '';
47
47
  const toolStats = {};
48
48
  let runSettled = false;
49
+ let sseConnected = false;
50
+ // 用于等待 SSE 连接就绪后再发 prompt,避免竞态
51
+ const sseReadyResolve = () => { sseConnected = true; };
49
52
  const subscribeEvents = async () => {
50
53
  try {
51
54
  const sse = await client.global.event({ signal: abortController.signal });
55
+ // SSE 连接已建立,通知主流程可以开始 prompt
56
+ sseReadyResolve();
57
+ log.debug('SSE stream connected');
52
58
  for await (const raw of sse.stream) {
53
59
  const ev = raw;
54
60
  const payload = ev?.payload;
@@ -65,6 +71,16 @@ export async function runOpenCodeSdk(prompt, sessionId, workDir, callbacks, opti
65
71
  }
66
72
  break;
67
73
  }
74
+ case 'session.next.text.ended': {
75
+ // text.ended 携带完整文本,作为 SSE 流的兜底
76
+ const fullText = payload.properties.text;
77
+ if (fullText && !accumulatedText) {
78
+ accumulatedText = fullText;
79
+ callbacks.onText(accumulatedText);
80
+ log.debug(`SSE text.ended fallback: got ${fullText.length} chars`);
81
+ }
82
+ break;
83
+ }
68
84
  case 'session.next.reasoning.delta': {
69
85
  const delta = payload.properties.delta;
70
86
  if (delta) {
@@ -73,6 +89,15 @@ export async function runOpenCodeSdk(prompt, sessionId, workDir, callbacks, opti
73
89
  }
74
90
  break;
75
91
  }
92
+ case 'session.next.reasoning.ended': {
93
+ // reasoning.ended 携带完整推理文本
94
+ const fullReasoning = payload.properties.text;
95
+ if (fullReasoning && !accumulatedThinking) {
96
+ accumulatedThinking = fullReasoning;
97
+ callbacks.onThinking?.(accumulatedThinking);
98
+ }
99
+ break;
100
+ }
76
101
  case 'session.next.tool.called': {
77
102
  const tool = payload.properties.tool;
78
103
  if (tool) {
@@ -81,6 +106,9 @@ export async function runOpenCodeSdk(prompt, sessionId, workDir, callbacks, opti
81
106
  }
82
107
  break;
83
108
  }
109
+ default:
110
+ log.debug(`SSE unhandled event: ${payload.type}`);
111
+ break;
84
112
  }
85
113
  }
86
114
  }
@@ -91,6 +119,24 @@ export async function runOpenCodeSdk(prompt, sessionId, workDir, callbacks, opti
91
119
  }
92
120
  };
93
121
  const background = subscribeEvents();
122
+ // 等待 SSE 连接就绪(最多 3 秒),避免 prompt 在 SSE 未连接时就开始
123
+ // 如果 3 秒内 SSE 未连接,仍然继续执行(SSE 可能后续连接并补收事件)
124
+ const sseReady = new Promise((resolve) => {
125
+ const check = setInterval(() => {
126
+ if (sseConnected) {
127
+ clearInterval(check);
128
+ resolve();
129
+ }
130
+ }, 100);
131
+ // 3 秒超时:SSE 可能需要重试连接,不等太久
132
+ setTimeout(() => {
133
+ clearInterval(check);
134
+ if (!sseConnected)
135
+ log.warn('SSE not connected within 3s, proceeding with prompt anyway');
136
+ resolve();
137
+ }, 3000);
138
+ });
139
+ await sseReady;
94
140
  try {
95
141
  const result = await client.session.prompt({
96
142
  sessionID: currentSessionId,
@@ -108,6 +154,10 @@ export async function runOpenCodeSdk(prompt, sessionId, workDir, callbacks, opti
108
154
  .map(p => p.text)
109
155
  .join('\n');
110
156
  const cost = data?.info?.cost ?? 0;
157
+ log.info(`SDK prompt completed: accumulatedText=${accumulatedText.length}, finalText=${finalText.length}, parts=${parts.length}, cost=${cost}`);
158
+ if (!accumulatedText && !finalText) {
159
+ log.warn(`SDK prompt returned empty output — data keys: ${data ? Object.keys(data).join(',') : 'null'}, parts types: ${parts.map(p => p.type).join(',')}`);
160
+ }
111
161
  callbacks.onComplete({
112
162
  success: true,
113
163
  result: finalText,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.8-beta.3",
3
+ "version": "1.11.8-beta.4",
4
4
  "description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",