feihong-code 0.2.3 → 0.6.0
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/.github/FUNDING.yml +4 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +37 -37
- package/.github/ISSUE_TEMPLATE/config.yml +14 -14
- package/.github/ISSUE_TEMPLATE/feature_request.md +28 -28
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -46
- package/.github/SECURITY.md +67 -67
- package/.github/dependabot.yml +16 -0
- package/.github/workflows/ci.yml +1 -1
- package/AGENT-GUIDE.md +382 -237
- package/CHANGELOG.md +392 -0
- package/CODE_OF_CONDUCT.md +56 -56
- package/CONTRIBUTING.md +68 -68
- package/LICENSE +22 -22
- package/README.md +586 -522
- package/README.self-evolve.md +274 -0
- package/dist/agent/code-review.js +2 -0
- package/dist/agent/code-review.js.map +1 -1
- package/dist/agent/code-writer.js +74 -18
- package/dist/agent/code-writer.js.map +1 -1
- package/dist/agent/context-compactor.js +13 -4
- package/dist/agent/context-compactor.js.map +1 -1
- package/dist/agent/experience.js +319 -84
- package/dist/agent/experience.js.map +1 -1
- package/dist/agent/orchestrator.js +233 -71
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/planner.js +30 -7
- package/dist/agent/planner.js.map +1 -1
- package/dist/agent/prompts.js +9 -0
- package/dist/agent/prompts.js.map +1 -1
- package/dist/agent/quality-gate.js +10 -4
- package/dist/agent/quality-gate.js.map +1 -1
- package/dist/agent/repo-context.js +181 -0
- package/dist/agent/repo-context.js.map +1 -0
- package/dist/agent/repo-reader.js +92 -99
- package/dist/agent/repo-reader.js.map +1 -1
- package/dist/agent/self-heal.js +80 -60
- package/dist/agent/self-heal.js.map +1 -1
- package/dist/agent/self-improver.js +65 -61
- package/dist/agent/self-improver.js.map +1 -1
- package/dist/agent/subagent-summary.js +31 -0
- package/dist/agent/subagent-summary.js.map +1 -0
- package/dist/agent/subagent.js +52 -2
- package/dist/agent/subagent.js.map +1 -1
- package/dist/agent/symbol-index.js +160 -0
- package/dist/agent/symbol-index.js.map +1 -0
- package/dist/agent/team.js +193 -0
- package/dist/agent/team.js.map +1 -0
- package/dist/cli/commands.js +124 -143
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/index.js +113 -106
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/repl.js +82 -7
- package/dist/cli/repl.js.map +1 -1
- package/dist/cli/run.js +600 -95
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/tui.js +145 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/version.js +1 -1
- package/dist/enterprise/audit.js +145 -23
- package/dist/enterprise/audit.js.map +1 -1
- package/dist/enterprise/index.js +14 -11
- package/dist/enterprise/index.js.map +1 -1
- package/dist/enterprise/policy.js +22 -10
- package/dist/enterprise/policy.js.map +1 -1
- package/dist/harness/executor.js +127 -0
- package/dist/harness/executor.js.map +1 -0
- package/dist/harness/harness.js +87 -0
- package/dist/harness/harness.js.map +1 -0
- package/dist/harness/index.js +31 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/loader.js +138 -0
- package/dist/harness/loader.js.map +1 -0
- package/dist/harness/reporter.js +34 -0
- package/dist/harness/reporter.js.map +1 -0
- package/dist/harness/types.js +10 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/verifier.js +48 -0
- package/dist/harness/verifier.js.map +1 -0
- package/dist/hello.js +14 -0
- package/dist/hello.js.map +1 -0
- package/dist/memory/auto-summarize.js +208 -0
- package/dist/memory/auto-summarize.js.map +1 -0
- package/dist/memory/index.js +228 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/models/model-router.js +100 -27
- package/dist/models/model-router.js.map +1 -1
- package/dist/models/model.dto.js +25 -3
- package/dist/models/model.dto.js.map +1 -1
- package/dist/models/providers/ollama.provider.js +12 -0
- package/dist/models/providers/ollama.provider.js.map +1 -1
- package/dist/models/providers/openai-compatible.provider.js +14 -1
- package/dist/models/providers/openai-compatible.provider.js.map +1 -1
- package/dist/plugins/plugin-loader.js +179 -0
- package/dist/plugins/plugin-loader.js.map +1 -0
- package/dist/runtime/event-log.js.map +1 -1
- package/dist/runtime/hooks.js +80 -0
- package/dist/runtime/hooks.js.map +1 -0
- package/dist/self-evolve/hook.js +60 -0
- package/dist/self-evolve/hook.js.map +1 -0
- package/dist/self-evolve/hook.ts +80 -0
- package/dist/self-evolve/manager.d.ts +8 -0
- package/dist/self-evolve/manager.js +401 -0
- package/dist/shared/config.js +35 -3
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/errors.js +6 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/i18n.js +535 -0
- package/dist/shared/i18n.js.map +1 -0
- package/dist/shared/secure-store.js +117 -0
- package/dist/shared/secure-store.js.map +1 -0
- package/dist/skills/grill.js +2 -1
- package/dist/skills/grill.js.map +1 -1
- package/dist/skills/self-heal.js +73 -0
- package/dist/skills/self-heal.js.map +1 -0
- package/dist/skills/skill-loader.js +131 -0
- package/dist/skills/skill-loader.js.map +1 -0
- package/dist/skills/skill-market.js +195 -0
- package/dist/skills/skill-market.js.map +1 -0
- package/dist/tools/analysis/code-analyzer.js +45 -18
- package/dist/tools/analysis/code-analyzer.js.map +1 -1
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp/index.js +84 -0
- package/dist/tools/mcp/index.js.map +1 -0
- package/dist/tools/mcp/mcp-client.js +194 -0
- package/dist/tools/mcp/mcp-client.js.map +1 -0
- package/dist/tools/sandbox.js +126 -0
- package/dist/tools/sandbox.js.map +1 -0
- package/dist/tools/shell/exec.js +72 -3
- package/dist/tools/shell/exec.js.map +1 -1
- package/dist/tools/shell/run-shell.tool.js +37 -7
- package/dist/tools/shell/run-shell.tool.js.map +1 -1
- package/dist/tools/skills/load-skill.tool.js +36 -0
- package/dist/tools/skills/load-skill.tool.js.map +1 -0
- package/dist/tools/tool.interface.js.map +1 -1
- package/dist/tools/tool.registry.js +69 -1
- package/dist/tools/tool.registry.js.map +1 -1
- package/dist/tools/web/web.tool.js +139 -0
- package/dist/tools/web/web.tool.js.map +1 -0
- package/dist/web/auth.js +178 -3
- package/dist/web/auth.js.map +1 -1
- package/dist/web/channels.js +171 -0
- package/dist/web/channels.js.map +1 -0
- package/dist/web/public/css/style.css +1546 -0
- package/dist/web/public/index.html +804 -37
- package/dist/web/public/js/api.js +309 -0
- package/dist/web/public/js/app.js +1472 -0
- package/dist/web/public/js/ui.js +832 -0
- package/dist/web/public/js/utils.js +170 -0
- package/dist/web/server.js +937 -11
- package/dist/web/server.js.map +1 -1
- package/dist/web/task-queue.js +470 -0
- package/dist/web/task-queue.js.map +1 -0
- package/dist/web/web-config.js +143 -0
- package/dist/web/web-config.js.map +1 -0
- package/docs/App/344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +299 -0
- package/docs/App/346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +554 -0
- package/docs/Deployment_Guide_EN.md +288 -0
- package/docs/SELF-EVOLVE-GUIDE.md +313 -0
- package/docs/Technical_Manual_EN.md +216 -0
- package/docs/User_Manual_EN.md +314 -0
- package/docs/error-codes.md +198 -0
- package/docs/screenshots/cli-demo.png +0 -0
- package/docs/screenshots/feature-comparison.png +0 -0
- package/docs/screenshots/web-console.png +0 -0
- package/docs/self-evolve-implementation.md +165 -0
- package/docs/self-evolve.md +166 -0
- package/docs//344/272/247/345/223/201/345/274/200/345/217/221/346/226/207/346/241/243.md +614 -614
- package/docs//344/274/201/344/270/232/351/203/250/347/275/262/344/270/216/345/220/210/350/247/204.md +267 -267
- package/docs//344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +318 -358
- package/docs//345/270/270/350/247/201/351/227/256/351/242/230/344/270/216/346/225/205/351/232/234/346/216/222/346/237/245.md +130 -130
- package/docs//346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +216 -303
- package/docs//346/236/266/346/236/204/344/270/216API.md +238 -238
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +196 -196
- package/docs//351/203/250/347/275/262/346/214/207/345/215/227.md +165 -165
- package/docs//351/203/250/347/275/262/350/257/264/346/230/216/344/271/246.md +288 -0
- package/docs//351/205/215/347/275/256/345/217/202/350/200/203.md +127 -127
- package/docs//351/241/265/351/235/242/345/212/237/350/203/275/345/244/215/347/233/230/344/270/216/345/206/222/347/203/237/346/265/213/350/257/225/346/212/245/345/221/212.html +117 -0
- package/package.json +108 -109
- package/tool-schema.json +117 -46
package/dist/cli/run.js
CHANGED
|
@@ -1,11 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.getEnterprise = getEnterprise;
|
|
37
|
+
exports.streamRenderer = streamRenderer;
|
|
38
|
+
exports.executeTask = executeTask;
|
|
4
39
|
exports.runGoal = runGoal;
|
|
5
40
|
exports.isOfflineByDefault = isOfflineByDefault;
|
|
6
41
|
exports.runPlanSkill = runPlanSkill;
|
|
7
42
|
exports.runGrillSkill = runGrillSkill;
|
|
43
|
+
exports.runReviewCmd = runReviewCmd;
|
|
8
44
|
exports.runGoalSkill = runGoalSkill;
|
|
45
|
+
exports.runSelfHealSkill = runSelfHealSkill;
|
|
9
46
|
exports.runParallelGoal = runParallelGoal;
|
|
10
47
|
exports.runSessions = runSessions;
|
|
11
48
|
exports.runResume = runResume;
|
|
@@ -16,6 +53,10 @@ exports.runPolicyCmd = runPolicyCmd;
|
|
|
16
53
|
exports.runAudit = runAudit;
|
|
17
54
|
exports.runAuditVerify = runAuditVerify;
|
|
18
55
|
exports.runTenants = runTenants;
|
|
56
|
+
exports.runDoctor = runDoctor;
|
|
57
|
+
exports.runPluginCmd = runPluginCmd;
|
|
58
|
+
exports.runTeamCmd = runTeamCmd;
|
|
59
|
+
exports.runSkillMarketCmd = runSkillMarketCmd;
|
|
19
60
|
exports.runModelStats = runModelStats;
|
|
20
61
|
exports.runExperiences = runExperiences;
|
|
21
62
|
exports.runServe = runServe;
|
|
@@ -23,6 +64,7 @@ exports.runCodeWrite = runCodeWrite;
|
|
|
23
64
|
exports.runQualityGate = runQualityGate;
|
|
24
65
|
exports.runSelfImprove = runSelfImprove;
|
|
25
66
|
exports.runSwe = runSwe;
|
|
67
|
+
exports.runHarness = runHarness;
|
|
26
68
|
exports.defaultApproverFor = defaultApproverFor;
|
|
27
69
|
exports.interactiveApprover = interactiveApprover;
|
|
28
70
|
/**
|
|
@@ -40,11 +82,15 @@ const os_1 = require("os");
|
|
|
40
82
|
const path_1 = require("path");
|
|
41
83
|
const readline_1 = require("readline");
|
|
42
84
|
const logger_1 = require("../shared/logger");
|
|
85
|
+
const i18n_1 = require("../shared/i18n");
|
|
43
86
|
const config_1 = require("../shared/config");
|
|
44
87
|
const errors_1 = require("../shared/errors");
|
|
45
88
|
const model_router_1 = require("../models/model-router");
|
|
46
89
|
const mock_provider_1 = require("../models/providers/mock.provider");
|
|
90
|
+
const openai_compatible_provider_1 = require("../models/providers/openai-compatible.provider");
|
|
91
|
+
const ollama_provider_1 = require("../models/providers/ollama.provider");
|
|
47
92
|
const tools_1 = require("../tools");
|
|
93
|
+
const mcp_1 = require("../tools/mcp");
|
|
48
94
|
const exec_1 = require("../tools/shell/exec");
|
|
49
95
|
const event_log_1 = require("../runtime/event-log");
|
|
50
96
|
const session_store_1 = require("../runtime/session-store");
|
|
@@ -52,16 +98,28 @@ const session_persist_1 = require("../runtime/session-persist");
|
|
|
52
98
|
const git_1 = require("../runtime/git");
|
|
53
99
|
const enterprise_1 = require("../enterprise");
|
|
54
100
|
const server_1 = require("../web/server");
|
|
101
|
+
const auto_summarize_1 = require("../memory/auto-summarize");
|
|
102
|
+
const plugin_loader_1 = require("../plugins/plugin-loader");
|
|
103
|
+
const team_1 = require("../agent/team");
|
|
104
|
+
const skill_market_1 = require("../skills/skill-market");
|
|
105
|
+
const skill_loader_1 = require("../skills/skill-loader");
|
|
55
106
|
const orchestrator_1 = require("../agent/orchestrator");
|
|
56
107
|
const parallel_orchestrator_1 = require("../agent/parallel-orchestrator");
|
|
57
108
|
const plan_1 = require("../skills/plan");
|
|
58
109
|
const grill_1 = require("../skills/grill");
|
|
59
110
|
const goal_1 = require("../skills/goal");
|
|
111
|
+
const self_heal_1 = require("../skills/self-heal");
|
|
60
112
|
const experience_1 = require("../agent/experience");
|
|
61
113
|
const code_writer_1 = require("../agent/code-writer");
|
|
62
114
|
const quality_gate_1 = require("../agent/quality-gate");
|
|
63
115
|
const self_improver_1 = require("../agent/self-improver");
|
|
64
116
|
const swe_agent_1 = require("../agent/swe-agent");
|
|
117
|
+
const subagent_summary_1 = require("../agent/subagent-summary");
|
|
118
|
+
const harness_1 = require("../harness/harness");
|
|
119
|
+
const loader_1 = require("../harness/loader");
|
|
120
|
+
const executor_1 = require("../harness/executor");
|
|
121
|
+
const verifier_1 = require("../harness/verifier");
|
|
122
|
+
const reporter_1 = require("../harness/reporter");
|
|
65
123
|
/** 离线演示脚本:写文件 → 总结,跑通完整链路 */
|
|
66
124
|
function buildDemoSteps() {
|
|
67
125
|
return [
|
|
@@ -114,22 +172,72 @@ function getSessionHome(offline) {
|
|
|
114
172
|
const home = process.env.FH_HOME ? expandHome(process.env.FH_HOME) : joinHome();
|
|
115
173
|
return (0, path_1.join)(home, 'sessions');
|
|
116
174
|
}
|
|
117
|
-
|
|
175
|
+
/** P0-1:把编排器事件流增量渲染到 stdout(流式输出)。抑制换行噪音:仅打印关键节点。 */
|
|
176
|
+
function renderStreamEvent(ev) {
|
|
177
|
+
switch (ev.type) {
|
|
178
|
+
case 'model.response':
|
|
179
|
+
// 有文本内容时直接流式打印(模拟 token 流);仅工具调用时打印调用摘要
|
|
180
|
+
if (ev.content.trim())
|
|
181
|
+
console.log(`🧠 ${ev.content.trim().slice(0, 300)}`);
|
|
182
|
+
else if (ev.toolCalls.length > 0)
|
|
183
|
+
console.log(`🔧 ${(0, i18n_1.t)('stream.toolCalling', { tools: ev.toolCalls.join(', ') })}`);
|
|
184
|
+
break;
|
|
185
|
+
case 'tool.call':
|
|
186
|
+
break; // 已在上层 model.response 摘要,避免重复
|
|
187
|
+
case 'tool.result':
|
|
188
|
+
console.log(ev.ok ? ` ✅ ${ev.name} ${(0, i18n_1.t)('stream.toolOk')}` : ` ❌ ${ev.name} ${(0, i18n_1.t)('stream.toolFail')} — ${ev.output.slice(0, 120)}`);
|
|
189
|
+
break;
|
|
190
|
+
case 'self-heal':
|
|
191
|
+
console.log(`🩹 ${(0, i18n_1.t)('stream.selfHeal', { category: ev.category })}`);
|
|
192
|
+
break;
|
|
193
|
+
case 'context.compact':
|
|
194
|
+
console.log(`📦 ${(0, i18n_1.t)('stream.compact', { from: ev.originalLength, to: ev.compressedLength })}`);
|
|
195
|
+
break;
|
|
196
|
+
case 'session.end':
|
|
197
|
+
console.log(`🏁 ${(0, i18n_1.t)('stream.done', { iter: ev.iterations, cost: '$' + ev.costUsd.toFixed(6) })}`);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** 流式输出事件渲染器(供 runGoal / runSwe 复用) */
|
|
202
|
+
function streamRenderer() {
|
|
203
|
+
return renderStreamEvent;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* P4-1 服务端可复用执行函数:装配编排器并执行目标,返回结构化结果。
|
|
207
|
+
* 不打印任何 console 输出(供 Web 任务队列等非 CLI 场景调用)。
|
|
208
|
+
* runGoal 在其上叠加展示层。
|
|
209
|
+
*/
|
|
210
|
+
async function executeTask(goal, opts = {}) {
|
|
118
211
|
const runId = (0, crypto_1.randomUUID)();
|
|
119
212
|
(0, logger_1.setRunId)(runId);
|
|
120
213
|
const security = { shellAllowlist: [], requireApproval: true };
|
|
121
214
|
const offline = opts.offline ?? isOfflineByDefault();
|
|
215
|
+
// Web 控制台可传入安全配置覆盖默认值
|
|
216
|
+
if (opts.security) {
|
|
217
|
+
security.shellAllowlist = opts.security.shellAllowlist ?? security.shellAllowlist;
|
|
218
|
+
security.requireApproval = opts.security.requireApproval ?? security.requireApproval;
|
|
219
|
+
security.sandboxMode = opts.security.sandboxMode ?? security.sandboxMode;
|
|
220
|
+
}
|
|
122
221
|
// M4:企业上下文(租户隔离 / RBAC / 审计 / 配额),配额超限在此 fail-fast
|
|
123
222
|
const rt = getEnterprise();
|
|
124
223
|
if (rt)
|
|
125
224
|
(0, enterprise_1.assertQuota)(rt);
|
|
126
225
|
// 离线模式用临时工作区,避免污染用户目录;并 git init 以支持 diff/rollback 演示
|
|
127
|
-
const
|
|
226
|
+
const targetCwd = opts.workspaceDir || process.cwd();
|
|
227
|
+
const cwd = offline ? (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'fhcode-demo-')) : targetCwd;
|
|
128
228
|
if (offline) {
|
|
129
229
|
await (0, exec_1.runCommand)('git init -q', cwd).catch(() => undefined);
|
|
130
230
|
}
|
|
231
|
+
// 注意:不再使用 process.chdir(targetCwd) —— 所有工具通过 ctx.cwd 显式传参,
|
|
232
|
+
// 全局切换 cwd 会导致并发任务互相覆盖工作目录。移除后可安全提高并发数。
|
|
131
233
|
let router;
|
|
132
|
-
|
|
234
|
+
let pluginSkillDirs = [];
|
|
235
|
+
// Web 控制台直接注入模型配置(绕过 loadConfig 缓存)
|
|
236
|
+
if (opts.modelProviders && opts.modelProviders.length > 0) {
|
|
237
|
+
const providers = opts.modelProviders.map((p) => p.type === 'ollama' ? new ollama_provider_1.OllamaProvider({ id: p.id, type: 'ollama', baseURL: p.baseURL, model: p.model || 'default', tags: ['code-gen', 'reasoning'] }) : new openai_compatible_provider_1.OpenAICompatibleProvider({ id: p.id, type: 'openai-compatible', baseURL: p.baseURL, model: p.model || 'gpt-4o', apiKey: p.apiKey, tags: ['code-gen', 'reasoning'] }));
|
|
238
|
+
router = new model_router_1.ModelRouter(providers, 'cost', 0);
|
|
239
|
+
}
|
|
240
|
+
else if (offline) {
|
|
133
241
|
router = new model_router_1.ModelRouter([new mock_provider_1.ScriptedMockProvider(buildDemoSteps())], 'cost', 0);
|
|
134
242
|
}
|
|
135
243
|
else {
|
|
@@ -137,11 +245,35 @@ async function runGoal(goal, opts = {}) {
|
|
|
137
245
|
router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
138
246
|
security.shellAllowlist = cfg.security.shellAllowlist;
|
|
139
247
|
security.requireApproval = cfg.security.requireApproval;
|
|
248
|
+
security.sandboxMode = cfg.security.sandboxMode;
|
|
249
|
+
security.networkRules = { networkAllow: cfg.security.networkAllow, networkDeny: cfg.security.networkDeny };
|
|
250
|
+
security.hooks = cfg.hooks;
|
|
251
|
+
pluginSkillDirs = cfg.plugins.skillDirs;
|
|
140
252
|
}
|
|
141
253
|
const tools = (0, tools_1.createDefaultRegistry)();
|
|
254
|
+
// P0-3:附加 MCP 外部工具(真实模式且有配置时)
|
|
255
|
+
let mcpClients = [];
|
|
256
|
+
if (!offline) {
|
|
257
|
+
const cfg = (0, config_1.loadConfig)();
|
|
258
|
+
mcpClients = await (0, mcp_1.attachMcpTools)(tools, cfg.mcp.servers);
|
|
259
|
+
}
|
|
142
260
|
const logDir = getSessionHome(offline);
|
|
143
261
|
const eventLog = new event_log_1.EventLog(runId, logDir);
|
|
144
262
|
const session = new session_store_1.SessionStore(runId, cwd);
|
|
263
|
+
// M3 多轮续接:把上一轮历史消息补入会话,使 session.snapshot() 返回完整对话(供下一轮 resume 持久化)
|
|
264
|
+
if (opts.resume && opts.resume.messages.length > 0) {
|
|
265
|
+
for (const m of opts.resume.messages)
|
|
266
|
+
session.append(m);
|
|
267
|
+
}
|
|
268
|
+
// 用户附件上下文:把本轮附件路径以系统提示形式注入到本次目标的前缀,
|
|
269
|
+
// 让模型知道有哪些文件可读取;具体处理由 LLM 决定(如读取图片/读文件等)。
|
|
270
|
+
let effectiveGoal = goal;
|
|
271
|
+
if (opts.attachments && opts.attachments.length > 0) {
|
|
272
|
+
const list = opts.attachments.map((p, i) => ` ${i + 1}. ${p}`).join('\n');
|
|
273
|
+
effectiveGoal =
|
|
274
|
+
`[用户附件] 本轮上传了 ${opts.attachments.length} 个文件,请按需读取:\n${list}\n\n` +
|
|
275
|
+
goal;
|
|
276
|
+
}
|
|
145
277
|
const approve = opts.approve ??
|
|
146
278
|
(process.stdin.isTTY ? interactiveApprover() : defaultApproverFor(security));
|
|
147
279
|
const guard = rt
|
|
@@ -158,9 +290,11 @@ async function runGoal(goal, opts = {}) {
|
|
|
158
290
|
guard,
|
|
159
291
|
maxCostUsd: rt?.maxCostUsd ?? 0,
|
|
160
292
|
persist: (cp) => (0, session_persist_1.saveCheckpoint)(logDir, cp),
|
|
293
|
+
onEvent: opts.renderer ?? (opts.stream ? streamRenderer() : undefined),
|
|
294
|
+
pluginSkillDirs,
|
|
295
|
+
signal: opts.signal,
|
|
161
296
|
});
|
|
162
297
|
if (rt) {
|
|
163
|
-
console.log(`[飞虹 Code] 身份 tenant=${rt.tenant.tenantId} user=${rt.tenant.userId} role=${rt.tenant.role}`);
|
|
164
298
|
rt.audit.record({
|
|
165
299
|
tenantId: rt.tenant.tenantId,
|
|
166
300
|
userId: rt.tenant.userId,
|
|
@@ -172,31 +306,44 @@ async function runGoal(goal, opts = {}) {
|
|
|
172
306
|
reason: offline ? '离线模式' : '真实模式',
|
|
173
307
|
});
|
|
174
308
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
309
|
+
try {
|
|
310
|
+
const result = await orchestrator.run(effectiveGoal, opts.resume);
|
|
311
|
+
if (rt) {
|
|
312
|
+
rt.audit.record({
|
|
313
|
+
tenantId: rt.tenant.tenantId,
|
|
314
|
+
userId: rt.tenant.userId,
|
|
315
|
+
role: rt.tenant.role,
|
|
316
|
+
runId,
|
|
317
|
+
action: 'session:end',
|
|
318
|
+
resource: `iterations=${result.iterations}`,
|
|
319
|
+
decision: 'info',
|
|
320
|
+
reason: `cost=$${result.costUsd.toFixed(6)}`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// M3 多轮续接:把本轮完整消息历史带回调用方(任务队列持久化后供下一轮 resume)
|
|
324
|
+
const history = session.snapshot().messages;
|
|
325
|
+
return { ...result, messages: history };
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
// P0-3:任务结束关闭 MCP 子进程
|
|
329
|
+
await (0, mcp_1.closeMcpClients)(mcpClients);
|
|
188
330
|
}
|
|
189
|
-
|
|
331
|
+
}
|
|
332
|
+
async function runGoal(goal, opts = {}) {
|
|
333
|
+
const result = await executeTask(goal, opts);
|
|
334
|
+
const offline = opts.offline ?? isOfflineByDefault();
|
|
335
|
+
const logDir = getSessionHome(offline);
|
|
336
|
+
console.log('\n' + (0, i18n_1.t)('run.resultTitle'));
|
|
190
337
|
console.log(result.finalAnswer);
|
|
191
|
-
console.log(
|
|
192
|
-
console.log(
|
|
338
|
+
console.log('\n' + (0, i18n_1.t)('run.summary', { iter: result.iterations, cost: '$' + result.costUsd.toFixed(6), log: result.logFile }));
|
|
339
|
+
console.log((0, i18n_1.t)('run.checkpoint', { path: (0, path_1.join)(logDir, `${result.runId}.session.json`) }));
|
|
193
340
|
if (offline) {
|
|
194
341
|
// 演示文件可能因策略拒绝而未生成,据实播报,避免误导
|
|
195
|
-
const demoFile = (0, path_1.join)(cwd, 'demo-output.txt');
|
|
196
|
-
logger_1.logger.info('offline-run done', { cwd, demoFile });
|
|
342
|
+
const demoFile = (0, path_1.join)(process.cwd(), 'demo-output.txt');
|
|
343
|
+
logger_1.logger.info('offline-run done', { cwd: process.cwd(), demoFile });
|
|
197
344
|
console.log((0, fs_1.existsSync)(demoFile)
|
|
198
|
-
?
|
|
199
|
-
:
|
|
345
|
+
? (0, i18n_1.t)('run.offlineFileYes', { file: demoFile })
|
|
346
|
+
: (0, i18n_1.t)('run.offlineFileNo', { cwd: process.cwd() }));
|
|
200
347
|
}
|
|
201
348
|
}
|
|
202
349
|
/** 默认是否离线:未配置 FH_PROVIDERS 或为空数组时离线 */
|
|
@@ -243,6 +390,24 @@ function runGrillSkill(target) {
|
|
|
243
390
|
];
|
|
244
391
|
return lines.filter(Boolean).join('\n');
|
|
245
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* M1.1a `fhcode review [路径] [--json]`:红队式代码审查的结构化版本。
|
|
395
|
+
* - 文本模式:与 /grill 输出一致(人类可读)
|
|
396
|
+
* - --json 模式:输出完整结构(scanned/findings/summary),供 IDE 内联评审等消费
|
|
397
|
+
*/
|
|
398
|
+
function runReviewCmd(path, asJson) {
|
|
399
|
+
const result = (0, grill_1.runGrill)(process.cwd(), path || '.');
|
|
400
|
+
if (asJson) {
|
|
401
|
+
console.log(JSON.stringify(result, null, 2));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
console.log(`【review】${result.summary}`);
|
|
405
|
+
for (const f of result.findings) {
|
|
406
|
+
console.log(` [${f.severity.toUpperCase()}] ${f.file}:${f.line} (${f.rule}) ${f.detail}`);
|
|
407
|
+
}
|
|
408
|
+
if (result.findings.length === 0)
|
|
409
|
+
console.log(' 未发现明显问题。');
|
|
410
|
+
}
|
|
246
411
|
/** /goal 技能:分解并保存高层目标(M4 起写入租户隔离目录 tenants/<id>/goals) */
|
|
247
412
|
function runGoalSkill(title) {
|
|
248
413
|
const goal = (0, goal_1.decomposeGoalToGoal)(title);
|
|
@@ -268,10 +433,36 @@ function runGoalSkill(title) {
|
|
|
268
433
|
}
|
|
269
434
|
return `【/goal】已保存\n${(0, goal_1.renderGoal)(goal)}\n文件: ${file}`;
|
|
270
435
|
}
|
|
436
|
+
/** /self-heal 技能:系统化自我修复错误(分类 → 根因 → 修复建议 → 验证步骤) */
|
|
437
|
+
function runSelfHealSkill(errorText) {
|
|
438
|
+
const out = (0, self_heal_1.runSelfHeal)(errorText);
|
|
439
|
+
if (getEnterprise()) {
|
|
440
|
+
// 审计留痕(脱敏由 audit.ts 处理),便于追溯自愈诊断过程
|
|
441
|
+
try {
|
|
442
|
+
const rt = getEnterprise();
|
|
443
|
+
if (rt) {
|
|
444
|
+
rt.audit.record({
|
|
445
|
+
tenantId: rt.tenant.tenantId,
|
|
446
|
+
userId: rt.tenant.userId,
|
|
447
|
+
role: rt.tenant.role,
|
|
448
|
+
runId: (0, crypto_1.randomUUID)(),
|
|
449
|
+
action: 'skill:self-heal',
|
|
450
|
+
resource: out.category,
|
|
451
|
+
decision: out.known ? 'info' : 'deny',
|
|
452
|
+
reason: out.known ? '自动分类完成' : '未能自动分类,需人工介入',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
/* 审计失败不阻断技能输出 */
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return out.text;
|
|
461
|
+
}
|
|
271
462
|
/** --parallel 并行多子代理执行(离线用 Mock;真实模式接入 FH_PROVIDERS 路由) */
|
|
272
463
|
async function runParallelGoal(goal) {
|
|
273
464
|
const offline = isOfflineByDefault();
|
|
274
|
-
console.log(
|
|
465
|
+
console.log((0, i18n_1.t)('run.parallelMode', { offline: offline ? (0, i18n_1.t)('run.modeOffline') : (0, i18n_1.t)('run.modeLive') }));
|
|
275
466
|
if (!offline) {
|
|
276
467
|
const cfg = (0, config_1.loadConfig)();
|
|
277
468
|
const router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
@@ -284,18 +475,18 @@ async function runParallelGoal(goal) {
|
|
|
284
475
|
router,
|
|
285
476
|
approve: defaultApproverFor(security),
|
|
286
477
|
});
|
|
287
|
-
console.log('\n
|
|
478
|
+
console.log('\n' + (0, i18n_1.t)('run.parallelResult'));
|
|
288
479
|
console.log(result.summary);
|
|
289
|
-
console.log(
|
|
480
|
+
console.log((0, i18n_1.t)('run.parallelRepo', { root: result.repoRoot, trees: result.worktrees.length }));
|
|
290
481
|
return;
|
|
291
482
|
}
|
|
292
483
|
const result = await (0, parallel_orchestrator_1.runParallel)(goal, {
|
|
293
484
|
offline: true,
|
|
294
485
|
mockFor: (task) => (0, parallel_orchestrator_1.defaultParallelMock)(task),
|
|
295
486
|
});
|
|
296
|
-
console.log('\n
|
|
487
|
+
console.log('\n' + (0, i18n_1.t)('run.parallelResult'));
|
|
297
488
|
console.log(result.summary);
|
|
298
|
-
console.log(
|
|
489
|
+
console.log((0, i18n_1.t)('run.parallelRepo', { root: result.repoRoot, trees: result.worktrees.length }));
|
|
299
490
|
}
|
|
300
491
|
/* ===================== M3:会话管理(resume / diff / rollback) ===================== */
|
|
301
492
|
/** 按完整 id 或前缀解析会话检查点(sessions 列表默认展示 8 位前缀,便于直接引用) */
|
|
@@ -318,13 +509,20 @@ async function runSessions() {
|
|
|
318
509
|
const home = getSessionHome(offline);
|
|
319
510
|
const cps = await (0, session_persist_1.listCheckpoints)(home);
|
|
320
511
|
if (cps.length === 0) {
|
|
321
|
-
console.log('
|
|
512
|
+
console.log((0, i18n_1.t)('run.noSessions'));
|
|
322
513
|
return;
|
|
323
514
|
}
|
|
324
|
-
console.log(
|
|
515
|
+
console.log((0, i18n_1.t)('run.sessionList', { mode: offline ? (0, i18n_1.t)('mode.offline') : (0, i18n_1.t)('mode.live'), home }));
|
|
325
516
|
for (const cp of cps) {
|
|
326
|
-
console.log(
|
|
327
|
-
|
|
517
|
+
console.log((0, i18n_1.t)('run.sessionItem', {
|
|
518
|
+
id: cp.runId.slice(0, 8),
|
|
519
|
+
status: cp.status,
|
|
520
|
+
iter: cp.iterations,
|
|
521
|
+
cost: '$' + cp.costUsd.toFixed(6),
|
|
522
|
+
files: cp.touchedFiles.length,
|
|
523
|
+
time: cp.updatedAt,
|
|
524
|
+
}));
|
|
525
|
+
console.log((0, i18n_1.t)('run.sessionGoal', { goal: cp.goal }));
|
|
328
526
|
}
|
|
329
527
|
}
|
|
330
528
|
/** 从检查点恢复中断的会话并续跑 */
|
|
@@ -333,10 +531,10 @@ async function runResume(runId) {
|
|
|
333
531
|
const home = getSessionHome(offline);
|
|
334
532
|
const cp = await resolveCheckpoint(home, runId);
|
|
335
533
|
if (cp.status === 'done') {
|
|
336
|
-
console.log(
|
|
534
|
+
console.log((0, i18n_1.t)('run.resumeDone', { id: runId.slice(0, 8) }));
|
|
337
535
|
return;
|
|
338
536
|
}
|
|
339
|
-
console.log(
|
|
537
|
+
console.log((0, i18n_1.t)('run.resumeStart', { id: runId.slice(0, 8), status: cp.status, iter: cp.iterations }));
|
|
340
538
|
const security = { shellAllowlist: [], requireApproval: true };
|
|
341
539
|
let router;
|
|
342
540
|
if (offline) {
|
|
@@ -382,9 +580,9 @@ async function runResume(runId) {
|
|
|
382
580
|
costUsd: cp.costUsd,
|
|
383
581
|
touchedFiles: cp.touchedFiles,
|
|
384
582
|
});
|
|
385
|
-
console.log('\n
|
|
583
|
+
console.log('\n' + (0, i18n_1.t)('run.resultTitle'));
|
|
386
584
|
console.log(result.finalAnswer);
|
|
387
|
-
console.log(
|
|
585
|
+
console.log((0, i18n_1.t)('run.summary', { iter: result.iterations, cost: '$' + result.costUsd.toFixed(6), log: result.logFile }));
|
|
388
586
|
}
|
|
389
587
|
/** 展示会话作用域的 diff(缺省为本工作区全量 diff) */
|
|
390
588
|
async function runDiff(id) {
|
|
@@ -392,11 +590,11 @@ async function runDiff(id) {
|
|
|
392
590
|
const home = getSessionHome(offline);
|
|
393
591
|
if (id) {
|
|
394
592
|
const cp = await resolveCheckpoint(home, id);
|
|
395
|
-
console.log(
|
|
593
|
+
console.log((0, i18n_1.t)('run.diffSession', { id: id.slice(0, 8), cwd: cp.cwd }));
|
|
396
594
|
console.log(await (0, git_1.gitDiff)(cp.cwd, cp.touchedFiles));
|
|
397
595
|
}
|
|
398
596
|
else {
|
|
399
|
-
console.log(
|
|
597
|
+
console.log((0, i18n_1.t)('run.diffCwd', { cwd: process.cwd() }));
|
|
400
598
|
console.log(await (0, git_1.gitDiff)(process.cwd()));
|
|
401
599
|
}
|
|
402
600
|
}
|
|
@@ -423,14 +621,14 @@ async function runRollback(id, yes) {
|
|
|
423
621
|
throw new errors_1.AppError('角色 viewer 无权执行回滚操作', 'RBAC_DENIED', 403);
|
|
424
622
|
}
|
|
425
623
|
}
|
|
426
|
-
console.log(
|
|
624
|
+
console.log((0, i18n_1.t)('run.rollbackStart', { id: id.slice(0, 8), n: cp.touchedFiles.length, cwd: cp.cwd }));
|
|
427
625
|
const res = await (0, git_1.gitRollback)(cp.cwd, cp.touchedFiles, { yes });
|
|
428
626
|
if (res.reverted.length)
|
|
429
|
-
console.log(
|
|
627
|
+
console.log((0, i18n_1.t)('run.rollbackReverted', { files: res.reverted.join(', ') }));
|
|
430
628
|
if (res.removed.length)
|
|
431
|
-
console.log(
|
|
629
|
+
console.log((0, i18n_1.t)('run.rollbackRemoved', { files: res.removed.join(', ') }));
|
|
432
630
|
if (res.errors.length)
|
|
433
|
-
console.log(
|
|
631
|
+
console.log((0, i18n_1.t)('run.rollbackNote', { errors: res.errors.join('; ') }));
|
|
434
632
|
if (yes)
|
|
435
633
|
await (0, session_persist_1.updateStatus)(home, id, 'done');
|
|
436
634
|
}
|
|
@@ -438,7 +636,7 @@ async function runRollback(id, yes) {
|
|
|
438
636
|
function requireEnterprise() {
|
|
439
637
|
const rt = getEnterprise();
|
|
440
638
|
if (!rt) {
|
|
441
|
-
throw new errors_1.AppError('
|
|
639
|
+
throw new errors_1.AppError((0, i18n_1.t)('err.enterpriseDisabled'), 'ENTERPRISE_DISABLED', 400);
|
|
442
640
|
}
|
|
443
641
|
return rt;
|
|
444
642
|
}
|
|
@@ -456,29 +654,36 @@ function runAudit(limit = 20) {
|
|
|
456
654
|
const rt = requireEnterprise();
|
|
457
655
|
const all = (0, enterprise_1.readAudit)(rt.tenant.auditDir);
|
|
458
656
|
if (all.length === 0) {
|
|
459
|
-
console.log(
|
|
657
|
+
console.log((0, i18n_1.t)('audit.empty', { tenant: rt.tenant.tenantId, dir: rt.tenant.auditDir }));
|
|
460
658
|
return;
|
|
461
659
|
}
|
|
462
660
|
const rows = all.slice(-limit);
|
|
463
|
-
console.log(
|
|
661
|
+
console.log((0, i18n_1.t)('audit.header', { rows: rows.length, all: all.length, tenant: rt.tenant.tenantId }));
|
|
464
662
|
for (const r of rows) {
|
|
465
|
-
console.log(
|
|
466
|
-
|
|
467
|
-
|
|
663
|
+
console.log((0, i18n_1.t)('audit.row', {
|
|
664
|
+
seq: String(r.seq).padStart(4, '0'),
|
|
665
|
+
ts: r.ts,
|
|
666
|
+
decision: r.decision.toUpperCase(),
|
|
667
|
+
action: r.action,
|
|
668
|
+
user: r.userId,
|
|
669
|
+
role: r.role,
|
|
670
|
+
run: String(r.runId).slice(0, 8),
|
|
671
|
+
}));
|
|
672
|
+
console.log((0, i18n_1.t)('audit.resource', { resource: r.resource }));
|
|
468
673
|
if (r.reason)
|
|
469
|
-
console.log(
|
|
674
|
+
console.log((0, i18n_1.t)('audit.reason', { reason: r.reason }));
|
|
470
675
|
}
|
|
471
|
-
console.log(
|
|
676
|
+
console.log((0, i18n_1.t)('audit.chainTail', { hash: all[all.length - 1].hash.slice(0, 16) }));
|
|
472
677
|
}
|
|
473
678
|
/** fhcode audit verify:校验哈希链完整性 */
|
|
474
679
|
function runAuditVerify() {
|
|
475
680
|
const rt = requireEnterprise();
|
|
476
681
|
const res = (0, enterprise_1.verifyAudit)(rt.tenant.auditDir);
|
|
477
682
|
if (res.ok) {
|
|
478
|
-
console.log(
|
|
683
|
+
console.log((0, i18n_1.t)('audit.verifyOk', { total: res.total }));
|
|
479
684
|
return;
|
|
480
685
|
}
|
|
481
|
-
console.log(
|
|
686
|
+
console.log((0, i18n_1.t)('audit.verifyFail', { total: res.total, brokenAt: res.brokenAt ?? 0 }));
|
|
482
687
|
console.log(` ${res.detail}`);
|
|
483
688
|
process.exitCode = 2;
|
|
484
689
|
}
|
|
@@ -487,33 +692,263 @@ function runTenants() {
|
|
|
487
692
|
requireEnterprise();
|
|
488
693
|
const list = (0, enterprise_1.listTenants)();
|
|
489
694
|
if (list.length === 0) {
|
|
490
|
-
console.log('
|
|
695
|
+
console.log((0, i18n_1.t)('tenants.empty'));
|
|
491
696
|
return;
|
|
492
697
|
}
|
|
493
|
-
console.log('
|
|
494
|
-
console.log('
|
|
495
|
-
for (const
|
|
496
|
-
console.log(
|
|
698
|
+
console.log((0, i18n_1.t)('tenants.header'));
|
|
699
|
+
console.log((0, i18n_1.t)('tenants.tableHeader'));
|
|
700
|
+
for (const tenant of list) {
|
|
701
|
+
console.log((0, i18n_1.t)('tenants.row', {
|
|
702
|
+
id: tenant.tenantId.padEnd(20),
|
|
703
|
+
sessions: String(tenant.sessions).padStart(5),
|
|
704
|
+
cost: '$' + tenant.costUsd.toFixed(6).padStart(10),
|
|
705
|
+
audit: String(tenant.auditRecords).padStart(7),
|
|
706
|
+
last: tenant.lastActiveAt,
|
|
707
|
+
}));
|
|
497
708
|
}
|
|
498
709
|
}
|
|
499
710
|
/* ===================== M6:自我进化 ===================== */
|
|
711
|
+
/** 探测 baseURL 连通性:任何 HTTP 响应(含 4xx/5xx)都视为可达,连接失败视为不可达 */
|
|
712
|
+
async function probeUrl(base) {
|
|
713
|
+
const controller = new AbortController();
|
|
714
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
715
|
+
try {
|
|
716
|
+
await fetch(base.replace(/\/+$/, ''), { method: 'GET', signal: controller.signal });
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
finally {
|
|
723
|
+
clearTimeout(timer);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* fhcode doctor:环境自检(版本 / git / 配置 / provider / 路径可写 / 网络连通)。
|
|
728
|
+
* 全部通过输出 ✅,异常项以 ⚠️ 列明,帮助快速定位接入问题。
|
|
729
|
+
*/
|
|
730
|
+
async function runDoctor() {
|
|
731
|
+
const checks = [];
|
|
732
|
+
// 1. Node 版本(engines >= 18)
|
|
733
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
734
|
+
checks.push({
|
|
735
|
+
name: (0, i18n_1.t)('doctor.node'),
|
|
736
|
+
ok: nodeMajor >= 18,
|
|
737
|
+
detail: `Node ${process.version}(要求 >=18)`,
|
|
738
|
+
});
|
|
739
|
+
// 2. git 可用性(diff/rollback/并行 worktree 依赖)
|
|
740
|
+
const git = await (0, exec_1.runCommand)('git --version', process.cwd(), 5000).catch(() => null);
|
|
741
|
+
checks.push({
|
|
742
|
+
name: (0, i18n_1.t)('doctor.git'),
|
|
743
|
+
ok: !!git && git.code === 0,
|
|
744
|
+
detail: git && git.code === 0 ? (git.stdout || git.stderr).trim() : (0, i18n_1.t)('doctor.gitMissing'),
|
|
745
|
+
});
|
|
746
|
+
// 3. 配置加载 + provider 明细 + 网络连通
|
|
747
|
+
try {
|
|
748
|
+
const cfg = (0, config_1.loadConfig)();
|
|
749
|
+
if (cfg.models.providers.length === 0) {
|
|
750
|
+
checks.push({ name: (0, i18n_1.t)('doctor.config'), ok: true, detail: (0, i18n_1.t)('doctor.configEmpty') });
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
checks.push({ name: (0, i18n_1.t)('doctor.config'), ok: true, detail: `${cfg.models.providers.length} providers` });
|
|
754
|
+
for (const p of cfg.models.providers) {
|
|
755
|
+
checks.push({
|
|
756
|
+
name: `${(0, i18n_1.t)('doctor.provider')} ${p.id}`,
|
|
757
|
+
ok: !!p.baseURL,
|
|
758
|
+
detail: `${p.type} @ ${p.baseURL || '(缺 baseURL)'}${p.model ? ` · model=${p.model}` : ''}`,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// 网络探测:仅真实模式且有 provider 时执行;离线模式直接跳过
|
|
763
|
+
if (!isOfflineByDefault() && cfg.models.providers.length > 0) {
|
|
764
|
+
const base = cfg.models.providers[0].baseURL;
|
|
765
|
+
if (base) {
|
|
766
|
+
const reachable = await probeUrl(base);
|
|
767
|
+
checks.push({
|
|
768
|
+
name: (0, i18n_1.t)('doctor.network'),
|
|
769
|
+
ok: reachable,
|
|
770
|
+
detail: reachable ? `${base} 可达` : `${base} 不可达`,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
checks.push({ name: (0, i18n_1.t)('doctor.network'), ok: true, detail: (0, i18n_1.t)('doctor.networkOffline') });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
checks.push({
|
|
780
|
+
name: (0, i18n_1.t)('doctor.config'),
|
|
781
|
+
ok: false,
|
|
782
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
// 4. 主目录可写(会话/审计/经验/统计落盘依赖)
|
|
786
|
+
const homeDir = (0, config_1.resolveHomeDir)();
|
|
787
|
+
let homeOk = true;
|
|
788
|
+
let homeDetail = homeDir;
|
|
789
|
+
try {
|
|
790
|
+
(0, fs_1.mkdirSync)(homeDir, { recursive: true });
|
|
791
|
+
const probe = (0, path_1.join)(homeDir, '.doctor-probe');
|
|
792
|
+
(0, fs_1.writeFileSync)(probe, 'ok');
|
|
793
|
+
(0, fs_1.rmSync)(probe, { force: true });
|
|
794
|
+
}
|
|
795
|
+
catch (e) {
|
|
796
|
+
homeOk = false;
|
|
797
|
+
homeDetail = `${homeDir}(${e instanceof Error ? e.message : String(e)})`;
|
|
798
|
+
}
|
|
799
|
+
checks.push({ name: (0, i18n_1.t)('doctor.home'), ok: homeOk, detail: homeDetail });
|
|
800
|
+
// 5. 沙箱模式(P0-2)
|
|
801
|
+
try {
|
|
802
|
+
const cfg = (0, config_1.loadConfig)();
|
|
803
|
+
const sandboxDetail = `${cfg.security.sandboxMode}` +
|
|
804
|
+
(cfg.security.networkDeny.length > 0 ? ` · deny: ${cfg.security.networkDeny.join(',')}` : '') +
|
|
805
|
+
(cfg.security.networkAllow.length > 0 ? ` · allow: ${cfg.security.networkAllow.join(',')}` : '');
|
|
806
|
+
checks.push({ name: (0, i18n_1.t)('doctor.sandbox'), ok: true, detail: sandboxDetail });
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
checks.push({ name: (0, i18n_1.t)('doctor.sandbox'), ok: false, detail: (0, i18n_1.t)('doctor.sandboxUnavailable') });
|
|
810
|
+
}
|
|
811
|
+
// 输出
|
|
812
|
+
console.log((0, i18n_1.t)('doctor.title'));
|
|
813
|
+
const failed = checks.filter((c) => !c.ok);
|
|
814
|
+
for (const c of checks) {
|
|
815
|
+
console.log(` ${c.ok ? '✅' : '⚠️'} ${c.name}: ${c.detail}`);
|
|
816
|
+
}
|
|
817
|
+
console.log(failed.length === 0 ? (0, i18n_1.t)('doctor.allOk') : (0, i18n_1.t)('doctor.issues', { n: failed.length }));
|
|
818
|
+
}
|
|
819
|
+
/* ===================== P3-3:插件管理 ===================== */
|
|
820
|
+
/** fhcode plugin install <source> / plugin list:插件打包分发管理 */
|
|
821
|
+
async function runPluginCmd(action, source) {
|
|
822
|
+
if (action === 'install') {
|
|
823
|
+
if (!source) {
|
|
824
|
+
console.error((0, i18n_1.t)('plugin.installUsage'));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
const { name, dir } = await (0, plugin_loader_1.installPlugin)(source);
|
|
829
|
+
console.log((0, i18n_1.t)('plugin.installed', { name, dir }));
|
|
830
|
+
}
|
|
831
|
+
catch (e) {
|
|
832
|
+
console.error((0, i18n_1.t)('plugin.installFailed') + (e instanceof Error ? e.message : String(e)));
|
|
833
|
+
process.exitCode = 1;
|
|
834
|
+
}
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
// list
|
|
838
|
+
const plugins = (0, plugin_loader_1.listPlugins)(process.cwd());
|
|
839
|
+
if (plugins.length === 0) {
|
|
840
|
+
console.log((0, i18n_1.t)('plugin.empty'));
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
console.log((0, i18n_1.t)('plugin.listTitle'));
|
|
844
|
+
for (const p of plugins) {
|
|
845
|
+
console.log(` ${p.name.padEnd(24)} v${p.version} ${p.description ?? ''}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
/* ===================== P4-2:Agent teams ===================== */
|
|
849
|
+
/** fhcode team "<目标>":多 agent 协作执行(共享任务清单 + 消息总线) */
|
|
850
|
+
async function runTeamCmd(goal) {
|
|
851
|
+
const offline = isOfflineByDefault();
|
|
852
|
+
console.log((0, i18n_1.t)('team.start', { mode: offline ? (0, i18n_1.t)('run.modeOffline') : (0, i18n_1.t)('run.modeLive') }));
|
|
853
|
+
// 目标拆解为任务清单(复用 planner 的并列连词拆分;拆不开则单任务)
|
|
854
|
+
const { decomposeGoal } = await Promise.resolve().then(() => __importStar(require('../agent/planner')));
|
|
855
|
+
const tasks = decomposeGoal(goal).map((t) => t.goal);
|
|
856
|
+
if (tasks.length === 0)
|
|
857
|
+
tasks.push(goal);
|
|
858
|
+
const report = await (0, team_1.runTeam)(tasks, {
|
|
859
|
+
runSubTask: async (focusedGoal) => {
|
|
860
|
+
const result = await executeTask(focusedGoal, { offline });
|
|
861
|
+
return { ok: result.ok, finalAnswer: result.finalAnswer, iterations: result.iterations };
|
|
862
|
+
},
|
|
863
|
+
pollIntervalMs: offline ? 0 : 100,
|
|
864
|
+
});
|
|
865
|
+
console.log('\n' + (0, i18n_1.t)('team.reportTitle'));
|
|
866
|
+
console.log(report.summary);
|
|
867
|
+
}
|
|
868
|
+
/* ===================== Skills 市场 ===================== */
|
|
869
|
+
/** 默认市场源(agentskills.io 官方规范端点;可用 --repo 或 FH_SKILL_MARKET 覆盖) */
|
|
870
|
+
const DEFAULT_MARKET = process.env.FH_SKILL_MARKET || 'https://agentskills.io';
|
|
871
|
+
/** fhcode skill-market search <关键词> | install <技能名> | list */
|
|
872
|
+
async function runSkillMarketCmd(action, query, market) {
|
|
873
|
+
const base = market || DEFAULT_MARKET;
|
|
874
|
+
if (action === 'list') {
|
|
875
|
+
// 列出本地已安装技能(复用技能发现,含打包/仓库/用户级)
|
|
876
|
+
const skills = (0, skill_loader_1.discoverSkills)(process.cwd());
|
|
877
|
+
if (skills.length === 0) {
|
|
878
|
+
console.log((0, i18n_1.t)('skillMarket.localEmpty'));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
console.log((0, i18n_1.t)('skillMarket.localTitle', { n: skills.length }));
|
|
882
|
+
for (const s of skills) {
|
|
883
|
+
console.log(` ${s.name.padEnd(24)} ${s.description.slice(0, 60)}`);
|
|
884
|
+
}
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
// search / install 需联网拉索引
|
|
888
|
+
let index;
|
|
889
|
+
try {
|
|
890
|
+
index = await (0, skill_market_1.fetchMarketIndex)(base);
|
|
891
|
+
}
|
|
892
|
+
catch (e) {
|
|
893
|
+
console.error((0, i18n_1.t)('skillMarket.fetchFailed', { base }) + (e instanceof Error ? e.message : String(e)));
|
|
894
|
+
process.exitCode = 1;
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (!(0, skill_market_1.isSchemaSupported)(index.schema)) {
|
|
898
|
+
console.warn((0, i18n_1.t)('skillMarket.schemaWarn', { schema: index.schema ?? '?' }));
|
|
899
|
+
}
|
|
900
|
+
if (action === 'search') {
|
|
901
|
+
const results = (0, skill_market_1.searchMarket)(index, query ?? '');
|
|
902
|
+
if (results.length === 0) {
|
|
903
|
+
console.log((0, i18n_1.t)('skillMarket.searchEmpty', { q: query ?? '' }));
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
console.log((0, i18n_1.t)('skillMarket.searchTitle', { q: query ?? '', n: results.length, base }));
|
|
907
|
+
for (const s of results) {
|
|
908
|
+
console.log(` ${s.name.padEnd(28)} [${s.type}] ${s.description.slice(0, 70)}`);
|
|
909
|
+
}
|
|
910
|
+
console.log((0, i18n_1.t)('skillMarket.installHint'));
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
// install
|
|
914
|
+
if (!query) {
|
|
915
|
+
console.error((0, i18n_1.t)('skillMarket.installUsage'));
|
|
916
|
+
process.exitCode = 1;
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const skill = index.skills.find((s) => s.name === query);
|
|
920
|
+
if (!skill) {
|
|
921
|
+
console.error((0, i18n_1.t)('skillMarket.notFound', { name: query }));
|
|
922
|
+
process.exitCode = 1;
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const destDir = (0, path_1.join)((0, config_1.resolveHomeDir)(), 'skills');
|
|
926
|
+
try {
|
|
927
|
+
const target = await (0, skill_market_1.installMarketSkill)(index, skill, destDir);
|
|
928
|
+
console.log((0, i18n_1.t)('skillMarket.installed', { name: skill.name, dir: target }));
|
|
929
|
+
}
|
|
930
|
+
catch (e) {
|
|
931
|
+
console.error((0, i18n_1.t)('skillMarket.installFailed') + (e instanceof Error ? e.message : String(e)));
|
|
932
|
+
process.exitCode = 1;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
500
935
|
/** fhcode model-stats:显示各模型性能统计 */
|
|
501
936
|
function runModelStats() {
|
|
502
|
-
const homeDir = (0,
|
|
937
|
+
const homeDir = (0, config_1.resolveHomeDir)();
|
|
503
938
|
const statsFile = (0, path_1.join)(homeDir, 'model-stats.jsonl');
|
|
504
939
|
if (!(0, fs_1.existsSync)(statsFile)) {
|
|
505
|
-
console.log('
|
|
940
|
+
console.log((0, i18n_1.t)('modelStats.empty'));
|
|
506
941
|
return;
|
|
507
942
|
}
|
|
508
943
|
const router = new model_router_1.ModelRouter([], 'cost', 0, statsFile);
|
|
509
944
|
router.loadStats(homeDir).then(() => {
|
|
510
945
|
const stats = router.getStats();
|
|
511
946
|
if (stats.length === 0) {
|
|
512
|
-
console.log('
|
|
947
|
+
console.log((0, i18n_1.t)('modelStats.noRecords'));
|
|
513
948
|
return;
|
|
514
949
|
}
|
|
515
|
-
console.log('
|
|
516
|
-
console.log('
|
|
950
|
+
console.log((0, i18n_1.t)('modelStats.title'));
|
|
951
|
+
console.log((0, i18n_1.t)('modelStats.tableHeader'));
|
|
517
952
|
for (const s of stats) {
|
|
518
953
|
console.log(` ${s.providerId.padEnd(16)} ${s.model.padEnd(18)} ${String(s.totalCalls).padStart(5)} ${String(s.successfulCalls).padStart(5)} ${String(s.failedCalls).padStart(5)} ${s.successRate.toFixed(2).padStart(6)} ${s.avgLatencyMs.toFixed(0).padStart(8)}ms $${s.totalCostUsd.toFixed(6)}`);
|
|
519
954
|
}
|
|
@@ -521,19 +956,19 @@ function runModelStats() {
|
|
|
521
956
|
}
|
|
522
957
|
/** fhcode experiences [路径]:列出经验库 */
|
|
523
958
|
function runExperiences(path) {
|
|
524
|
-
const experienceDir = path || (0, path_1.join)(
|
|
959
|
+
const experienceDir = path || (0, path_1.join)((0, config_1.resolveHomeDir)(), 'experiences');
|
|
525
960
|
(0, experience_1.listExperiences)(experienceDir).then((experiences) => {
|
|
526
961
|
if (experiences.length === 0) {
|
|
527
|
-
console.log('
|
|
962
|
+
console.log((0, i18n_1.t)('exp.empty'));
|
|
528
963
|
return;
|
|
529
964
|
}
|
|
530
|
-
console.log(
|
|
531
|
-
console.log('
|
|
965
|
+
console.log((0, i18n_1.t)('exp.header', { n: experiences.length, dir: experienceDir }));
|
|
966
|
+
console.log((0, i18n_1.t)('exp.tableHeader'));
|
|
532
967
|
for (const exp of experiences.slice(0, 10)) {
|
|
533
968
|
console.log(` ${exp.id.padEnd(30)} ${exp.type.padEnd(16)} ${exp.title.slice(0, 25).padEnd(25)} ${(exp.metadata.successRate * 100).toFixed(0).padStart(4)}% ${String(exp.metadata.sessionCount).padStart(4)}`);
|
|
534
969
|
}
|
|
535
970
|
if (experiences.length > 10) {
|
|
536
|
-
console.log(
|
|
971
|
+
console.log((0, i18n_1.t)('exp.more', { n: experiences.length }));
|
|
537
972
|
}
|
|
538
973
|
});
|
|
539
974
|
}
|
|
@@ -541,9 +976,11 @@ function runExperiences(path) {
|
|
|
541
976
|
/** fhcode serve:启动 Web 管理控制台。无 FH_WEB_PORT 用 8080;无 FH_WEB_TOKEN 自动生成。 */
|
|
542
977
|
function runServe(port) {
|
|
543
978
|
const handle = (0, server_1.startWebServer)({ port });
|
|
544
|
-
console.log(
|
|
545
|
-
console.log(
|
|
546
|
-
console.log(
|
|
979
|
+
console.log((0, i18n_1.t)('serve.url', { url: handle.url }));
|
|
980
|
+
console.log((0, i18n_1.t)('serve.token', { token: handle.token }));
|
|
981
|
+
console.log((0, i18n_1.t)('serve.stop'));
|
|
982
|
+
// 启动每日记忆总结定时器(每天 00:00 UTC)
|
|
983
|
+
(0, auto_summarize_1.scheduleDailySummary)();
|
|
547
984
|
// 注意:app.listen 保持事件循环运行,进程持续存活直到收到 SIGINT;本函数返回后 main() 结束不影响服务。
|
|
548
985
|
}
|
|
549
986
|
/* ===================== M8:自主编程能力 ===================== */
|
|
@@ -586,9 +1023,9 @@ export function calculateTieredCommission(plan: CommissionPlan, amount: number):
|
|
|
586
1023
|
}
|
|
587
1024
|
`;
|
|
588
1025
|
const result = await writer.run(goal, sampleCode, 'generated/commission.ts');
|
|
589
|
-
console.log(
|
|
1026
|
+
console.log('\n' + (0, i18n_1.t)('codewrite.resultTitle'));
|
|
590
1027
|
console.log(result.summary);
|
|
591
|
-
console.log(
|
|
1028
|
+
console.log((0, i18n_1.t)('codewrite.files', { files: result.finalFiles.join(', ') }));
|
|
592
1029
|
}
|
|
593
1030
|
/** fhcode quality-gate [路径]:质量门禁审查 */
|
|
594
1031
|
function runQualityGate(path) {
|
|
@@ -598,30 +1035,44 @@ function runQualityGate(path) {
|
|
|
598
1035
|
console.log(gate.report(results));
|
|
599
1036
|
const failed = results.filter((r) => !r.passed);
|
|
600
1037
|
if (failed.length > 0) {
|
|
601
|
-
console.log(
|
|
1038
|
+
console.log('\n' + (0, i18n_1.t)('quality.failed', { n: failed.length }));
|
|
602
1039
|
}
|
|
603
1040
|
}
|
|
604
|
-
/** fhcode self-improve:自我改进统计 */
|
|
605
|
-
function runSelfImprove() {
|
|
1041
|
+
/** fhcode self-improve:自我改进统计 + 经验库概览 + 学习提示预览 */
|
|
1042
|
+
async function runSelfImprove() {
|
|
606
1043
|
const improver = (0, self_improver_1.createSelfImprover)();
|
|
607
1044
|
const records = improver.loadImprovements();
|
|
608
1045
|
const stats = improver.getStats();
|
|
609
|
-
console.log(
|
|
610
|
-
console.log(
|
|
611
|
-
console.log(
|
|
612
|
-
console.log(
|
|
1046
|
+
console.log((0, i18n_1.t)('selfimp.title'));
|
|
1047
|
+
console.log((0, i18n_1.t)('selfimp.reflections', { n: stats.totalReflections }));
|
|
1048
|
+
console.log((0, i18n_1.t)('selfimp.successRate', { p: (stats.successRate * 100).toFixed(1) }));
|
|
1049
|
+
console.log((0, i18n_1.t)('selfimp.avgDuration', { ms: stats.avgDurationMs.toFixed(0) }));
|
|
1050
|
+
// 经验库概览(与 orchestrator 共用同一库,体现回流闭环)
|
|
1051
|
+
const exps = await (0, experience_1.listExperiences)(improver.experienceStoreDir);
|
|
1052
|
+
const totalWeight = exps.reduce((s, e) => s + e.metadata.sessionCount, 0);
|
|
1053
|
+
console.log('\n' + (0, i18n_1.t)('selfimp.expLib', { n: exps.length, w: totalWeight }));
|
|
1054
|
+
if (exps.length > 0) {
|
|
1055
|
+
console.log('\n' + (0, i18n_1.t)('selfimp.topExp'));
|
|
1056
|
+
for (const e of exps.slice(0, 6)) {
|
|
1057
|
+
console.log((0, i18n_1.t)('selfimp.expItem', { count: e.metadata.sessionCount, type: e.type, title: e.title }));
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
613
1060
|
if (records.length > 0) {
|
|
614
|
-
console.log(
|
|
1061
|
+
console.log('\n' + (0, i18n_1.t)('selfimp.recent'));
|
|
615
1062
|
for (const rec of records.slice(-5).reverse()) {
|
|
616
|
-
console.log(
|
|
1063
|
+
console.log((0, i18n_1.t)('selfimp.record', { ts: rec.timestamp.slice(0, 19), ok: rec.success ? '✅' : '❌', n: rec.patterns.length }));
|
|
617
1064
|
for (const imp of rec.improvements.slice(0, 3)) {
|
|
618
|
-
console.log(
|
|
1065
|
+
console.log((0, i18n_1.t)('selfimp.improvement', { imp }));
|
|
619
1066
|
}
|
|
620
1067
|
}
|
|
621
1068
|
}
|
|
622
1069
|
else {
|
|
623
|
-
console.log('\n
|
|
1070
|
+
console.log('\n' + (0, i18n_1.t)('selfimp.noRecords'));
|
|
624
1071
|
}
|
|
1072
|
+
// 学习提示预览:模拟一次任务,展示将注入模型的经验
|
|
1073
|
+
console.log('\n' + (0, i18n_1.t)('selfimp.learnPreview', { goal: '实现一个 REST API 功能' }));
|
|
1074
|
+
const learned = await improver.getLearnedPrompt('实现一个 REST API 功能');
|
|
1075
|
+
console.log(learned || (0, i18n_1.t)('selfimp.noLearned'));
|
|
625
1076
|
}
|
|
626
1077
|
/**
|
|
627
1078
|
* fhcode swe "<目标>":全自动软件工程 Agent
|
|
@@ -643,11 +1094,7 @@ async function runSwe(goal, opts = {}) {
|
|
|
643
1094
|
if (!offline) {
|
|
644
1095
|
const cfg = (0, config_1.loadConfig)();
|
|
645
1096
|
if (!cfg.models.providers.length) {
|
|
646
|
-
console.error(
|
|
647
|
-
' 1) 环境变量: FH_MODEL_NAME=<模型> FH_MODEL_TYPE=ollama|openai-compatible FH_MODEL_BASE_URL=<地址> [FH_MODEL_API_KEY=<令牌>]\n' +
|
|
648
|
-
' 2) 配置文件: ./fhcode.config.json 的 models.providers 数组\n' +
|
|
649
|
-
' 3) FH_PROVIDERS 环境变量(JSON 数组)\n' +
|
|
650
|
-
'本地 Ollama 示例: FH_MODEL_NAME=qwen2.5-coder:1.5b FH_MODEL_TYPE=ollama fhcode swe "..."');
|
|
1097
|
+
console.error((0, i18n_1.t)('swe.noProvider'));
|
|
651
1098
|
return;
|
|
652
1099
|
}
|
|
653
1100
|
}
|
|
@@ -664,8 +1111,17 @@ async function runSwe(goal, opts = {}) {
|
|
|
664
1111
|
router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
665
1112
|
security.shellAllowlist = cfg.security.shellAllowlist;
|
|
666
1113
|
security.requireApproval = cfg.security.requireApproval;
|
|
1114
|
+
security.sandboxMode = cfg.security.sandboxMode;
|
|
1115
|
+
security.networkRules = { networkAllow: cfg.security.networkAllow, networkDeny: cfg.security.networkDeny };
|
|
1116
|
+
security.hooks = cfg.hooks;
|
|
667
1117
|
}
|
|
668
1118
|
const tools = (0, tools_1.createDefaultRegistry)();
|
|
1119
|
+
// P0-3:附加 MCP 外部工具(真实模式且有配置时),子任务结束即关闭
|
|
1120
|
+
let mcpClients = [];
|
|
1121
|
+
if (!offline) {
|
|
1122
|
+
const cfg = (0, config_1.loadConfig)();
|
|
1123
|
+
mcpClients = await (0, mcp_1.attachMcpTools)(tools, cfg.mcp.servers);
|
|
1124
|
+
}
|
|
669
1125
|
const logDir = getSessionHome(offline);
|
|
670
1126
|
const eventLog = new event_log_1.EventLog(runId, logDir);
|
|
671
1127
|
const session = new session_store_1.SessionStore(runId, cwd);
|
|
@@ -682,19 +1138,24 @@ async function runSwe(goal, opts = {}) {
|
|
|
682
1138
|
security,
|
|
683
1139
|
approve,
|
|
684
1140
|
guard,
|
|
685
|
-
maxIterations: opts.maxIterations ??
|
|
1141
|
+
maxIterations: opts.maxIterations ?? 15,
|
|
686
1142
|
maxCostUsd: rt?.maxCostUsd ?? 0,
|
|
1143
|
+
// P1-1:子任务用低成本模型分担(编排器主模型保持 code-gen,worker 加 cheap 优先)
|
|
1144
|
+
tags: ['code-gen', 'cheap'],
|
|
687
1145
|
persist: (cp) => (0, session_persist_1.saveCheckpoint)(logDir, cp),
|
|
688
1146
|
});
|
|
689
1147
|
const result = await orchestrator.run(focusedGoal);
|
|
1148
|
+
await (0, mcp_1.closeMcpClients)(mcpClients);
|
|
1149
|
+
// P2-2:子代理结果摘要化回主上下文(隔离中间大输出)
|
|
1150
|
+
const summarized = (0, subagent_summary_1.summarizeSubTaskAnswer)(result.finalAnswer);
|
|
690
1151
|
return {
|
|
691
1152
|
ok: result.ok,
|
|
692
|
-
finalAnswer:
|
|
1153
|
+
finalAnswer: summarized.text,
|
|
693
1154
|
iterations: result.iterations,
|
|
694
1155
|
touchedFiles: [],
|
|
695
1156
|
};
|
|
696
1157
|
};
|
|
697
|
-
console.log(
|
|
1158
|
+
console.log((0, i18n_1.t)('swe.start', { offline: offline ? (0, i18n_1.t)('run.modeOffline') : (0, i18n_1.t)('run.modeLive'), cwd }));
|
|
698
1159
|
const report = await (0, swe_agent_1.runSweAgent)(goal, {
|
|
699
1160
|
cwd,
|
|
700
1161
|
runSubTask,
|
|
@@ -703,7 +1164,7 @@ async function runSwe(goal, opts = {}) {
|
|
|
703
1164
|
verifyOnly: !!opts.verifyOnly,
|
|
704
1165
|
planOnly: !!opts.planOnly,
|
|
705
1166
|
});
|
|
706
|
-
console.log('\n
|
|
1167
|
+
console.log('\n' + (0, i18n_1.t)('swe.reportTitle'));
|
|
707
1168
|
console.log(report.summary);
|
|
708
1169
|
if (rt) {
|
|
709
1170
|
rt.audit.record({
|
|
@@ -718,10 +1179,54 @@ async function runSwe(goal, opts = {}) {
|
|
|
718
1179
|
});
|
|
719
1180
|
}
|
|
720
1181
|
}
|
|
1182
|
+
/** fhcode harness [--split lite|verified] [--limit N] [--offset N] [--mode mock|real] [--report 路径] [--json] */
|
|
1183
|
+
async function runHarness(opts) {
|
|
1184
|
+
console.log((0, i18n_1.t)('harness.start', { mode: opts.mode, split: opts.split, limit: String(opts.limit) }));
|
|
1185
|
+
// 真实模式就绪检查:未配置任何模型供应商时给出明确接入指引
|
|
1186
|
+
if (opts.mode === 'real') {
|
|
1187
|
+
const cfg = (0, config_1.loadConfig)();
|
|
1188
|
+
if (!cfg.models.providers.length) {
|
|
1189
|
+
console.error((0, i18n_1.t)('harness.noProvider'));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
const loader = new loader_1.SwebenchLoader({ split: opts.split });
|
|
1194
|
+
const executor = opts.mode === 'real' ? new executor_1.RealModelExecutor() : new executor_1.MockOrchestratorExecutor();
|
|
1195
|
+
const verifier = new verifier_1.FileExistsVerifier();
|
|
1196
|
+
const harness = new harness_1.Harness({
|
|
1197
|
+
loader,
|
|
1198
|
+
executor,
|
|
1199
|
+
verifier,
|
|
1200
|
+
reporter: opts.json ? new reporter_1.JsonReporter() : new reporter_1.MarkdownReporter(),
|
|
1201
|
+
limit: opts.limit,
|
|
1202
|
+
offset: opts.offset,
|
|
1203
|
+
onProgress: (r, i, total) => {
|
|
1204
|
+
console.log(` [${i}/${total}] ${r.ok ? '✅' : '❌'} ${r.instance_id} iter=${r.iterations} tools=${r.toolCalls}`);
|
|
1205
|
+
},
|
|
1206
|
+
});
|
|
1207
|
+
const { report, rendered } = await harness.run();
|
|
1208
|
+
if (opts.report) {
|
|
1209
|
+
(0, fs_1.writeFileSync)(opts.report, rendered, 'utf8');
|
|
1210
|
+
console.log((0, i18n_1.t)('harness.reportWritten', { path: opts.report }));
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
console.log('\n' + rendered);
|
|
1214
|
+
}
|
|
1215
|
+
console.log((0, i18n_1.t)('harness.summary', {
|
|
1216
|
+
completed: String(report.summary.completed),
|
|
1217
|
+
total: String(report.summary.total),
|
|
1218
|
+
rate: String(Math.round(report.summary.rate * 100)),
|
|
1219
|
+
}));
|
|
1220
|
+
// 有失败实例 → 退出码非零(供 CI 门禁复用)
|
|
1221
|
+
if (report.summary.completed < report.summary.total)
|
|
1222
|
+
process.exitCode = 1;
|
|
1223
|
+
console.log((0, i18n_1.t)('app.signature'));
|
|
1224
|
+
}
|
|
721
1225
|
/* ===================== 审批器 ===================== */
|
|
722
1226
|
function expandHome(p) {
|
|
1227
|
+
// 统一用 os.homedir()(Windows 上 process.env.HOME 可能缺失,导致 ~ 展开为空路径)
|
|
723
1228
|
if (p.startsWith('~'))
|
|
724
|
-
return (0, path_1.join)(
|
|
1229
|
+
return (0, path_1.join)((0, os_1.homedir)(), p.slice(1));
|
|
725
1230
|
return p;
|
|
726
1231
|
}
|
|
727
1232
|
function joinHome() {
|
|
@@ -752,7 +1257,7 @@ function defaultApproverFor(security) {
|
|
|
752
1257
|
function interactiveApprover() {
|
|
753
1258
|
return (action) => new Promise((resolve) => {
|
|
754
1259
|
const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
755
|
-
const ask = () => rl.question(
|
|
1260
|
+
const ask = () => rl.question((0, i18n_1.t)('approve.prompt', { action }), (ans) => {
|
|
756
1261
|
rl.close();
|
|
757
1262
|
resolve(/^(y|yes|是)$/i.test(ans.trim()));
|
|
758
1263
|
});
|