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