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
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* system-interaction.js — 系统交互工具 (3)
|
|
3
|
+
*
|
|
4
|
+
* 为 Agent 提供与本地操作系统交互的能力:
|
|
5
|
+
*
|
|
6
|
+
* 1. run_safe_command 安全执行终端命令 (受 SafetyPolicy 约束)
|
|
7
|
+
* 2. write_project_file 写入/创建项目文件 (受文件范围约束)
|
|
8
|
+
* 3. get_environment_info 获取运行环境信息
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ 安全设计:
|
|
11
|
+
* - run_safe_command 在工具层即执行命令黑名单/白名单检查
|
|
12
|
+
* - write_project_file 在工具层即执行文件路径范围检查
|
|
13
|
+
* - 两者均依赖 AgentRuntime 注入的 safetyPolicy 上下文
|
|
14
|
+
* - 即使 safetyPolicy 未注入,工具自身也有基础安全兜底
|
|
15
|
+
*
|
|
16
|
+
* @module system-interaction
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { execFile } from 'node:child_process';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { promisify } from 'node:util';
|
|
24
|
+
|
|
25
|
+
const execFileAsync = promisify(execFile);
|
|
26
|
+
|
|
27
|
+
// ─── 常量 ────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/** 工具层兜底: 始终拒绝的危险命令模式 (无论 SafetyPolicy 是否注入) */
|
|
30
|
+
const HARDCODED_BLACKLIST = [
|
|
31
|
+
/\brm\s+-rf\s+[\/~]/,
|
|
32
|
+
/\bsudo\b/,
|
|
33
|
+
/\bmkfs\b/,
|
|
34
|
+
/\bdd\s+if=/,
|
|
35
|
+
/\b(shutdown|reboot|halt)\b/,
|
|
36
|
+
/>\s*\/dev\//,
|
|
37
|
+
/\bcurl\b.*\|\s*(bash|sh)/,
|
|
38
|
+
/\bchmod\s+777/,
|
|
39
|
+
/\bpasswd\b/,
|
|
40
|
+
/\bkillall\b/,
|
|
41
|
+
/\bfork\s*bomb/i,
|
|
42
|
+
/:\(\)\s*\{\s*:\|:\s*&\s*\}\s*;/, // fork bomb pattern
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** 工具层兜底: 无 SafetyPolicy 时仅允许的安全命令前缀 */
|
|
46
|
+
const FALLBACK_SAFE_PREFIXES = [
|
|
47
|
+
'ls', 'cat', 'head', 'tail', 'grep', 'find', 'wc',
|
|
48
|
+
'echo', 'pwd', 'date', 'which', 'file', 'stat',
|
|
49
|
+
'git log', 'git status', 'git diff', 'git branch', 'git show',
|
|
50
|
+
'npm list', 'npm outdated', 'node -v', 'npm -v',
|
|
51
|
+
'python --version', 'python3 --version',
|
|
52
|
+
'env', 'printenv',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** 命令执行超时 (ms) */
|
|
56
|
+
const COMMAND_TIMEOUT = 30_000;
|
|
57
|
+
|
|
58
|
+
/** 输出截断长度 (bytes) */
|
|
59
|
+
const MAX_OUTPUT_LENGTH = 16_000;
|
|
60
|
+
|
|
61
|
+
/** 文件写入最大尺寸 (bytes) */
|
|
62
|
+
const MAX_WRITE_SIZE = 512 * 1024;
|
|
63
|
+
|
|
64
|
+
// ─── 内部工具函数 ────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 硬编码黑名单检查 — 工具层兜底, 无论是否有 SafetyPolicy 都生效
|
|
68
|
+
*/
|
|
69
|
+
function _isHardBlacklisted(command) {
|
|
70
|
+
for (const pattern of HARDCODED_BLACKLIST) {
|
|
71
|
+
if (pattern.test(command)) return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 无 SafetyPolicy 时的白名单兜底
|
|
78
|
+
*/
|
|
79
|
+
function _isFallbackSafe(command) {
|
|
80
|
+
const trimmed = command.trim();
|
|
81
|
+
return FALLBACK_SAFE_PREFIXES.some(prefix => trimmed.startsWith(prefix));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 截断过长输出
|
|
86
|
+
*/
|
|
87
|
+
function _truncate(text, max = MAX_OUTPUT_LENGTH) {
|
|
88
|
+
if (!text || text.length <= max) return text;
|
|
89
|
+
return `${text.slice(0, max)}\n\n... [输出已截断, 共 ${text.length} 字符]`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 获取 projectRoot — 优先从 context 获取, 兜底用 cwd
|
|
94
|
+
*/
|
|
95
|
+
function _getProjectRoot(ctx) {
|
|
96
|
+
return ctx.projectRoot || ctx.container?.get?.('projectRoot') || process.cwd();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ═══════════════════════════════════════════════════════
|
|
100
|
+
// 1. run_safe_command — 安全执行终端命令
|
|
101
|
+
// ═══════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
export const runSafeCommand = {
|
|
104
|
+
name: 'run_safe_command',
|
|
105
|
+
description:
|
|
106
|
+
'在项目目录下安全执行终端命令。' +
|
|
107
|
+
'命令受安全策略约束: 危险命令(sudo/rm -rf/shutdown 等)被自动拦截。' +
|
|
108
|
+
'适用于: 查看 git 状态、运行测试、检查依赖版本、执行构建等。' +
|
|
109
|
+
'超时 30 秒, 输出超过 16KB 会被截断。' +
|
|
110
|
+
'如果需要管道或重定向, 请用 sh -c "..." 包装。',
|
|
111
|
+
parameters: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
command: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: '要执行的终端命令, 如 "git status" 或 "npm test"',
|
|
117
|
+
},
|
|
118
|
+
cwd: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: '工作目录 (相对于项目根目录), 缺省为项目根目录',
|
|
121
|
+
},
|
|
122
|
+
timeout: {
|
|
123
|
+
type: 'number',
|
|
124
|
+
description: '超时时间(毫秒), 默认 30000',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
required: ['command'],
|
|
128
|
+
},
|
|
129
|
+
handler: async (params, ctx) => {
|
|
130
|
+
const { command, cwd, timeout } = params;
|
|
131
|
+
const projectRoot = _getProjectRoot(ctx);
|
|
132
|
+
|
|
133
|
+
if (!command || typeof command !== 'string' || command.trim().length === 0) {
|
|
134
|
+
return { error: '命令不能为空' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── 安全检查 Layer 1: 硬编码黑名单 (无条件拦截) ──
|
|
138
|
+
if (_isHardBlacklisted(command)) {
|
|
139
|
+
return { error: `安全拦截: 命令 "${command}" 匹配危险模式, 已被阻止执行` };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── 安全检查 Layer 2: SafetyPolicy (如果注入) ──
|
|
143
|
+
const safetyPolicy = ctx.safetyPolicy || null;
|
|
144
|
+
if (safetyPolicy) {
|
|
145
|
+
const check = safetyPolicy.checkCommand(command);
|
|
146
|
+
if (!check.safe) {
|
|
147
|
+
return { error: `SafetyPolicy 拦截: ${check.reason}` };
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// 无 SafetyPolicy 时使用白名单兜底
|
|
151
|
+
if (!_isFallbackSafe(command)) {
|
|
152
|
+
return {
|
|
153
|
+
error: `无安全策略: 命令 "${command}" 不在安全白名单中。` +
|
|
154
|
+
`允许的命令前缀: ${FALLBACK_SAFE_PREFIXES.join(', ')}`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── 解析工作目录 ──
|
|
160
|
+
let workDir = projectRoot;
|
|
161
|
+
if (cwd) {
|
|
162
|
+
workDir = path.isAbsolute(cwd) ? cwd : path.resolve(projectRoot, cwd);
|
|
163
|
+
// 范围检查
|
|
164
|
+
if (!workDir.startsWith(path.resolve(projectRoot))) {
|
|
165
|
+
return { error: `工作目录 "${cwd}" 超出项目范围 "${projectRoot}"` };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!fs.existsSync(workDir)) {
|
|
170
|
+
return { error: `工作目录 "${workDir}" 不存在` };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── 执行命令 ──
|
|
174
|
+
const effectiveTimeout = timeout || COMMAND_TIMEOUT;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const { stdout, stderr } = await execFileAsync('sh', ['-c', command], {
|
|
178
|
+
cwd: workDir,
|
|
179
|
+
timeout: effectiveTimeout,
|
|
180
|
+
maxBuffer: 1024 * 1024, // 1MB 缓冲
|
|
181
|
+
env: {
|
|
182
|
+
...process.env,
|
|
183
|
+
// 禁用交互式 pager
|
|
184
|
+
GIT_PAGER: 'cat',
|
|
185
|
+
PAGER: 'cat',
|
|
186
|
+
LESS: '-FRX',
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
exitCode: 0,
|
|
192
|
+
stdout: _truncate(stdout),
|
|
193
|
+
stderr: _truncate(stderr),
|
|
194
|
+
command,
|
|
195
|
+
cwd: workDir,
|
|
196
|
+
};
|
|
197
|
+
} catch (err) {
|
|
198
|
+
// 超时
|
|
199
|
+
if (err.killed) {
|
|
200
|
+
return {
|
|
201
|
+
error: `命令执行超时 (${effectiveTimeout}ms)`,
|
|
202
|
+
command,
|
|
203
|
+
stdout: _truncate(err.stdout || ''),
|
|
204
|
+
stderr: _truncate(err.stderr || ''),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 非零退出
|
|
209
|
+
return {
|
|
210
|
+
exitCode: err.code ?? 1,
|
|
211
|
+
stdout: _truncate(err.stdout || ''),
|
|
212
|
+
stderr: _truncate(err.stderr || err.message || ''),
|
|
213
|
+
command,
|
|
214
|
+
cwd: workDir,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// ═══════════════════════════════════════════════════════
|
|
221
|
+
// 2. write_project_file — 写入项目文件
|
|
222
|
+
// ═══════════════════════════════════════════════════════
|
|
223
|
+
|
|
224
|
+
export const writeProjectFile = {
|
|
225
|
+
name: 'write_project_file',
|
|
226
|
+
description:
|
|
227
|
+
'在项目目录内创建或覆盖写入文件。' +
|
|
228
|
+
'自动创建不存在的中间目录。文件路径必须在项目范围内。' +
|
|
229
|
+
'适用于: 生成配置文件、创建代码文件、写入分析报告等。' +
|
|
230
|
+
'最大写入 512KB。',
|
|
231
|
+
parameters: {
|
|
232
|
+
type: 'object',
|
|
233
|
+
properties: {
|
|
234
|
+
filePath: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: '目标文件路径 (相对于项目根目录或绝对路径)',
|
|
237
|
+
},
|
|
238
|
+
content: {
|
|
239
|
+
type: 'string',
|
|
240
|
+
description: '要写入的文件内容',
|
|
241
|
+
},
|
|
242
|
+
append: {
|
|
243
|
+
type: 'boolean',
|
|
244
|
+
description: '是否追加模式 (默认 false = 覆盖写入)',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
required: ['filePath', 'content'],
|
|
248
|
+
},
|
|
249
|
+
handler: async (params, ctx) => {
|
|
250
|
+
const { filePath, content, append } = params;
|
|
251
|
+
const projectRoot = _getProjectRoot(ctx);
|
|
252
|
+
|
|
253
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
254
|
+
return { error: '文件路径不能为空' };
|
|
255
|
+
}
|
|
256
|
+
if (typeof content !== 'string') {
|
|
257
|
+
return { error: '文件内容必须为字符串' };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── 大小限制 ──
|
|
261
|
+
if (Buffer.byteLength(content, 'utf-8') > MAX_WRITE_SIZE) {
|
|
262
|
+
return { error: `文件内容超过大小限制 (${MAX_WRITE_SIZE / 1024}KB)` };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── 路径解析与安全检查 ──
|
|
266
|
+
const resolved = path.isAbsolute(filePath)
|
|
267
|
+
? path.resolve(filePath)
|
|
268
|
+
: path.resolve(projectRoot, filePath);
|
|
269
|
+
|
|
270
|
+
const scopeRoot = path.resolve(projectRoot);
|
|
271
|
+
if (!resolved.startsWith(scopeRoot + path.sep) && resolved !== scopeRoot) {
|
|
272
|
+
return { error: `文件路径 "${filePath}" 超出项目范围 "${projectRoot}"` };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// SafetyPolicy 路径检查
|
|
276
|
+
const safetyPolicy = ctx.safetyPolicy || null;
|
|
277
|
+
if (safetyPolicy) {
|
|
278
|
+
const check = safetyPolicy.checkFilePath(resolved);
|
|
279
|
+
if (!check.safe) {
|
|
280
|
+
return { error: `SafetyPolicy 拦截: ${check.reason}` };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── 危险路径兜底 ──
|
|
285
|
+
const dangerousPatterns = [
|
|
286
|
+
/node_modules\//,
|
|
287
|
+
/\.git\//,
|
|
288
|
+
/\.env$/,
|
|
289
|
+
/\.env\.local$/,
|
|
290
|
+
/package-lock\.json$/,
|
|
291
|
+
/yarn\.lock$/,
|
|
292
|
+
/pnpm-lock\.yaml$/,
|
|
293
|
+
];
|
|
294
|
+
const relPath = path.relative(scopeRoot, resolved);
|
|
295
|
+
for (const p of dangerousPatterns) {
|
|
296
|
+
if (p.test(relPath)) {
|
|
297
|
+
return { error: `安全拦截: 不允许写入 "${relPath}" (匹配受保护路径模式)` };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── 写入文件 ──
|
|
302
|
+
try {
|
|
303
|
+
// 确保目录存在
|
|
304
|
+
const dir = path.dirname(resolved);
|
|
305
|
+
if (!fs.existsSync(dir)) {
|
|
306
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (append) {
|
|
310
|
+
fs.appendFileSync(resolved, content, 'utf-8');
|
|
311
|
+
} else {
|
|
312
|
+
fs.writeFileSync(resolved, content, 'utf-8');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const stat = fs.statSync(resolved);
|
|
316
|
+
return {
|
|
317
|
+
success: true,
|
|
318
|
+
filePath: relPath,
|
|
319
|
+
absolutePath: resolved,
|
|
320
|
+
size: stat.size,
|
|
321
|
+
mode: append ? 'append' : 'overwrite',
|
|
322
|
+
};
|
|
323
|
+
} catch (err) {
|
|
324
|
+
return { error: `写入文件失败: ${err.message}` };
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// ═══════════════════════════════════════════════════════
|
|
330
|
+
// 3. get_environment_info — 获取运行环境信息
|
|
331
|
+
// ═══════════════════════════════════════════════════════
|
|
332
|
+
|
|
333
|
+
export const getEnvironmentInfo = {
|
|
334
|
+
name: 'get_environment_info',
|
|
335
|
+
description:
|
|
336
|
+
'获取当前运行环境的系统信息。' +
|
|
337
|
+
'包括: 操作系统、Node.js 版本、项目路径、Git 分支、依赖管理器等。' +
|
|
338
|
+
'适用于: 环境诊断、构建问题排查、项目状态检查。',
|
|
339
|
+
parameters: {
|
|
340
|
+
type: 'object',
|
|
341
|
+
properties: {
|
|
342
|
+
sections: {
|
|
343
|
+
type: 'array',
|
|
344
|
+
items: {
|
|
345
|
+
type: 'string',
|
|
346
|
+
enum: ['os', 'node', 'git', 'project', 'all'],
|
|
347
|
+
},
|
|
348
|
+
description: '要获取的信息部分, 默认 ["all"]',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
required: [],
|
|
352
|
+
},
|
|
353
|
+
handler: async (params, ctx) => {
|
|
354
|
+
const sections = params.sections || ['all'];
|
|
355
|
+
const all = sections.includes('all');
|
|
356
|
+
const projectRoot = _getProjectRoot(ctx);
|
|
357
|
+
const info = {};
|
|
358
|
+
|
|
359
|
+
// ── OS 信息 ──
|
|
360
|
+
if (all || sections.includes('os')) {
|
|
361
|
+
info.os = {
|
|
362
|
+
platform: os.platform(),
|
|
363
|
+
arch: os.arch(),
|
|
364
|
+
release: os.release(),
|
|
365
|
+
hostname: os.hostname(),
|
|
366
|
+
uptime: `${Math.floor(os.uptime() / 3600)}h ${Math.floor((os.uptime() % 3600) / 60)}m`,
|
|
367
|
+
memory: {
|
|
368
|
+
total: `${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`,
|
|
369
|
+
free: `${Math.round(os.freemem() / (1024 * 1024 * 1024))}GB`,
|
|
370
|
+
},
|
|
371
|
+
cpus: os.cpus().length,
|
|
372
|
+
shell: process.env.SHELL || process.env.COMSPEC || 'unknown',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── Node 信息 ──
|
|
377
|
+
if (all || sections.includes('node')) {
|
|
378
|
+
info.node = {
|
|
379
|
+
version: process.version,
|
|
380
|
+
execPath: process.execPath,
|
|
381
|
+
pid: process.pid,
|
|
382
|
+
env: {
|
|
383
|
+
NODE_ENV: process.env.NODE_ENV || 'unset',
|
|
384
|
+
npm_package_version: process.env.npm_package_version || 'N/A',
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// npm/pnpm/yarn 版本
|
|
389
|
+
for (const pm of ['npm', 'pnpm', 'yarn']) {
|
|
390
|
+
try {
|
|
391
|
+
const { stdout } = await execFileAsync(pm, ['--version'], {
|
|
392
|
+
timeout: 5000,
|
|
393
|
+
});
|
|
394
|
+
info.node[`${pm}_version`] = stdout.trim();
|
|
395
|
+
} catch {
|
|
396
|
+
// 未安装, 跳过
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Git 信息 ──
|
|
402
|
+
if (all || sections.includes('git')) {
|
|
403
|
+
info.git = {};
|
|
404
|
+
try {
|
|
405
|
+
const { stdout: branch } = await execFileAsync(
|
|
406
|
+
'git', ['branch', '--show-current'],
|
|
407
|
+
{ cwd: projectRoot, timeout: 5000 },
|
|
408
|
+
);
|
|
409
|
+
info.git.branch = branch.trim();
|
|
410
|
+
|
|
411
|
+
const { stdout: status } = await execFileAsync(
|
|
412
|
+
'git', ['status', '--porcelain'],
|
|
413
|
+
{ cwd: projectRoot, timeout: 5000 },
|
|
414
|
+
);
|
|
415
|
+
const lines = status.trim().split('\n').filter(Boolean);
|
|
416
|
+
info.git.dirty = lines.length > 0;
|
|
417
|
+
info.git.changedFiles = lines.length;
|
|
418
|
+
|
|
419
|
+
const { stdout: lastCommit } = await execFileAsync(
|
|
420
|
+
'git', ['log', '-1', '--format=%h %s (%cr)'],
|
|
421
|
+
{ cwd: projectRoot, timeout: 5000 },
|
|
422
|
+
);
|
|
423
|
+
info.git.lastCommit = lastCommit.trim();
|
|
424
|
+
|
|
425
|
+
const { stdout: remoteUrl } = await execFileAsync(
|
|
426
|
+
'git', ['remote', 'get-url', 'origin'],
|
|
427
|
+
{ cwd: projectRoot, timeout: 5000 },
|
|
428
|
+
);
|
|
429
|
+
info.git.remote = remoteUrl.trim();
|
|
430
|
+
} catch {
|
|
431
|
+
info.git.error = '非 Git 仓库或 Git 未安装';
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ── 项目信息 ──
|
|
436
|
+
if (all || sections.includes('project')) {
|
|
437
|
+
info.project = {
|
|
438
|
+
root: projectRoot,
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// package.json
|
|
442
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
443
|
+
if (fs.existsSync(pkgPath)) {
|
|
444
|
+
try {
|
|
445
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
446
|
+
info.project.name = pkg.name;
|
|
447
|
+
info.project.version = pkg.version;
|
|
448
|
+
info.project.type = pkg.type || 'commonjs';
|
|
449
|
+
info.project.dependencies = Object.keys(pkg.dependencies || {}).length;
|
|
450
|
+
info.project.devDependencies = Object.keys(pkg.devDependencies || {}).length;
|
|
451
|
+
} catch { /* invalid package.json */ }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Podfile / Cartfile / build.gradle / CMakeLists / Makefile 检测
|
|
455
|
+
const projectIndicators = [
|
|
456
|
+
{ file: 'Podfile', type: 'CocoaPods (iOS)' },
|
|
457
|
+
{ file: 'Cartfile', type: 'Carthage (iOS)' },
|
|
458
|
+
{ file: 'Package.swift', type: 'Swift Package Manager' },
|
|
459
|
+
{ file: 'build.gradle', type: 'Gradle (Android/Java)' },
|
|
460
|
+
{ file: 'pom.xml', type: 'Maven (Java)' },
|
|
461
|
+
{ file: 'CMakeLists.txt', type: 'CMake (C/C++)' },
|
|
462
|
+
{ file: 'Makefile', type: 'Make' },
|
|
463
|
+
{ file: 'Cargo.toml', type: 'Cargo (Rust)' },
|
|
464
|
+
{ file: 'go.mod', type: 'Go Modules' },
|
|
465
|
+
{ file: 'requirements.txt', type: 'pip (Python)' },
|
|
466
|
+
{ file: 'pyproject.toml', type: 'Python project' },
|
|
467
|
+
{ file: 'Gemfile', type: 'Bundler (Ruby)' },
|
|
468
|
+
];
|
|
469
|
+
info.project.buildSystems = projectIndicators
|
|
470
|
+
.filter(({ file }) => fs.existsSync(path.join(projectRoot, file)))
|
|
471
|
+
.map(({ type }) => type);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return info;
|
|
475
|
+
},
|
|
476
|
+
};
|
|
@@ -18,7 +18,6 @@ import { detectTriggers, REGEX } from './DirectiveDetector.js';
|
|
|
18
18
|
import { handleAlink } from './handlers/AlinkHandler.js';
|
|
19
19
|
/* ── Handler imports ── */
|
|
20
20
|
import { handleCreate } from './handlers/CreateHandler.js';
|
|
21
|
-
import { handleDraft } from './handlers/DraftHandler.js';
|
|
22
21
|
import { handleGuard } from './handlers/GuardHandler.js';
|
|
23
22
|
import { handleHeader } from './handlers/HeaderHandler.js';
|
|
24
23
|
import { handleSearch } from './handlers/SearchHandler.js';
|
|
@@ -53,8 +52,6 @@ const DEFAULT_FILE_PATTERN = [
|
|
|
53
52
|
'**/*.cpp',
|
|
54
53
|
'**/*.cc',
|
|
55
54
|
'**/*.hpp',
|
|
56
|
-
// Draft
|
|
57
|
-
'**/_draft_*.md',
|
|
58
55
|
];
|
|
59
56
|
const IGNORED = [
|
|
60
57
|
'**/node_modules/**',
|
|
@@ -221,11 +218,6 @@ export class FileWatcher {
|
|
|
221
218
|
|
|
222
219
|
const filename = basename(fullPath);
|
|
223
220
|
|
|
224
|
-
// _draft_*.md 文件自动处理
|
|
225
|
-
if (REGEX.DRAFT_FILE.test(filename)) {
|
|
226
|
-
await handleDraft(this, fullPath, relativePath, data);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
221
|
// 检测指令
|
|
230
222
|
const triggers = detectTriggers(data, filename);
|
|
231
223
|
|
|
@@ -16,7 +16,7 @@ import { REGEX } from '../DirectiveDetector.js';
|
|
|
16
16
|
* @param {string} createOption 'c' | 'f' | undefined
|
|
17
17
|
*/
|
|
18
18
|
export async function handleCreate(watcher, fullPath, relativePath, createOption) {
|
|
19
|
-
const XA = await import('../../../
|
|
19
|
+
const XA = await import('../../../platform/ios/xcode/XcodeAutomation.js');
|
|
20
20
|
const CM = await import('../../../infrastructure/external/ClipboardManager.js');
|
|
21
21
|
|
|
22
22
|
// 1. 读剪贴板(仅 -c 模式)
|
|
@@ -137,8 +137,12 @@ async function silentCreateCandidate(watcher, text, relativePath) {
|
|
|
137
137
|
try {
|
|
138
138
|
const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
|
|
139
139
|
const container = getServiceContainer();
|
|
140
|
-
const
|
|
141
|
-
const aiResult = await
|
|
140
|
+
const agentFactory = container.get('agentFactory');
|
|
141
|
+
const aiResult = await agentFactory.scanKnowledge({
|
|
142
|
+
label: title,
|
|
143
|
+
files: [{ name: title, content: text, language: lang }],
|
|
144
|
+
task: 'summarize',
|
|
145
|
+
});
|
|
142
146
|
if (aiResult && !aiResult.error) {
|
|
143
147
|
title = aiResult.title || title;
|
|
144
148
|
summary = aiResult.summary || '';
|
|
@@ -53,22 +53,23 @@ export async function handleDraft(watcher, fullPath, relativePath, content) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// AI 摘要回退(通过
|
|
56
|
+
// AI 摘要回退(通过 Agent 统一管道)
|
|
57
57
|
try {
|
|
58
58
|
const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
|
|
59
59
|
const container = getServiceContainer();
|
|
60
|
-
const
|
|
60
|
+
const agentFactory = container.get('agentFactory');
|
|
61
61
|
const lang = LanguageService.inferLang(relativePath) || 'unknown';
|
|
62
|
-
const result = await
|
|
63
|
-
|
|
64
|
-
language: lang,
|
|
62
|
+
const result = await agentFactory.scanKnowledge({
|
|
63
|
+
label: relativePath,
|
|
64
|
+
files: [{ name: relativePath, content, language: lang }],
|
|
65
|
+
task: 'summarize',
|
|
65
66
|
});
|
|
66
67
|
if (result && !result.error && result.title && result.code) {
|
|
67
68
|
await watcher._appendCandidates([result], 'draft-file');
|
|
68
69
|
watcher._notify(`已创建候选「${result.title}」`);
|
|
69
70
|
}
|
|
70
71
|
} catch {
|
|
71
|
-
/*
|
|
72
|
+
/* AgentFactory 不可用 */
|
|
72
73
|
}
|
|
73
74
|
} catch (e) {
|
|
74
75
|
console.warn('[Watcher] 草稿文件解析失败:', e.message);
|