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.
Files changed (152) hide show
  1. package/.github/FUNDING.yml +4 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -37
  3. package/.github/ISSUE_TEMPLATE/config.yml +14 -14
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +28 -28
  5. package/.github/PULL_REQUEST_TEMPLATE.md +46 -46
  6. package/.github/SECURITY.md +67 -67
  7. package/.github/dependabot.yml +16 -0
  8. package/.github/workflows/ci.yml +1 -1
  9. package/AGENT-GUIDE.md +382 -237
  10. package/CHANGELOG.md +405 -73
  11. package/CODE_OF_CONDUCT.md +56 -56
  12. package/CONTRIBUTING.md +68 -68
  13. package/LICENSE +22 -22
  14. package/README.md +31 -25
  15. package/README.self-evolve.md +274 -0
  16. package/dist/agent/code-review.js +2 -0
  17. package/dist/agent/code-review.js.map +1 -1
  18. package/dist/agent/code-writer.js +70 -17
  19. package/dist/agent/code-writer.js.map +1 -1
  20. package/dist/agent/context-compactor.js +13 -4
  21. package/dist/agent/context-compactor.js.map +1 -1
  22. package/dist/agent/experience.js +262 -62
  23. package/dist/agent/experience.js.map +1 -1
  24. package/dist/agent/orchestrator.js +148 -68
  25. package/dist/agent/orchestrator.js.map +1 -1
  26. package/dist/agent/quality-gate.js +10 -4
  27. package/dist/agent/quality-gate.js.map +1 -1
  28. package/dist/agent/repo-context.js +181 -0
  29. package/dist/agent/repo-context.js.map +1 -0
  30. package/dist/agent/repo-reader.js +92 -99
  31. package/dist/agent/repo-reader.js.map +1 -1
  32. package/dist/agent/self-heal.js +38 -48
  33. package/dist/agent/self-heal.js.map +1 -1
  34. package/dist/agent/self-improver.js +65 -61
  35. package/dist/agent/self-improver.js.map +1 -1
  36. package/dist/agent/subagent-summary.js +31 -0
  37. package/dist/agent/subagent-summary.js.map +1 -0
  38. package/dist/agent/subagent.js +52 -2
  39. package/dist/agent/subagent.js.map +1 -1
  40. package/dist/agent/symbol-index.js +160 -0
  41. package/dist/agent/symbol-index.js.map +1 -0
  42. package/dist/agent/team.js +193 -0
  43. package/dist/agent/team.js.map +1 -0
  44. package/dist/cli/commands.js +124 -143
  45. package/dist/cli/commands.js.map +1 -1
  46. package/dist/cli/index.js +113 -106
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/cli/repl.js +82 -7
  49. package/dist/cli/repl.js.map +1 -1
  50. package/dist/cli/run.js +601 -95
  51. package/dist/cli/run.js.map +1 -1
  52. package/dist/cli/tui.js +145 -0
  53. package/dist/cli/tui.js.map +1 -0
  54. package/dist/cli/version.js +1 -1
  55. package/dist/enterprise/audit.js +75 -12
  56. package/dist/enterprise/audit.js.map +1 -1
  57. package/dist/enterprise/index.js +14 -11
  58. package/dist/enterprise/index.js.map +1 -1
  59. package/dist/enterprise/policy.js +22 -10
  60. package/dist/enterprise/policy.js.map +1 -1
  61. package/dist/harness/executor.js +127 -0
  62. package/dist/harness/executor.js.map +1 -0
  63. package/dist/harness/harness.js +87 -0
  64. package/dist/harness/harness.js.map +1 -0
  65. package/dist/harness/index.js +31 -0
  66. package/dist/harness/index.js.map +1 -0
  67. package/dist/harness/loader.js +138 -0
  68. package/dist/harness/loader.js.map +1 -0
  69. package/dist/harness/reporter.js +34 -0
  70. package/dist/harness/reporter.js.map +1 -0
  71. package/dist/harness/types.js +10 -0
  72. package/dist/harness/types.js.map +1 -0
  73. package/dist/harness/verifier.js +48 -0
  74. package/dist/harness/verifier.js.map +1 -0
  75. package/dist/hello.js +14 -0
  76. package/dist/hello.js.map +1 -0
  77. package/dist/memory/auto-summarize.js +208 -0
  78. package/dist/memory/auto-summarize.js.map +1 -0
  79. package/dist/memory/index.js +228 -0
  80. package/dist/memory/index.js.map +1 -0
  81. package/dist/models/model-router.js +23 -10
  82. package/dist/models/model-router.js.map +1 -1
  83. package/dist/plugins/plugin-loader.js +179 -0
  84. package/dist/plugins/plugin-loader.js.map +1 -0
  85. package/dist/runtime/event-log.js.map +1 -1
  86. package/dist/runtime/hooks.js +80 -0
  87. package/dist/runtime/hooks.js.map +1 -0
  88. package/dist/self-evolve/hook.js +60 -0
  89. package/dist/self-evolve/hook.js.map +1 -0
  90. package/dist/shared/config.js +35 -3
  91. package/dist/shared/config.js.map +1 -1
  92. package/dist/shared/i18n.js +535 -0
  93. package/dist/shared/i18n.js.map +1 -0
  94. package/dist/skills/grill.js +2 -1
  95. package/dist/skills/grill.js.map +1 -1
  96. package/dist/skills/self-heal.js +73 -0
  97. package/dist/skills/self-heal.js.map +1 -0
  98. package/dist/skills/skill-loader.js +131 -0
  99. package/dist/skills/skill-loader.js.map +1 -0
  100. package/dist/skills/skill-market.js +195 -0
  101. package/dist/skills/skill-market.js.map +1 -0
  102. package/dist/tools/analysis/code-analyzer.js +45 -18
  103. package/dist/tools/analysis/code-analyzer.js.map +1 -1
  104. package/dist/tools/index.js +5 -0
  105. package/dist/tools/index.js.map +1 -1
  106. package/dist/tools/mcp/index.js +84 -0
  107. package/dist/tools/mcp/index.js.map +1 -0
  108. package/dist/tools/mcp/mcp-client.js +172 -0
  109. package/dist/tools/mcp/mcp-client.js.map +1 -0
  110. package/dist/tools/sandbox.js +126 -0
  111. package/dist/tools/sandbox.js.map +1 -0
  112. package/dist/tools/shell/exec.js +25 -0
  113. package/dist/tools/shell/exec.js.map +1 -1
  114. package/dist/tools/shell/run-shell.tool.js +4 -1
  115. package/dist/tools/shell/run-shell.tool.js.map +1 -1
  116. package/dist/tools/skills/load-skill.tool.js +36 -0
  117. package/dist/tools/skills/load-skill.tool.js.map +1 -0
  118. package/dist/tools/tool.interface.js.map +1 -1
  119. package/dist/tools/tool.registry.js +37 -1
  120. package/dist/tools/tool.registry.js.map +1 -1
  121. package/dist/tools/web/web.tool.js +139 -0
  122. package/dist/tools/web/web.tool.js.map +1 -0
  123. package/dist/web/auth.js +42 -3
  124. package/dist/web/auth.js.map +1 -1
  125. package/dist/web/channels.js +171 -0
  126. package/dist/web/channels.js.map +1 -0
  127. package/dist/web/public/index.html +3253 -39
  128. package/dist/web/public/index.html.tmp +3117 -0
  129. package/dist/web/public/index_new.html +3196 -0
  130. package/dist/web/server.js +790 -11
  131. package/dist/web/server.js.map +1 -1
  132. package/dist/web/task-queue.js +352 -0
  133. package/dist/web/task-queue.js.map +1 -0
  134. package/dist/web/web-config.js +143 -0
  135. package/dist/web/web-config.js.map +1 -0
  136. package/docs/Deployment_Guide_EN.md +288 -0
  137. package/docs/Technical_Manual_EN.md +216 -0
  138. package/docs/User_Manual_EN.md +314 -0
  139. package/docs/self-evolve-implementation.md +165 -0
  140. package/docs/self-evolve.md +166 -0
  141. package/docs//344/272/247/345/223/201/345/274/200/345/217/221/346/226/207/346/241/243.md +614 -614
  142. 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
  143. package/docs//344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +318 -358
  144. 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
  145. package/docs//346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +216 -303
  146. package/docs//346/236/266/346/236/204/344/270/216API.md +238 -238
  147. package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +196 -196
  148. package/docs//351/203/250/347/275/262/346/214/207/345/215/227.md +165 -165
  149. package/docs//351/203/250/347/275/262/350/257/264/346/230/216/344/271/246.md +288 -0
  150. package/docs//351/205/215/347/275/256/345/217/202/350/200/203.md +127 -127
  151. package/package.json +114 -109
  152. 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
- async function runGoal(goal, opts = {}) {
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 cwd = offline ? (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'fhcode-demo-')) : process.cwd();
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
- if (offline) {
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
- 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
- });
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
- console.log('\n===== 执行结果 =====');
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(`\n迭代 ${result.iterations} · 成本 $${result.costUsd.toFixed(6)} · 日志 ${result.logFile}`);
192
- console.log(`会话检查点: ${(0, path_1.join)(logDir, `${runId}.session.json`)}(可用 fhcode sessions / resume / diff 管理)`);
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
- ? `(离线模式演示文件已写入: ${demoFile})`
199
- : `(离线模式未产生演示文件,工作区: ${cwd};如为策略拒绝可用 fhcode audit 查看原因)`);
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(`[飞虹 Code] 并行模式 (offline=${offline})`);
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(`仓库根: ${result.repoRoot} · 工作树已清理: ${result.worktrees.length}`);
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(`仓库根: ${result.repoRoot} · 工作树已清理: ${result.worktrees.length}`);
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(`历史会话(${offline ? '离线' : '真实'}模式,目录 ${home}):`);
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(`- ${cp.runId.slice(0, 8)} | ${cp.status} | 迭代${cp.iterations} | $${cp.costUsd.toFixed(6)} | 文件${cp.touchedFiles.length} | ${cp.updatedAt}`);
327
- console.log(` 目标: ${cp.goal}`);
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(`会话 ${runId.slice(0, 8)} 已完成,无需恢复。`);
535
+ console.log((0, i18n_1.t)('run.resumeDone', { id: runId.slice(0, 8) }));
337
536
  return;
338
537
  }
339
- console.log(`[飞虹 Code] 恢复会话 ${runId.slice(0, 8)} (状态: ${cp.status}, 已迭代 ${cp.iterations})`);
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(`迭代 ${result.iterations} · 成本 $${result.costUsd.toFixed(6)} · 日志 ${result.logFile}`);
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(`[飞虹 Code] 会话 ${id.slice(0, 8)} 的变更 (cwd=${cp.cwd}):`);
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(`[飞虹 Code] 当前目录 (${process.cwd()}) 工作区变更:`);
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(`[飞虹 Code] 回滚会话 ${id.slice(0, 8)} ${cp.touchedFiles.length} 个文件 (cwd=${cp.cwd})`);
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(`已还原(已跟踪): ${res.reverted.join(', ')}`);
628
+ console.log((0, i18n_1.t)('run.rollbackReverted', { files: res.reverted.join(', ') }));
430
629
  if (res.removed.length)
431
- console.log(`已删除(未跟踪): ${res.removed.join(', ')}`);
630
+ console.log((0, i18n_1.t)('run.rollbackRemoved', { files: res.removed.join(', ') }));
432
631
  if (res.errors.length)
433
- console.log(`注意: ${res.errors.join('; ')}`);
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('企业模式已关闭(FH_ENTERPRISE=false),该命令不可用。', 'ENTERPRISE_DISABLED', 400);
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(`(租户 ${rt.tenant.tenantId} 暂无审计记录,目录 ${rt.tenant.auditDir})`);
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(`审计记录 ${rows.length}/${all.length} 条(租户 ${rt.tenant.tenantId}):`);
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(`#${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}`);
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(` 理由: ${r.reason}`);
675
+ console.log((0, i18n_1.t)('audit.reason', { reason: r.reason }));
470
676
  }
471
- console.log(`链尾哈希: ${all[all.length - 1].hash.slice(0, 16)}…`);
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(`✅ 审计链完整:${res.total} 条记录,哈希链自洽未被篡改。`);
684
+ console.log((0, i18n_1.t)('audit.verifyOk', { total: res.total }));
479
685
  return;
480
686
  }
481
- console.log(`❌ 审计链校验失败:共 ${res.total} 条,断点在第 ${res.brokenAt} 条`);
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(' 租户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}`);
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, path_1.join)((0, os_1.homedir)(), '.feihong-code');
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('模型性能统计(M6):');
516
- console.log(' 提供者ID 模型 总调用 成功 失败 成功率 平均延迟 总成本');
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)(require('os').homedir(), '.feihong-code', 'experiences');
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(`经验库 (${experiences.length} 条,来源: ${experienceDir}):`);
531
- console.log(' ID 类型 标题 成功率 使用次数');
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(` ... ${experiences.length} 条,显示前 10 条`);
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(`[飞虹 Code] Web 控制台: ${handle.url}`);
545
- console.log(`[飞虹 Code] 访问令牌 (FH_WEB_TOKEN): ${handle.token}`);
546
- console.log(`[飞虹 Code] 按 Ctrl+C 停止`);
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(`\n===== M8 自主编写结果 =====`);
1027
+ console.log('\n' + (0, i18n_1.t)('codewrite.resultTitle'));
590
1028
  console.log(result.summary);
591
- console.log(`生成文件: ${result.finalFiles.join(', ')}`);
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(`\n⚠️ ${failed.length} 个文件未通过门禁,请修复后再提交`);
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('===== M8 自我改进统计 =====');
610
- console.log(`总反思次数: ${stats.totalReflections}`);
611
- console.log(`成功率: ${(stats.successRate * 100).toFixed(1)}%`);
612
- console.log(`平均耗时: ${stats.avgDurationMs.toFixed(0)}ms`);
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(`\n最近改进记录:`);
1062
+ console.log('\n' + (0, i18n_1.t)('selfimp.recent'));
615
1063
  for (const rec of records.slice(-5).reverse()) {
616
- console.log(` ${rec.timestamp.slice(0, 19)} | ${rec.success ? '✅' : '❌'} | 模式: ${rec.patterns.length} 条`);
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(` → ${imp}`);
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('[飞虹 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 "..."');
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 ?? 6,
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: result.finalAnswer,
1154
+ finalAnswer: summarized.text,
693
1155
  iterations: result.iterations,
694
1156
  touchedFiles: [],
695
1157
  };
696
1158
  };
697
- console.log(`[飞虹 Code] 启动全自动软件工程 Agent (offline=${offline}, 仓库=${cwd})`);
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===== 全自动软件工程 Agent 报告 =====');
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)(process.env.HOME || process.cwd(), p.slice(1));
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(`[审批] 是否允许执行: ${action}\n 输入 y/yes 允许,其他拒绝: `, (ans) => {
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
  });