linco-connect 1.0.8 → 1.0.9
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/package.json +1 -1
- package/src/agents/codex.js +25 -8
- package/src/agents/hermes.js +24 -8
- package/src/claudeRunner.js +27 -1
- package/src/session.js +3 -0
- package/src/slashCommands.js +42 -2
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/package.json
CHANGED
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',
|
package/src/claudeRunner.js
CHANGED
|
@@ -18,7 +18,17 @@ function executeClaudeQuery(input, ws, session, config) {
|
|
|
18
18
|
const textForCheck = typeof input === 'string' ? input : extractText(input);
|
|
19
19
|
|
|
20
20
|
if (isDangerousCommand(textForCheck)) {
|
|
21
|
-
config.logger?.warn('dangerous command detected', {
|
|
21
|
+
config.logger?.warn('dangerous command detected', {
|
|
22
|
+
sessionId: session.id,
|
|
23
|
+
chars: textForCheck.length,
|
|
24
|
+
autoApprove: session.autoApprove === true,
|
|
25
|
+
});
|
|
26
|
+
if (session.autoApprove === true) {
|
|
27
|
+
sendSystem(ws, '✅ 已自动批准危险操作确认。');
|
|
28
|
+
enqueueClaudeQuery(input, ws, session, config);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
const preview = textForCheck.slice(0, 200);
|
|
23
33
|
send(ws, 'danger_warning', {
|
|
24
34
|
text: `⚠️ 检测到可能的危险操作,请确认是否继续执行:\n\n"${preview}${textForCheck.length > 200 ? '...' : ''}"`
|
|
@@ -449,8 +459,24 @@ function handleControlRequest(parsed, ws, session, config) {
|
|
|
449
459
|
sessionId: session.id,
|
|
450
460
|
requestId: requestID,
|
|
451
461
|
toolName,
|
|
462
|
+
autoApprove: session.autoApprove === true,
|
|
452
463
|
});
|
|
453
464
|
|
|
465
|
+
if (session.autoApprove === true) {
|
|
466
|
+
try {
|
|
467
|
+
respondPermission(session, true, requestID);
|
|
468
|
+
config.logger?.info('permission auto-approved', {
|
|
469
|
+
sessionId: session.id,
|
|
470
|
+
requestId: requestID,
|
|
471
|
+
toolName,
|
|
472
|
+
});
|
|
473
|
+
sendSystem(ws, `✅ 已自动批准工具使用:${toolName}`);
|
|
474
|
+
} catch (err) {
|
|
475
|
+
sendError(ws, `❌ 自动批准工具权限失败: ${err.message}`);
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
454
480
|
send(ws, 'permission_request', {
|
|
455
481
|
requestId: requestID,
|
|
456
482
|
toolName,
|
package/src/session.js
CHANGED
|
@@ -33,6 +33,7 @@ function createSession(config, { externalSessionId, agentType = 'claude' } = {})
|
|
|
33
33
|
const workspace = resolveSavedWorkspace(metadata.workspace || legacyMetadata.workspace, defaultWorkspace);
|
|
34
34
|
const agentSessionId = metadata.agentSessionId || metadata.claudeSessionId || legacyMetadata.agentSessionId || legacyMetadata.claudeSessionId || null;
|
|
35
35
|
const agentSessionHistory = metadata.agentSessionHistory || [];
|
|
36
|
+
const autoApprove = metadata.autoApprove !== false;
|
|
36
37
|
|
|
37
38
|
const activeEntry = agentSessionHistory.find(e => e.id === agentSessionId);
|
|
38
39
|
const messageCount = activeEntry?.messageCount ?? 0;
|
|
@@ -60,6 +61,7 @@ function createSession(config, { externalSessionId, agentType = 'claude' } = {})
|
|
|
60
61
|
currentInputForNoOutput: null,
|
|
61
62
|
messageQueue: [],
|
|
62
63
|
pendingDanger: null,
|
|
64
|
+
autoApprove,
|
|
63
65
|
pendingPermission: null,
|
|
64
66
|
pendingPermissions: new Map(),
|
|
65
67
|
streamState: createStreamState(),
|
|
@@ -170,6 +172,7 @@ function saveSessionMetadata(session) {
|
|
|
170
172
|
agentSessionId: session.agentSessionId || session.claudeSessionId || null,
|
|
171
173
|
claudeSessionId: session.agentType === 'claude' ? (session.agentSessionId || session.claudeSessionId || null) : null,
|
|
172
174
|
agentSessionHistory: session.agentSessionHistory || [],
|
|
175
|
+
autoApprove: session.autoApprove === true,
|
|
173
176
|
updatedAt: new Date().toISOString(),
|
|
174
177
|
};
|
|
175
178
|
fs.writeFileSync(sessionMetadataPath(session), `${JSON.stringify(metadata, null, 2)}\n`);
|
package/src/slashCommands.js
CHANGED
|
@@ -3,8 +3,8 @@ const path = require('path');
|
|
|
3
3
|
const { resetOutgoingAttachments, startOutboxWatcher, stopOutboxWatcher } = require('./outgoingAttachmentHandler');
|
|
4
4
|
const { sendError, sendSystem } = require('./protocol');
|
|
5
5
|
const { removeAgentSessionFromHistory, saveSessionMetadata } = require('./session');
|
|
6
|
-
const { stopAgentProcess } = require('./agentRunner');
|
|
7
|
-
const { clearPendingPermissions } = require('./permissionState');
|
|
6
|
+
const { resolvePendingDanger, resolvePendingPermission, stopAgentProcess } = require('./agentRunner');
|
|
7
|
+
const { clearPendingPermissions, pendingPermissionIds } = require('./permissionState');
|
|
8
8
|
|
|
9
9
|
function localCommandsHelp() {
|
|
10
10
|
return `📋 Linco 本地命令:
|
|
@@ -20,6 +20,7 @@ function localCommandsHelp() {
|
|
|
20
20
|
/list [条数] - 列出当前 IM 会话下最近的 Agent Session 历史(默认 10 条)
|
|
21
21
|
/switch <序号> - 切换到指定 Agent Session(恢复上下文)
|
|
22
22
|
/delete <序号> - 从历史记录中删除指定 Agent Session
|
|
23
|
+
/approve on/off - 开启或关闭自动审批(默认开启)
|
|
23
24
|
/usage - 显示 Token 用量统计`;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -97,6 +98,10 @@ outbox 目录: ${session.outboxDir}`);
|
|
|
97
98
|
handleDelete(parts[1], ws, session);
|
|
98
99
|
return true;
|
|
99
100
|
|
|
101
|
+
case '/approve':
|
|
102
|
+
handleApprove(parts[1], ws, session, config);
|
|
103
|
+
return true;
|
|
104
|
+
|
|
100
105
|
case '/usage':
|
|
101
106
|
handleUsage(ws, session);
|
|
102
107
|
return true;
|
|
@@ -127,6 +132,7 @@ Agent session: ${session.agentSessionId || session.claudeSessionId || '(尚未
|
|
|
127
132
|
Agent 进程: ${processRunning ? '运行中' : '未运行'}
|
|
128
133
|
当前 turn: ${session.isTurnActive ? '进行中' : '空闲'}
|
|
129
134
|
排队消息: ${session.messageQueue.length}
|
|
135
|
+
自动审批: ${session.autoApprove ? '开启' : '关闭'}
|
|
130
136
|
待确认: ${session.pendingPermission ? '工具权限' : session.pendingDanger ? '危险操作' : '无'}`);
|
|
131
137
|
}
|
|
132
138
|
|
|
@@ -362,6 +368,40 @@ function handleUsage(ws, session) {
|
|
|
362
368
|
sendSystem(ws, text);
|
|
363
369
|
}
|
|
364
370
|
|
|
371
|
+
function handleApprove(mode, ws, session, config) {
|
|
372
|
+
const value = String(mode || '').trim().toLowerCase();
|
|
373
|
+
|
|
374
|
+
if (!value || value === 'status') {
|
|
375
|
+
sendSystem(ws, `自动审批当前为:${session.autoApprove ? '开启' : '关闭'}。\n使用 /approve on 开启,/approve off 关闭。默认开启。`);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!['on', 'off'].includes(value)) {
|
|
380
|
+
sendError(ws, '❌ /approve 参数只能是 on 或 off,例如 /approve on。');
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
session.autoApprove = value === 'on';
|
|
385
|
+
saveSessionMetadata(session);
|
|
386
|
+
|
|
387
|
+
let approvedPending = 0;
|
|
388
|
+
let approvedDanger = false;
|
|
389
|
+
if (session.autoApprove) {
|
|
390
|
+
const provider = session.agentType || 'claude';
|
|
391
|
+
for (const requestId of pendingPermissionIds(session, provider)) {
|
|
392
|
+
const resolved = resolvePendingPermission(true, ws, session, config, requestId);
|
|
393
|
+
if (resolved) approvedPending += 1;
|
|
394
|
+
}
|
|
395
|
+
if (session.pendingDanger) {
|
|
396
|
+
approvedDanger = !!resolvePendingDanger(true, ws, session, config);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
sendSystem(ws, session.autoApprove
|
|
401
|
+
? `✅ 自动审批已开启:后续工具/命令权限请求和危险操作确认会自动允许。${approvedPending ? `\n已自动批准当前等待中的 ${approvedPending} 个权限请求。` : ''}${approvedDanger ? '\n已自动批准当前等待中的危险操作确认。' : ''}`
|
|
402
|
+
: '✅ 自动审批已关闭:后续工具/命令权限请求和危险操作确认会回到手动确认。');
|
|
403
|
+
}
|
|
404
|
+
|
|
365
405
|
module.exports = {
|
|
366
406
|
handleSlashCommand,
|
|
367
407
|
};
|