autosnippet 3.2.8 → 3.2.10

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.
Files changed (114) hide show
  1. package/bin/cli.js +6 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +23 -26
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/cli/deploy/FileManifest.js +1 -1
  8. package/lib/core/AstAnalyzer.js +1 -1
  9. package/lib/core/discovery/index.js +2 -2
  10. package/lib/external/ai/AiProvider.js +66 -172
  11. package/lib/external/ai/providers/GoogleGeminiProvider.js +29 -5
  12. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  13. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  14. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  18. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +291 -204
  19. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
  20. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  21. package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
  22. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  23. package/lib/http/HttpServer.js +1 -1
  24. package/lib/http/middleware/requestLogger.js +1 -0
  25. package/lib/http/routes/ai.js +240 -35
  26. package/lib/http/routes/candidates.js +2 -3
  27. package/lib/http/routes/extract.js +13 -11
  28. package/lib/http/routes/modules.js +2 -2
  29. package/lib/http/routes/recipes.js +5 -5
  30. package/lib/http/routes/remote.js +134 -255
  31. package/lib/http/routes/violations.js +0 -54
  32. package/lib/http/utils/sse-sessions.js +1 -1
  33. package/lib/infrastructure/logging/Logger.js +5 -4
  34. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  35. package/lib/injection/ServiceContainer.js +64 -17
  36. package/lib/platform/ScreenCaptureService.js +177 -0
  37. package/lib/platform/ios/routes/spm.js +2 -2
  38. package/lib/service/agent/AgentEventBus.js +207 -0
  39. package/lib/service/agent/AgentFactory.js +535 -0
  40. package/lib/service/agent/AgentMessage.js +240 -0
  41. package/lib/service/agent/AgentRouter.js +228 -0
  42. package/lib/service/agent/AgentRuntime.js +1056 -0
  43. package/lib/service/agent/AgentState.js +217 -0
  44. package/lib/service/agent/IntentClassifier.js +331 -0
  45. package/lib/service/agent/LarkTransport.js +389 -0
  46. package/lib/service/agent/capabilities.js +409 -0
  47. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  48. package/lib/service/{chat → agent/context}/ExplorationTracker.js +112 -33
  49. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +5 -3
  50. package/lib/service/agent/core/LoopContext.js +170 -0
  51. package/lib/service/agent/core/MessageAdapter.js +223 -0
  52. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  53. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +15 -98
  54. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  55. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  56. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  57. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
  58. package/lib/service/agent/domain/insight-producer.js +270 -0
  59. package/lib/service/agent/domain/scan-prompts.js +444 -0
  60. package/lib/service/agent/forced-summary.js +266 -0
  61. package/lib/service/agent/index.js +91 -0
  62. package/lib/service/{chat → agent}/memory/ActiveContext.js +29 -1
  63. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  64. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  65. package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
  66. package/lib/service/{chat → agent}/memory/index.js +1 -1
  67. package/lib/service/agent/policies.js +442 -0
  68. package/lib/service/agent/presets.js +305 -0
  69. package/lib/service/agent/strategies.js +756 -0
  70. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  71. package/lib/service/agent/tools/ai-analysis.js +75 -0
  72. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  73. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  74. package/lib/service/{chat → agent}/tools/index.js +27 -21
  75. package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
  76. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  77. package/lib/service/agent/tools/scan-recipe.js +189 -0
  78. package/lib/service/agent/tools/system-interaction.js +476 -0
  79. package/lib/service/automation/DirectiveDetector.js +0 -1
  80. package/lib/service/automation/FileWatcher.js +0 -8
  81. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  82. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  83. package/lib/service/module/ModuleService.js +40 -73
  84. package/lib/service/skills/SignalCollector.js +26 -19
  85. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  86. package/lib/shared/FieldSpec.js +1 -1
  87. package/lib/shared/StyleGuide.js +1 -1
  88. package/package.json +4 -1
  89. package/resources/native-ui/screenshot.swift +228 -0
  90. package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
  91. package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
  92. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  93. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
  94. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  95. package/lib/http/routes/spm.js +0 -5
  96. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  97. package/lib/service/chat/ChatAgent.js +0 -1602
  98. package/lib/service/chat/Memory.js +0 -161
  99. package/lib/service/chat/ProducerAgent.js +0 -431
  100. package/lib/service/chat/ReasoningTrace.js +0 -523
  101. package/lib/service/chat/TaskPipeline.js +0 -357
  102. package/lib/service/chat/WorkingMemory.js +0 -359
  103. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  104. package/lib/service/chat/tools/ai-analysis.js +0 -267
  105. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  106. package/lib/service/chat/tools.js +0 -18
  107. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  108. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  109. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  110. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  111. /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
  112. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  113. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  114. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -1,29 +1,28 @@
1
1
  /**
2
- * Remote Command Router — 飞书 Bot IDE 编程桥接
2
+ * Remote Command Router — 飞书 Bot IDE 编程桥接 + AI Agent 知识管理
3
3
  *
4
- * 架构(长连接模式):
5
- * Mac 本地启动 WSClient 长连接 飞书推送消息到本机
6
- * 解析系统命令 / 写入 remote_commands 队列
7
- * VSCode 扩展轮询 GET /pending注入 Copilot Chat
8
- * 扩展回写 POST /result 飞书 Bot 回复用户
4
+ * 架构(自然语言路由模式 v2):
5
+ * 飞书消息 LarkTransportIntentClassifier 意图分类
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 { execSync } from 'node:child_process';
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();
@@ -286,60 +285,60 @@ setInterval(() => {
286
285
  }, CLEANUP_INTERVAL_MS);
287
286
 
288
287
  // ═══════════════════════════════════════════════════════
289
- // 系统命令处理
288
+ // LarkTransport — 自然语言意图路由
290
289
  // ═══════════════════════════════════════════════════════
291
290
 
292
- const SYSTEM_COMMANDS = {
293
- '/help': handleHelp,
294
- '/status': handleStatus,
295
- '/check': handleStatus,
296
- '/queue': handleQueue,
297
- '/cancel': handleCancel,
298
- '/clear': handleClear,
299
- '/ping': handlePing,
300
- '/screen': handleScreen,
301
- };
302
-
303
- function isSystemCommand(text) {
304
- const cmd = text.split(/\s/)[0].toLowerCase();
305
- return SYSTEM_COMMANDS[cmd] || null;
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
+ }
310
+
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
+ });
307
321
 
308
- async function handleHelp(_args, messageId) {
309
- await replyLark(messageId, [
310
- '🤖 AutoSnippet 远程编程 — 命令帮助',
311
- '',
312
- '直接发送文字 → 注入 Copilot Agent Mode 执行编程',
313
- '',
314
- '系统命令:',
315
- ' /status — 连接诊断 + 队列状态',
316
- ' /queue — 查看待执行队列',
317
- ' /cancel — 取消所有 pending 指令',
318
- ' /clear — 清空历史记录',
319
- ' /ping — 测试连通性',
320
- ' /screen — 截取 IDE 画面发到飞书',
321
- ' /help — 显示此帮助',
322
- '',
323
- '💡 远程模式自动开启全局 Auto-Approve,',
324
- ' Copilot 将自动执行工具调用/编辑/终端操作。',
325
- ].join('\n'));
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
- async function handleStatus(_args, messageId) {
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
- await replyLark(messageId, lines.join('\n'));
383
+ return lines.join('\n');
388
384
  }
389
385
 
390
- async function handleQueue(_args, messageId) {
391
- try {
392
- const db = getDb();
393
- ensureTable(db);
394
- const rows = db.prepare(
395
- "SELECT id, command, status, created_at FROM remote_commands WHERE status IN ('pending', 'running') ORDER BY created_at ASC LIMIT 10"
396
- ).all();
397
-
398
- if (rows.length === 0) {
399
- await replyLark(messageId, '📋 队列为空,没有待执行的指令。');
400
- return;
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
- await replyLark(messageId, `📋 当前队列 (${rows.length} 条)\n\n${lines.join('\n')}`);
410
- } catch (err) {
411
- await replyLark(messageId, `❌ 查询失败: ${err.message}`);
399
+ if (meta.chatId) {
400
+ _activeChatId = meta.chatId;
401
+ _persistActiveChatId(meta.chatId);
412
402
  }
413
- }
414
403
 
415
- async function handleCancel(_args, messageId) {
416
- try {
417
- const db = getDb();
418
- ensureTable(db);
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
- async function handleClear(_args, messageId) {
430
- try {
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
- async function handlePing(_args, messageId) {
443
- await replyLark(messageId, `🏓 pong! (${new Date().toLocaleTimeString('zh-CN')})`);
412
+ return { id };
444
413
  }
445
414
 
446
- /**
447
- * /screen — 截取 IDE 窗口截图并发送到飞书
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
- db.prepare(`
524
- INSERT INTO remote_commands (id, source, chat_id, message_id, user_id, user_name, command, status, created_at)
525
- VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?)
526
- `).run(id, 'lark', chatId || '', messageId || '', userId, userName, textContent, now);
527
-
528
- logger.info(`[Remote/Lark] Command queued: ${id} — "${textContent.slice(0, 50)}"`);
529
-
530
- // 立即唤醒 long-poll 等待中的扩展端
531
- wakeWaiters();
532
-
533
- // 查当前队列深度
534
- const queueDepth = db.prepare("SELECT COUNT(*) as c FROM remote_commands WHERE status IN ('pending', 'running')").get()?.c || 1;
535
- const queueInfo = queueDepth > 1 ? `\n\n当前队列: ${queueDepth} 条指令` : '';
536
-
537
- await replyLark(messageId, `📝 收到,已加入执行队列。${queueInfo}`);
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
- * 获取 VSCode 主窗口 CGWindowID(macOS only)
841
- * 通过 swift 调用 CoreGraphics API 找到 owner 包含 "Code" 且面积最大的窗口
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 _getVSCodeWindowId() {
765
+ async function captureIDEScreenshot(opts = {}) {
844
766
  try {
845
- const script = `
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
- }
874
-
875
- /**
876
- * 截取 IDE 窗口截图
877
- * @returns {{ path: string|null, error: string|null }} 临时图片文件路径,或错误信息
878
- */
879
- function captureIDEScreenshot() {
880
- const tmpFile = join(tmpdir(), `asd-screenshot-${Date.now()}.jpg`);
767
+ const { screenshot } = await import('../../platform/ScreenCaptureService.js');
881
768
 
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
- try {
886
- const wid = _getVSCodeWindowId();
887
- if (wid > 0) {
888
- attempts.push({ label: 'window', cmd: `screencapture -x -t jpg -l${wid} "${tmpFile}"` });
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
- attempts.push({ label: 'fullscreen', cmd: `screencapture -x -t jpg "${tmpFile}"` });
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
- for (const { label, cmd } of attempts) {
896
- try {
897
- logger.info(`[Remote/Screenshot] Trying ${label}: ${cmd}`);
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
- return { path: null, error: '所有截图方案均失败。请在「系统设置 隐私与安全性 → 屏幕录制」中授权启动 asd ui 的终端应用(如 iTerm2、Terminal.app)。' };
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 + 后台执行 ChatAgent → 返回 { sessionId }
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
- // ChatAgent 相关标签 — 终端高亮显示
5
+ // Agent 系统相关标签 — 终端高亮显示
6
6
  const AGENT_TAGS = [
7
- 'ChatAgent',
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
- * - ChatAgent 相关日志: 高亮 cyan/magenta,显示完整信息
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
- // ChatAgent 日志 — 高亮显示
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
- if (duration > this.config.slowRequestThreshold) {
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,