linco-connect 1.0.6 → 1.0.8
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/agentRunner.js +2 -2
- package/src/agents/codex.js +60 -13
- package/src/agents/hermes.js +32 -8
- package/src/claudeRunner.js +35 -12
- package/src/imConnector.js +1 -1
- package/src/permissionState.js +83 -0
- package/src/session.js +3 -0
- package/src/slashCommands.js +2 -1
- package/src/wsServer.js +2 -2
package/package.json
CHANGED
package/src/agentRunner.js
CHANGED
|
@@ -23,8 +23,8 @@ function resolvePendingDanger(confirmed, ws, session, config) {
|
|
|
23
23
|
return providerFor(session).resolvePendingDanger?.(confirmed, ws, session, config) || false;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function resolvePendingPermission(allowed, ws, session, config) {
|
|
27
|
-
return providerFor(session).resolvePendingPermission?.(allowed, ws, session, config) || false;
|
|
26
|
+
function resolvePendingPermission(allowed, ws, session, config, requestId) {
|
|
27
|
+
return providerFor(session).resolvePendingPermission?.(allowed, ws, session, config, requestId) || false;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function stopAgentProcess(session, options = {}) {
|
package/src/agents/codex.js
CHANGED
|
@@ -4,6 +4,13 @@ const { send, sendError, sendSystem } = 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');
|
|
7
|
+
const {
|
|
8
|
+
clearPendingPermissions,
|
|
9
|
+
getPendingPermission,
|
|
10
|
+
pendingPermissionIds,
|
|
11
|
+
removePendingPermission,
|
|
12
|
+
setPendingPermission,
|
|
13
|
+
} = require('../permissionState');
|
|
7
14
|
|
|
8
15
|
function execute(input, ws, session, config) {
|
|
9
16
|
const textForCheck = stringifyInput(input);
|
|
@@ -44,6 +51,7 @@ function runAppServerTurn(input, ws, session, config) {
|
|
|
44
51
|
session.currentInputForNoOutput = input;
|
|
45
52
|
session.sawPartialAssistantText = false;
|
|
46
53
|
session.codexAssistantEnded = false;
|
|
54
|
+
session.codexEmittedAgentMessageIds = new Set();
|
|
47
55
|
resetCodexAssistantText(session);
|
|
48
56
|
session._lastWs = ws;
|
|
49
57
|
session._lastConfig = config;
|
|
@@ -165,6 +173,7 @@ function ensureAppServer(session, config) {
|
|
|
165
173
|
session.codexAppServer = null;
|
|
166
174
|
}
|
|
167
175
|
clearTurnState(session);
|
|
176
|
+
clearPendingPermissions(session, 'codex');
|
|
168
177
|
// drain pending requests
|
|
169
178
|
for (const [, pending] of session.codexPendingRequests) {
|
|
170
179
|
pending.reject(new Error(`app-server 已退出,code=${code}`));
|
|
@@ -298,6 +307,31 @@ function resetCodexAssistantText(session) {
|
|
|
298
307
|
resetTextStream(ensureCodexStreamState(session));
|
|
299
308
|
}
|
|
300
309
|
|
|
310
|
+
function codexAgentMessageId(params) {
|
|
311
|
+
return String(params.item?.id || params.itemId || params.id || '').trim();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function markCodexAgentMessageEmitted(session, itemId) {
|
|
315
|
+
const id = String(itemId || '').trim();
|
|
316
|
+
if (!id) return;
|
|
317
|
+
if (!(session.codexEmittedAgentMessageIds instanceof Set)) {
|
|
318
|
+
session.codexEmittedAgentMessageIds = new Set();
|
|
319
|
+
}
|
|
320
|
+
session.codexEmittedAgentMessageIds.add(id);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function hasCodexAgentMessageEmitted(session, itemId) {
|
|
324
|
+
const id = String(itemId || '').trim();
|
|
325
|
+
return Boolean(id && session.codexEmittedAgentMessageIds?.has(id));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function shouldAppendCompletedAgentMessage(session, params) {
|
|
329
|
+
const itemId = codexAgentMessageId(params);
|
|
330
|
+
if (itemId) return !hasCodexAgentMessageEmitted(session, itemId);
|
|
331
|
+
if (!session.sawPartialAssistantText) return true;
|
|
332
|
+
return params.item?.phase === 'final_answer';
|
|
333
|
+
}
|
|
334
|
+
|
|
301
335
|
function nextRpcId(session) {
|
|
302
336
|
session.codexRpcId = (session.codexRpcId || 0) + 1;
|
|
303
337
|
return session.codexRpcId;
|
|
@@ -357,16 +391,16 @@ function handleServerRequest(message, session) {
|
|
|
357
391
|
const cmd = params.command || params.tool || '';
|
|
358
392
|
session._log?.info('codex command execution approval requested', { method, command: cmd });
|
|
359
393
|
|
|
360
|
-
if (session
|
|
394
|
+
if (getPendingPermission(session, String(message.id), 'codex')) return;
|
|
361
395
|
|
|
362
|
-
session
|
|
396
|
+
setPendingPermission(session, {
|
|
363
397
|
provider: 'codex',
|
|
364
398
|
requestId: String(message.id),
|
|
365
399
|
toolName: 'exec',
|
|
366
400
|
input: cmd,
|
|
367
401
|
_codexMethod: method,
|
|
368
402
|
_rpcId: message.id,
|
|
369
|
-
};
|
|
403
|
+
});
|
|
370
404
|
|
|
371
405
|
if (ws) {
|
|
372
406
|
send(ws, 'permission_request', {
|
|
@@ -509,6 +543,7 @@ function handleAppServerMessage(message, session) {
|
|
|
509
543
|
const delta = params.delta || '';
|
|
510
544
|
if (delta) {
|
|
511
545
|
appendCodexAssistantText(delta, ws, session, () => send(ws, 'assistant_start', {}));
|
|
546
|
+
markCodexAgentMessageEmitted(session, codexAgentMessageId(params));
|
|
512
547
|
}
|
|
513
548
|
return;
|
|
514
549
|
}
|
|
@@ -533,10 +568,13 @@ function handleAppServerMessage(message, session) {
|
|
|
533
568
|
return;
|
|
534
569
|
}
|
|
535
570
|
|
|
536
|
-
// final content fallback if
|
|
571
|
+
// final content fallback if this item did not emit deltas
|
|
537
572
|
const text = extractFinalText(params);
|
|
538
|
-
|
|
573
|
+
const agentMessageId = codexAgentMessageId(params);
|
|
574
|
+
if (text && shouldAppendCompletedAgentMessage(session, params)) {
|
|
575
|
+
if (session.sawPartialAssistantText) appendCodexAssistantText('\n\n', ws, session, () => send(ws, 'assistant_start', {}));
|
|
539
576
|
appendCodexAssistantText(text, ws, session, () => send(ws, 'assistant_start', {}));
|
|
577
|
+
markCodexAgentMessageEmitted(session, agentMessageId);
|
|
540
578
|
}
|
|
541
579
|
// Safety fallback: if turn/completed never arrives, clear isTurnActive so the next message doesn't get stuck.
|
|
542
580
|
if (session.turnCompletedTimerId) clearTimeout(session.turnCompletedTimerId);
|
|
@@ -644,13 +682,17 @@ function updateCodexSessionStats(session, params = {}) {
|
|
|
644
682
|
updateAgentSessionHistory(session);
|
|
645
683
|
}
|
|
646
684
|
|
|
685
|
+
function isCodexAssistantMessageType(type) {
|
|
686
|
+
return ['agentMessage', 'agent_message', 'message'].includes(type);
|
|
687
|
+
}
|
|
688
|
+
|
|
647
689
|
function extractFinalText(params) {
|
|
648
690
|
if (typeof params.text === 'string') return params.text;
|
|
649
691
|
if (typeof params.delta === 'string') return params.delta;
|
|
650
692
|
if (Array.isArray(params.content)) {
|
|
651
693
|
return params.content.map(item => typeof item === 'string' ? item : item?.text || '').join('');
|
|
652
694
|
}
|
|
653
|
-
if (params.item?.type
|
|
695
|
+
if (isCodexAssistantMessageType(params.item?.type) && typeof params.item.text === 'string') {
|
|
654
696
|
return params.item.text;
|
|
655
697
|
}
|
|
656
698
|
return '';
|
|
@@ -865,7 +907,7 @@ function extractText(event) {
|
|
|
865
907
|
if (Array.isArray(event.content)) {
|
|
866
908
|
return event.content.map(item => typeof item === 'string' ? item : item?.text || '').join('');
|
|
867
909
|
}
|
|
868
|
-
if (typeof event.item?.text === 'string' &&
|
|
910
|
+
if (typeof event.item?.text === 'string' && isCodexAssistantMessageType(event.item.type)) {
|
|
869
911
|
return event.item.text;
|
|
870
912
|
}
|
|
871
913
|
if (event.item?.type === 'message' && Array.isArray(event.item.content)) {
|
|
@@ -886,13 +928,17 @@ function resolvePendingDanger(confirmed, ws, session, config) {
|
|
|
886
928
|
return true;
|
|
887
929
|
}
|
|
888
930
|
|
|
889
|
-
function resolvePendingPermission(approved, ws, session, config) {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
931
|
+
function resolvePendingPermission(approved, ws, session, config, requestId) {
|
|
932
|
+
const pending = getPendingPermission(session, requestId, 'codex');
|
|
933
|
+
if (!pending) {
|
|
934
|
+
session._log?.warn('codex permission response without pending request', {
|
|
935
|
+
requestId: requestId || '',
|
|
936
|
+
pendingRequestIds: pendingPermissionIds(session, 'codex'),
|
|
937
|
+
});
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
894
940
|
|
|
895
|
-
session.
|
|
941
|
+
removePendingPermission(session, pending.requestId);
|
|
896
942
|
session._log?.info('codex permission response', { approved, toolName: pending.toolName, rpcId: pending._rpcId });
|
|
897
943
|
|
|
898
944
|
// Respond to Codex RPC
|
|
@@ -928,6 +974,7 @@ function stop(session, options = {}) {
|
|
|
928
974
|
}
|
|
929
975
|
stopSessionProcess(session, options);
|
|
930
976
|
clearTurnState(session);
|
|
977
|
+
clearPendingPermissions(session, 'codex');
|
|
931
978
|
if (session.codexPendingRequests) {
|
|
932
979
|
for (const [, pending] of session.codexPendingRequests) {
|
|
933
980
|
pending.reject(new Error('用户已停止'));
|
package/src/agents/hermes.js
CHANGED
|
@@ -4,6 +4,13 @@ const { persistAgentSessionId, stopAgentProcess: stopSessionProcess, updateAgent
|
|
|
4
4
|
const { getOutboxDir } = require('../outgoingAttachmentHandler');
|
|
5
5
|
const { createTextStreamBuffer, appendTextStream, flushTextStream, resetTextStream } = require('../streamBuffer');
|
|
6
6
|
const { DEFAULT_GATEWAY_URL, ensureHermesGateway, normalizeGatewayUrl } = require('../hermesGateway');
|
|
7
|
+
const {
|
|
8
|
+
clearPendingPermissions,
|
|
9
|
+
getPendingPermission,
|
|
10
|
+
pendingPermissionIds,
|
|
11
|
+
removePendingPermission,
|
|
12
|
+
setPendingPermission,
|
|
13
|
+
} = require('../permissionState');
|
|
7
14
|
|
|
8
15
|
function extractText(input) {
|
|
9
16
|
if (!Array.isArray(input)) return String(input || '');
|
|
@@ -178,16 +185,26 @@ function handleHermesEvent(event, ws, session, config) {
|
|
|
178
185
|
}
|
|
179
186
|
|
|
180
187
|
function handleApprovalRequest(event, ws, session) {
|
|
181
|
-
const requestId =
|
|
188
|
+
const requestId = String(
|
|
189
|
+
event.requestId ||
|
|
190
|
+
event.request_id ||
|
|
191
|
+
event.approvalId ||
|
|
192
|
+
event.approval_id ||
|
|
193
|
+
event.id ||
|
|
194
|
+
`hermes-${event.run_id || session.hermesRunId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
195
|
+
);
|
|
182
196
|
const input = event.command || event.description || event.preview || '';
|
|
183
|
-
|
|
197
|
+
|
|
198
|
+
if (getPendingPermission(session, requestId, 'hermes')) return;
|
|
199
|
+
|
|
200
|
+
setPendingPermission(session, {
|
|
184
201
|
provider: 'hermes',
|
|
185
202
|
requestId,
|
|
186
203
|
runId: event.run_id || session.hermesRunId,
|
|
187
204
|
toolName: 'exec',
|
|
188
205
|
input,
|
|
189
206
|
choices: event.choices || [],
|
|
190
|
-
};
|
|
207
|
+
});
|
|
191
208
|
send(ws, 'permission_request', {
|
|
192
209
|
requestId,
|
|
193
210
|
toolName: 'exec',
|
|
@@ -222,7 +239,7 @@ function finishTurn(ws, session, config, options = {}) {
|
|
|
222
239
|
session.hermesGatewayUrl = null;
|
|
223
240
|
session.isTurnActive = false;
|
|
224
241
|
session.currentInputForNoOutput = null;
|
|
225
|
-
session
|
|
242
|
+
clearPendingPermissions(session, 'hermes');
|
|
226
243
|
flushHermesAssistantText(ws, session);
|
|
227
244
|
resetHermesAssistantText(session);
|
|
228
245
|
if (drain) drainQueue(ws, session, config);
|
|
@@ -358,14 +375,21 @@ async function resolvePendingDanger(confirmed, ws, session, config) {
|
|
|
358
375
|
return true;
|
|
359
376
|
}
|
|
360
377
|
|
|
361
|
-
async function resolvePendingPermission(approved, ws, session, config) {
|
|
362
|
-
const pending = session
|
|
363
|
-
if (!pending
|
|
378
|
+
async function resolvePendingPermission(approved, ws, session, config, requestId) {
|
|
379
|
+
const pending = getPendingPermission(session, requestId, 'hermes');
|
|
380
|
+
if (!pending) {
|
|
381
|
+
config.logger?.warn('hermes permission response without pending request', {
|
|
382
|
+
sessionId: session.id,
|
|
383
|
+
requestId: requestId || '',
|
|
384
|
+
pendingRequestIds: pendingPermissionIds(session, 'hermes'),
|
|
385
|
+
});
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
364
388
|
|
|
365
389
|
const agentConfig = config.agents?.hermes || {};
|
|
366
390
|
const gatewayUrl = session.hermesGatewayUrl || normalizeGatewayUrl(agentConfig.gatewayUrl || agentConfig.baseUrl || DEFAULT_GATEWAY_URL);
|
|
367
391
|
const runId = pending.runId || session.hermesRunId;
|
|
368
|
-
session.
|
|
392
|
+
removePendingPermission(session, pending.requestId);
|
|
369
393
|
|
|
370
394
|
try {
|
|
371
395
|
await fetchJson(`${gatewayUrl}/v1/runs/${encodeURIComponent(runId)}/approval`, agentConfig, {
|
package/src/claudeRunner.js
CHANGED
|
@@ -6,6 +6,13 @@ const { buildOutboxSystemPrompt, getOutboxDir } = require('./outgoingAttachmentH
|
|
|
6
6
|
const { send, sendError, sendSystem } = require('./protocol');
|
|
7
7
|
const { appendTextStream, flushTextStream, resetTextStream } = require('./streamBuffer');
|
|
8
8
|
const { persistClaudeSessionId, stopAgentProcess, updateAgentSessionHistory } = require('./session');
|
|
9
|
+
const {
|
|
10
|
+
getPendingPermission,
|
|
11
|
+
hasPendingPermissions,
|
|
12
|
+
pendingPermissionIds,
|
|
13
|
+
removePendingPermission,
|
|
14
|
+
setPendingPermission,
|
|
15
|
+
} = require('./permissionState');
|
|
9
16
|
|
|
10
17
|
function executeClaudeQuery(input, ws, session, config) {
|
|
11
18
|
const textForCheck = typeof input === 'string' ? input : extractText(input);
|
|
@@ -319,9 +326,7 @@ function handleClaudeMessage(parsed, ws, session, config) {
|
|
|
319
326
|
handleControlRequest(parsed, ws, session, config);
|
|
320
327
|
break;
|
|
321
328
|
case 'control_cancel_request':
|
|
322
|
-
|
|
323
|
-
session.pendingPermission = null;
|
|
324
|
-
}
|
|
329
|
+
removePendingPermission(session, parsed.request_id);
|
|
325
330
|
break;
|
|
326
331
|
}
|
|
327
332
|
}
|
|
@@ -423,11 +428,22 @@ function handleControlRequest(parsed, ws, session, config) {
|
|
|
423
428
|
const input = sanitizeToolInput(toolName, request.input || {});
|
|
424
429
|
const inputText = summarizeInput(input);
|
|
425
430
|
|
|
426
|
-
session
|
|
431
|
+
if (requestID && getPendingPermission(session, requestID, 'claude')) {
|
|
432
|
+
config.logger?.info('duplicate permission request ignored', {
|
|
433
|
+
sessionId: session.id,
|
|
434
|
+
requestId: requestID,
|
|
435
|
+
toolName,
|
|
436
|
+
});
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const pending = {
|
|
441
|
+
provider: 'claude',
|
|
427
442
|
requestId: requestID,
|
|
428
443
|
toolName,
|
|
429
444
|
input,
|
|
430
445
|
};
|
|
446
|
+
setPendingPermission(session, pending);
|
|
431
447
|
|
|
432
448
|
config.logger?.info('permission request received', {
|
|
433
449
|
sessionId: session.id,
|
|
@@ -442,8 +458,8 @@ function handleControlRequest(parsed, ws, session, config) {
|
|
|
442
458
|
});
|
|
443
459
|
}
|
|
444
460
|
|
|
445
|
-
function respondPermission(session, approved) {
|
|
446
|
-
const pending = session
|
|
461
|
+
function respondPermission(session, approved, requestId) {
|
|
462
|
+
const pending = getPendingPermission(session, requestId, 'claude');
|
|
447
463
|
if (!pending) return false;
|
|
448
464
|
|
|
449
465
|
const response = approved
|
|
@@ -458,16 +474,23 @@ function respondPermission(session, approved) {
|
|
|
458
474
|
response,
|
|
459
475
|
},
|
|
460
476
|
});
|
|
461
|
-
session.
|
|
477
|
+
removePendingPermission(session, pending.requestId);
|
|
462
478
|
return true;
|
|
463
479
|
}
|
|
464
480
|
|
|
465
|
-
function resolvePendingPermission(approved, ws, session, config) {
|
|
466
|
-
|
|
481
|
+
function resolvePendingPermission(approved, ws, session, config, requestId) {
|
|
482
|
+
const pending = getPendingPermission(session, requestId, 'claude');
|
|
483
|
+
if (!pending) {
|
|
484
|
+
config.logger?.warn('permission response without pending request', {
|
|
485
|
+
sessionId: session.id,
|
|
486
|
+
requestId: requestId || '',
|
|
487
|
+
pendingRequestIds: pendingPermissionIds(session, 'claude'),
|
|
488
|
+
});
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
467
491
|
|
|
468
492
|
try {
|
|
469
|
-
|
|
470
|
-
respondPermission(session, approved);
|
|
493
|
+
respondPermission(session, approved, pending.requestId);
|
|
471
494
|
config.logger?.info('permission response sent', {
|
|
472
495
|
sessionId: session.id,
|
|
473
496
|
requestId: pending?.requestId,
|
|
@@ -499,7 +522,7 @@ function resolvePendingDanger(approved, ws, session, config) {
|
|
|
499
522
|
}
|
|
500
523
|
|
|
501
524
|
function drainMessageQueue(ws, session, config) {
|
|
502
|
-
if (session.isTurnActive || session
|
|
525
|
+
if (session.isTurnActive || hasPendingPermissions(session, 'claude')) return;
|
|
503
526
|
const next = session.messageQueue.shift();
|
|
504
527
|
if (!next) return;
|
|
505
528
|
config.logger?.info('message dequeued', { sessionId: session.id, queueLength: session.messageQueue.length });
|
package/src/imConnector.js
CHANGED
|
@@ -222,7 +222,7 @@ class ImConnector {
|
|
|
222
222
|
return;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
if (!resolvePendingPermission(!!msg.approved, session.ws, session, this.config)) {
|
|
225
|
+
if (!resolvePendingPermission(!!msg.approved, session.ws, session, this.config, msg.requestId)) {
|
|
226
226
|
sendError(session.ws, '没有待确认的工具权限请求');
|
|
227
227
|
}
|
|
228
228
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
function ensurePendingPermissionMap(session) {
|
|
2
|
+
if (session.pendingPermissions instanceof Map) return session.pendingPermissions;
|
|
3
|
+
|
|
4
|
+
const map = new Map();
|
|
5
|
+
if (session.pendingPermission?.requestId) {
|
|
6
|
+
map.set(String(session.pendingPermission.requestId), session.pendingPermission);
|
|
7
|
+
}
|
|
8
|
+
session.pendingPermissions = map;
|
|
9
|
+
return map;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function setPendingPermission(session, pending) {
|
|
13
|
+
if (!pending?.requestId) return;
|
|
14
|
+
const map = ensurePendingPermissionMap(session);
|
|
15
|
+
map.set(String(pending.requestId), pending);
|
|
16
|
+
syncLegacyPendingPermission(session);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getPendingPermission(session, requestId, provider) {
|
|
20
|
+
const map = ensurePendingPermissionMap(session);
|
|
21
|
+
const normalized = String(requestId || '').trim();
|
|
22
|
+
|
|
23
|
+
if (normalized) {
|
|
24
|
+
const pending = map.get(normalized) || null;
|
|
25
|
+
return matchesProvider(pending, provider) ? pending : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const matching = Array.from(map.values()).filter(pending => matchesProvider(pending, provider));
|
|
29
|
+
return matching.length === 1 ? matching[0] : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function removePendingPermission(session, requestId) {
|
|
33
|
+
const map = ensurePendingPermissionMap(session);
|
|
34
|
+
const normalized = String(requestId || '').trim();
|
|
35
|
+
if (normalized) map.delete(normalized);
|
|
36
|
+
syncLegacyPendingPermission(session);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clearPendingPermissions(session, provider) {
|
|
40
|
+
const map = ensurePendingPermissionMap(session);
|
|
41
|
+
if (!provider) {
|
|
42
|
+
map.clear();
|
|
43
|
+
syncLegacyPendingPermission(session);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const [requestId, pending] of map) {
|
|
48
|
+
if (matchesProvider(pending, provider)) map.delete(requestId);
|
|
49
|
+
}
|
|
50
|
+
syncLegacyPendingPermission(session);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasPendingPermissions(session, provider) {
|
|
54
|
+
const map = ensurePendingPermissionMap(session);
|
|
55
|
+
if (!provider) return map.size > 0;
|
|
56
|
+
return Array.from(map.values()).some(pending => matchesProvider(pending, provider));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function pendingPermissionIds(session, provider) {
|
|
60
|
+
return Array.from(ensurePendingPermissionMap(session).values())
|
|
61
|
+
.filter(pending => matchesProvider(pending, provider))
|
|
62
|
+
.map(pending => String(pending.requestId));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function matchesProvider(pending, provider) {
|
|
66
|
+
if (!pending) return false;
|
|
67
|
+
return !provider || pending.provider === provider;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function syncLegacyPendingPermission(session) {
|
|
71
|
+
const remaining = Array.from(ensurePendingPermissionMap(session).values());
|
|
72
|
+
session.pendingPermission = remaining.length ? remaining[remaining.length - 1] : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
clearPendingPermissions,
|
|
77
|
+
ensurePendingPermissionMap,
|
|
78
|
+
getPendingPermission,
|
|
79
|
+
hasPendingPermissions,
|
|
80
|
+
pendingPermissionIds,
|
|
81
|
+
removePendingPermission,
|
|
82
|
+
setPendingPermission,
|
|
83
|
+
};
|
package/src/session.js
CHANGED
|
@@ -61,6 +61,7 @@ function createSession(config, { externalSessionId, agentType = 'claude' } = {})
|
|
|
61
61
|
messageQueue: [],
|
|
62
62
|
pendingDanger: null,
|
|
63
63
|
pendingPermission: null,
|
|
64
|
+
pendingPermissions: new Map(),
|
|
64
65
|
streamState: createStreamState(),
|
|
65
66
|
sawPartialAssistantText: false,
|
|
66
67
|
outgoingAttachments: new Map(),
|
|
@@ -294,6 +295,8 @@ function resetConversationState(session, { clearAgentSession = true, clearClaude
|
|
|
294
295
|
session.messageQueue = [];
|
|
295
296
|
session.pendingDanger = null;
|
|
296
297
|
session.pendingPermission = null;
|
|
298
|
+
if (session.pendingPermissions?.clear) session.pendingPermissions.clear();
|
|
299
|
+
else session.pendingPermissions = new Map();
|
|
297
300
|
session.sawPartialAssistantText = false;
|
|
298
301
|
clearStreamState(session);
|
|
299
302
|
}
|
package/src/slashCommands.js
CHANGED
|
@@ -4,6 +4,7 @@ const { resetOutgoingAttachments, startOutboxWatcher, stopOutboxWatcher } = requ
|
|
|
4
4
|
const { sendError, sendSystem } = require('./protocol');
|
|
5
5
|
const { removeAgentSessionFromHistory, saveSessionMetadata } = require('./session');
|
|
6
6
|
const { stopAgentProcess } = require('./agentRunner');
|
|
7
|
+
const { clearPendingPermissions } = require('./permissionState');
|
|
7
8
|
|
|
8
9
|
function localCommandsHelp() {
|
|
9
10
|
return `📋 Linco 本地命令:
|
|
@@ -276,7 +277,7 @@ function handleSwitch(indexOrId, ws, session, config) {
|
|
|
276
277
|
session.currentInputForNoOutput = null;
|
|
277
278
|
session.messageQueue = [];
|
|
278
279
|
session.pendingDanger = null;
|
|
279
|
-
session
|
|
280
|
+
clearPendingPermissions(session);
|
|
280
281
|
}
|
|
281
282
|
|
|
282
283
|
// Verify the change persisted correctly
|
package/src/wsServer.js
CHANGED
|
@@ -173,7 +173,7 @@ function handleMessage(data, ws, session, config) {
|
|
|
173
173
|
}
|
|
174
174
|
if (msg.type === 'permission_response') {
|
|
175
175
|
config.logger?.info('permission response received (linco)', { sessionKey: msg.sessionKey, approved: !!msg.approved });
|
|
176
|
-
if (!resolvePendingPermission(!!msg.approved, session.ws, session, config)) {
|
|
176
|
+
if (!resolvePendingPermission(!!msg.approved, session.ws, session, config, msg.requestId)) {
|
|
177
177
|
sendError(session.ws, '❌ 没有待确认的工具权限请求');
|
|
178
178
|
}
|
|
179
179
|
return;
|
|
@@ -192,7 +192,7 @@ function handleMessage(data, ws, session, config) {
|
|
|
192
192
|
|
|
193
193
|
if (msg.type === 'permission_response') {
|
|
194
194
|
config.logger?.info('permission response received', { sessionId: session.id, approved: !!msg.approved });
|
|
195
|
-
if (!resolvePendingPermission(!!msg.approved, ws, session, config)) {
|
|
195
|
+
if (!resolvePendingPermission(!!msg.approved, ws, session, config, msg.requestId)) {
|
|
196
196
|
sendError(ws, '❌ 没有待确认的工具权限请求');
|
|
197
197
|
}
|
|
198
198
|
return;
|