feihong-code 0.2.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/ISSUE_TEMPLATE/bug_report.md +37 -0
- package/.github/ISSUE_TEMPLATE/config.yml +14 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +28 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/SECURITY.md +67 -0
- package/.github/workflows/ci.yml +140 -0
- package/AGENT-GUIDE.md +237 -0
- package/CHANGELOG.md +73 -0
- package/CODE_OF_CONDUCT.md +56 -0
- package/CONTRIBUTING.md +68 -0
- package/LICENSE +22 -0
- package/README.md +500 -0
- package/dist/agent/code-review.js +115 -0
- package/dist/agent/code-review.js.map +1 -0
- package/dist/agent/code-writer.js +227 -0
- package/dist/agent/code-writer.js.map +1 -0
- package/dist/agent/context-compactor.js +108 -0
- package/dist/agent/context-compactor.js.map +1 -0
- package/dist/agent/experience.js +190 -0
- package/dist/agent/experience.js.map +1 -0
- package/dist/agent/orchestrator.js +212 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/parallel-orchestrator.js +94 -0
- package/dist/agent/parallel-orchestrator.js.map +1 -0
- package/dist/agent/planner.js +53 -0
- package/dist/agent/planner.js.map +1 -0
- package/dist/agent/prompts.js +26 -0
- package/dist/agent/prompts.js.map +1 -0
- package/dist/agent/quality-gate.js +143 -0
- package/dist/agent/quality-gate.js.map +1 -0
- package/dist/agent/repo-reader.js +384 -0
- package/dist/agent/repo-reader.js.map +1 -0
- package/dist/agent/repo-underwriter.js +132 -0
- package/dist/agent/repo-underwriter.js.map +1 -0
- package/dist/agent/self-heal.js +134 -0
- package/dist/agent/self-heal.js.map +1 -0
- package/dist/agent/self-improver.js +148 -0
- package/dist/agent/self-improver.js.map +1 -0
- package/dist/agent/subagent.js +75 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/swe-agent.js +162 -0
- package/dist/agent/swe-agent.js.map +1 -0
- package/dist/agent/swe-planner.js +155 -0
- package/dist/agent/swe-planner.js.map +1 -0
- package/dist/agent/swe-verifier.js +113 -0
- package/dist/agent/swe-verifier.js.map +1 -0
- package/dist/cli/commands.js +167 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/index.js +179 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/repl.js +67 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli/run.js +762 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/version.js +14 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/enterprise/audit.js +294 -0
- package/dist/enterprise/audit.js.map +1 -0
- package/dist/enterprise/guard.js +58 -0
- package/dist/enterprise/guard.js.map +1 -0
- package/dist/enterprise/index.js +99 -0
- package/dist/enterprise/index.js.map +1 -0
- package/dist/enterprise/policy.js +242 -0
- package/dist/enterprise/policy.js.map +1 -0
- package/dist/enterprise/quota.js +58 -0
- package/dist/enterprise/quota.js.map +1 -0
- package/dist/enterprise/tenant.js +143 -0
- package/dist/enterprise/tenant.js.map +1 -0
- package/dist/models/cost.js +13 -0
- package/dist/models/cost.js.map +1 -0
- package/dist/models/model-router.js +170 -0
- package/dist/models/model-router.js.map +1 -0
- package/dist/models/model.dto.js +61 -0
- package/dist/models/model.dto.js.map +1 -0
- package/dist/models/model.interface.js +3 -0
- package/dist/models/model.interface.js.map +1 -0
- package/dist/models/providers/mock.provider.js +35 -0
- package/dist/models/providers/mock.provider.js.map +1 -0
- package/dist/models/providers/ollama.provider.js +88 -0
- package/dist/models/providers/ollama.provider.js.map +1 -0
- package/dist/models/providers/openai-compatible.provider.js +106 -0
- package/dist/models/providers/openai-compatible.provider.js.map +1 -0
- package/dist/runtime/event-log.js +42 -0
- package/dist/runtime/event-log.js.map +1 -0
- package/dist/runtime/git.js +106 -0
- package/dist/runtime/git.js.map +1 -0
- package/dist/runtime/session-persist.js +60 -0
- package/dist/runtime/session-persist.js.map +1 -0
- package/dist/runtime/session-store.js +34 -0
- package/dist/runtime/session-store.js.map +1 -0
- package/dist/runtime/worktree.js +117 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/shared/config.js +184 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/errors.js +66 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/logger.js +51 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/types.js +9 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/skills/goal.js +60 -0
- package/dist/skills/goal.js.map +1 -0
- package/dist/skills/grill.js +129 -0
- package/dist/skills/grill.js.map +1 -0
- package/dist/skills/plan.js +40 -0
- package/dist/skills/plan.js.map +1 -0
- package/dist/tools/analysis/code-analyzer.js +136 -0
- package/dist/tools/analysis/code-analyzer.js.map +1 -0
- package/dist/tools/file/edit.tool.js +43 -0
- package/dist/tools/file/edit.tool.js.map +1 -0
- package/dist/tools/file/list.tool.js +36 -0
- package/dist/tools/file/list.tool.js.map +1 -0
- package/dist/tools/file/read.tool.js +34 -0
- package/dist/tools/file/read.tool.js.map +1 -0
- package/dist/tools/file/write.tool.js +39 -0
- package/dist/tools/file/write.tool.js.map +1 -0
- package/dist/tools/generator/code-generator.js +119 -0
- package/dist/tools/generator/code-generator.js.map +1 -0
- package/dist/tools/generator/test-generator.js +65 -0
- package/dist/tools/generator/test-generator.js.map +1 -0
- package/dist/tools/index.js +53 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/safe-path.js +36 -0
- package/dist/tools/safe-path.js.map +1 -0
- package/dist/tools/search/grep.tool.js +81 -0
- package/dist/tools/search/grep.tool.js.map +1 -0
- package/dist/tools/shell/exec.js +50 -0
- package/dist/tools/shell/exec.js.map +1 -0
- package/dist/tools/shell/run-shell.tool.js +49 -0
- package/dist/tools/shell/run-shell.tool.js.map +1 -0
- package/dist/tools/tool.interface.js +8 -0
- package/dist/tools/tool.interface.js.map +1 -0
- package/dist/tools/tool.registry.js +75 -0
- package/dist/tools/tool.registry.js.map +1 -0
- package/dist/tools/verify/build-check.tool.js +37 -0
- package/dist/tools/verify/build-check.tool.js.map +1 -0
- package/dist/tools/verify/test-run.tool.js +37 -0
- package/dist/tools/verify/test-run.tool.js.map +1 -0
- package/dist/web/auth.js +25 -0
- package/dist/web/auth.js.map +1 -0
- package/dist/web/public/index.html +39 -0
- package/dist/web/server.js +60 -0
- package/dist/web/server.js.map +1 -0
- package/docs//344/272/247/345/223/201/345/274/200/345/217/221/346/226/207/346/241/243.md +614 -0
- 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 -0
- package/docs//344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +358 -0
- 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 -0
- package/docs//346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +303 -0
- package/docs//346/236/266/346/236/204/344/270/216API.md +238 -0
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +196 -0
- package/docs//351/203/250/347/275/262/346/214/207/345/215/227.md +165 -0
- package/docs//351/205/215/347/275/256/345/217/202/350/200/203.md +127 -0
- package/package.json +83 -0
- package/tool-schema.json +127 -0
package/dist/cli/run.js
ADDED
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getEnterprise = getEnterprise;
|
|
4
|
+
exports.runGoal = runGoal;
|
|
5
|
+
exports.isOfflineByDefault = isOfflineByDefault;
|
|
6
|
+
exports.runPlanSkill = runPlanSkill;
|
|
7
|
+
exports.runGrillSkill = runGrillSkill;
|
|
8
|
+
exports.runGoalSkill = runGoalSkill;
|
|
9
|
+
exports.runParallelGoal = runParallelGoal;
|
|
10
|
+
exports.runSessions = runSessions;
|
|
11
|
+
exports.runResume = runResume;
|
|
12
|
+
exports.runDiff = runDiff;
|
|
13
|
+
exports.runRollback = runRollback;
|
|
14
|
+
exports.runWhoami = runWhoami;
|
|
15
|
+
exports.runPolicyCmd = runPolicyCmd;
|
|
16
|
+
exports.runAudit = runAudit;
|
|
17
|
+
exports.runAuditVerify = runAuditVerify;
|
|
18
|
+
exports.runTenants = runTenants;
|
|
19
|
+
exports.runModelStats = runModelStats;
|
|
20
|
+
exports.runExperiences = runExperiences;
|
|
21
|
+
exports.runServe = runServe;
|
|
22
|
+
exports.runCodeWrite = runCodeWrite;
|
|
23
|
+
exports.runQualityGate = runQualityGate;
|
|
24
|
+
exports.runSelfImprove = runSelfImprove;
|
|
25
|
+
exports.runSwe = runSwe;
|
|
26
|
+
exports.defaultApproverFor = defaultApproverFor;
|
|
27
|
+
exports.interactiveApprover = interactiveApprover;
|
|
28
|
+
/**
|
|
29
|
+
* 飞虹 Code (Muse Code 参照复刻)
|
|
30
|
+
* 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
|
|
31
|
+
*
|
|
32
|
+
* 运行装配:把模型路由、工具、运行时、编排器组装成一次任务执行。
|
|
33
|
+
* 无 API key 时自动进入离线模式(ScriptedMockProvider 驱动闭环验证)。
|
|
34
|
+
*
|
|
35
|
+
* M3 增强:会话检查点持久化 + resume/diff/rollback 管理命令 + 交互式审批。
|
|
36
|
+
*/
|
|
37
|
+
const crypto_1 = require("crypto");
|
|
38
|
+
const fs_1 = require("fs");
|
|
39
|
+
const os_1 = require("os");
|
|
40
|
+
const path_1 = require("path");
|
|
41
|
+
const readline_1 = require("readline");
|
|
42
|
+
const logger_1 = require("../shared/logger");
|
|
43
|
+
const config_1 = require("../shared/config");
|
|
44
|
+
const errors_1 = require("../shared/errors");
|
|
45
|
+
const model_router_1 = require("../models/model-router");
|
|
46
|
+
const mock_provider_1 = require("../models/providers/mock.provider");
|
|
47
|
+
const tools_1 = require("../tools");
|
|
48
|
+
const exec_1 = require("../tools/shell/exec");
|
|
49
|
+
const event_log_1 = require("../runtime/event-log");
|
|
50
|
+
const session_store_1 = require("../runtime/session-store");
|
|
51
|
+
const session_persist_1 = require("../runtime/session-persist");
|
|
52
|
+
const git_1 = require("../runtime/git");
|
|
53
|
+
const enterprise_1 = require("../enterprise");
|
|
54
|
+
const server_1 = require("../web/server");
|
|
55
|
+
const orchestrator_1 = require("../agent/orchestrator");
|
|
56
|
+
const parallel_orchestrator_1 = require("../agent/parallel-orchestrator");
|
|
57
|
+
const plan_1 = require("../skills/plan");
|
|
58
|
+
const grill_1 = require("../skills/grill");
|
|
59
|
+
const goal_1 = require("../skills/goal");
|
|
60
|
+
const experience_1 = require("../agent/experience");
|
|
61
|
+
const code_writer_1 = require("../agent/code-writer");
|
|
62
|
+
const quality_gate_1 = require("../agent/quality-gate");
|
|
63
|
+
const self_improver_1 = require("../agent/self-improver");
|
|
64
|
+
const swe_agent_1 = require("../agent/swe-agent");
|
|
65
|
+
/** 离线演示脚本:写文件 → 总结,跑通完整链路 */
|
|
66
|
+
function buildDemoSteps() {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
message: {
|
|
70
|
+
role: 'assistant',
|
|
71
|
+
content: '',
|
|
72
|
+
toolCalls: [
|
|
73
|
+
{
|
|
74
|
+
id: 'call_1',
|
|
75
|
+
name: 'write_file',
|
|
76
|
+
arguments: {
|
|
77
|
+
path: 'demo-output.txt',
|
|
78
|
+
content: '飞虹 Code 离线闭环验证成功。\n需求 → 模型 → 工具执行 → 结果回填 → 总结,全程无需任何 API key。\n',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
message: {
|
|
86
|
+
role: 'assistant',
|
|
87
|
+
content: '已完成:在工作区写入 demo-output.txt,内容为离线闭环验证成功的确认文本。' +
|
|
88
|
+
'本次任务在未配置任何大模型的情况下,跑通了「模型请求 → 工具执行 → 结果回填 → 总结」的完整链路,' +
|
|
89
|
+
'验证 Agent 编排、工具系统、运行时事件日志均已就绪。配置 FH_PROVIDERS 后即可接入真实模型。',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
/* ===================== M4:企业运行时(惰性单例) ===================== */
|
|
95
|
+
let enterpriseRt = null;
|
|
96
|
+
/** 获取企业运行时(租户/策略/审计/配额)。FH_ENTERPRISE=false 时返回 null(退化为 M3 行为) */
|
|
97
|
+
function getEnterprise() {
|
|
98
|
+
if (!(0, enterprise_1.isEnterpriseEnabled)())
|
|
99
|
+
return null;
|
|
100
|
+
if (!enterpriseRt)
|
|
101
|
+
enterpriseRt = (0, enterprise_1.createEnterpriseRuntime)();
|
|
102
|
+
return enterpriseRt;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 会话家目录:与 EventLog 同目录,便于 list/load/resume 统一定位。
|
|
106
|
+
* M4 起按租户隔离;离线且未显式设置 FH_HOME 时仍走临时目录,避免污染用户环境。
|
|
107
|
+
*/
|
|
108
|
+
function getSessionHome(offline) {
|
|
109
|
+
if (offline && !process.env.FH_HOME)
|
|
110
|
+
return (0, path_1.join)((0, os_1.tmpdir)(), 'fhcode-demo-logs');
|
|
111
|
+
const rt = getEnterprise();
|
|
112
|
+
if (rt)
|
|
113
|
+
return rt.tenant.sessionDir;
|
|
114
|
+
const home = process.env.FH_HOME ? expandHome(process.env.FH_HOME) : joinHome();
|
|
115
|
+
return (0, path_1.join)(home, 'sessions');
|
|
116
|
+
}
|
|
117
|
+
async function runGoal(goal, opts = {}) {
|
|
118
|
+
const runId = (0, crypto_1.randomUUID)();
|
|
119
|
+
(0, logger_1.setRunId)(runId);
|
|
120
|
+
const security = { shellAllowlist: [], requireApproval: true };
|
|
121
|
+
const offline = opts.offline ?? isOfflineByDefault();
|
|
122
|
+
// M4:企业上下文(租户隔离 / RBAC / 审计 / 配额),配额超限在此 fail-fast
|
|
123
|
+
const rt = getEnterprise();
|
|
124
|
+
if (rt)
|
|
125
|
+
(0, enterprise_1.assertQuota)(rt);
|
|
126
|
+
// 离线模式用临时工作区,避免污染用户目录;并 git init 以支持 diff/rollback 演示
|
|
127
|
+
const cwd = offline ? (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'fhcode-demo-')) : process.cwd();
|
|
128
|
+
if (offline) {
|
|
129
|
+
await (0, exec_1.runCommand)('git init -q', cwd).catch(() => undefined);
|
|
130
|
+
}
|
|
131
|
+
let router;
|
|
132
|
+
if (offline) {
|
|
133
|
+
router = new model_router_1.ModelRouter([new mock_provider_1.ScriptedMockProvider(buildDemoSteps())], 'cost', 0);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const cfg = (0, config_1.loadConfig)();
|
|
137
|
+
router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
138
|
+
security.shellAllowlist = cfg.security.shellAllowlist;
|
|
139
|
+
security.requireApproval = cfg.security.requireApproval;
|
|
140
|
+
}
|
|
141
|
+
const tools = (0, tools_1.createDefaultRegistry)();
|
|
142
|
+
const logDir = getSessionHome(offline);
|
|
143
|
+
const eventLog = new event_log_1.EventLog(runId, logDir);
|
|
144
|
+
const session = new session_store_1.SessionStore(runId, cwd);
|
|
145
|
+
const approve = opts.approve ??
|
|
146
|
+
(process.stdin.isTTY ? interactiveApprover() : defaultApproverFor(security));
|
|
147
|
+
const guard = rt
|
|
148
|
+
? rt.makeGuard({ runId, cwd, shellAllowlist: security.shellAllowlist, approve })
|
|
149
|
+
: undefined;
|
|
150
|
+
const orchestrator = new orchestrator_1.Orchestrator({
|
|
151
|
+
router,
|
|
152
|
+
tools,
|
|
153
|
+
eventLog,
|
|
154
|
+
session,
|
|
155
|
+
cwd,
|
|
156
|
+
security,
|
|
157
|
+
approve,
|
|
158
|
+
guard,
|
|
159
|
+
maxCostUsd: rt?.maxCostUsd ?? 0,
|
|
160
|
+
persist: (cp) => (0, session_persist_1.saveCheckpoint)(logDir, cp),
|
|
161
|
+
});
|
|
162
|
+
if (rt) {
|
|
163
|
+
console.log(`[飞虹 Code] 身份 tenant=${rt.tenant.tenantId} user=${rt.tenant.userId} role=${rt.tenant.role}`);
|
|
164
|
+
rt.audit.record({
|
|
165
|
+
tenantId: rt.tenant.tenantId,
|
|
166
|
+
userId: rt.tenant.userId,
|
|
167
|
+
role: rt.tenant.role,
|
|
168
|
+
runId,
|
|
169
|
+
action: 'session:start',
|
|
170
|
+
resource: goal,
|
|
171
|
+
decision: 'info',
|
|
172
|
+
reason: offline ? '离线模式' : '真实模式',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
console.log(`[飞虹 Code] 开始任务 (runId=${runId.slice(0, 8)}${offline ? ', 离线模式' : ''})`);
|
|
176
|
+
const result = await orchestrator.run(goal);
|
|
177
|
+
if (rt) {
|
|
178
|
+
rt.audit.record({
|
|
179
|
+
tenantId: rt.tenant.tenantId,
|
|
180
|
+
userId: rt.tenant.userId,
|
|
181
|
+
role: rt.tenant.role,
|
|
182
|
+
runId,
|
|
183
|
+
action: 'session:end',
|
|
184
|
+
resource: `iterations=${result.iterations}`,
|
|
185
|
+
decision: 'info',
|
|
186
|
+
reason: `cost=$${result.costUsd.toFixed(6)}`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
console.log('\n===== 执行结果 =====');
|
|
190
|
+
console.log(result.finalAnswer);
|
|
191
|
+
console.log(`\n迭代 ${result.iterations} 次 · 成本 $${result.costUsd.toFixed(6)} · 日志 ${result.logFile}`);
|
|
192
|
+
console.log(`会话检查点: ${(0, path_1.join)(logDir, `${runId}.session.json`)}(可用 fhcode sessions / resume / diff 管理)`);
|
|
193
|
+
if (offline) {
|
|
194
|
+
// 演示文件可能因策略拒绝而未生成,据实播报,避免误导
|
|
195
|
+
const demoFile = (0, path_1.join)(cwd, 'demo-output.txt');
|
|
196
|
+
logger_1.logger.info('offline-run done', { cwd, demoFile });
|
|
197
|
+
console.log((0, fs_1.existsSync)(demoFile)
|
|
198
|
+
? `(离线模式演示文件已写入: ${demoFile})`
|
|
199
|
+
: `(离线模式未产生演示文件,工作区: ${cwd};如为策略拒绝可用 fhcode audit 查看原因)`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** 默认是否离线:未配置 FH_PROVIDERS 或为空数组时离线 */
|
|
203
|
+
function isOfflineByDefault() {
|
|
204
|
+
// M9.1 实测修复:此前仅看 FH_PROVIDERS,导致文档示例「单环境变量快速接入」
|
|
205
|
+
// (FH_MODEL_NAME=... FH_MODEL_TYPE=ollama)实际仍落入离线 mock,真实模型接不进来。
|
|
206
|
+
if (process.env.FH_OFFLINE === 'true')
|
|
207
|
+
return true;
|
|
208
|
+
if (process.env.FH_OFFLINE === 'false')
|
|
209
|
+
return false;
|
|
210
|
+
// 任一真实模型接入方式存在即进入真实模式
|
|
211
|
+
if (process.env.FH_PROVIDERS || process.env.FH_MODEL_NAME)
|
|
212
|
+
return false;
|
|
213
|
+
try {
|
|
214
|
+
const cfg = (0, config_1.loadConfigFile)();
|
|
215
|
+
if (cfg?.models?.providers && cfg.models.providers.length > 0)
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
/* 配置文件缺失/损坏忽略 */
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
/* ===================== M2:技能与并行入口 ===================== */
|
|
224
|
+
/** /plan 技能:生成结构化实现计划(只读,不修改代码) */
|
|
225
|
+
function runPlanSkill(goal) {
|
|
226
|
+
const out = (0, plan_1.runPlan)(goal);
|
|
227
|
+
const lines = [
|
|
228
|
+
`【/plan】目标: ${out.goal}`,
|
|
229
|
+
`预计并行工作树: ${out.estimatedWorktrees}`,
|
|
230
|
+
`步骤:`,
|
|
231
|
+
...out.items.map((it) => ` ${it.step}. ${it.action}\n 目标: ${it.target} | 风险: ${it.risk}`),
|
|
232
|
+
`备注: ${out.note}`,
|
|
233
|
+
];
|
|
234
|
+
return lines.join('\n');
|
|
235
|
+
}
|
|
236
|
+
/** /grill 技能:红队式代码审查(只读) */
|
|
237
|
+
function runGrillSkill(target) {
|
|
238
|
+
const result = (0, grill_1.runGrill)(process.cwd(), target || '.');
|
|
239
|
+
const lines = [
|
|
240
|
+
`【/grill】${result.summary}`,
|
|
241
|
+
...result.findings.map((f) => ` [${f.severity.toUpperCase()}] ${f.file}:${f.line} (${f.rule}) ${f.detail}`),
|
|
242
|
+
result.findings.length === 0 ? ' 未发现明显问题。' : '',
|
|
243
|
+
];
|
|
244
|
+
return lines.filter(Boolean).join('\n');
|
|
245
|
+
}
|
|
246
|
+
/** /goal 技能:分解并保存高层目标(M4 起写入租户隔离目录 tenants/<id>/goals) */
|
|
247
|
+
function runGoalSkill(title) {
|
|
248
|
+
const goal = (0, goal_1.decomposeGoalToGoal)(title);
|
|
249
|
+
const rt = getEnterprise();
|
|
250
|
+
// saveGoal 内部会拼接 goals 子目录,这里给它租户根目录
|
|
251
|
+
const home = rt
|
|
252
|
+
? (0, path_1.dirname)(rt.tenant.goalDir)
|
|
253
|
+
: process.env.FH_HOME
|
|
254
|
+
? expandHome(process.env.FH_HOME)
|
|
255
|
+
: joinHome();
|
|
256
|
+
const file = (0, goal_1.saveGoal)(goal, home);
|
|
257
|
+
if (rt) {
|
|
258
|
+
rt.audit.record({
|
|
259
|
+
tenantId: rt.tenant.tenantId,
|
|
260
|
+
userId: rt.tenant.userId,
|
|
261
|
+
role: rt.tenant.role,
|
|
262
|
+
runId: goal.id,
|
|
263
|
+
action: 'skill:goal',
|
|
264
|
+
resource: title,
|
|
265
|
+
decision: 'info',
|
|
266
|
+
reason: `保存至 ${file}`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return `【/goal】已保存\n${(0, goal_1.renderGoal)(goal)}\n文件: ${file}`;
|
|
270
|
+
}
|
|
271
|
+
/** --parallel 并行多子代理执行(离线用 Mock;真实模式接入 FH_PROVIDERS 路由) */
|
|
272
|
+
async function runParallelGoal(goal) {
|
|
273
|
+
const offline = isOfflineByDefault();
|
|
274
|
+
console.log(`[飞虹 Code] 并行模式 (offline=${offline})`);
|
|
275
|
+
if (!offline) {
|
|
276
|
+
const cfg = (0, config_1.loadConfig)();
|
|
277
|
+
const router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
278
|
+
const security = {
|
|
279
|
+
shellAllowlist: cfg.security.shellAllowlist,
|
|
280
|
+
requireApproval: cfg.security.requireApproval,
|
|
281
|
+
};
|
|
282
|
+
const result = await (0, parallel_orchestrator_1.runParallel)(goal, {
|
|
283
|
+
offline: false,
|
|
284
|
+
router,
|
|
285
|
+
approve: defaultApproverFor(security),
|
|
286
|
+
});
|
|
287
|
+
console.log('\n===== 并行执行结果 =====');
|
|
288
|
+
console.log(result.summary);
|
|
289
|
+
console.log(`仓库根: ${result.repoRoot} · 工作树已清理: ${result.worktrees.length}`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const result = await (0, parallel_orchestrator_1.runParallel)(goal, {
|
|
293
|
+
offline: true,
|
|
294
|
+
mockFor: (task) => (0, parallel_orchestrator_1.defaultParallelMock)(task),
|
|
295
|
+
});
|
|
296
|
+
console.log('\n===== 并行执行结果 =====');
|
|
297
|
+
console.log(result.summary);
|
|
298
|
+
console.log(`仓库根: ${result.repoRoot} · 工作树已清理: ${result.worktrees.length}`);
|
|
299
|
+
}
|
|
300
|
+
/* ===================== M3:会话管理(resume / diff / rollback) ===================== */
|
|
301
|
+
/** 按完整 id 或前缀解析会话检查点(sessions 列表默认展示 8 位前缀,便于直接引用) */
|
|
302
|
+
async function resolveCheckpoint(home, id) {
|
|
303
|
+
const exact = await (0, session_persist_1.loadCheckpoint)(home, id);
|
|
304
|
+
if (exact)
|
|
305
|
+
return exact;
|
|
306
|
+
const all = await (0, session_persist_1.listCheckpoints)(home);
|
|
307
|
+
const matches = all.filter((c) => c.runId.startsWith(id));
|
|
308
|
+
if (matches.length === 1)
|
|
309
|
+
return matches[0];
|
|
310
|
+
if (matches.length > 1) {
|
|
311
|
+
throw new errors_1.AppError(`会话前缀 ${id} 匹配到多个会话,请使用完整 runId`, 'SESSION_AMBIGUOUS', 400);
|
|
312
|
+
}
|
|
313
|
+
throw new errors_1.AppError(`未找到会话检查点: ${id}`, 'SESSION_NOT_FOUND', 404);
|
|
314
|
+
}
|
|
315
|
+
/** 列出历史会话检查点 */
|
|
316
|
+
async function runSessions() {
|
|
317
|
+
const offline = isOfflineByDefault();
|
|
318
|
+
const home = getSessionHome(offline);
|
|
319
|
+
const cps = await (0, session_persist_1.listCheckpoints)(home);
|
|
320
|
+
if (cps.length === 0) {
|
|
321
|
+
console.log('(无历史会话)');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
console.log(`历史会话(${offline ? '离线' : '真实'}模式,目录 ${home}):`);
|
|
325
|
+
for (const cp of cps) {
|
|
326
|
+
console.log(`- ${cp.runId.slice(0, 8)} | ${cp.status} | 迭代${cp.iterations} | $${cp.costUsd.toFixed(6)} | 文件${cp.touchedFiles.length} | ${cp.updatedAt}`);
|
|
327
|
+
console.log(` 目标: ${cp.goal}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** 从检查点恢复中断的会话并续跑 */
|
|
331
|
+
async function runResume(runId) {
|
|
332
|
+
const offline = isOfflineByDefault();
|
|
333
|
+
const home = getSessionHome(offline);
|
|
334
|
+
const cp = await resolveCheckpoint(home, runId);
|
|
335
|
+
if (cp.status === 'done') {
|
|
336
|
+
console.log(`会话 ${runId.slice(0, 8)} 已完成,无需恢复。`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
console.log(`[飞虹 Code] 恢复会话 ${runId.slice(0, 8)} (状态: ${cp.status}, 已迭代 ${cp.iterations} 次)`);
|
|
340
|
+
const security = { shellAllowlist: [], requireApproval: true };
|
|
341
|
+
let router;
|
|
342
|
+
if (offline) {
|
|
343
|
+
router = new model_router_1.ModelRouter([new mock_provider_1.ScriptedMockProvider(buildDemoSteps())], 'cost', 0);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
const cfg = (0, config_1.loadConfig)();
|
|
347
|
+
router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
348
|
+
security.shellAllowlist = cfg.security.shellAllowlist;
|
|
349
|
+
security.requireApproval = cfg.security.requireApproval;
|
|
350
|
+
}
|
|
351
|
+
const tools = (0, tools_1.createDefaultRegistry)();
|
|
352
|
+
// 用完整 runId 建日志(入参可能只是 8 位前缀),保证事件与检查点同名可对齐
|
|
353
|
+
const eventLog = new event_log_1.EventLog(cp.runId, home);
|
|
354
|
+
const session = session_store_1.SessionStore.restore(cp);
|
|
355
|
+
const approve = process.stdin.isTTY ? interactiveApprover() : defaultApproverFor(security);
|
|
356
|
+
const rt = getEnterprise();
|
|
357
|
+
if (rt)
|
|
358
|
+
(0, enterprise_1.assertQuota)(rt);
|
|
359
|
+
const guard = rt
|
|
360
|
+
? rt.makeGuard({
|
|
361
|
+
runId: cp.runId,
|
|
362
|
+
cwd: cp.cwd,
|
|
363
|
+
shellAllowlist: security.shellAllowlist,
|
|
364
|
+
approve,
|
|
365
|
+
})
|
|
366
|
+
: undefined;
|
|
367
|
+
const orchestrator = new orchestrator_1.Orchestrator({
|
|
368
|
+
router,
|
|
369
|
+
tools,
|
|
370
|
+
eventLog,
|
|
371
|
+
session,
|
|
372
|
+
cwd: cp.cwd,
|
|
373
|
+
security,
|
|
374
|
+
approve,
|
|
375
|
+
guard,
|
|
376
|
+
maxCostUsd: rt?.maxCostUsd ?? 0,
|
|
377
|
+
persist: (c) => (0, session_persist_1.saveCheckpoint)(home, c),
|
|
378
|
+
});
|
|
379
|
+
const result = await orchestrator.run(cp.goal, {
|
|
380
|
+
messages: cp.messages,
|
|
381
|
+
iterations: cp.iterations,
|
|
382
|
+
costUsd: cp.costUsd,
|
|
383
|
+
touchedFiles: cp.touchedFiles,
|
|
384
|
+
});
|
|
385
|
+
console.log('\n===== 恢复执行结果 =====');
|
|
386
|
+
console.log(result.finalAnswer);
|
|
387
|
+
console.log(`迭代 ${result.iterations} 次 · 成本 $${result.costUsd.toFixed(6)} · 日志 ${result.logFile}`);
|
|
388
|
+
}
|
|
389
|
+
/** 展示会话作用域的 diff(缺省为本工作区全量 diff) */
|
|
390
|
+
async function runDiff(id) {
|
|
391
|
+
const offline = isOfflineByDefault();
|
|
392
|
+
const home = getSessionHome(offline);
|
|
393
|
+
if (id) {
|
|
394
|
+
const cp = await resolveCheckpoint(home, id);
|
|
395
|
+
console.log(`[飞虹 Code] 会话 ${id.slice(0, 8)} 的变更 (cwd=${cp.cwd}):`);
|
|
396
|
+
console.log(await (0, git_1.gitDiff)(cp.cwd, cp.touchedFiles));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.log(`[飞虹 Code] 当前目录 (${process.cwd()}) 工作区变更:`);
|
|
400
|
+
console.log(await (0, git_1.gitDiff)(process.cwd()));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** 回滚会话 touchedFiles(破坏性,需 --yes) */
|
|
404
|
+
async function runRollback(id, yes) {
|
|
405
|
+
const offline = isOfflineByDefault();
|
|
406
|
+
const home = getSessionHome(offline);
|
|
407
|
+
const cp = await resolveCheckpoint(home, id);
|
|
408
|
+
// M4:回滚是破坏性动作,viewer 角色一律禁止,且无论成败都留痕
|
|
409
|
+
const rt = getEnterprise();
|
|
410
|
+
if (rt) {
|
|
411
|
+
const allowed = rt.tenant.role !== 'viewer';
|
|
412
|
+
rt.audit.record({
|
|
413
|
+
tenantId: rt.tenant.tenantId,
|
|
414
|
+
userId: rt.tenant.userId,
|
|
415
|
+
role: rt.tenant.role,
|
|
416
|
+
runId: cp.runId,
|
|
417
|
+
action: 'session:rollback',
|
|
418
|
+
resource: cp.touchedFiles.join(', ') || '(无文件)',
|
|
419
|
+
decision: allowed ? (yes ? 'allow' : 'rejected') : 'deny',
|
|
420
|
+
reason: allowed ? (yes ? '已确认 --yes' : '缺少 --yes 确认') : '角色 viewer 无回滚权限',
|
|
421
|
+
});
|
|
422
|
+
if (!allowed) {
|
|
423
|
+
throw new errors_1.AppError('角色 viewer 无权执行回滚操作', 'RBAC_DENIED', 403);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
console.log(`[飞虹 Code] 回滚会话 ${id.slice(0, 8)} 的 ${cp.touchedFiles.length} 个文件 (cwd=${cp.cwd})`);
|
|
427
|
+
const res = await (0, git_1.gitRollback)(cp.cwd, cp.touchedFiles, { yes });
|
|
428
|
+
if (res.reverted.length)
|
|
429
|
+
console.log(`已还原(已跟踪): ${res.reverted.join(', ')}`);
|
|
430
|
+
if (res.removed.length)
|
|
431
|
+
console.log(`已删除(未跟踪): ${res.removed.join(', ')}`);
|
|
432
|
+
if (res.errors.length)
|
|
433
|
+
console.log(`注意: ${res.errors.join('; ')}`);
|
|
434
|
+
if (yes)
|
|
435
|
+
await (0, session_persist_1.updateStatus)(home, id, 'done');
|
|
436
|
+
}
|
|
437
|
+
/* ===================== M4:企业管理命令 ===================== */
|
|
438
|
+
function requireEnterprise() {
|
|
439
|
+
const rt = getEnterprise();
|
|
440
|
+
if (!rt) {
|
|
441
|
+
throw new errors_1.AppError('企业模式已关闭(FH_ENTERPRISE=false),该命令不可用。', 'ENTERPRISE_DISABLED', 400);
|
|
442
|
+
}
|
|
443
|
+
return rt;
|
|
444
|
+
}
|
|
445
|
+
/** fhcode whoami:展示当前租户/用户/角色/隔离目录/配额 */
|
|
446
|
+
function runWhoami() {
|
|
447
|
+
console.log((0, enterprise_1.renderWhoami)(requireEnterprise()));
|
|
448
|
+
}
|
|
449
|
+
/** fhcode policy:展示生效策略与角色矩阵 */
|
|
450
|
+
function runPolicyCmd() {
|
|
451
|
+
const rt = requireEnterprise();
|
|
452
|
+
console.log((0, enterprise_1.renderPolicy)(rt.policy, rt.tenant.role));
|
|
453
|
+
}
|
|
454
|
+
/** fhcode audit [--limit N]:查看审计记录(默认最近 20 条) */
|
|
455
|
+
function runAudit(limit = 20) {
|
|
456
|
+
const rt = requireEnterprise();
|
|
457
|
+
const all = (0, enterprise_1.readAudit)(rt.tenant.auditDir);
|
|
458
|
+
if (all.length === 0) {
|
|
459
|
+
console.log(`(租户 ${rt.tenant.tenantId} 暂无审计记录,目录 ${rt.tenant.auditDir})`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const rows = all.slice(-limit);
|
|
463
|
+
console.log(`审计记录 ${rows.length}/${all.length} 条(租户 ${rt.tenant.tenantId}):`);
|
|
464
|
+
for (const r of rows) {
|
|
465
|
+
console.log(`#${String(r.seq).padStart(4, '0')} ${r.ts} [${r.decision.toUpperCase()}] ${r.action}` +
|
|
466
|
+
` by ${r.userId}(${r.role}) run=${String(r.runId).slice(0, 8)}`);
|
|
467
|
+
console.log(` 资源: ${r.resource}`);
|
|
468
|
+
if (r.reason)
|
|
469
|
+
console.log(` 理由: ${r.reason}`);
|
|
470
|
+
}
|
|
471
|
+
console.log(`链尾哈希: ${all[all.length - 1].hash.slice(0, 16)}…`);
|
|
472
|
+
}
|
|
473
|
+
/** fhcode audit verify:校验哈希链完整性 */
|
|
474
|
+
function runAuditVerify() {
|
|
475
|
+
const rt = requireEnterprise();
|
|
476
|
+
const res = (0, enterprise_1.verifyAudit)(rt.tenant.auditDir);
|
|
477
|
+
if (res.ok) {
|
|
478
|
+
console.log(`✅ 审计链完整:${res.total} 条记录,哈希链自洽未被篡改。`);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
console.log(`❌ 审计链校验失败:共 ${res.total} 条,断点在第 ${res.brokenAt} 条`);
|
|
482
|
+
console.log(` ${res.detail}`);
|
|
483
|
+
process.exitCode = 2;
|
|
484
|
+
}
|
|
485
|
+
/** fhcode tenants:列出全部租户与用量 */
|
|
486
|
+
function runTenants() {
|
|
487
|
+
requireEnterprise();
|
|
488
|
+
const list = (0, enterprise_1.listTenants)();
|
|
489
|
+
if (list.length === 0) {
|
|
490
|
+
console.log('(暂无租户数据,执行一次任务后自动创建)');
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
console.log('租户用量汇总:');
|
|
494
|
+
console.log(' 租户ID 会话数 累计成本 审计条数 最近活跃');
|
|
495
|
+
for (const t of list) {
|
|
496
|
+
console.log(` ${t.tenantId.padEnd(20)} ${String(t.sessions).padStart(5)} $${t.costUsd.toFixed(6).padStart(10)} ${String(t.auditRecords).padStart(7)} ${t.lastActiveAt}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/* ===================== M6:自我进化 ===================== */
|
|
500
|
+
/** fhcode model-stats:显示各模型性能统计 */
|
|
501
|
+
function runModelStats() {
|
|
502
|
+
const homeDir = (0, path_1.join)((0, os_1.homedir)(), '.feihong-code');
|
|
503
|
+
const statsFile = (0, path_1.join)(homeDir, 'model-stats.jsonl');
|
|
504
|
+
if (!(0, fs_1.existsSync)(statsFile)) {
|
|
505
|
+
console.log('(暂无模型性能数据,执行任务后自动生成)');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const router = new model_router_1.ModelRouter([], 'cost', 0, statsFile);
|
|
509
|
+
router.loadStats(homeDir).then(() => {
|
|
510
|
+
const stats = router.getStats();
|
|
511
|
+
if (stats.length === 0) {
|
|
512
|
+
console.log('(无模型统计记录)');
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
console.log('模型性能统计(M6):');
|
|
516
|
+
console.log(' 提供者ID 模型 总调用 成功 失败 成功率 平均延迟 总成本');
|
|
517
|
+
for (const s of stats) {
|
|
518
|
+
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
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
/** fhcode experiences [路径]:列出经验库 */
|
|
523
|
+
function runExperiences(path) {
|
|
524
|
+
const experienceDir = path || (0, path_1.join)(require('os').homedir(), '.feihong-code', 'experiences');
|
|
525
|
+
(0, experience_1.listExperiences)(experienceDir).then((experiences) => {
|
|
526
|
+
if (experiences.length === 0) {
|
|
527
|
+
console.log('(暂无经验记录,完成任务后自动积累)');
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
console.log(`经验库 (${experiences.length} 条,来源: ${experienceDir}):`);
|
|
531
|
+
console.log(' ID 类型 标题 成功率 使用次数');
|
|
532
|
+
for (const exp of experiences.slice(0, 10)) {
|
|
533
|
+
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
|
+
}
|
|
535
|
+
if (experiences.length > 10) {
|
|
536
|
+
console.log(` ... 共 ${experiences.length} 条,显示前 10 条`);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
/* ===================== M5:Web 控制台(serve) ===================== */
|
|
541
|
+
/** fhcode serve:启动 Web 管理控制台。无 FH_WEB_PORT 用 8080;无 FH_WEB_TOKEN 自动生成。 */
|
|
542
|
+
function runServe(port) {
|
|
543
|
+
const handle = (0, server_1.startWebServer)({ port });
|
|
544
|
+
console.log(`[飞虹 Code] Web 控制台: ${handle.url}`);
|
|
545
|
+
console.log(`[飞虹 Code] 访问令牌 (FH_WEB_TOKEN): ${handle.token}`);
|
|
546
|
+
console.log(`[飞虹 Code] 按 Ctrl+C 停止`);
|
|
547
|
+
// 注意:app.listen 保持事件循环运行,进程持续存活直到收到 SIGINT;本函数返回后 main() 结束不影响服务。
|
|
548
|
+
}
|
|
549
|
+
/* ===================== M8:自主编程能力 ===================== */
|
|
550
|
+
/** fhcode code-write <目标>:自主编写代码(规划→编写→测试→审查→修复) */
|
|
551
|
+
async function runCodeWrite(goal) {
|
|
552
|
+
const writer = (0, code_writer_1.createCodeWriter)(process.cwd());
|
|
553
|
+
// 离线演示:生成一个简单的工具函数
|
|
554
|
+
const sampleCode = `/**
|
|
555
|
+
* 飞虹 Code (Muse Code 参照复刻)
|
|
556
|
+
* 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
|
|
557
|
+
*
|
|
558
|
+
* 示例:M8 自主编写演示
|
|
559
|
+
*/
|
|
560
|
+
export function calculateCommission(base: number, rate: number): number {
|
|
561
|
+
if (rate < 0 || rate > 1) {
|
|
562
|
+
throw new Error('佣金比率必须在 0-1 之间');
|
|
563
|
+
}
|
|
564
|
+
return Math.round(base * rate * 100) / 100;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export interface CommissionPlan {
|
|
568
|
+
name: string;
|
|
569
|
+
baseRate: number;
|
|
570
|
+
tierRates: Array<{ min: number; rate: number }>;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function calculateTieredCommission(plan: CommissionPlan, amount: number): number {
|
|
574
|
+
let total = 0;
|
|
575
|
+
let remaining = amount;
|
|
576
|
+
for (const tier of plan.tierRates.sort((a, b) => b.min - a.min)) {
|
|
577
|
+
if (remaining <= 0) break;
|
|
578
|
+
const tierAmount = Math.min(remaining, amount - tier.min);
|
|
579
|
+
if (tierAmount > 0) {
|
|
580
|
+
total += tierAmount * tier.rate;
|
|
581
|
+
remaining -= tierAmount;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
total += Math.max(0, amount - plan.tierRates[0]?.min || 0) * plan.baseRate;
|
|
585
|
+
return Math.round(total * 100) / 100;
|
|
586
|
+
}
|
|
587
|
+
`;
|
|
588
|
+
const result = await writer.run(goal, sampleCode, 'generated/commission.ts');
|
|
589
|
+
console.log(`\n===== M8 自主编写结果 =====`);
|
|
590
|
+
console.log(result.summary);
|
|
591
|
+
console.log(`生成文件: ${result.finalFiles.join(', ')}`);
|
|
592
|
+
}
|
|
593
|
+
/** fhcode quality-gate [路径]:质量门禁审查 */
|
|
594
|
+
function runQualityGate(path) {
|
|
595
|
+
const targetPath = path || process.cwd();
|
|
596
|
+
const gate = (0, quality_gate_1.createQualityGate)();
|
|
597
|
+
const results = gate.gateDirectory(targetPath, 10);
|
|
598
|
+
console.log(gate.report(results));
|
|
599
|
+
const failed = results.filter((r) => !r.passed);
|
|
600
|
+
if (failed.length > 0) {
|
|
601
|
+
console.log(`\n⚠️ ${failed.length} 个文件未通过门禁,请修复后再提交`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/** fhcode self-improve:自我改进统计 */
|
|
605
|
+
function runSelfImprove() {
|
|
606
|
+
const improver = (0, self_improver_1.createSelfImprover)();
|
|
607
|
+
const records = improver.loadImprovements();
|
|
608
|
+
const stats = improver.getStats();
|
|
609
|
+
console.log('===== M8 自我改进统计 =====');
|
|
610
|
+
console.log(`总反思次数: ${stats.totalReflections}`);
|
|
611
|
+
console.log(`成功率: ${(stats.successRate * 100).toFixed(1)}%`);
|
|
612
|
+
console.log(`平均耗时: ${stats.avgDurationMs.toFixed(0)}ms`);
|
|
613
|
+
if (records.length > 0) {
|
|
614
|
+
console.log(`\n最近改进记录:`);
|
|
615
|
+
for (const rec of records.slice(-5).reverse()) {
|
|
616
|
+
console.log(` ${rec.timestamp.slice(0, 19)} | ${rec.success ? '✅' : '❌'} | 模式: ${rec.patterns.length} 条`);
|
|
617
|
+
for (const imp of rec.improvements.slice(0, 3)) {
|
|
618
|
+
console.log(` → ${imp}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
console.log('\n(暂无改进记录,完成任务后自动生成)');
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* fhcode swe "<目标>":全自动软件工程 Agent
|
|
628
|
+
* 读取整个仓库 → 任务拆解 → 逐任务(实现+验证+自愈) → 产出报告。
|
|
629
|
+
* 实现阶段复用 Orchestrator(ReAct + 工具 + 自愈);验证阶段跑构建/测试。
|
|
630
|
+
*/
|
|
631
|
+
async function runSwe(goal, opts = {}) {
|
|
632
|
+
const offline = opts.offline ?? isOfflineByDefault();
|
|
633
|
+
const cwd = opts.repo
|
|
634
|
+
? require('path').resolve(opts.repo)
|
|
635
|
+
: offline
|
|
636
|
+
? (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'fhcode-swe-'))
|
|
637
|
+
: process.cwd();
|
|
638
|
+
const rt = getEnterprise();
|
|
639
|
+
if (rt)
|
|
640
|
+
(0, enterprise_1.assertQuota)(rt);
|
|
641
|
+
const security = { shellAllowlist: [], requireApproval: true };
|
|
642
|
+
// 真实模式就绪检查:未配置任何模型供应商时给出明确接入指引,避免盲目失败
|
|
643
|
+
if (!offline) {
|
|
644
|
+
const cfg = (0, config_1.loadConfig)();
|
|
645
|
+
if (!cfg.models.providers.length) {
|
|
646
|
+
console.error('[飞虹 Code] 未配置真实模型供应商,无法进入真实模式。请通过以下任一方式接入:\n' +
|
|
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 "..."');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/** 实现单个子任务的回调:内部装配一个 Orchestrator 实例并运行 */
|
|
655
|
+
const runSubTask = async (focusedGoal) => {
|
|
656
|
+
const runId = (0, crypto_1.randomUUID)();
|
|
657
|
+
(0, logger_1.setRunId)(runId);
|
|
658
|
+
let router;
|
|
659
|
+
if (offline) {
|
|
660
|
+
router = new model_router_1.ModelRouter([new mock_provider_1.ScriptedMockProvider(buildDemoSteps())], 'cost', 0);
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const cfg = (0, config_1.loadConfig)();
|
|
664
|
+
router = model_router_1.ModelRouter.fromConfig(cfg);
|
|
665
|
+
security.shellAllowlist = cfg.security.shellAllowlist;
|
|
666
|
+
security.requireApproval = cfg.security.requireApproval;
|
|
667
|
+
}
|
|
668
|
+
const tools = (0, tools_1.createDefaultRegistry)();
|
|
669
|
+
const logDir = getSessionHome(offline);
|
|
670
|
+
const eventLog = new event_log_1.EventLog(runId, logDir);
|
|
671
|
+
const session = new session_store_1.SessionStore(runId, cwd);
|
|
672
|
+
const approve = process.stdin.isTTY ? interactiveApprover() : defaultApproverFor(security);
|
|
673
|
+
const guard = rt
|
|
674
|
+
? rt.makeGuard({ runId, cwd, shellAllowlist: security.shellAllowlist, approve })
|
|
675
|
+
: undefined;
|
|
676
|
+
const orchestrator = new orchestrator_1.Orchestrator({
|
|
677
|
+
router,
|
|
678
|
+
tools,
|
|
679
|
+
eventLog,
|
|
680
|
+
session,
|
|
681
|
+
cwd,
|
|
682
|
+
security,
|
|
683
|
+
approve,
|
|
684
|
+
guard,
|
|
685
|
+
maxIterations: opts.maxIterations ?? 6,
|
|
686
|
+
maxCostUsd: rt?.maxCostUsd ?? 0,
|
|
687
|
+
persist: (cp) => (0, session_persist_1.saveCheckpoint)(logDir, cp),
|
|
688
|
+
});
|
|
689
|
+
const result = await orchestrator.run(focusedGoal);
|
|
690
|
+
return {
|
|
691
|
+
ok: result.ok,
|
|
692
|
+
finalAnswer: result.finalAnswer,
|
|
693
|
+
iterations: result.iterations,
|
|
694
|
+
touchedFiles: [],
|
|
695
|
+
};
|
|
696
|
+
};
|
|
697
|
+
console.log(`[飞虹 Code] 启动全自动软件工程 Agent (offline=${offline}, 仓库=${cwd})`);
|
|
698
|
+
const report = await (0, swe_agent_1.runSweAgent)(goal, {
|
|
699
|
+
cwd,
|
|
700
|
+
runSubTask,
|
|
701
|
+
maxTasks: opts.maxTasks ?? 8,
|
|
702
|
+
maxRetries: opts.maxRetries ?? 2,
|
|
703
|
+
verifyOnly: !!opts.verifyOnly,
|
|
704
|
+
planOnly: !!opts.planOnly,
|
|
705
|
+
});
|
|
706
|
+
console.log('\n===== 全自动软件工程 Agent 报告 =====');
|
|
707
|
+
console.log(report.summary);
|
|
708
|
+
if (rt) {
|
|
709
|
+
rt.audit.record({
|
|
710
|
+
tenantId: rt.tenant.tenantId,
|
|
711
|
+
userId: rt.tenant.userId,
|
|
712
|
+
role: rt.tenant.role,
|
|
713
|
+
runId: 'swe',
|
|
714
|
+
action: 'swe:run',
|
|
715
|
+
resource: goal,
|
|
716
|
+
decision: report.overall === 'failed' ? 'deny' : report.overall === 'partial' ? 'info' : 'allow',
|
|
717
|
+
reason: `tasks=${report.executedTasks}/${report.plannedTasks} passed=${report.completedTasks} overall=${report.overall}`,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/* ===================== 审批器 ===================== */
|
|
722
|
+
function expandHome(p) {
|
|
723
|
+
if (p.startsWith('~'))
|
|
724
|
+
return (0, path_1.join)(process.env.HOME || process.cwd(), p.slice(1));
|
|
725
|
+
return p;
|
|
726
|
+
}
|
|
727
|
+
function joinHome() {
|
|
728
|
+
return (0, path_1.join)((0, os_1.homedir)(), '.feihong-code');
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* 非交互默认审批器:CLI 无交互审批通道时,命中 shell 白名单的命令自动通过,
|
|
732
|
+
* 其余一律拒绝(安全优先,由日志留痕)。配合 FH_SHELL_ALLOW 使用。
|
|
733
|
+
*/
|
|
734
|
+
function defaultApproverFor(security) {
|
|
735
|
+
return async (action) => {
|
|
736
|
+
if (!security.requireApproval)
|
|
737
|
+
return true;
|
|
738
|
+
const cmd = action.replace(/^run_shell:\s*/, '').trim();
|
|
739
|
+
const head = cmd.split(/\s+/)[0] || '';
|
|
740
|
+
if (security.shellAllowlist.includes(head)) {
|
|
741
|
+
logger_1.logger.info('审批自动通过(命中白名单)', { action });
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
logger_1.logger.warn('审批拒绝(无交互审批通道且未命中白名单)', { action });
|
|
745
|
+
return false;
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* 交互式审批器:TTY 环境下向用户发起 yes/no 确认。
|
|
750
|
+
* 命中白名单时直接通过;其它高危操作(shell/写文件)须经用户显式批准。
|
|
751
|
+
*/
|
|
752
|
+
function interactiveApprover() {
|
|
753
|
+
return (action) => new Promise((resolve) => {
|
|
754
|
+
const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
755
|
+
const ask = () => rl.question(`[审批] 是否允许执行: ${action}\n 输入 y/yes 允许,其他拒绝: `, (ans) => {
|
|
756
|
+
rl.close();
|
|
757
|
+
resolve(/^(y|yes|是)$/i.test(ans.trim()));
|
|
758
|
+
});
|
|
759
|
+
ask();
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
//# sourceMappingURL=run.js.map
|