autosnippet 3.2.7 → 3.2.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/bin/cli.js +13 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +26 -29
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +27 -5
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +149 -270
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +70 -28
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +33 -22
- package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +49 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
- package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -357
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /package/lib/service/{chat → agent}/tools/query.js +0 -0
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Remote Command Router — 飞书 Bot
|
|
2
|
+
* Remote Command Router — 飞书 Bot ↔ IDE 编程桥接 + AI Agent 知识管理
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* 架构(自然语言路由模式 v2):
|
|
5
|
+
* 飞书消息 → LarkTransport → IntentClassifier 意图分类
|
|
6
|
+
* → bot_agent: 知识管理任务 → AgentRuntime 直接处理 → 飞书回复
|
|
7
|
+
* → ide_agent: 编程任务 → remote_commands 队列 → VSCode 扩展 → Copilot Chat
|
|
8
|
+
* → system: 状态/截图 → 本地直接处理
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* 设计原则:
|
|
11
|
+
* ✓ 零命令交互 — 全部使用自然语言,AI 自动判断意图
|
|
12
|
+
* ✓ 双 Agent 分流 — 知识任务服务端处理,编程任务转发 IDE
|
|
11
13
|
* ✓ 飞书 WS 随路由加载自动启动
|
|
12
|
-
* ✓ 系统命令: /help /status /queue /cancel /clear /ping /screen
|
|
13
14
|
* ✓ 超时自动清理(pending 120s / running 600s)
|
|
14
15
|
* ✓ 消息去重 + 非文本提示
|
|
15
16
|
* ✓ SDK Client 回复 + REST 回退
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import crypto from 'node:crypto';
|
|
19
|
-
import {
|
|
20
|
-
import { readFileSync, unlinkSync, existsSync, statSync } from 'node:fs';
|
|
21
|
-
import { tmpdir } from 'node:os';
|
|
22
|
-
import { join } from 'node:path';
|
|
20
|
+
import { readFileSync, unlinkSync, existsSync } from 'node:fs';
|
|
23
21
|
import express from 'express';
|
|
24
22
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
25
23
|
import { getServiceContainer } from '../../injection/ServiceContainer.js';
|
|
26
24
|
import { asyncHandler } from '../middleware/errorHandler.js';
|
|
25
|
+
import { LarkTransport } from '../../service/agent/LarkTransport.js';
|
|
27
26
|
|
|
28
27
|
const router = express.Router();
|
|
29
28
|
const logger = Logger.getInstance();
|
|
@@ -120,7 +119,7 @@ let _larkClient = null;
|
|
|
120
119
|
let _wsConnected = false;
|
|
121
120
|
let _wsStarting = false;
|
|
122
121
|
|
|
123
|
-
async function startLarkWS() {
|
|
122
|
+
async function startLarkWS({ silent = false } = {}) {
|
|
124
123
|
// 如果已连接且对象存在 → 直接返回
|
|
125
124
|
if (_wsClient && _wsConnected) return { success: true, message: 'Already connected' };
|
|
126
125
|
if (_wsStarting) return { success: true, message: 'Connection in progress' };
|
|
@@ -173,16 +172,18 @@ async function startLarkWS() {
|
|
|
173
172
|
|
|
174
173
|
logger.info('[Remote/Lark] ✅ WebSocket long connection established');
|
|
175
174
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
175
|
+
// 向飞书发送上线通知(仅首次启动,重连时静默)
|
|
176
|
+
if (!silent) {
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
sendLarkNotification([
|
|
179
|
+
'🟢 IDE 桥接已上线',
|
|
180
|
+
`时间: ${new Date().toLocaleString('zh-CN')}`,
|
|
181
|
+
`平台: macOS | Node ${process.version}`,
|
|
182
|
+
'',
|
|
183
|
+
'发送任意文字即可远程编程,/help 查看命令。',
|
|
184
|
+
].join('\n')).catch(() => {});
|
|
185
|
+
}, 1000);
|
|
186
|
+
}
|
|
186
187
|
|
|
187
188
|
return { success: true, message: 'Connected via WebSocket' };
|
|
188
189
|
} catch (err) {
|
|
@@ -246,14 +247,12 @@ setInterval(async () => {
|
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
// WSClient 不存在或已标记断开 →
|
|
250
|
+
// WSClient 不存在或已标记断开 → 自动重连(静默,不打扰用户)
|
|
250
251
|
if (!_wsClient && !_wsStarting) {
|
|
251
252
|
logger.info('[Remote/Lark] Connection lost, auto-reconnecting...');
|
|
252
|
-
const result = await startLarkWS();
|
|
253
|
+
const result = await startLarkWS({ silent: true });
|
|
253
254
|
if (result.success) {
|
|
254
255
|
logger.info('[Remote/Lark] ✅ Auto-reconnected successfully');
|
|
255
|
-
// 重连成功通知
|
|
256
|
-
sendLarkNotification('🔄 IDE 桥接重连成功').catch(() => {});
|
|
257
256
|
} else {
|
|
258
257
|
logger.warn(`[Remote/Lark] Auto-reconnect failed: ${result.message}`);
|
|
259
258
|
}
|
|
@@ -286,60 +285,60 @@ setInterval(() => {
|
|
|
286
285
|
}, CLEANUP_INTERVAL_MS);
|
|
287
286
|
|
|
288
287
|
// ═══════════════════════════════════════════════════════
|
|
289
|
-
//
|
|
288
|
+
// LarkTransport — 自然语言意图路由
|
|
290
289
|
// ═══════════════════════════════════════════════════════
|
|
291
290
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
291
|
+
/** @type {LarkTransport|null} */
|
|
292
|
+
let _larkTransport = null;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 获取或创建 LarkTransport 实例
|
|
296
|
+
* 延迟初始化,等待 ServiceContainer 就绪
|
|
297
|
+
*/
|
|
298
|
+
function getLarkTransport() {
|
|
299
|
+
if (_larkTransport) return _larkTransport;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const container = getServiceContainer();
|
|
303
|
+
const agentFactory = container.get('agentFactory');
|
|
304
|
+
const aiProvider = container.get('aiProvider');
|
|
305
|
+
|
|
306
|
+
if (!agentFactory) {
|
|
307
|
+
logger.warn('[Remote/Lark] AgentFactory not available, transport not ready');
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
307
310
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
'
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
].join('\n'));
|
|
311
|
+
_larkTransport = new LarkTransport({
|
|
312
|
+
agentFactory,
|
|
313
|
+
aiProvider,
|
|
314
|
+
replyFn: replyLark,
|
|
315
|
+
sendFn: sendLarkNotification,
|
|
316
|
+
sendImageFn: sendLarkScreenshot,
|
|
317
|
+
getStatusFn: getStatusText,
|
|
318
|
+
enqueueIdeFn: enqueueIdeCommand,
|
|
319
|
+
isUserAllowed,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
logger.info('[Remote/Lark] LarkTransport initialized');
|
|
323
|
+
return _larkTransport;
|
|
324
|
+
} catch (err) {
|
|
325
|
+
logger.warn(`[Remote/Lark] LarkTransport init failed: ${err.message}`);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
|
|
330
|
+
/**
|
|
331
|
+
* 生成系统状态文本 (给 LarkTransport 系统操作使用)
|
|
332
|
+
*/
|
|
333
|
+
async function getStatusText() {
|
|
329
334
|
const lines = ['📊 状态面板', ''];
|
|
330
335
|
const now = Math.floor(Date.now() / 1000);
|
|
331
336
|
let ideOk = false;
|
|
332
337
|
|
|
333
|
-
// 1. 飞书 WebSocket
|
|
334
338
|
lines.push(`① 飞书 WebSocket: ${_wsConnected ? '✅ 已连接' : '❌ 断开'}`);
|
|
335
|
-
|
|
336
|
-
// 2. API 服务器
|
|
337
339
|
lines.push('② API 服务器: ✅ 运行中 (port ' + (process.env.PORT || 3000) + ')');
|
|
338
|
-
|
|
339
|
-
// 3. 活跃会话
|
|
340
340
|
lines.push(`③ 活跃会话: ${_activeChatId ? '✅ ' + _activeChatId.slice(0, 16) + '...' : '⚠️ 无活跃会话'}`);
|
|
341
341
|
|
|
342
|
-
// 4. IDE 扩展
|
|
343
342
|
try {
|
|
344
343
|
const db = getDb();
|
|
345
344
|
ensureTable(db);
|
|
@@ -365,7 +364,6 @@ async function handleStatus(_args, messageId) {
|
|
|
365
364
|
}
|
|
366
365
|
}
|
|
367
366
|
|
|
368
|
-
// 5. 队列
|
|
369
367
|
const counts = {};
|
|
370
368
|
for (const s of ['pending', 'running', 'completed', 'timeout']) {
|
|
371
369
|
counts[s] = db.prepare('SELECT COUNT(*) as c FROM remote_commands WHERE status = ?').get(s)?.c || 0;
|
|
@@ -376,165 +374,87 @@ async function handleStatus(_args, messageId) {
|
|
|
376
374
|
lines.push('⑤ 队列: ❓ 查询失败');
|
|
377
375
|
}
|
|
378
376
|
|
|
379
|
-
// 6. 通知通道
|
|
380
377
|
lines.push(`⑥ 通知通道: ${isLarkNotificationReady() ? '✅ 就绪' : '❌ 未就绪'}`);
|
|
381
378
|
|
|
382
|
-
// 总结
|
|
383
379
|
const allGood = _wsConnected && _activeChatId && ideOk && isLarkNotificationReady();
|
|
384
380
|
lines.push('');
|
|
385
381
|
lines.push(allGood ? '🟢 全链路正常,可以远程编程!' : '🟡 部分链路异常,请检查上方标记。');
|
|
386
382
|
|
|
387
|
-
|
|
383
|
+
return lines.join('\n');
|
|
388
384
|
}
|
|
389
385
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const lines = rows.map((r, i) => {
|
|
404
|
-
const icon = r.status === 'running' ? '🔄' : '⏳';
|
|
405
|
-
const cmd = r.command.length > 40 ? r.command.slice(0, 40) + '...' : r.command;
|
|
406
|
-
return `${i + 1}. ${icon} ${cmd} (${r.id.slice(-8)})`;
|
|
407
|
-
});
|
|
386
|
+
/**
|
|
387
|
+
* 写入 IDE 编程指令队列 (供 LarkTransport 的 ide_agent 路由使用)
|
|
388
|
+
*
|
|
389
|
+
* @param {string} command — 自然语言编程指令
|
|
390
|
+
* @param {Object} meta — { chatId, messageId, senderId, senderName }
|
|
391
|
+
* @returns {Promise<{id: string}>}
|
|
392
|
+
*/
|
|
393
|
+
async function enqueueIdeCommand(command, meta = {}) {
|
|
394
|
+
const db = getDb();
|
|
395
|
+
ensureTable(db);
|
|
396
|
+
const id = genId();
|
|
397
|
+
const now = Math.floor(Date.now() / 1000);
|
|
408
398
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
399
|
+
if (meta.chatId) {
|
|
400
|
+
_activeChatId = meta.chatId;
|
|
401
|
+
_persistActiveChatId(meta.chatId);
|
|
412
402
|
}
|
|
413
|
-
}
|
|
414
403
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const now = Math.floor(Date.now() / 1000);
|
|
420
|
-
const result = db.prepare(
|
|
421
|
-
'UPDATE remote_commands SET status = ?, completed_at = ? WHERE status = ?'
|
|
422
|
-
).run('cancelled', now, 'pending');
|
|
423
|
-
await replyLark(messageId, `🗑 已取消 ${result.changes} 条待执行指令。`);
|
|
424
|
-
} catch (err) {
|
|
425
|
-
await replyLark(messageId, `❌ 取消失败: ${err.message}`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
404
|
+
db.prepare(`
|
|
405
|
+
INSERT INTO remote_commands (id, source, chat_id, message_id, user_id, user_name, command, status, created_at)
|
|
406
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?)
|
|
407
|
+
`).run(id, 'lark', meta.chatId || '', meta.messageId || '', meta.senderId || '', meta.senderName || 'lark_user', command, now);
|
|
428
408
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const db = getDb();
|
|
432
|
-
ensureTable(db);
|
|
433
|
-
const result = db.prepare(
|
|
434
|
-
"DELETE FROM remote_commands WHERE status IN ('completed', 'timeout', 'cancelled')"
|
|
435
|
-
).run();
|
|
436
|
-
await replyLark(messageId, `🧹 已清理 ${result.changes} 条历史记录。`);
|
|
437
|
-
} catch (err) {
|
|
438
|
-
await replyLark(messageId, `❌ 清理失败: ${err.message}`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
409
|
+
logger.info(`[Remote/Lark] IDE command queued: ${id} — "${command.slice(0, 50)}"`);
|
|
410
|
+
wakeWaiters();
|
|
441
411
|
|
|
442
|
-
|
|
443
|
-
await replyLark(messageId, `🏓 pong! (${new Date().toLocaleTimeString('zh-CN')})`);
|
|
412
|
+
return { id };
|
|
444
413
|
}
|
|
445
414
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
async function handleScreen(_args, messageId) {
|
|
450
|
-
await replyLark(messageId, '📸 正在截取 IDE 画面...');
|
|
451
|
-
try {
|
|
452
|
-
const result = await sendLarkScreenshot('');
|
|
453
|
-
if (!result.success) {
|
|
454
|
-
await replyLark(messageId, `❌ 截图失败: ${result.message}`);
|
|
455
|
-
}
|
|
456
|
-
// 成功时 sendLarkScreenshot 已自动发送图片消息
|
|
457
|
-
} catch (err) {
|
|
458
|
-
await replyLark(messageId, `❌ 截图异常: ${err.message}`);
|
|
459
|
-
}
|
|
415
|
+
function getProjectRoot() {
|
|
416
|
+
const container = getServiceContainer();
|
|
417
|
+
return container.singletons?._projectRoot || process.env.ASD_PROJECT_DIR || process.cwd();
|
|
460
418
|
}
|
|
461
419
|
|
|
462
420
|
// ═══════════════════════════════════════════════════════
|
|
463
|
-
// 飞书消息处理
|
|
421
|
+
// 飞书消息处理 — 通过 LarkTransport 路由
|
|
464
422
|
// ═══════════════════════════════════════════════════════
|
|
465
423
|
|
|
466
424
|
async function handleLarkMessage(data) {
|
|
467
425
|
const message = data?.message || data?.event?.message || {};
|
|
468
|
-
const sender = data?.sender || data?.event?.sender || {};
|
|
469
426
|
const messageId = message.message_id;
|
|
470
427
|
const chatId = message.chat_id;
|
|
471
|
-
const msgType = message.message_type;
|
|
472
428
|
|
|
473
429
|
if (isDuplicate(messageId)) return;
|
|
474
430
|
|
|
475
|
-
//
|
|
476
|
-
const senderId = sender.sender_id?.user_id || sender.sender_id?.open_id || '';
|
|
477
|
-
if (!isUserAllowed(senderId)) {
|
|
478
|
-
logger.warn(`[Remote/Lark] Blocked unauthorized user: ${senderId}`);
|
|
479
|
-
await replyLark(messageId, '🔒 权限不足,你不在授权用户列表中。');
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (msgType !== 'text') {
|
|
484
|
-
await replyLark(messageId, '🤖 目前只支持文本指令。\n发 /help 查看帮助。');
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
let textContent = '';
|
|
489
|
-
try {
|
|
490
|
-
const content = JSON.parse(message.content || '{}');
|
|
491
|
-
textContent = (content.text || '').trim();
|
|
492
|
-
} catch {
|
|
493
|
-
textContent = '';
|
|
494
|
-
}
|
|
495
|
-
if (!textContent) return;
|
|
496
|
-
|
|
497
|
-
textContent = textContent.replace(/@_user_\d+/g, '').trim();
|
|
498
|
-
if (!textContent) return;
|
|
499
|
-
|
|
500
|
-
// ── 系统命令拦截 ──
|
|
501
|
-
const sysHandler = isSystemCommand(textContent);
|
|
502
|
-
if (sysHandler) {
|
|
503
|
-
const args = textContent.split(/\s+/).slice(1).join(' ');
|
|
504
|
-
await sysHandler(args, messageId);
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// ── 写入编程指令队列 ──
|
|
509
|
-
const db = getDb();
|
|
510
|
-
ensureTable(db);
|
|
511
|
-
const id = genId();
|
|
512
|
-
const now = Math.floor(Date.now() / 1000);
|
|
513
|
-
|
|
514
|
-
const userId = sender.sender_id?.user_id || sender.sender_id?.open_id || '';
|
|
515
|
-
const userName = sender.sender_id?.user_id || 'lark_user';
|
|
516
|
-
|
|
517
|
-
// 记录活跃会话(供主动通知使用)
|
|
431
|
+
// 更新活跃会话
|
|
518
432
|
if (chatId) {
|
|
519
433
|
_activeChatId = chatId;
|
|
520
434
|
_persistActiveChatId(chatId);
|
|
521
435
|
}
|
|
522
436
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
437
|
+
// 通过 LarkTransport 路由 (自然语言意图分类)
|
|
438
|
+
const transport = getLarkTransport();
|
|
439
|
+
if (transport) {
|
|
440
|
+
await transport.receive(data);
|
|
441
|
+
} else {
|
|
442
|
+
// Transport 未就绪 → 降级为旧队列模式
|
|
443
|
+
logger.warn('[Remote/Lark] Transport not ready, falling back to queue mode');
|
|
444
|
+
const sender = data?.sender || data?.event?.sender || {};
|
|
445
|
+
let text = '';
|
|
446
|
+
try {
|
|
447
|
+
const content = JSON.parse(message.content || '{}');
|
|
448
|
+
text = (content.text || '').trim().replace(/@_user_\d+/g, '').trim();
|
|
449
|
+
} catch { text = ''; }
|
|
450
|
+
|
|
451
|
+
if (text) {
|
|
452
|
+
const senderId = sender.sender_id?.user_id || sender.sender_id?.open_id || '';
|
|
453
|
+
const senderName = sender.sender_id?.user_id || 'lark_user';
|
|
454
|
+
await enqueueIdeCommand(text, { chatId, messageId, senderId, senderName });
|
|
455
|
+
await replyLark(messageId, '📝 收到,已加入执行队列。(Agent 模式未就绪)');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
538
458
|
}
|
|
539
459
|
|
|
540
460
|
// ═══════════════════════════════════════════════════════
|
|
@@ -833,90 +753,49 @@ async function replyLark(messageId, text) {
|
|
|
833
753
|
}
|
|
834
754
|
|
|
835
755
|
// ═══════════════════════════════════════════════════════
|
|
836
|
-
// IDE 窗口截图 +
|
|
756
|
+
// IDE 窗口截图 + 飞书发送(ScreenCaptureKit,息屏可用)
|
|
837
757
|
// ═══════════════════════════════════════════════════════
|
|
838
758
|
|
|
839
759
|
/**
|
|
840
|
-
*
|
|
841
|
-
*
|
|
760
|
+
* 截取 IDE 窗口截图(通过 ScreenCaptureKit 原生 API,息屏时可用)
|
|
761
|
+
* @param {object} [opts]
|
|
762
|
+
* @param {string} [opts.windowTitle] — 窗口标题关键词(默认 "Code")
|
|
763
|
+
* @returns {Promise<{path: string|null, error: string|null}>}
|
|
842
764
|
*/
|
|
843
|
-
function
|
|
765
|
+
async function captureIDEScreenshot(opts = {}) {
|
|
844
766
|
try {
|
|
845
|
-
const
|
|
846
|
-
import CoreGraphics
|
|
847
|
-
let list = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as! [[String: Any]]
|
|
848
|
-
var best = 0, bestArea = 0
|
|
849
|
-
for w in list {
|
|
850
|
-
guard let o = w["kCGWindowOwnerName"] as? String, o.contains("Code"),
|
|
851
|
-
let b = w["kCGWindowBounds"] as? [String: Double],
|
|
852
|
-
let width = b["Width"], let height = b["Height"],
|
|
853
|
-
let id = w["kCGWindowNumber"] as? Int,
|
|
854
|
-
width > 100, height > 100 else { continue }
|
|
855
|
-
let area = Int(width * height)
|
|
856
|
-
if area > bestArea { bestArea = area; best = id }
|
|
857
|
-
}
|
|
858
|
-
print(best)
|
|
859
|
-
`;
|
|
860
|
-
const out = execSync(`swift -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
861
|
-
timeout: 10000,
|
|
862
|
-
encoding: 'utf-8',
|
|
863
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
864
|
-
}).trim();
|
|
865
|
-
const wid = parseInt(out, 10);
|
|
866
|
-
logger.info(`[Remote/Screenshot] VSCode windowId: ${wid}`);
|
|
867
|
-
return wid > 0 ? wid : 0;
|
|
868
|
-
} catch (err) {
|
|
869
|
-
const stderr = err.stderr ? err.stderr.toString().slice(0, 200) : '';
|
|
870
|
-
logger.warn(`[Remote/Screenshot] Failed to get VSCode windowId: ${err.message}${stderr ? ' | stderr: ' + stderr : ''}`);
|
|
871
|
-
return 0;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
767
|
+
const { screenshot } = await import('../../platform/ScreenCaptureService.js');
|
|
874
768
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
function captureIDEScreenshot() {
|
|
880
|
-
const tmpFile = join(tmpdir(), `asd-screenshot-${Date.now()}.jpg`);
|
|
881
|
-
|
|
882
|
-
// ── 尝试方案列表(按优先级)──
|
|
883
|
-
const attempts = [];
|
|
769
|
+
// 统一使用窗口截取(desktopIndependentWindow,亮屏/息屏均可用)
|
|
770
|
+
// 优先截取 IDE 窗口
|
|
771
|
+
const windowTitle = opts.windowTitle || 'Code';
|
|
772
|
+
let result = await screenshot({ windowTitle, format: 'png' });
|
|
884
773
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
774
|
+
// IDE 窗口未找到 → 尝试常见 IDE 名称
|
|
775
|
+
if (!result.success) {
|
|
776
|
+
for (const alt of ['Visual Studio', 'Cursor', 'Xcode', 'IntelliJ', 'WebStorm']) {
|
|
777
|
+
if (alt.toLowerCase() === windowTitle.toLowerCase()) continue;
|
|
778
|
+
result = await screenshot({ windowTitle: alt, format: 'png' });
|
|
779
|
+
if (result.success) break;
|
|
780
|
+
}
|
|
889
781
|
}
|
|
890
|
-
} catch { /* swift failed, skip window capture */ }
|
|
891
782
|
|
|
892
|
-
|
|
893
|
-
|
|
783
|
+
// 仍未找到 → 不指定窗口名,Swift 工具会自动选最大窗口
|
|
784
|
+
if (!result.success) {
|
|
785
|
+
logger.info(`[Remote/Screenshot] IDE window not found, capturing largest available window`);
|
|
786
|
+
result = await screenshot({ format: 'png' });
|
|
787
|
+
}
|
|
894
788
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
execSync(cmd, { timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
899
|
-
if (existsSync(tmpFile)) {
|
|
900
|
-
const { size } = statSync(tmpFile);
|
|
901
|
-
if (size > 0) {
|
|
902
|
-
logger.info(`[Remote/Screenshot] Captured via ${label}: ${tmpFile} (${size} bytes)`);
|
|
903
|
-
return { path: tmpFile, error: null };
|
|
904
|
-
}
|
|
905
|
-
// 文件存在但为空
|
|
906
|
-
logger.warn(`[Remote/Screenshot] ${label}: file created but empty`);
|
|
907
|
-
try { unlinkSync(tmpFile); } catch { /* ignore */ }
|
|
908
|
-
} else {
|
|
909
|
-
logger.warn(`[Remote/Screenshot] ${label}: file not created`);
|
|
910
|
-
}
|
|
911
|
-
} catch (err) {
|
|
912
|
-
const stderr = err.stderr ? err.stderr.toString().slice(0, 300) : '';
|
|
913
|
-
const detail = `${err.message}${stderr ? ' | stderr: ' + stderr : ''}`;
|
|
914
|
-
logger.warn(`[Remote/Screenshot] ${label} failed: ${detail}`);
|
|
915
|
-
// 继续尝试下一种方案
|
|
789
|
+
if (result.success) {
|
|
790
|
+
logger.info(`[Remote/Screenshot] Captured: ${result.path} (${result.width}x${result.height})`);
|
|
791
|
+
return { path: result.path, error: null };
|
|
916
792
|
}
|
|
917
|
-
}
|
|
918
793
|
|
|
919
|
-
|
|
794
|
+
return { path: null, error: result.error || 'Screenshot failed' };
|
|
795
|
+
} catch (err) {
|
|
796
|
+
logger.warn(`[Remote/Screenshot] ScreenCaptureKit error: ${err.message}`);
|
|
797
|
+
return { path: null, error: err.message };
|
|
798
|
+
}
|
|
920
799
|
}
|
|
921
800
|
|
|
922
801
|
/**
|
|
@@ -1007,8 +886,8 @@ export async function sendLarkScreenshot(caption = '') {
|
|
|
1007
886
|
return { success: false, message: 'Lark not connected or no active chat' };
|
|
1008
887
|
}
|
|
1009
888
|
|
|
1010
|
-
// 1.
|
|
1011
|
-
const capture = captureIDEScreenshot();
|
|
889
|
+
// 1. 截图(ScreenCaptureKit,息屏可用)
|
|
890
|
+
const capture = await captureIDEScreenshot();
|
|
1012
891
|
if (!capture.path) {
|
|
1013
892
|
return { success: false, message: capture.error || 'Screenshot capture failed' };
|
|
1014
893
|
}
|
|
@@ -91,58 +91,4 @@ router.post(
|
|
|
91
91
|
})
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
-
/**
|
|
95
|
-
* POST /api/v1/violations/rules/generate
|
|
96
|
-
* AI 根据语义描述生成 Guard 规则
|
|
97
|
-
*/
|
|
98
|
-
router.post(
|
|
99
|
-
'/rules/generate',
|
|
100
|
-
asyncHandler(async (req, res) => {
|
|
101
|
-
const { description } = req.body;
|
|
102
|
-
|
|
103
|
-
if (!description || typeof description !== 'string' || !description.trim()) {
|
|
104
|
-
throw new ValidationError('description is required');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const container = getServiceContainer();
|
|
108
|
-
const chatAgent = container.get('chatAgent');
|
|
109
|
-
const result = await chatAgent.executeTool('generate_guard_rule', {
|
|
110
|
-
description: description.trim(),
|
|
111
|
-
language: 'objc',
|
|
112
|
-
severity: 'warning',
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (result?.error) {
|
|
116
|
-
throw new ValidationError(result.error);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 从 generate_guard_rule 工具返回的 rule 中提取并规范化
|
|
120
|
-
const rule = result.rule || result;
|
|
121
|
-
|
|
122
|
-
const normalized = {
|
|
123
|
-
ruleId: String(rule.name || rule.ruleId || '')
|
|
124
|
-
.trim()
|
|
125
|
-
.replace(/\s+/g, '-'),
|
|
126
|
-
message: String(rule.description || rule.message || '').trim(),
|
|
127
|
-
severity: rule.severity === 'error' ? 'error' : 'warning',
|
|
128
|
-
pattern: String(rule.pattern || '').trim(),
|
|
129
|
-
languages:
|
|
130
|
-
Array.isArray(rule.languages) && rule.languages.length > 0
|
|
131
|
-
? rule.languages
|
|
132
|
-
: ['objc', 'swift'],
|
|
133
|
-
note: rule.note != null ? String(rule.note).trim() : rule.description_cn || '',
|
|
134
|
-
dimension: ['file', 'target', 'project'].includes(rule.dimension) ? rule.dimension : '',
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
if (!normalized.ruleId || !normalized.message || !normalized.pattern) {
|
|
138
|
-
throw new ValidationError('AI 返回的规则缺少 ruleId、message 或 pattern');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
res.json({
|
|
142
|
-
success: true,
|
|
143
|
-
data: normalized,
|
|
144
|
-
});
|
|
145
|
-
})
|
|
146
|
-
);
|
|
147
|
-
|
|
148
94
|
export default router;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SSE Session Manager — 基于 EventSource 的流式会话管理
|
|
3
3
|
*
|
|
4
4
|
* 架构:
|
|
5
|
-
* POST /chat/stream → 创建 session + 后台执行
|
|
5
|
+
* POST /chat/stream → 创建 session + 后台执行 AgentRuntime → 返回 { sessionId }
|
|
6
6
|
* GET /chat/events/:sessionId → EventSource 端点, 回放缓冲事件 + 实时推送
|
|
7
7
|
*
|
|
8
8
|
* 为什么不用 fetch + ReadableStream:
|
|
@@ -2,9 +2,10 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import winston from 'winston';
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Agent 系统相关标签 — 终端高亮显示
|
|
6
6
|
const AGENT_TAGS = [
|
|
7
|
-
'
|
|
7
|
+
'AgentRuntime',
|
|
8
|
+
'AgentFactory',
|
|
8
9
|
'ToolRegistry',
|
|
9
10
|
'SignalCollector',
|
|
10
11
|
'SkillAdvisor',
|
|
@@ -38,7 +39,7 @@ const LEVEL_COLORS = {
|
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* 精简 Console 格式
|
|
41
|
-
* -
|
|
42
|
+
* - Agent 相关日志: 高亮 cyan/magenta,显示完整信息
|
|
42
43
|
* - warn/error: 醒目颜色完整显示
|
|
43
44
|
* - HTTP 日志: 精简并降低视觉权重
|
|
44
45
|
* - 其他 info/debug: 一行精简格式
|
|
@@ -65,7 +66,7 @@ const compactConsoleFormat = winston.format.printf(({ level, message, timestamp,
|
|
|
65
66
|
);
|
|
66
67
|
|
|
67
68
|
if (isAgentLog) {
|
|
68
|
-
//
|
|
69
|
+
// Agent 日志 — 高亮显示
|
|
69
70
|
const metaStr =
|
|
70
71
|
Object.keys(meta).length > 0
|
|
71
72
|
? ` ${JSON.stringify(meta, null, 0).replace(/"/g, '').replace(/,/g, ', ')}`
|
|
@@ -120,8 +120,9 @@ export class PerformanceMonitor {
|
|
|
120
120
|
this.metrics.responseTimes.shift();
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
//
|
|
124
|
-
|
|
123
|
+
// 慢请求记录(排除设计上就是长耗时的 long-poll 端点)
|
|
124
|
+
const isLongPoll = route.includes('/remote/wait');
|
|
125
|
+
if (duration > this.config.slowRequestThreshold && !isLongPoll) {
|
|
125
126
|
this.metrics.slowRequests.push({
|
|
126
127
|
...requestData,
|
|
127
128
|
duration,
|