linco-connect 1.1.0 → 1.1.2
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/package.json +1 -1
- package/src/agents/codex.js +5 -1
- package/src/agents/hermes.js +4 -1
- package/src/agents/openclaw.js +4 -1
- package/src/attachmentHandler.js +2 -1
- package/src/claudeRunner.js +8 -3
- package/src/lincoProtocol.js +11 -0
- package/src/protocol.js +31 -0
- package/src/slashCommands.js +21 -16
package/package.json
CHANGED
package/src/agents/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const { isDangerousCommand } = require('../danger');
|
|
3
|
-
const { send, sendError, sendSystem } = require('../protocol');
|
|
3
|
+
const { send, sendError, sendSystem, sendTurnEnd } = require('../protocol');
|
|
4
4
|
const { persistAgentSessionId, stopAgentProcess: stopSessionProcess, updateAgentSessionHistory } = require('../session');
|
|
5
5
|
const { getOutboxDir } = require('../outgoingAttachmentHandler');
|
|
6
6
|
const { createTextStreamBuffer, appendTextStream, flushTextStream, resetTextStream } = require('../streamBuffer');
|
|
@@ -603,6 +603,7 @@ function handleAppServerMessage(message, session) {
|
|
|
603
603
|
} else {
|
|
604
604
|
sendSystem(ws, 'Codex 本次执行没有输出。');
|
|
605
605
|
}
|
|
606
|
+
sendTurnEnd(ws, session, 'timeout');
|
|
606
607
|
const cfg = session._lastConfig;
|
|
607
608
|
if (cfg) drainQueue(ws, session, cfg);
|
|
608
609
|
}
|
|
@@ -622,6 +623,7 @@ function handleAppServerMessage(message, session) {
|
|
|
622
623
|
} else {
|
|
623
624
|
sendSystem(ws, 'Codex 本次执行没有输出。');
|
|
624
625
|
}
|
|
626
|
+
sendTurnEnd(ws, session);
|
|
625
627
|
const cfg = session._lastConfig;
|
|
626
628
|
if (cfg) {
|
|
627
629
|
drainQueue(ws, session, cfg);
|
|
@@ -631,6 +633,7 @@ function handleAppServerMessage(message, session) {
|
|
|
631
633
|
|
|
632
634
|
if (method === 'error' || method.includes('error')) {
|
|
633
635
|
sendError(ws, params.message || JSON.stringify(params));
|
|
636
|
+
sendTurnEnd(ws, session, 'error', { error: params.message || JSON.stringify(params) });
|
|
634
637
|
clearTurnState(session);
|
|
635
638
|
return;
|
|
636
639
|
}
|
|
@@ -861,6 +864,7 @@ function runExecTurn(input, ws, session, config) {
|
|
|
861
864
|
if (code === 0) {
|
|
862
865
|
updateCodexSessionStats(session);
|
|
863
866
|
}
|
|
867
|
+
sendTurnEnd(ws, session, code === 0 ? 'completed' : 'error', code === 0 ? {} : { error: stderr.trim() || `Codex 退出,状态码: ${code}` });
|
|
864
868
|
drainQueue(ws, session, config);
|
|
865
869
|
});
|
|
866
870
|
|
package/src/agents/hermes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { isDangerousCommand } = require('../danger');
|
|
2
|
-
const { send, sendError, sendSystem } = require('../protocol');
|
|
2
|
+
const { send, sendError, sendSystem, sendTurnEnd } = require('../protocol');
|
|
3
3
|
const { persistAgentSessionId, stopAgentProcess: stopSessionProcess, updateAgentSessionHistory, createAgentSessionEntry, saveSessionMetadata } = require('../session');
|
|
4
4
|
const { getOutboxDir } = require('../outgoingAttachmentHandler');
|
|
5
5
|
const { createTextStreamBuffer, appendTextStream, flushTextStream, resetTextStream } = require('../streamBuffer');
|
|
@@ -177,10 +177,12 @@ function handleHermesEvent(event, ws, session, config) {
|
|
|
177
177
|
return;
|
|
178
178
|
case 'run.failed':
|
|
179
179
|
sendError(ws, event.error || 'Hermes run 执行失败');
|
|
180
|
+
sendTurnEnd(ws, session, 'error', { error: event.error || 'Hermes run 执行失败' });
|
|
180
181
|
finishTurn(ws, session, config);
|
|
181
182
|
return;
|
|
182
183
|
case 'run.cancelled':
|
|
183
184
|
sendSystem(ws, 'Hermes run 已停止。');
|
|
185
|
+
sendTurnEnd(ws, session, 'cancelled');
|
|
184
186
|
finishTurn(ws, session, config);
|
|
185
187
|
return;
|
|
186
188
|
default:
|
|
@@ -240,6 +242,7 @@ function completeRun(event, ws, session, config) {
|
|
|
240
242
|
sendSystem(ws, 'Hermes 本次执行没有输出。');
|
|
241
243
|
}
|
|
242
244
|
updateHermesSessionStats(session, event.usage);
|
|
245
|
+
sendTurnEnd(ws, session);
|
|
243
246
|
finishTurn(ws, session, config);
|
|
244
247
|
}
|
|
245
248
|
|
package/src/agents/openclaw.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { isDangerousCommand } = require('../danger');
|
|
4
|
-
const { send, sendError, sendSystem } = require('../protocol');
|
|
4
|
+
const { send, sendError, sendSystem, sendTurnEnd } = require('../protocol');
|
|
5
5
|
const {
|
|
6
6
|
createAgentSessionEntry,
|
|
7
7
|
persistAgentSessionId,
|
|
@@ -211,12 +211,14 @@ function handleChatEvent(payload, ws, session, config) {
|
|
|
211
211
|
flushOpenClawAssistantText(ws, session);
|
|
212
212
|
if (session.streamState?.assistantStarted) send(ws, 'assistant_end', {});
|
|
213
213
|
sendSystem(ws, 'OpenClaw run stopped.');
|
|
214
|
+
sendTurnEnd(ws, session, 'cancelled');
|
|
214
215
|
finishTurn(ws, session, config, { drain: false });
|
|
215
216
|
return;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
if (payload.state === 'error') {
|
|
219
220
|
sendError(ws, payload.errorMessage || 'OpenClaw run failed.');
|
|
221
|
+
sendTurnEnd(ws, session, 'error', { error: payload.errorMessage || 'OpenClaw run failed.' });
|
|
220
222
|
finishTurn(ws, session, config);
|
|
221
223
|
}
|
|
222
224
|
}
|
|
@@ -227,6 +229,7 @@ function completeRun(payload, ws, session, config) {
|
|
|
227
229
|
if (hadOutput) send(ws, 'assistant_end', {});
|
|
228
230
|
else sendSystem(ws, 'OpenClaw returned no output.');
|
|
229
231
|
updateOpenClawSessionStats(session, payload.usage);
|
|
232
|
+
sendTurnEnd(ws, session);
|
|
230
233
|
finishTurn(ws, session, config);
|
|
231
234
|
}
|
|
232
235
|
|
package/src/attachmentHandler.js
CHANGED
|
@@ -2,7 +2,7 @@ const crypto = require('crypto');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { ensureDir } = require('./config');
|
|
5
|
-
const { sendError, sendSystem } = require('./protocol');
|
|
5
|
+
const { sendError, sendSystem, sendTurnEnd } = require('./protocol');
|
|
6
6
|
|
|
7
7
|
const RESERVED_WINDOWS_NAMES = new Set(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);
|
|
8
8
|
const SUPPORTED_IMAGE_MEDIA_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
|
|
@@ -25,6 +25,7 @@ function handleMessageWithAttachments(msg, ws, session, config, executeAgentQuer
|
|
|
25
25
|
savedAttachments = saveAttachments(session, attachments, config);
|
|
26
26
|
} catch (err) {
|
|
27
27
|
sendError(ws, `❌ 附件处理失败: ${err.message}`);
|
|
28
|
+
sendTurnEnd(ws, session, 'error', { error: err.message });
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
|
package/src/claudeRunner.js
CHANGED
|
@@ -3,7 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { isDangerousCommand } = require('./danger');
|
|
5
5
|
const { buildOutboxSystemPrompt, getOutboxDir } = require('./outgoingAttachmentHandler');
|
|
6
|
-
const { send, sendError, sendSystem } = require('./protocol');
|
|
6
|
+
const { send, sendError, sendSystem, sendTurnEnd } = require('./protocol');
|
|
7
7
|
const { appendTextStream, flushTextStream, resetTextStream } = require('./streamBuffer');
|
|
8
8
|
const { persistClaudeSessionId, stopAgentProcess, updateAgentSessionHistory } = require('./session');
|
|
9
9
|
const {
|
|
@@ -193,7 +193,9 @@ function ensureClaudeProcess(ws, session, config) {
|
|
|
193
193
|
if (session.isTurnActive) {
|
|
194
194
|
session.isTurnActive = false;
|
|
195
195
|
session.currentInputForNoOutput = null;
|
|
196
|
-
|
|
196
|
+
const message = code === 0 || code === null ? '⚠️ Claude 会话已结束。' : `❌ Claude 进程退出,退出码: ${code}`;
|
|
197
|
+
sendError(ws, message);
|
|
198
|
+
sendTurnEnd(ws, session, 'error', { error: message });
|
|
197
199
|
drainMessageQueue(ws, session, config);
|
|
198
200
|
}
|
|
199
201
|
});
|
|
@@ -205,7 +207,9 @@ function ensureClaudeProcess(ws, session, config) {
|
|
|
205
207
|
session.isTurnActive = false;
|
|
206
208
|
session.currentInputForNoOutput = null;
|
|
207
209
|
flushAssistantText(ws, session);
|
|
208
|
-
|
|
210
|
+
const message = `❌ 无法启动 Claude: ${err.message}\n请确认已安装 Claude Code 并设置好 API 密钥。`;
|
|
211
|
+
sendError(ws, message);
|
|
212
|
+
sendTurnEnd(ws, session, 'error', { error: message });
|
|
209
213
|
});
|
|
210
214
|
|
|
211
215
|
return child;
|
|
@@ -328,6 +332,7 @@ function handleClaudeMessage(parsed, ws, session, config) {
|
|
|
328
332
|
usage: session.usage,
|
|
329
333
|
messageCount: session.messageCount,
|
|
330
334
|
});
|
|
335
|
+
sendTurnEnd(ws, session);
|
|
331
336
|
session.isTurnActive = false;
|
|
332
337
|
session.currentInputForNoOutput = null;
|
|
333
338
|
drainMessageQueue(ws, session, config);
|
package/src/lincoProtocol.js
CHANGED
|
@@ -132,6 +132,17 @@ function mapLocalEventToLinco(event, session, config, linco) {
|
|
|
132
132
|
};
|
|
133
133
|
case 'outgoing_attachment':
|
|
134
134
|
return mapOutgoingAttachment(event, base, session);
|
|
135
|
+
case 'turn_end':
|
|
136
|
+
return {
|
|
137
|
+
...base,
|
|
138
|
+
...event,
|
|
139
|
+
type: 'turn_end',
|
|
140
|
+
requestId: event.requestId || event.request_id || base.messageId,
|
|
141
|
+
streamId: event.streamId || event.stream_id || linco.streamId || base.streamId,
|
|
142
|
+
sessionKey: event.sessionKey || event.session_key || session.id,
|
|
143
|
+
reason: event.reason || 'completed',
|
|
144
|
+
ts: event.ts || Date.now(),
|
|
145
|
+
};
|
|
135
146
|
default:
|
|
136
147
|
return {
|
|
137
148
|
...base,
|
package/src/protocol.js
CHANGED
|
@@ -10,8 +10,39 @@ function sendError(ws, text) {
|
|
|
10
10
|
send(ws, 'error', { text });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function buildTurnEndPayload(session, reason = 'completed', payload = {}) {
|
|
14
|
+
const linco = session?.linco || {};
|
|
15
|
+
return {
|
|
16
|
+
requestId: payload.requestId || payload.request_id || linco.messageId,
|
|
17
|
+
streamId: payload.streamId || payload.stream_id || linco.streamId,
|
|
18
|
+
sessionKey: payload.sessionKey || payload.session_key || session?.id,
|
|
19
|
+
reason,
|
|
20
|
+
ts: Date.now(),
|
|
21
|
+
...payload,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function turnEndKey(payload) {
|
|
26
|
+
return [
|
|
27
|
+
payload.requestId || '',
|
|
28
|
+
payload.streamId || '',
|
|
29
|
+
payload.sessionKey || '',
|
|
30
|
+
].join('|');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sendTurnEnd(ws, session, reason = 'completed', payload = {}) {
|
|
34
|
+
const turnEndPayload = buildTurnEndPayload(session, reason, payload);
|
|
35
|
+
const key = turnEndKey(turnEndPayload);
|
|
36
|
+
if (session && session.lastTurnEndKey === key) return false;
|
|
37
|
+
if (session) session.lastTurnEndKey = key;
|
|
38
|
+
send(ws, 'turn_end', turnEndPayload);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
13
42
|
module.exports = {
|
|
14
43
|
send,
|
|
15
44
|
sendError,
|
|
16
45
|
sendSystem,
|
|
46
|
+
sendTurnEnd,
|
|
47
|
+
buildTurnEndPayload,
|
|
17
48
|
};
|
package/src/slashCommands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { resetOutgoingAttachments, startOutboxWatcher, stopOutboxWatcher } = require('./outgoingAttachmentHandler');
|
|
4
|
-
const { sendError, sendSystem } = require('./protocol');
|
|
4
|
+
const { sendError, sendSystem, sendTurnEnd } = require('./protocol');
|
|
5
5
|
const { removeAgentSessionFromHistory, saveSessionMetadata } = require('./session');
|
|
6
6
|
const { resolvePendingDanger, resolvePendingPermission, stopAgentProcess } = require('./agentRunner');
|
|
7
7
|
const { clearPendingPermissions, pendingPermissionIds } = require('./permissionState');
|
|
@@ -35,7 +35,7 @@ function handleSlashCommand(text, ws, session, config) {
|
|
|
35
35
|
📎 支持附件:默认允许普通文件(如 csv、xlsx、sql、txt、md、pdf、docx 等),高风险可执行/脚本文件默认拦截
|
|
36
36
|
📤 Agent 生成文件后可放入当前会话 outbox 目录自动发送到对话框
|
|
37
37
|
🔁 其他 /xxx 命令会透传给当前 Agent,优先使用当前 Agent 原生斜杠命令能力`);
|
|
38
|
-
return
|
|
38
|
+
return completeLocalCommand(ws, session);
|
|
39
39
|
|
|
40
40
|
case '/commands':
|
|
41
41
|
sendSystem(ws, `${localCommandsHelp()}
|
|
@@ -44,38 +44,38 @@ function handleSlashCommand(text, ws, session, config) {
|
|
|
44
44
|
除上述本地命令外,其他 /xxx 会直接发送给当前 Agent,例如 /model、/status、/memory、/compact 等。
|
|
45
45
|
|
|
46
46
|
⚠️ 注意:部分 Agent 原生命令只在交互式 CLI/TUI 中更新界面;在 Linco 的 stream-json 模式下可能返回“无输出”或提示当前环境不可用。遇到这种情况不是连接失败,而是该命令不适合当前桥接模式。`);
|
|
47
|
-
return
|
|
47
|
+
return completeLocalCommand(ws, session);
|
|
48
48
|
|
|
49
49
|
case '/status':
|
|
50
50
|
sendStatus(ws, session);
|
|
51
|
-
return
|
|
51
|
+
return completeLocalCommand(ws, session);
|
|
52
52
|
|
|
53
53
|
case '/pwd':
|
|
54
54
|
if (isHermesSession(session)) {
|
|
55
55
|
sendHermesWorkspaceNotice(ws);
|
|
56
|
-
return
|
|
56
|
+
return completeLocalCommand(ws, session);
|
|
57
57
|
}
|
|
58
58
|
sendSystem(ws, `📂 ${session.workspace}`);
|
|
59
|
-
return
|
|
59
|
+
return completeLocalCommand(ws, session);
|
|
60
60
|
|
|
61
61
|
case '/cd':
|
|
62
62
|
if (isHermesSession(session)) {
|
|
63
63
|
sendHermesWorkspaceNotice(ws);
|
|
64
|
-
return
|
|
64
|
+
return completeLocalCommand(ws, session);
|
|
65
65
|
}
|
|
66
66
|
handleCd(parts[1], ws, session, config);
|
|
67
|
-
return
|
|
67
|
+
return completeLocalCommand(ws, session);
|
|
68
68
|
|
|
69
69
|
case '/new':
|
|
70
70
|
stopAgentProcess(session, { clearAgentSession: true });
|
|
71
71
|
resetOutgoingAttachments(session, config);
|
|
72
72
|
sendSystem(ws, '🆕 已开启新会话,之前上下文已清除。');
|
|
73
|
-
return
|
|
73
|
+
return completeLocalCommand(ws, session);
|
|
74
74
|
|
|
75
75
|
case '/stop':
|
|
76
76
|
stopAgentProcess(session, { clearAgentSession: false });
|
|
77
77
|
sendSystem(ws, '⏹️ 已停止当前 Agent 进程,下次消息会尝试恢复当前会话。');
|
|
78
|
-
return
|
|
78
|
+
return completeLocalCommand(ws, session);
|
|
79
79
|
|
|
80
80
|
case '/base':
|
|
81
81
|
sendSystem(ws, `🗄️ Linco 运行信息:
|
|
@@ -84,33 +84,38 @@ Linco Home: ${config.lincoHome}
|
|
|
84
84
|
会话运行目录: ${session.runtimeDir}
|
|
85
85
|
附件目录: ${session.attachmentsDir}
|
|
86
86
|
outbox 目录: ${session.outboxDir}`);
|
|
87
|
-
return
|
|
87
|
+
return completeLocalCommand(ws, session);
|
|
88
88
|
|
|
89
89
|
case '/list':
|
|
90
90
|
handleList(parts[1], ws, session);
|
|
91
|
-
return
|
|
91
|
+
return completeLocalCommand(ws, session);
|
|
92
92
|
|
|
93
93
|
case '/switch':
|
|
94
94
|
handleSwitch(parts[1], ws, session, config);
|
|
95
|
-
return
|
|
95
|
+
return completeLocalCommand(ws, session);
|
|
96
96
|
|
|
97
97
|
case '/delete':
|
|
98
98
|
handleDelete(parts[1], ws, session);
|
|
99
|
-
return
|
|
99
|
+
return completeLocalCommand(ws, session);
|
|
100
100
|
|
|
101
101
|
case '/approve':
|
|
102
102
|
handleApprove(parts[1], ws, session, config);
|
|
103
|
-
return
|
|
103
|
+
return completeLocalCommand(ws, session);
|
|
104
104
|
|
|
105
105
|
case '/usage':
|
|
106
106
|
handleUsage(ws, session);
|
|
107
|
-
return
|
|
107
|
+
return completeLocalCommand(ws, session);
|
|
108
108
|
|
|
109
109
|
default:
|
|
110
110
|
return false;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
function completeLocalCommand(ws, session) {
|
|
115
|
+
sendTurnEnd(ws, session);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
114
119
|
function sendStatus(ws, session) {
|
|
115
120
|
const processRunning = !!(
|
|
116
121
|
(session.agentProcess || session.claudeProcess) &&
|