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 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
- - 权限确认:Agent 请求工具权限时可在前端批准或拒绝。
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
 
@@ -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
- [values.agentType]: {
161
- ...(current.channels?.linco?.agents?.[values.agentType] || {}),
166
+ [agentType]: {
167
+ ...(current.channels?.linco?.agents?.[agentType] || {}),
162
168
  defaultAccount: values.account,
163
169
  accounts: {
164
- ...(current.channels?.linco?.agents?.[values.agentType]?.accounts || {}),
170
+ ...(current.channels?.linco?.agents?.[agentType]?.accounts || {}),
165
171
  [values.account]: {
166
- ...(current.channels?.linco?.agents?.[values.agentType]?.accounts?.[values.account] || {}),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linco-connect",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "自研 IM 桥接多 Agent 服务",
5
5
  "main": "server.js",
6
6
  "bin": {
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 = {}) {
@@ -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) {
@@ -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
- const preview = textForCheck.slice(0, 200);
19
- session.pendingDanger = { input };
20
- send(ws, 'danger_warning', {
21
- text: `⚠️ 检测到可能的危险操作,请确认是否继续执行:
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
- return;
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 — send to frontend for user 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;
@@ -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
- const preview = textForCheck.slice(0, 200);
27
- session.pendingDanger = { input };
28
- send(ws, 'danger_warning', {
29
- text: `⚠️ 检测到可能的危险操作,请确认是否继续执行:\n\n"${preview}${textForCheck.length > 200 ? '...' : ''}"`,
30
- });
31
- return;
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',