linco-connect 1.0.8 → 1.1.0
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/README.md +5 -2
- package/bin/linco-connect.js +23 -4
- package/package.json +1 -1
- package/public/index.html +3 -2
- package/src/agentRunner.js +2 -0
- package/src/agents/codex.js +25 -8
- package/src/agents/hermes.js +24 -8
- package/src/agents/openclaw.js +609 -0
- package/src/attachmentHandler.js +34 -5
- package/src/claudeRunner.js +27 -1
- package/src/config.js +20 -4
- package/src/httpStatic.js +2 -2
- package/src/imConnector.js +78 -3
- package/src/lincoProtocol.js +24 -1
- package/src/openclawGateway.js +442 -0
- package/src/session.js +3 -0
- package/src/slashCommands.js +46 -2
- package/src/wsServer.js +5 -1
package/README.md
CHANGED
|
@@ -32,11 +32,11 @@ Codex: wss://chat.ddjf.info/socket/ai/codex
|
|
|
32
32
|
- 会话隔离:每个 Agent 和远端 session 都创建独立 session、workspace、attachments、outbox。
|
|
33
33
|
- Markdown 展示:支持 GFM Markdown、代码块、表格、引用和安全链接。
|
|
34
34
|
- 工具调用展示:Agent 工具输入、输出和错误以可折叠卡片展示。
|
|
35
|
-
-
|
|
35
|
+
- 权限确认:工具/命令权限请求和危险操作确认默认自动允许;可用 `/approve off` 为当前会话恢复手动确认。
|
|
36
36
|
- 附件上传:支持普通文件和图片;图片作为多模态内容发送,普通文件保存为本地路径引用。
|
|
37
37
|
- 图片粘贴:支持从剪贴板粘贴图片。
|
|
38
38
|
- 文件下发:Agent 生成文件放入 outbox 后,前端自动展示下载/预览卡片。
|
|
39
|
-
- 斜杠命令:内置 `/help`、`/pwd`、`/cd`、`/new`、`/stop`、`/base`、`/status`、`/list`。
|
|
39
|
+
- 斜杠命令:内置 `/help`、`/pwd`、`/cd`、`/new`、`/stop`、`/base`、`/status`、`/list`、`/approve`。
|
|
40
40
|
- Windows Git Bash 检测:Windows 下自动查找 Git Bash,也可手动指定。
|
|
41
41
|
|
|
42
42
|
## 前置条件
|
|
@@ -377,6 +377,9 @@ outbox 目录: .../outbox
|
|
|
377
377
|
| `/base` | 显示 Linco 运行目录、附件目录和 outbox 目录 |
|
|
378
378
|
| `/list` | 列出当前 IM 会话最近 10 条 Agent Session 历史 |
|
|
379
379
|
| `/list <条数>` | 按指定条数列出最近的 Agent Session 历史 |
|
|
380
|
+
| `/approve` | 显示当前自动审批状态 |
|
|
381
|
+
| `/approve on` | 开启当前会话自动审批,后续工具/命令权限请求和危险操作确认自动允许(默认) |
|
|
382
|
+
| `/approve off` | 关闭当前会话自动审批,恢复手动确认 |
|
|
380
383
|
|
|
381
384
|
除上述本地命令外,其他 `/xxx` 会透传给当前 Agent CLI。
|
|
382
385
|
|
package/bin/linco-connect.js
CHANGED
|
@@ -16,6 +16,10 @@ const {
|
|
|
16
16
|
} = require('../src/config');
|
|
17
17
|
const { ensureLocalToken, localUrlWithToken } = require('../src/localAuth');
|
|
18
18
|
const { checkGatewayHealth, resolveHermesGatewayOptions } = require('../src/hermesGateway');
|
|
19
|
+
const {
|
|
20
|
+
checkGatewayHealth: checkOpenClawGatewayHealth,
|
|
21
|
+
resolveOpenClawGatewayOptions,
|
|
22
|
+
} = require('../src/openclawGateway');
|
|
19
23
|
const pkg = require('../package.json');
|
|
20
24
|
|
|
21
25
|
const rootDir = path.resolve(__dirname, '..');
|
|
@@ -147,6 +151,8 @@ function mergeInitConfig(current, values) {
|
|
|
147
151
|
appSecret: values.appSecret,
|
|
148
152
|
enabled: true,
|
|
149
153
|
};
|
|
154
|
+
if (values.agentType === 'openclaw') accountEntry.openclawAgentId = 'main';
|
|
155
|
+
const agentType = values.agentType || 'claude';
|
|
150
156
|
|
|
151
157
|
return {
|
|
152
158
|
...current,
|
|
@@ -157,13 +163,13 @@ function mergeInitConfig(current, values) {
|
|
|
157
163
|
...(current.channels?.linco || {}),
|
|
158
164
|
agents: {
|
|
159
165
|
...(current.channels?.linco?.agents || {}),
|
|
160
|
-
[
|
|
161
|
-
...(current.channels?.linco?.agents?.[
|
|
166
|
+
[agentType]: {
|
|
167
|
+
...(current.channels?.linco?.agents?.[agentType] || {}),
|
|
162
168
|
defaultAccount: values.account,
|
|
163
169
|
accounts: {
|
|
164
|
-
...(current.channels?.linco?.agents?.[
|
|
170
|
+
...(current.channels?.linco?.agents?.[agentType]?.accounts || {}),
|
|
165
171
|
[values.account]: {
|
|
166
|
-
...(current.channels?.linco?.agents?.[
|
|
172
|
+
...(current.channels?.linco?.agents?.[agentType]?.accounts?.[values.account] || {}),
|
|
167
173
|
...accountEntry,
|
|
168
174
|
},
|
|
169
175
|
},
|
|
@@ -425,6 +431,19 @@ async function doctorCommand() {
|
|
|
425
431
|
} else {
|
|
426
432
|
checks.push([`${agentType} Gateway`, false, `未运行,且 autoStartGateway=false ${options.gatewayUrl}`]);
|
|
427
433
|
}
|
|
434
|
+
} else if (agentType === 'openclaw') {
|
|
435
|
+
const options = resolveOpenClawGatewayOptions(agent);
|
|
436
|
+
const health = await checkOpenClawGatewayHealth(options.gatewayUrl, agent, { timeoutMs: 3000 });
|
|
437
|
+
if (health.ok) {
|
|
438
|
+
checks.push([`${agentType} Gateway`, true, options.gatewayUrl]);
|
|
439
|
+
} else if (agent.autoStartGateway !== false) {
|
|
440
|
+
const hasCli = !agent.enabled || (path.isAbsolute(options.openclawBin) ? fs.existsSync(options.openclawBin) : commandExists(options.openclawBin));
|
|
441
|
+
checks.push([`${agentType} Gateway`, true, `not running; OpenClaw messages will auto-start ${options.gatewayUrl}`]);
|
|
442
|
+
checks.push([`${agentType} CLI`, hasCli, options.openclawBin]);
|
|
443
|
+
} else {
|
|
444
|
+
checks.push([`${agentType} Gateway`, false, `not running and autoStartGateway=false ${options.gatewayUrl}`]);
|
|
445
|
+
}
|
|
446
|
+
checks.push([`${agentType} Agent ID`, true, agent.openclawAgentId || 'main']);
|
|
428
447
|
} else {
|
|
429
448
|
checks.push([`${agentType} CLI`, !agent.enabled || (path.isAbsolute(agent.bin) ? fs.existsSync(agent.bin) : commandExists(agent.bin)), agent.bin]);
|
|
430
449
|
}
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -577,6 +577,7 @@
|
|
|
577
577
|
<option value="claude">Claude Code</option>
|
|
578
578
|
<option value="codex">Codex</option>
|
|
579
579
|
<option value="hermes">Hermes</option>
|
|
580
|
+
<option value="openclaw">OpenClaw</option>
|
|
580
581
|
</select>
|
|
581
582
|
</div>
|
|
582
583
|
<div id="messages"></div>
|
|
@@ -1180,11 +1181,11 @@
|
|
|
1180
1181
|
|
|
1181
1182
|
function normalizeAgentType(value) {
|
|
1182
1183
|
const type = String(value || 'claude').trim().toLowerCase();
|
|
1183
|
-
return ['claude', 'codex', 'hermes'].includes(type) ? type : 'claude';
|
|
1184
|
+
return ['claude', 'codex', 'hermes', 'openclaw'].includes(type) ? type : 'claude';
|
|
1184
1185
|
}
|
|
1185
1186
|
|
|
1186
1187
|
function agentLabel(type) {
|
|
1187
|
-
return type === 'codex' ? 'Codex' : type === 'hermes' ? 'Hermes' : 'Claude Code';
|
|
1188
|
+
return type === 'codex' ? 'Codex' : type === 'hermes' ? 'Hermes' : type === 'openclaw' ? 'OpenClaw' : 'Claude Code';
|
|
1188
1189
|
}
|
|
1189
1190
|
|
|
1190
1191
|
function setAgentSelection(agentType, options = {}) {
|
package/src/agentRunner.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const claude = require('./agents/claude');
|
|
2
2
|
const codex = require('./agents/codex');
|
|
3
3
|
const hermes = require('./agents/hermes');
|
|
4
|
+
const openclaw = require('./agents/openclaw');
|
|
4
5
|
|
|
5
6
|
const providers = {
|
|
6
7
|
claude,
|
|
7
8
|
codex,
|
|
8
9
|
hermes,
|
|
10
|
+
openclaw,
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
function providerFor(session) {
|
package/src/agents/codex.js
CHANGED
|
@@ -15,14 +15,18 @@ const {
|
|
|
15
15
|
function execute(input, ws, session, config) {
|
|
16
16
|
const textForCheck = stringifyInput(input);
|
|
17
17
|
if (isDangerousCommand(textForCheck)) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
if (session.autoApprove === true) {
|
|
19
|
+
sendSystem(ws, '✅ 已自动批准危险操作确认。');
|
|
20
|
+
} else {
|
|
21
|
+
const preview = textForCheck.slice(0, 200);
|
|
22
|
+
session.pendingDanger = { input };
|
|
23
|
+
send(ws, 'danger_warning', {
|
|
24
|
+
text: `⚠️ 检测到可能的危险操作,请确认是否继续执行:
|
|
22
25
|
|
|
23
26
|
"${preview}${textForCheck.length > 200 ? '...' : ''}"`,
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
if (session.isTurnActive) {
|
|
@@ -386,13 +390,25 @@ function handleServerRequest(message, session) {
|
|
|
386
390
|
return;
|
|
387
391
|
}
|
|
388
392
|
|
|
389
|
-
// Command execution approval —
|
|
393
|
+
// Command execution approval — auto-approve by default, or ask the user when disabled.
|
|
390
394
|
if (method === 'item/commandExecution/requestApproval' || method === 'execCommandApproval') {
|
|
391
395
|
const cmd = params.command || params.tool || '';
|
|
392
|
-
session._log?.info('codex command execution approval requested', { method, command: cmd });
|
|
396
|
+
session._log?.info('codex command execution approval requested', { method, command: cmd, autoApprove: session.autoApprove === true });
|
|
393
397
|
|
|
394
398
|
if (getPendingPermission(session, String(message.id), 'codex')) return;
|
|
395
399
|
|
|
400
|
+
if (session.autoApprove === true) {
|
|
401
|
+
sendJsonRpc(session.codexAppServer, {
|
|
402
|
+
jsonrpc: '2.0',
|
|
403
|
+
id: message.id,
|
|
404
|
+
result: { decision: 'accept' },
|
|
405
|
+
});
|
|
406
|
+
if (ws) {
|
|
407
|
+
sendSystem(ws, `✅ 已自动批准命令执行:${cmd}`);
|
|
408
|
+
}
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
396
412
|
setPendingPermission(session, {
|
|
397
413
|
provider: 'codex',
|
|
398
414
|
requestId: String(message.id),
|
|
@@ -858,6 +874,7 @@ function buildExecArgs(session, agentConfig) {
|
|
|
858
874
|
args.push('--json');
|
|
859
875
|
if (!session.agentSessionId) args.push('--cd', session.workspace);
|
|
860
876
|
if (agentConfig.model) args.push('--model', agentConfig.model);
|
|
877
|
+
if (session.autoApprove === true) args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
861
878
|
if (session.agentSessionId) args.push(session.agentSessionId);
|
|
862
879
|
args.push('-');
|
|
863
880
|
return args;
|
package/src/agents/hermes.js
CHANGED
|
@@ -23,12 +23,16 @@ function extractText(input) {
|
|
|
23
23
|
function execute(input, ws, session, config) {
|
|
24
24
|
const textForCheck = stringifyInput(input);
|
|
25
25
|
if (isDangerousCommand(textForCheck)) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
if (session.autoApprove === true) {
|
|
27
|
+
sendSystem(ws, '✅ 已自动批准危险操作确认。');
|
|
28
|
+
} else {
|
|
29
|
+
const preview = textForCheck.slice(0, 200);
|
|
30
|
+
session.pendingDanger = { input };
|
|
31
|
+
send(ws, 'danger_warning', {
|
|
32
|
+
text: `⚠️ 检测到可能的危险操作,请确认是否继续执行:\n\n"${preview}${textForCheck.length > 200 ? '...' : ''}"`,
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
if (session.isTurnActive) {
|
|
@@ -163,7 +167,7 @@ function handleHermesEvent(event, ws, session, config) {
|
|
|
163
167
|
send(ws, 'thinking', { text: event.text || '' });
|
|
164
168
|
return;
|
|
165
169
|
case 'approval.request':
|
|
166
|
-
handleApprovalRequest(event, ws, session);
|
|
170
|
+
handleApprovalRequest(event, ws, session, config);
|
|
167
171
|
return;
|
|
168
172
|
case 'approval.responded':
|
|
169
173
|
sendSystem(ws, event.choice === 'deny' ? '🚫 已拒绝 Hermes 操作。' : '✅ 已批准 Hermes 操作。');
|
|
@@ -184,7 +188,7 @@ function handleHermesEvent(event, ws, session, config) {
|
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
|
|
187
|
-
function handleApprovalRequest(event, ws, session) {
|
|
191
|
+
function handleApprovalRequest(event, ws, session, config) {
|
|
188
192
|
const requestId = String(
|
|
189
193
|
event.requestId ||
|
|
190
194
|
event.request_id ||
|
|
@@ -205,6 +209,18 @@ function handleApprovalRequest(event, ws, session) {
|
|
|
205
209
|
input,
|
|
206
210
|
choices: event.choices || [],
|
|
207
211
|
});
|
|
212
|
+
|
|
213
|
+
if (session.autoApprove === true) {
|
|
214
|
+
config.logger?.info('hermes permission auto-approved', {
|
|
215
|
+
sessionId: session.id,
|
|
216
|
+
requestId,
|
|
217
|
+
});
|
|
218
|
+
resolvePendingPermission(true, ws, session, config, requestId).catch(err => {
|
|
219
|
+
sendError(ws, `Hermes 自动审批失败: ${err.message}`);
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
208
224
|
send(ws, 'permission_request', {
|
|
209
225
|
requestId,
|
|
210
226
|
toolName: 'exec',
|