@xdevops/issue-auto-finish 1.0.1 → 1.0.3

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 (133) hide show
  1. package/dist/KnowledgeAnalyzer-EZSJT2MJ.js +13 -0
  2. package/dist/KnowledgeAnalyzer-EZSJT2MJ.js.map +1 -0
  3. package/dist/KnowledgeStore-4ROC6F56.js +10 -0
  4. package/dist/KnowledgeStore-4ROC6F56.js.map +1 -0
  5. package/dist/ai-runner/AIRunner.d.ts +2 -0
  6. package/dist/ai-runner/AIRunner.d.ts.map +1 -1
  7. package/dist/ai-runner/BaseAIRunner.d.ts +9 -0
  8. package/dist/ai-runner/BaseAIRunner.d.ts.map +1 -1
  9. package/dist/ai-runner-RGAJPOOW.js +16 -0
  10. package/dist/ai-runner-RGAJPOOW.js.map +1 -0
  11. package/dist/analyze-I7UOJB4F.js +72 -0
  12. package/dist/analyze-I7UOJB4F.js.map +1 -0
  13. package/dist/chunk-3JUHZGX5.js +171 -0
  14. package/dist/chunk-3JUHZGX5.js.map +1 -0
  15. package/dist/chunk-5JYCGAU3.js +318 -0
  16. package/dist/chunk-5JYCGAU3.js.map +1 -0
  17. package/dist/chunk-5VUB3UUK.js +643 -0
  18. package/dist/chunk-5VUB3UUK.js.map +1 -0
  19. package/dist/{chunk-IDUKWCC2.js → chunk-C6ZJVIPZ.js} +1151 -80
  20. package/dist/chunk-C6ZJVIPZ.js.map +1 -0
  21. package/dist/{chunk-OWVT3Z34.js → chunk-JFYAXNNS.js} +121 -31
  22. package/dist/chunk-JFYAXNNS.js.map +1 -0
  23. package/dist/chunk-KISVPNSV.js +188 -0
  24. package/dist/chunk-KISVPNSV.js.map +1 -0
  25. package/dist/{chunk-I3T573SU.js → chunk-LEQYGOMJ.js} +65 -2
  26. package/dist/chunk-LEQYGOMJ.js.map +1 -0
  27. package/dist/{chunk-TBIEB3JY.js → chunk-N5YK6YVI.js} +592 -767
  28. package/dist/chunk-N5YK6YVI.js.map +1 -0
  29. package/dist/{chunk-RIUI4ROA.js → chunk-PECYMYAK.js} +2 -2
  30. package/dist/chunk-SWG2Y7YX.js +410 -0
  31. package/dist/chunk-SWG2Y7YX.js.map +1 -0
  32. package/dist/chunk-TZ6C7HL5.js +59 -0
  33. package/dist/chunk-TZ6C7HL5.js.map +1 -0
  34. package/dist/cli/commands/analyze.d.ts +8 -0
  35. package/dist/cli/commands/analyze.d.ts.map +1 -0
  36. package/dist/cli.js +67 -3
  37. package/dist/cli.js.map +1 -1
  38. package/dist/clients/GongfengClient.d.ts +5 -0
  39. package/dist/clients/GongfengClient.d.ts.map +1 -1
  40. package/dist/config-RI7NLDXI.js +7 -0
  41. package/dist/config-RI7NLDXI.js.map +1 -0
  42. package/dist/config.d.ts +19 -0
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/{doctor-B26Q6JWI.js → doctor-ZPGIBA5N.js} +3 -3
  45. package/dist/events/EventBus.d.ts +1 -1
  46. package/dist/events/EventBus.d.ts.map +1 -1
  47. package/dist/git/GitOperations.d.ts +12 -0
  48. package/dist/git/GitOperations.d.ts.map +1 -1
  49. package/dist/i18n/locales/en.d.ts.map +1 -1
  50. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +11 -5
  53. package/dist/{init-L3VIWCOV.js → init-LZGCIHE7.js} +8 -4
  54. package/dist/{init-L3VIWCOV.js.map → init-LZGCIHE7.js.map} +1 -1
  55. package/dist/knowledge/KnowledgeAnalyzer.d.ts +31 -0
  56. package/dist/knowledge/KnowledgeAnalyzer.d.ts.map +1 -0
  57. package/dist/knowledge/KnowledgeDefaults.d.ts +7 -0
  58. package/dist/knowledge/KnowledgeDefaults.d.ts.map +1 -0
  59. package/dist/knowledge/KnowledgeEntry.d.ts +30 -0
  60. package/dist/knowledge/KnowledgeEntry.d.ts.map +1 -0
  61. package/dist/knowledge/KnowledgeLoader.d.ts +18 -0
  62. package/dist/knowledge/KnowledgeLoader.d.ts.map +1 -0
  63. package/dist/knowledge/KnowledgeStore.d.ts +35 -0
  64. package/dist/knowledge/KnowledgeStore.d.ts.map +1 -0
  65. package/dist/knowledge/ProjectKnowledge.d.ts +79 -0
  66. package/dist/knowledge/ProjectKnowledge.d.ts.map +1 -0
  67. package/dist/knowledge/analyze-prompt.d.ts +2 -0
  68. package/dist/knowledge/analyze-prompt.d.ts.map +1 -0
  69. package/dist/knowledge/importers/GongfengExtractor.d.ts +27 -0
  70. package/dist/knowledge/importers/GongfengExtractor.d.ts.map +1 -0
  71. package/dist/knowledge/importers/IwikiImporter.d.ts +21 -0
  72. package/dist/knowledge/importers/IwikiImporter.d.ts.map +1 -0
  73. package/dist/knowledge/index.d.ts +12 -0
  74. package/dist/knowledge/index.d.ts.map +1 -0
  75. package/dist/lib.js +19 -10
  76. package/dist/orchestrator/PipelineOrchestrator.d.ts +5 -1
  77. package/dist/orchestrator/PipelineOrchestrator.d.ts.map +1 -1
  78. package/dist/phases/BasePhase.d.ts.map +1 -1
  79. package/dist/poller/IssuePoller.d.ts +5 -0
  80. package/dist/poller/IssuePoller.d.ts.map +1 -1
  81. package/dist/prompts/chat-templates.d.ts +4 -0
  82. package/dist/prompts/chat-templates.d.ts.map +1 -0
  83. package/dist/prompts/templates.d.ts +11 -0
  84. package/dist/prompts/templates.d.ts.map +1 -1
  85. package/dist/rules/RuleResolver.d.ts +4 -0
  86. package/dist/rules/RuleResolver.d.ts.map +1 -1
  87. package/dist/run.js +11 -5
  88. package/dist/run.js.map +1 -1
  89. package/dist/services/ChatService.d.ts +39 -0
  90. package/dist/services/ChatService.d.ts.map +1 -0
  91. package/dist/shutdown/ShutdownSignal.d.ts +3 -0
  92. package/dist/shutdown/ShutdownSignal.d.ts.map +1 -0
  93. package/dist/{start-TVN4SS6E.js → start-NMQHUKGF.js} +1 -1
  94. package/dist/tracker/IssueState.d.ts +1 -0
  95. package/dist/tracker/IssueState.d.ts.map +1 -1
  96. package/dist/tracker/IssueTracker.d.ts +2 -0
  97. package/dist/tracker/IssueTracker.d.ts.map +1 -1
  98. package/dist/updater/AutoUpdater.d.ts +33 -0
  99. package/dist/updater/AutoUpdater.d.ts.map +1 -0
  100. package/dist/updater/UpdateExecutor.d.ts +7 -0
  101. package/dist/updater/UpdateExecutor.d.ts.map +1 -0
  102. package/dist/updater/VersionChecker.d.ts +22 -0
  103. package/dist/updater/VersionChecker.d.ts.map +1 -0
  104. package/dist/web/WebServer.d.ts +4 -0
  105. package/dist/web/WebServer.d.ts.map +1 -1
  106. package/dist/web/routes/api.d.ts +4 -0
  107. package/dist/web/routes/api.d.ts.map +1 -1
  108. package/dist/web/routes/chat.d.ts +7 -0
  109. package/dist/web/routes/chat.d.ts.map +1 -0
  110. package/dist/web/routes/knowledge.d.ts +13 -0
  111. package/dist/web/routes/knowledge.d.ts.map +1 -0
  112. package/dist/web/routes/setup.d.ts.map +1 -1
  113. package/dist/webhook/CommandExecutor.d.ts +4 -0
  114. package/dist/webhook/CommandExecutor.d.ts.map +1 -1
  115. package/dist/webhook/CommandParser.d.ts +2 -2
  116. package/dist/webhook/CommandParser.d.ts.map +1 -1
  117. package/dist/webhook/WebhookHandler.d.ts +8 -0
  118. package/dist/webhook/WebhookHandler.d.ts.map +1 -1
  119. package/dist/webhook/WebhookServer.d.ts +2 -0
  120. package/dist/webhook/WebhookServer.d.ts.map +1 -1
  121. package/package.json +4 -2
  122. package/src/web/frontend/dist/assets/index-AcJ0lPIv.js +67 -0
  123. package/src/web/frontend/dist/assets/index-BbRt5BAr.css +1 -0
  124. package/src/web/frontend/dist/index.html +2 -2
  125. package/dist/chunk-I3T573SU.js.map +0 -1
  126. package/dist/chunk-IDUKWCC2.js.map +0 -1
  127. package/dist/chunk-OWVT3Z34.js.map +0 -1
  128. package/dist/chunk-TBIEB3JY.js.map +0 -1
  129. package/src/web/frontend/dist/assets/index-CQdlU9PE.js +0 -65
  130. package/src/web/frontend/dist/assets/index-CgMEkyZJ.css +0 -1
  131. /package/dist/{chunk-RIUI4ROA.js.map → chunk-PECYMYAK.js.map} +0 -0
  132. /package/dist/{doctor-B26Q6JWI.js.map → doctor-ZPGIBA5N.js.map} +0 -0
  133. /package/dist/{start-TVN4SS6E.js.map → start-NMQHUKGF.js.map} +0 -0
@@ -1,3 +1,6 @@
1
+ import {
2
+ createSetupRouter
3
+ } from "./chunk-LEQYGOMJ.js";
1
4
  import {
2
5
  BrainstormService,
3
6
  CLASSIC_PIPELINE,
@@ -8,25 +11,38 @@ import {
8
11
  PipelineOrchestrator,
9
12
  PlanPersistence,
10
13
  collectStateLabels,
11
- createAIRunner,
12
14
  eventBus,
13
15
  getE2eEnabled,
14
16
  getNoteSyncEnabled,
15
17
  getPipelineDef,
16
18
  isNoteSyncEnabledForIssue,
17
- loadConfig,
18
- logger,
19
19
  setE2eOverride,
20
20
  setNoteSyncOverride,
21
21
  validatePhaseRegistry
22
- } from "./chunk-TBIEB3JY.js";
22
+ } from "./chunk-N5YK6YVI.js";
23
23
  import {
24
- createSetupRouter
25
- } from "./chunk-I3T573SU.js";
24
+ IwikiImporter,
25
+ getProjectKnowledge,
26
+ loadKnowledge
27
+ } from "./chunk-3JUHZGX5.js";
26
28
  import {
27
29
  setLocale,
28
30
  t
29
- } from "./chunk-OWVT3Z34.js";
31
+ } from "./chunk-JFYAXNNS.js";
32
+ import {
33
+ KnowledgeStore
34
+ } from "./chunk-5JYCGAU3.js";
35
+ import {
36
+ loadConfig
37
+ } from "./chunk-KISVPNSV.js";
38
+ import {
39
+ createAIRunner,
40
+ isShuttingDown,
41
+ setShuttingDown
42
+ } from "./chunk-SWG2Y7YX.js";
43
+ import {
44
+ logger
45
+ } from "./chunk-TZ6C7HL5.js";
30
46
 
31
47
  // src/supplement/SupplementStore.ts
32
48
  import fs from "fs";
@@ -125,6 +141,7 @@ var IssuePoller = class {
125
141
  driveTimer = null;
126
142
  activeIssues = /* @__PURE__ */ new Set();
127
143
  lastAutoApproveCheckMs = 0;
144
+ discoveryPaused = false;
128
145
  constructor(config, gongfeng, tracker, orchestrator) {
129
146
  this.config = config;
130
147
  this.gongfeng = gongfeng;
@@ -153,7 +170,28 @@ var IssuePoller = class {
153
170
  getActiveIssueIids() {
154
171
  return [...this.activeIssues];
155
172
  }
173
+ getActiveCount() {
174
+ return this.activeIssues.size;
175
+ }
176
+ forceReleaseIssue(issueIid) {
177
+ const had = this.activeIssues.has(issueIid);
178
+ if (had) {
179
+ this.activeIssues.delete(issueIid);
180
+ logger3.info("Force-released issue from activeIssues", { issueIid });
181
+ }
182
+ return had;
183
+ }
184
+ pauseDiscovery() {
185
+ this.discoveryPaused = true;
186
+ logger3.info("Discovery paused (auto-update drain)");
187
+ }
188
+ resumeDiscovery() {
189
+ this.discoveryPaused = false;
190
+ logger3.info("Discovery resumed");
191
+ }
156
192
  async discover() {
193
+ if (isShuttingDown()) return;
194
+ if (this.discoveryPaused) return;
157
195
  try {
158
196
  logger3.debug("Discovering new issues...");
159
197
  const issues = await this.gongfeng.listIssues("opened", AUTO_FINISH_LABEL);
@@ -177,6 +215,7 @@ var IssuePoller = class {
177
215
  }
178
216
  }
179
217
  drive() {
218
+ if (isShuttingDown()) return;
180
219
  this.maybeAutoApproveWaiting();
181
220
  const maxConcurrent = this.config.poll.maxConcurrent;
182
221
  const available = maxConcurrent - this.activeIssues.size;
@@ -308,6 +347,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
308
347
  let git;
309
348
  let gongfeng;
310
349
  let supplementStore;
350
+ let autoUpdater;
351
+ let poller;
311
352
  if (config !== void 0) {
312
353
  tracker = trackerOrDeps;
313
354
  cfg = config;
@@ -323,6 +364,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
323
364
  git = deps.mainGit;
324
365
  gongfeng = deps.gongfeng;
325
366
  supplementStore = deps.supplementStore;
367
+ autoUpdater = deps.autoUpdater;
368
+ poller = deps.poller;
326
369
  }
327
370
  const router = Router();
328
371
  router.get("/api/issues", (_req, res) => {
@@ -395,6 +438,7 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
395
438
  router.post("/api/issues/:iid/restart", async (req, res) => {
396
439
  const iid = parseInt(req.params.iid, 10);
397
440
  try {
441
+ poller?.forceReleaseIssue(iid);
398
442
  await orch.restartIssue(iid);
399
443
  res.json({ success: true, message: `Issue #${iid} restarted` });
400
444
  } catch (err) {
@@ -802,6 +846,43 @@ data: ${JSON.stringify({ time: (/* @__PURE__ */ new Date()).toISOString() })}
802
846
  const htmlBody = await marked(content);
803
847
  res.type("html").send(renderDocPage(iid, title, htmlBody, filename));
804
848
  });
849
+ router.get("/api/system/update-status", (_req, res) => {
850
+ if (!autoUpdater) {
851
+ res.status(501).json({ error: "Auto-updater not available" });
852
+ return;
853
+ }
854
+ res.json(autoUpdater.getStatus());
855
+ });
856
+ router.post("/api/system/update-check", async (_req, res) => {
857
+ if (!autoUpdater) {
858
+ res.status(501).json({ error: "Auto-updater not available" });
859
+ return;
860
+ }
861
+ try {
862
+ const result = await autoUpdater.checkForUpdate();
863
+ res.json(result);
864
+ } catch (err) {
865
+ const msg = err.message;
866
+ logger4.error("Manual update check failed", { error: msg });
867
+ res.status(500).json({ error: msg });
868
+ }
869
+ });
870
+ router.post("/api/system/update", async (_req, res) => {
871
+ if (!autoUpdater) {
872
+ res.status(501).json({ error: "Auto-updater not available" });
873
+ return;
874
+ }
875
+ try {
876
+ res.json({ success: true, message: "Update triggered" });
877
+ autoUpdater.triggerUpdate().catch((err) => {
878
+ logger4.error("Manual update failed", { error: err.message });
879
+ });
880
+ } catch (err) {
881
+ const msg = err.message;
882
+ logger4.error("Failed to trigger update", { error: msg });
883
+ res.status(500).json({ error: msg });
884
+ }
885
+ });
805
886
  return router;
806
887
  }
807
888
  var DOC_LABELS_FUNC = (filename) => t(`docLabel.${filename}`) || filename;
@@ -1063,9 +1144,553 @@ function extractTitle(sdd) {
1063
1144
  return "\u8111\u66B4\u751F\u6210\u7684\u9700\u6C42";
1064
1145
  }
1065
1146
 
1147
+ // src/web/routes/chat.ts
1148
+ import { Router as Router3 } from "express";
1149
+ var logger6 = logger.child("ChatRoutes");
1150
+ function sseWriter2(res) {
1151
+ res.setHeader("Content-Type", "text/event-stream");
1152
+ res.setHeader("Cache-Control", "no-cache");
1153
+ res.setHeader("Connection", "keep-alive");
1154
+ res.setHeader("X-Accel-Buffering", "no");
1155
+ res.flushHeaders();
1156
+ return (event) => {
1157
+ try {
1158
+ res.write(`event: ${event.type}
1159
+ data: ${JSON.stringify(event)}
1160
+
1161
+ `);
1162
+ } catch {
1163
+ logger6.warn("Failed to write SSE chat event");
1164
+ }
1165
+ };
1166
+ }
1167
+ function createChatRouter(deps) {
1168
+ const router = Router3();
1169
+ const { chatService } = deps;
1170
+ router.post("/api/chat/sessions", (req, res) => {
1171
+ try {
1172
+ const { title } = req.body;
1173
+ const session = chatService.createSession(title?.trim());
1174
+ res.json({ success: true, session });
1175
+ } catch (err) {
1176
+ logger6.error("Failed to create chat session", { error: err.message });
1177
+ res.status(500).json({ error: err.message });
1178
+ }
1179
+ });
1180
+ router.get("/api/chat/sessions", (_req, res) => {
1181
+ try {
1182
+ const sessions = chatService.listSessions();
1183
+ res.json({ success: true, sessions });
1184
+ } catch (err) {
1185
+ logger6.error("Failed to list chat sessions", { error: err.message });
1186
+ res.status(500).json({ error: err.message });
1187
+ }
1188
+ });
1189
+ router.get("/api/chat/sessions/:id", (req, res) => {
1190
+ const session = chatService.getSession(req.params.id);
1191
+ if (!session) {
1192
+ res.status(404).json({ error: "Session not found" });
1193
+ return;
1194
+ }
1195
+ res.json({ success: true, session });
1196
+ });
1197
+ router.delete("/api/chat/sessions/:id", (req, res) => {
1198
+ const deleted = chatService.deleteSession(req.params.id);
1199
+ if (!deleted) {
1200
+ res.status(404).json({ error: "Session not found" });
1201
+ return;
1202
+ }
1203
+ res.json({ success: true });
1204
+ });
1205
+ router.post("/api/chat/sessions/:id/messages", async (req, res) => {
1206
+ const session = chatService.getSession(req.params.id);
1207
+ if (!session) {
1208
+ res.status(404).json({ error: "Session not found" });
1209
+ return;
1210
+ }
1211
+ const { content } = req.body;
1212
+ if (!content?.trim()) {
1213
+ res.status(400).json({ error: "content is required" });
1214
+ return;
1215
+ }
1216
+ const write = sseWriter2(res);
1217
+ try {
1218
+ await chatService.sendMessage(req.params.id, content.trim(), write);
1219
+ res.write(`event: done
1220
+ data: ${JSON.stringify({ type: "done" })}
1221
+
1222
+ `);
1223
+ } catch (err) {
1224
+ write({ type: "error", data: { message: err.message } });
1225
+ } finally {
1226
+ res.end();
1227
+ }
1228
+ });
1229
+ return router;
1230
+ }
1231
+
1232
+ // src/web/routes/knowledge.ts
1233
+ import { Router as Router4 } from "express";
1234
+ var logger7 = logger.child("KnowledgeRoutes");
1235
+ function createKnowledgeRouter(deps) {
1236
+ const router = Router4();
1237
+ const { knowledgeStore, iwikiImporter, gongfengExtractor, config } = deps;
1238
+ router.get("/api/knowledge/entries", (req, res) => {
1239
+ try {
1240
+ const type = req.query.type;
1241
+ const q = req.query.q;
1242
+ let entries;
1243
+ if (q) {
1244
+ entries = knowledgeStore.search(q);
1245
+ } else {
1246
+ entries = knowledgeStore.list(type || void 0);
1247
+ }
1248
+ res.json({ success: true, entries });
1249
+ } catch (err) {
1250
+ logger7.error("Failed to list knowledge entries", { error: err.message });
1251
+ res.status(500).json({ error: err.message });
1252
+ }
1253
+ });
1254
+ router.get("/api/knowledge/entries/:id", (req, res) => {
1255
+ try {
1256
+ const entry = knowledgeStore.get(req.params.id);
1257
+ if (!entry) {
1258
+ res.status(404).json({ error: "Entry not found" });
1259
+ return;
1260
+ }
1261
+ res.json({ success: true, entry });
1262
+ } catch (err) {
1263
+ logger7.error("Failed to get knowledge entry", { error: err.message });
1264
+ res.status(500).json({ error: err.message });
1265
+ }
1266
+ });
1267
+ router.post("/api/knowledge/entries", (req, res) => {
1268
+ try {
1269
+ const { title, content, tags, type } = req.body;
1270
+ if (!title?.trim() || !content?.trim()) {
1271
+ res.status(400).json({ error: "title and content are required" });
1272
+ return;
1273
+ }
1274
+ const entry = knowledgeStore.create({
1275
+ type: type || "custom",
1276
+ title: title.trim(),
1277
+ content: content.trim(),
1278
+ tags: tags || []
1279
+ });
1280
+ res.json({ success: true, entry });
1281
+ } catch (err) {
1282
+ logger7.error("Failed to create knowledge entry", { error: err.message });
1283
+ res.status(500).json({ error: err.message });
1284
+ }
1285
+ });
1286
+ router.put("/api/knowledge/entries/:id", (req, res) => {
1287
+ try {
1288
+ const { title, content, tags } = req.body;
1289
+ const entry = knowledgeStore.update(req.params.id, { title, content, tags });
1290
+ if (!entry) {
1291
+ res.status(404).json({ error: "Entry not found" });
1292
+ return;
1293
+ }
1294
+ res.json({ success: true, entry });
1295
+ } catch (err) {
1296
+ logger7.error("Failed to update knowledge entry", { error: err.message });
1297
+ res.status(500).json({ error: err.message });
1298
+ }
1299
+ });
1300
+ router.delete("/api/knowledge/entries/:id", (req, res) => {
1301
+ try {
1302
+ const deleted = knowledgeStore.delete(req.params.id);
1303
+ if (!deleted) {
1304
+ res.status(404).json({ error: "Entry not found" });
1305
+ return;
1306
+ }
1307
+ res.json({ success: true });
1308
+ } catch (err) {
1309
+ logger7.error("Failed to delete knowledge entry", { error: err.message });
1310
+ res.status(500).json({ error: err.message });
1311
+ }
1312
+ });
1313
+ router.post("/api/knowledge/analyze", (req, res) => {
1314
+ res.setHeader("Content-Type", "text/event-stream");
1315
+ res.setHeader("Cache-Control", "no-cache");
1316
+ res.setHeader("Connection", "keep-alive");
1317
+ res.setHeader("X-Accel-Buffering", "no");
1318
+ res.flushHeaders();
1319
+ const workDir = config.project.workDir;
1320
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1321
+ const total = 4;
1322
+ let heartbeat;
1323
+ let closed = false;
1324
+ function sendProgress(data) {
1325
+ if (closed) return;
1326
+ try {
1327
+ res.write("data: " + JSON.stringify({ ...data, startedAt }) + "\n\n");
1328
+ } catch {
1329
+ }
1330
+ }
1331
+ (async () => {
1332
+ try {
1333
+ sendProgress({ step: "collecting", current: 1, total, message: "\u6536\u96C6\u9879\u76EE\u4FE1\u606F..." });
1334
+ const { collectStaticInfo } = await import("./KnowledgeAnalyzer-EZSJT2MJ.js");
1335
+ await collectStaticInfo(workDir);
1336
+ sendProgress({ step: "analyzing", current: 2, total, message: "AI \u5206\u6790\u4E2D..." });
1337
+ const aiStart = Date.now();
1338
+ heartbeat = setInterval(() => {
1339
+ sendProgress({ step: "analyzing", current: 2, total, message: "AI \u5206\u6790\u4E2D...", elapsed: Date.now() - aiStart });
1340
+ }, 3e3);
1341
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-RGAJPOOW.js");
1342
+ const runner = createAIRunner2(config.ai);
1343
+ const { analyze } = await import("./KnowledgeAnalyzer-EZSJT2MJ.js");
1344
+ const knowledge = await analyze({ workDir, aiRunner: runner });
1345
+ clearInterval(heartbeat);
1346
+ heartbeat = void 0;
1347
+ sendProgress({ step: "saving", current: 3, total, message: "\u4FDD\u5B58\u5206\u6790\u7ED3\u679C..." });
1348
+ const { projectKnowledgeToMarkdown } = await import("./KnowledgeStore-4ROC6F56.js");
1349
+ const markdown = projectKnowledgeToMarkdown(knowledge);
1350
+ knowledgeStore.upsertProjectMeta(markdown, knowledge);
1351
+ knowledgeStore.setLastAnalyzedAt(knowledge.generatedAt);
1352
+ sendProgress({ step: "done", current: 4, total, message: "\u5206\u6790\u5B8C\u6210" });
1353
+ } catch (err) {
1354
+ if (heartbeat) clearInterval(heartbeat);
1355
+ sendProgress({ step: "error", error: err.message });
1356
+ } finally {
1357
+ res.end();
1358
+ }
1359
+ })();
1360
+ req.on("close", () => {
1361
+ closed = true;
1362
+ if (heartbeat) clearInterval(heartbeat);
1363
+ res.end();
1364
+ });
1365
+ });
1366
+ router.post("/api/knowledge/analyze-incremental", (req, res) => {
1367
+ res.setHeader("Content-Type", "text/event-stream");
1368
+ res.setHeader("Cache-Control", "no-cache");
1369
+ res.setHeader("Connection", "keep-alive");
1370
+ res.setHeader("X-Accel-Buffering", "no");
1371
+ res.flushHeaders();
1372
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1373
+ const total = 3;
1374
+ let heartbeat;
1375
+ let closed = false;
1376
+ function sendProgress(data) {
1377
+ if (closed) return;
1378
+ try {
1379
+ res.write("data: " + JSON.stringify({ ...data, startedAt }) + "\n\n");
1380
+ } catch {
1381
+ }
1382
+ }
1383
+ (async () => {
1384
+ try {
1385
+ sendProgress({ step: "collecting", current: 1, total, message: "\u68C0\u6D4B\u53D8\u66F4\u5E76\u6536\u96C6\u4FE1\u606F..." });
1386
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-RGAJPOOW.js");
1387
+ const { analyzeIncremental } = await import("./KnowledgeAnalyzer-EZSJT2MJ.js");
1388
+ const runner = createAIRunner2(config.ai);
1389
+ sendProgress({ step: "analyzing", current: 2, total, message: "\u589E\u91CF\u5206\u6790\u4E2D..." });
1390
+ const aiStart = Date.now();
1391
+ heartbeat = setInterval(() => {
1392
+ sendProgress({ step: "analyzing", current: 2, total, message: "\u589E\u91CF\u5206\u6790\u4E2D...", elapsed: Date.now() - aiStart });
1393
+ }, 3e3);
1394
+ const result = await analyzeIncremental({
1395
+ workDir: config.project.workDir,
1396
+ aiRunner: runner,
1397
+ store: knowledgeStore
1398
+ });
1399
+ clearInterval(heartbeat);
1400
+ heartbeat = void 0;
1401
+ if (!result) {
1402
+ sendProgress({ step: "no_changes", current: total, total, message: "\u81EA\u4E0A\u6B21\u5206\u6790\u4EE5\u6765\u65E0\u53D8\u66F4" });
1403
+ } else {
1404
+ sendProgress({ step: "done", current: 3, total, message: "\u589E\u91CF\u5206\u6790\u5B8C\u6210", changedFiles: result.changedFiles });
1405
+ }
1406
+ } catch (err) {
1407
+ if (heartbeat) clearInterval(heartbeat);
1408
+ sendProgress({ step: "error", error: err.message });
1409
+ } finally {
1410
+ res.end();
1411
+ }
1412
+ })();
1413
+ req.on("close", () => {
1414
+ closed = true;
1415
+ if (heartbeat) clearInterval(heartbeat);
1416
+ res.end();
1417
+ });
1418
+ });
1419
+ router.post("/api/knowledge/import/iwiki", async (req, res) => {
1420
+ try {
1421
+ const { url } = req.body;
1422
+ if (!url?.trim()) {
1423
+ res.status(400).json({ error: "url is required" });
1424
+ return;
1425
+ }
1426
+ const entry = await iwikiImporter.importFromUrl(url.trim());
1427
+ res.json({ success: true, entry });
1428
+ } catch (err) {
1429
+ logger7.error("Failed to import iwiki page", { error: err.message });
1430
+ res.status(500).json({ error: err.message });
1431
+ }
1432
+ });
1433
+ router.post("/api/knowledge/import/gongfeng", async (req, res) => {
1434
+ try {
1435
+ if (!gongfengExtractor) {
1436
+ res.status(400).json({ error: "Gongfeng extractor not available" });
1437
+ return;
1438
+ }
1439
+ const { type, iid } = req.body;
1440
+ if (!type || !iid) {
1441
+ res.status(400).json({ error: "type and iid are required" });
1442
+ return;
1443
+ }
1444
+ const workDir = config.project.workDir;
1445
+ const entry = type === "issue" ? await gongfengExtractor.extractFromIssue(iid, workDir) : await gongfengExtractor.extractFromMR(iid, workDir);
1446
+ res.json({ success: true, entry });
1447
+ } catch (err) {
1448
+ logger7.error("Failed to extract from gongfeng", { error: err.message });
1449
+ res.status(500).json({ error: err.message });
1450
+ }
1451
+ });
1452
+ router.post("/api/knowledge/entries/:id/sync", async (req, res) => {
1453
+ try {
1454
+ const entry = knowledgeStore.get(req.params.id);
1455
+ if (!entry) {
1456
+ res.status(404).json({ error: "Entry not found" });
1457
+ return;
1458
+ }
1459
+ if (entry.type === "iwiki") {
1460
+ const updated = await iwikiImporter.sync(req.params.id);
1461
+ res.json({ success: true, entry: updated });
1462
+ } else {
1463
+ res.status(400).json({ error: "Only iwiki entries support sync" });
1464
+ }
1465
+ } catch (err) {
1466
+ logger7.error("Failed to sync entry", { error: err.message });
1467
+ res.status(500).json({ error: err.message });
1468
+ }
1469
+ });
1470
+ router.get("/api/knowledge/stats", (_req, res) => {
1471
+ try {
1472
+ const stats = knowledgeStore.getStats();
1473
+ res.json({ success: true, stats });
1474
+ } catch (err) {
1475
+ logger7.error("Failed to get knowledge stats", { error: err.message });
1476
+ res.status(500).json({ error: err.message });
1477
+ }
1478
+ });
1479
+ return router;
1480
+ }
1481
+
1482
+ // src/services/ChatService.ts
1483
+ import { randomUUID } from "crypto";
1484
+
1485
+ // src/prompts/chat-templates.ts
1486
+ var SUMMARY_MAX_CHARS = 500;
1487
+ function buildChatSystemPrompt(knowledge, knowledgeEntries) {
1488
+ let knowledgeSection = "";
1489
+ if (knowledge) {
1490
+ const parts = [];
1491
+ if (knowledge.structure) {
1492
+ parts.push(`- \u4E3B\u8981\u8BED\u8A00\uFF1A${knowledge.structure.primaryLanguage}`);
1493
+ if (knowledge.structure.frameworks.length > 0) {
1494
+ parts.push(`- \u6846\u67B6\uFF1A${knowledge.structure.frameworks.join(", ")}`);
1495
+ }
1496
+ if (knowledge.structure.description) {
1497
+ parts.push(`- \u63CF\u8FF0\uFF1A${knowledge.structure.description}`);
1498
+ }
1499
+ }
1500
+ if (knowledge.toolchain) {
1501
+ parts.push(`- \u5305\u7BA1\u7406\u5668\uFF1A${knowledge.toolchain.packageManager}`);
1502
+ }
1503
+ if (parts.length > 0) {
1504
+ knowledgeSection = `
1505
+ **\u9879\u76EE\u77E5\u8BC6\u5E93\uFF1A**
1506
+ ${parts.join("\n")}
1507
+ `;
1508
+ }
1509
+ }
1510
+ let entriesSection = "";
1511
+ if (knowledgeEntries && knowledgeEntries.length > 0) {
1512
+ const entryParts = knowledgeEntries.map((e) => {
1513
+ const typeLabel = { "project-meta": "\u9879\u76EE\u5206\u6790", custom: "\u81EA\u5B9A\u4E49", iwiki: "iwiki\u6587\u6863", gongfeng: "\u5DE5\u8702\u63D0\u70BC" }[e.type] || e.type;
1514
+ const summary = e.content.length > SUMMARY_MAX_CHARS ? e.content.slice(0, SUMMARY_MAX_CHARS) + "..." : e.content;
1515
+ return `### [${typeLabel}] ${e.title}
1516
+ ${summary}`;
1517
+ });
1518
+ entriesSection = `
1519
+ **\u77E5\u8BC6\u6761\u76EE\uFF08\u5171 ${knowledgeEntries.length} \u6761\uFF09\uFF1A**
1520
+
1521
+ ${entryParts.join("\n\n")}
1522
+ `;
1523
+ }
1524
+ return `\u4F60\u662F\u4E00\u4E2A\u667A\u80FD\u52A9\u624B\uFF0C\u4E13\u6CE8\u4E8E\u4E3A\u5F00\u53D1\u8005\u63D0\u4F9B\u6280\u672F\u54A8\u8BE2\u548C\u9879\u76EE\u5E2E\u52A9\u3002
1525
+
1526
+ \u4F60\u7684\u804C\u8D23\uFF1A
1527
+ 1. \u89E3\u7B54\u6280\u672F\u95EE\u9898\uFF08\u7F16\u7A0B\u8BED\u8A00\u3001\u6846\u67B6\u3001\u5DE5\u5177\u94FE\u7B49\uFF09
1528
+ 2. \u63D0\u4F9B\u9879\u76EE\u76F8\u5173\u7684\u4F7F\u7528\u6307\u5357\u548C\u6700\u4F73\u5B9E\u8DF5
1529
+ 3. \u5E2E\u52A9\u5206\u6790\u548C\u89E3\u51B3\u5F00\u53D1\u4E2D\u9047\u5230\u7684\u95EE\u9898
1530
+ 4. \u7ED9\u51FA\u4EE3\u7801\u793A\u4F8B\u548C\u5B9E\u73B0\u5EFA\u8BAE
1531
+
1532
+ \u56DE\u590D\u539F\u5219\uFF1A
1533
+ - \u4F7F\u7528\u6E05\u6670\u7B80\u6D01\u7684\u8BED\u8A00
1534
+ - \u7ED9\u51FA\u5177\u4F53\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE
1535
+ - \u5FC5\u8981\u65F6\u63D0\u4F9B\u4EE3\u7801\u793A\u4F8B
1536
+ - \u5982\u679C\u4E0D\u786E\u5B9A\uFF0C\u660E\u786E\u8BF4\u660E\u5E76\u7ED9\u51FA\u53EF\u80FD\u7684\u65B9\u5411
1537
+ - \u4F7F\u7528 Markdown \u683C\u5F0F\u7EC4\u7EC7\u56DE\u590D
1538
+ ${knowledgeSection}${entriesSection}
1539
+ \u8BF7\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u7684\u95EE\u9898\uFF0C\u4E0D\u8981\u8F93\u51FA\u591A\u4F59\u7684\u5BD2\u6684\u3002`;
1540
+ }
1541
+
1542
+ // src/services/ChatService.ts
1543
+ var logger8 = logger.child("Chat");
1544
+ function agentConfigToAIConfig(agentCfg, timeoutMs) {
1545
+ return {
1546
+ mode: agentCfg.mode,
1547
+ binary: agentCfg.binary,
1548
+ phaseTimeoutMs: timeoutMs,
1549
+ nvmNodeVersion: agentCfg.nvmNodeVersion,
1550
+ model: agentCfg.model
1551
+ };
1552
+ }
1553
+ var ChatService = class {
1554
+ sessions = /* @__PURE__ */ new Map();
1555
+ runner;
1556
+ config;
1557
+ workDir;
1558
+ knowledgeStore;
1559
+ constructor(config, knowledgeStore) {
1560
+ this.config = config.chat;
1561
+ this.workDir = config.project.workDir;
1562
+ this.knowledgeStore = knowledgeStore;
1563
+ this.runner = createAIRunner(
1564
+ agentConfigToAIConfig(this.config.agent, this.config.timeoutMs)
1565
+ );
1566
+ }
1567
+ createSession(title) {
1568
+ const session = {
1569
+ id: randomUUID(),
1570
+ title: title || "\u65B0\u5BF9\u8BDD",
1571
+ messages: [],
1572
+ status: "idle",
1573
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1574
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1575
+ };
1576
+ this.sessions.set(session.id, session);
1577
+ logger8.info("Created chat session", { sessionId: session.id });
1578
+ return session;
1579
+ }
1580
+ getSession(id) {
1581
+ return this.sessions.get(id);
1582
+ }
1583
+ listSessions() {
1584
+ return Array.from(this.sessions.values()).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1585
+ }
1586
+ deleteSession(id) {
1587
+ return this.sessions.delete(id);
1588
+ }
1589
+ async sendMessage(sessionId, content, onEvent) {
1590
+ const session = this.requireSession(sessionId);
1591
+ if (session.messages.length >= this.config.maxSessionMessages) {
1592
+ throw new Error(`Session message limit reached (${this.config.maxSessionMessages})`);
1593
+ }
1594
+ const userMessage = {
1595
+ id: randomUUID(),
1596
+ role: "user",
1597
+ content,
1598
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1599
+ };
1600
+ session.messages.push(userMessage);
1601
+ session.status = "thinking";
1602
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1603
+ const assistantMessageId = randomUUID();
1604
+ let assistantContent = "";
1605
+ try {
1606
+ onEvent?.({
1607
+ type: "message:thinking",
1608
+ data: { status: "connecting" },
1609
+ messageId: assistantMessageId
1610
+ });
1611
+ const isFirstMessage = session.messages.length === 1;
1612
+ const knowledgeEntries = this.knowledgeStore?.getAllEntries();
1613
+ const prompt = isFirstMessage ? `${buildChatSystemPrompt(getProjectKnowledge(), knowledgeEntries)}
1614
+
1615
+ \u7528\u6237\u95EE\u9898\uFF1A${content}` : content;
1616
+ const result = await this.runner.run({
1617
+ prompt,
1618
+ workDir: this.workDir,
1619
+ timeoutMs: this.config.timeoutMs,
1620
+ sessionId: session.aiSessionId,
1621
+ continueSession: !!session.aiSessionId,
1622
+ onStreamEvent: (evt) => {
1623
+ if (evt.content && typeof evt.content === "object") {
1624
+ const content2 = evt.content;
1625
+ if (content2.type === "assistant") {
1626
+ const message = content2.message;
1627
+ if (message?.content && Array.isArray(message.content)) {
1628
+ for (const block of message.content) {
1629
+ if (block.type === "text" && block.text) {
1630
+ assistantContent += block.text;
1631
+ }
1632
+ }
1633
+ }
1634
+ }
1635
+ } else if (evt.content && typeof evt.content === "string") {
1636
+ if (evt.type === "partial" || evt.type === "raw") {
1637
+ assistantContent += evt.content;
1638
+ }
1639
+ }
1640
+ onEvent?.({
1641
+ type: "message:chunk",
1642
+ data: evt,
1643
+ messageId: assistantMessageId
1644
+ });
1645
+ }
1646
+ });
1647
+ const finalContent = assistantContent.trim() ? assistantContent : result.success ? result.output : result.output || "AI \u672A\u8FD4\u56DE\u5185\u5BB9";
1648
+ const assistantMessage = {
1649
+ id: assistantMessageId,
1650
+ role: "assistant",
1651
+ content: finalContent,
1652
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1653
+ };
1654
+ session.messages.push(assistantMessage);
1655
+ session.aiSessionId = result.sessionId ?? session.aiSessionId;
1656
+ session.status = result.success ? "idle" : "error";
1657
+ if (!result.success) {
1658
+ session.error = result.output;
1659
+ }
1660
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1661
+ if (session.title === "\u65B0\u5BF9\u8BDD" && session.messages.length >= 2) {
1662
+ session.title = content.slice(0, 50) + (content.length > 50 ? "..." : "");
1663
+ }
1664
+ onEvent?.({
1665
+ type: "message:complete",
1666
+ data: { message: assistantMessage },
1667
+ messageId: assistantMessageId
1668
+ });
1669
+ return assistantMessage;
1670
+ } catch (err) {
1671
+ session.status = "error";
1672
+ session.error = err.message;
1673
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1674
+ onEvent?.({
1675
+ type: "error",
1676
+ data: { message: err.message },
1677
+ messageId: assistantMessageId
1678
+ });
1679
+ throw err;
1680
+ }
1681
+ }
1682
+ requireSession(id) {
1683
+ const session = this.sessions.get(id);
1684
+ if (!session) {
1685
+ throw new Error(`Chat session not found: ${id}`);
1686
+ }
1687
+ return session;
1688
+ }
1689
+ };
1690
+
1066
1691
  // src/web/WebServer.ts
1067
1692
  var __dirname = path3.dirname(fileURLToPath(import.meta.url));
1068
- var logger6 = logger.child("WebServer");
1693
+ var logger9 = logger.child("WebServer");
1069
1694
  var WebServer = class {
1070
1695
  app;
1071
1696
  server = null;
@@ -1093,7 +1718,9 @@ var WebServer = class {
1093
1718
  orchestrator: deps.orchestrator,
1094
1719
  mainGit: deps.mainGit,
1095
1720
  gongfeng: deps.gongfeng,
1096
- supplementStore: deps.supplementStore
1721
+ supplementStore: deps.supplementStore,
1722
+ autoUpdater: deps.autoUpdater,
1723
+ poller: deps.poller
1097
1724
  };
1098
1725
  }
1099
1726
  this.app = express();
@@ -1110,6 +1737,25 @@ var WebServer = class {
1110
1737
  });
1111
1738
  this.app.use(brainstormRouter);
1112
1739
  }
1740
+ const knowledgeDataDir = path3.join(apiDeps.config.project.workDir, "data", "knowledge");
1741
+ const knowledgeStore = new KnowledgeStore(knowledgeDataDir);
1742
+ if (apiDeps.config.chat.enabled) {
1743
+ const chatService = new ChatService(apiDeps.config, knowledgeStore);
1744
+ const chatRouter = createChatRouter({ chatService });
1745
+ this.app.use(chatRouter);
1746
+ }
1747
+ const iwikiImporter = new IwikiImporter(knowledgeStore, {
1748
+ authCookie: apiDeps.config.iwiki?.authCookie,
1749
+ authToken: apiDeps.config.iwiki?.authToken
1750
+ });
1751
+ const legacyKnowledgePath = path3.join(apiDeps.config.project.workDir, "knowledge.json");
1752
+ knowledgeStore.migrateFromLegacy(legacyKnowledgePath);
1753
+ const knowledgeRouter = createKnowledgeRouter({
1754
+ knowledgeStore,
1755
+ iwikiImporter,
1756
+ config: apiDeps.config
1757
+ });
1758
+ this.app.use(knowledgeRouter);
1113
1759
  const publicDir = apiDeps.config.web.frontendDistDir;
1114
1760
  this.app.use(express.static(publicDir));
1115
1761
  this.app.get("{*path}", (_req, res) => {
@@ -1119,7 +1765,7 @@ var WebServer = class {
1119
1765
  start() {
1120
1766
  return new Promise((resolve) => {
1121
1767
  this.server = this.app.listen(this.port, () => {
1122
- logger6.info(`Web UI available at http://localhost:${this.port}`);
1768
+ logger9.info(`Web UI available at http://localhost:${this.port}`);
1123
1769
  resolve();
1124
1770
  });
1125
1771
  });
@@ -1128,7 +1774,7 @@ var WebServer = class {
1128
1774
  if (this.server) {
1129
1775
  this.server.close();
1130
1776
  this.server = null;
1131
- logger6.info("Web server stopped");
1777
+ logger9.info("Web server stopped");
1132
1778
  }
1133
1779
  }
1134
1780
  };
@@ -1137,10 +1783,10 @@ var WebServer = class {
1137
1783
  import express2 from "express";
1138
1784
 
1139
1785
  // src/webhook/WebhookHandler.ts
1140
- import { Router as Router3 } from "express";
1786
+ import { Router as Router5 } from "express";
1141
1787
 
1142
1788
  // src/webhook/CommandParser.ts
1143
- var TRIGGER = "@issue-auto";
1789
+ var TRIGGERS = ["@issue-auto", "@iaf"];
1144
1790
  var PHASE_ALIAS = {
1145
1791
  "\u5206\u6790": "analysis",
1146
1792
  "\u9700\u6C42\u5206\u6790": "analysis",
@@ -1238,15 +1884,27 @@ var EXACT_PATTERNS = [
1238
1884
  {
1239
1885
  regex: /(?:清理评论|清除评论|删除评论)/,
1240
1886
  build: () => ({ intent: "clean-notes" })
1887
+ },
1888
+ {
1889
+ regex: /fix-conflict\b/i,
1890
+ build: () => ({ intent: "fix-conflict" })
1891
+ },
1892
+ {
1893
+ regex: /(?:修复冲突|解决冲突|处理冲突|冲突修复|fix\s*冲突)/,
1894
+ build: () => ({ intent: "fix-conflict" })
1241
1895
  }
1242
1896
  ];
1243
1897
  function containsTrigger(text) {
1244
- return text.includes(TRIGGER);
1898
+ const lower = text.toLowerCase();
1899
+ return TRIGGERS.some((t2) => lower.includes(t2));
1245
1900
  }
1246
1901
  function extractCommandText(text) {
1247
- const idx = text.indexOf(TRIGGER);
1248
- if (idx < 0) return null;
1249
- return text.slice(idx + TRIGGER.length).trim();
1902
+ const lower = text.toLowerCase();
1903
+ for (const trigger of TRIGGERS) {
1904
+ const idx = lower.indexOf(trigger);
1905
+ if (idx >= 0) return text.slice(idx + trigger.length).trim();
1906
+ }
1907
+ return null;
1250
1908
  }
1251
1909
  function parseExact(commandText) {
1252
1910
  for (const { regex, build } of EXACT_PATTERNS) {
@@ -1259,28 +1917,30 @@ function parseExact(commandText) {
1259
1917
  // src/webhook/CommandExecutor.ts
1260
1918
  import path4 from "path";
1261
1919
  import fs3 from "fs";
1262
- var logger7 = logger.child("CommandExecutor");
1920
+ var logger10 = logger.child("CommandExecutor");
1263
1921
  var CommandExecutor = class {
1264
1922
  tracker;
1265
1923
  orchestrator;
1266
1924
  supplementStore;
1267
1925
  gongfeng;
1268
1926
  config;
1927
+ poller;
1269
1928
  constructor(deps) {
1270
1929
  this.tracker = deps.tracker;
1271
1930
  this.orchestrator = deps.orchestrator;
1272
1931
  this.supplementStore = deps.supplementStore;
1273
1932
  this.gongfeng = deps.gongfeng;
1274
1933
  this.config = deps.config;
1934
+ this.poller = deps.poller;
1275
1935
  }
1276
1936
  async execute(issueIid, issueId, command) {
1277
- logger7.info("Executing webhook command", { issueIid, command });
1937
+ logger10.info("Executing webhook command", { issueIid, command });
1278
1938
  let result;
1279
1939
  try {
1280
1940
  result = await this.dispatch(issueIid, command);
1281
1941
  } catch (err) {
1282
1942
  const msg = err.message;
1283
- logger7.error("Webhook command failed", { issueIid, error: msg });
1943
+ logger10.error("Webhook command failed", { issueIid, error: msg });
1284
1944
  result = { success: false, message: msg };
1285
1945
  }
1286
1946
  await this.replyToIssue(issueId, result);
@@ -1308,6 +1968,8 @@ var CommandExecutor = class {
1308
1968
  return this.handleStopPreview(iid);
1309
1969
  case "clean-notes":
1310
1970
  return this.handleCleanNotes(iid);
1971
+ case "fix-conflict":
1972
+ return this.handleFixConflict(iid);
1311
1973
  default:
1312
1974
  return { success: false, message: "Unknown command" };
1313
1975
  }
@@ -1399,6 +2061,7 @@ var CommandExecutor = class {
1399
2061
  }
1400
2062
  async handleRestart(iid) {
1401
2063
  try {
2064
+ this.poller?.forceReleaseIssue(iid);
1402
2065
  await this.orchestrator.restartIssue(iid);
1403
2066
  } catch (err) {
1404
2067
  return this.fail(err.message);
@@ -1452,6 +2115,22 @@ var CommandExecutor = class {
1452
2115
  return this.fail(err.message);
1453
2116
  }
1454
2117
  }
2118
+ handleFixConflict(iid) {
2119
+ const record = this.tracker.get(iid);
2120
+ if (!record) return this.notTracked(iid);
2121
+ const isCompleted = record.state === "completed" /* Completed */;
2122
+ const isConflictRetry = record.state === "failed" /* Failed */ && record.failedAtState === "resolving_conflict" /* ResolvingConflict */;
2123
+ if (!isCompleted && !isConflictRetry) {
2124
+ return this.fail(t("conflict.invalidState", { state: record.state }));
2125
+ }
2126
+ if (!record.mrUrl) {
2127
+ return this.fail(t("conflict.noMr", { iid }));
2128
+ }
2129
+ this.orchestrator.resolveConflict(iid).catch((err) => {
2130
+ logger10.error("Async conflict resolution failed", { iid, error: err.message });
2131
+ });
2132
+ return this.ok(t("conflict.startedMsg"));
2133
+ }
1455
2134
  saveSupplement(iid, context) {
1456
2135
  const existing = this.supplementStore.get(iid);
1457
2136
  const prev = existing?.freeText?.trim() ?? "";
@@ -1474,7 +2153,7 @@ ${context}` : context;
1474
2153
  const prefix = result.success ? "" : "> **Failed**\n>\n> ";
1475
2154
  await this.gongfeng.createIssueNote(issueId, `${prefix}${result.message}`);
1476
2155
  } catch (err) {
1477
- logger7.warn("Failed to reply", { issueId, error: err.message });
2156
+ logger10.warn("Failed to reply", { issueId, error: err.message });
1478
2157
  }
1479
2158
  }
1480
2159
  ok(message) {
@@ -1530,9 +2209,9 @@ var NoteDeduplicator = class {
1530
2209
  };
1531
2210
 
1532
2211
  // src/webhook/WebhookHandler.ts
1533
- var logger8 = logger.child("WebhookHandler");
2212
+ var logger11 = logger.child("WebhookHandler");
1534
2213
  function createWebhookRouter(deps) {
1535
- const router = Router3();
2214
+ const router = Router5();
1536
2215
  const executor = new CommandExecutor(deps);
1537
2216
  const dedup = new NoteDeduplicator();
1538
2217
  const { config, intentRecognizer } = deps;
@@ -1545,19 +2224,15 @@ function createWebhookRouter(deps) {
1545
2224
  if (config.webhook.secret) {
1546
2225
  const token = req.headers["x-gitlab-token"];
1547
2226
  if (token !== config.webhook.secret) {
1548
- logger8.warn("Webhook token mismatch");
2227
+ logger11.warn("Webhook token mismatch");
1549
2228
  res.status(401).json({ error: "Invalid token" });
1550
2229
  return;
1551
2230
  }
1552
2231
  }
1553
2232
  const event = req.body;
1554
- if (!isNoteOnIssue(event)) {
1555
- res.json({ ignored: true, reason: "not a note on issue" });
1556
- return;
1557
- }
1558
2233
  const noteBody = event.object_attributes.note;
1559
2234
  if (!containsTrigger(noteBody)) {
1560
- res.json({ ignored: true, reason: "no @issue-auto trigger" });
2235
+ res.json({ ignored: true, reason: "no trigger found" });
1561
2236
  return;
1562
2237
  }
1563
2238
  if (isSelfNote(event, selfToken)) {
@@ -1566,32 +2241,46 @@ function createWebhookRouter(deps) {
1566
2241
  }
1567
2242
  const noteId = event.object_attributes.id;
1568
2243
  if (dedup.isDuplicate(noteId)) {
1569
- logger8.debug("Duplicate note, skipping", { noteId });
2244
+ logger11.debug("Duplicate note, skipping", { noteId });
1570
2245
  res.json({ ignored: true, reason: "duplicate" });
1571
2246
  return;
1572
2247
  }
1573
- const issueIid = event.issue?.iid;
1574
- const issueId = event.issue?.id;
1575
- if (!issueIid) {
1576
- res.status(400).json({ error: "Missing issue iid in webhook payload" });
1577
- return;
1578
- }
1579
- if (!issueId) {
1580
- logger8.info("Webhook received with null issue id (likely a test ping)", { issueIid });
1581
- res.json({ accepted: true, noteId, issueIid, test: true });
1582
- return;
2248
+ if (isNoteOnIssue(event)) {
2249
+ const issueIid = event.issue?.iid;
2250
+ const issueId = event.issue?.id;
2251
+ if (!issueIid) {
2252
+ res.status(400).json({ error: "Missing issue iid in webhook payload" });
2253
+ return;
2254
+ }
2255
+ if (!issueId) {
2256
+ logger11.info("Webhook received with null issue id (likely a test ping)", { issueIid });
2257
+ res.json({ accepted: true, noteId, issueIid, test: true });
2258
+ return;
2259
+ }
2260
+ res.json({ accepted: true, noteId, issueIid });
2261
+ processCommandAsync(
2262
+ noteBody,
2263
+ issueIid,
2264
+ issueId,
2265
+ executor,
2266
+ intentRecognizer,
2267
+ config,
2268
+ deps.tracker,
2269
+ deps.gongfeng
2270
+ );
2271
+ } else if (isNoteOnMergeRequest(event)) {
2272
+ const mr = event.merge_request;
2273
+ res.json({ accepted: true, noteId, mrIid: mr.iid });
2274
+ processMRCommandAsync(
2275
+ noteBody,
2276
+ mr,
2277
+ executor,
2278
+ deps.tracker,
2279
+ deps.gongfeng
2280
+ );
2281
+ } else {
2282
+ res.json({ ignored: true, reason: "not a note on issue or MR" });
1583
2283
  }
1584
- res.json({ accepted: true, noteId, issueIid });
1585
- processCommandAsync(
1586
- noteBody,
1587
- issueIid,
1588
- issueId,
1589
- executor,
1590
- intentRecognizer,
1591
- config,
1592
- deps.tracker,
1593
- deps.gongfeng
1594
- );
1595
2284
  });
1596
2285
  return router;
1597
2286
  }
@@ -1604,19 +2293,19 @@ async function processCommandAsync(noteBody, issueIid, issueId, executor, intent
1604
2293
  if (!command && intentRecognizer && config.webhook.llmFallback) {
1605
2294
  const record = tracker.get(issueIid);
1606
2295
  const state = record?.state;
1607
- logger8.info("Falling back to LLM intent recognition", { issueIid });
2296
+ logger11.info("Falling back to LLM intent recognition", { issueIid });
1608
2297
  command = await intentRecognizer.recognize(cmdText, state);
1609
2298
  }
1610
2299
  if (!command) {
1611
- logger8.info("Could not parse command from note", { issueIid, text: cmdText.slice(0, 100) });
2300
+ logger11.info("Could not parse command from note", { issueIid, text: cmdText.slice(0, 100) });
1612
2301
  await gongfeng.createIssueNote(issueId, HELP_TEXT_FUNC()).catch(
1613
- (e) => logger8.warn("Failed to reply help text", { error: e.message })
2302
+ (e) => logger11.warn("Failed to reply help text", { error: e.message })
1614
2303
  );
1615
2304
  return;
1616
2305
  }
1617
2306
  await executor.execute(issueIid, issueId, command);
1618
2307
  } catch (err) {
1619
- logger8.error("Failed to process webhook command", {
2308
+ logger11.error("Failed to process webhook command", {
1620
2309
  issueIid,
1621
2310
  error: err.message
1622
2311
  });
@@ -1625,13 +2314,62 @@ async function processCommandAsync(noteBody, issueIid, issueId, executor, intent
1625
2314
  function isNoteOnIssue(event) {
1626
2315
  return event.object_kind === "note" && event.object_attributes?.noteable_type?.toLowerCase() === "issue" && !!event.issue;
1627
2316
  }
2317
+ function isNoteOnMergeRequest(event) {
2318
+ return event.object_kind === "note" && (event.object_attributes?.noteable_type?.toLowerCase() === "review" || event.object_attributes?.noteable_type?.toLowerCase() === "mergerequest") && !!event.merge_request;
2319
+ }
2320
+ var MR_SUPPORTED_INTENTS = /* @__PURE__ */ new Set(["fix-conflict", "status"]);
2321
+ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng) {
2322
+ try {
2323
+ const cmdText = extractCommandText(noteBody);
2324
+ if (!cmdText) return;
2325
+ const command = parseExact(cmdText);
2326
+ if (!command) {
2327
+ await gongfeng.createMergeRequestNote(mr.iid, HELP_TEXT_FUNC()).catch(
2328
+ (e) => logger11.warn("Failed to reply help text on MR", { error: e.message })
2329
+ );
2330
+ return;
2331
+ }
2332
+ if (!MR_SUPPORTED_INTENTS.has(command.intent)) {
2333
+ await gongfeng.createMergeRequestNote(
2334
+ mr.iid,
2335
+ `\`${command.intent}\` command is not supported on MR comments. Please use Issue comments instead.`
2336
+ ).catch(
2337
+ (e) => logger11.warn("Failed to reply on MR", { error: e.message })
2338
+ );
2339
+ return;
2340
+ }
2341
+ const allRecords = tracker.getAll();
2342
+ const record = allRecords.find((r) => r.branchName === mr.source_branch);
2343
+ if (!record) {
2344
+ await gongfeng.createMergeRequestNote(
2345
+ mr.iid,
2346
+ `Could not find a tracked issue for branch \`${mr.source_branch}\`.`
2347
+ ).catch(
2348
+ (e) => logger11.warn("Failed to reply on MR", { error: e.message })
2349
+ );
2350
+ return;
2351
+ }
2352
+ const result = await executor.execute(record.issueIid, record.issueId, command);
2353
+ try {
2354
+ const prefix = result.success ? "" : "> **Failed**\n>\n> ";
2355
+ await gongfeng.createMergeRequestNote(mr.iid, `${prefix}${result.message}`);
2356
+ } catch (err) {
2357
+ logger11.warn("Failed to reply on MR", { mrIid: mr.iid, error: err.message });
2358
+ }
2359
+ } catch (err) {
2360
+ logger11.error("Failed to process MR webhook command", {
2361
+ mrIid: mr.iid,
2362
+ error: err.message
2363
+ });
2364
+ }
2365
+ }
1628
2366
  function isSelfNote(_event, _selfToken) {
1629
2367
  return false;
1630
2368
  }
1631
2369
 
1632
2370
  // src/webhook/IntentRecognizer.ts
1633
2371
  import { spawn } from "child_process";
1634
- var logger9 = logger.child("IntentRecognizer");
2372
+ var logger12 = logger.child("IntentRecognizer");
1635
2373
  var VALID_INTENTS = [
1636
2374
  "retry",
1637
2375
  "retry-from",
@@ -1639,7 +2377,8 @@ var VALID_INTENTS = [
1639
2377
  "approve",
1640
2378
  "reject",
1641
2379
  "status",
1642
- "restart"
2380
+ "restart",
2381
+ "fix-conflict"
1643
2382
  ];
1644
2383
  var VALID_PHASES = [
1645
2384
  "analysis",
@@ -1672,7 +2411,7 @@ ${userComment}`;
1672
2411
  const rawOutput = await this.runLLM(userPrompt);
1673
2412
  return this.parseResponse(rawOutput);
1674
2413
  } catch (err) {
1675
- logger9.error("Intent recognition failed", { error: err.message });
2414
+ logger12.error("Intent recognition failed", { error: err.message });
1676
2415
  return null;
1677
2416
  }
1678
2417
  }
@@ -1680,20 +2419,20 @@ ${userComment}`;
1680
2419
  parseResponse(raw) {
1681
2420
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
1682
2421
  if (!jsonMatch) {
1683
- logger9.warn("No JSON found in LLM response", { raw: raw.slice(0, 200) });
2422
+ logger12.warn("No JSON found in LLM response", { raw: raw.slice(0, 200) });
1684
2423
  return null;
1685
2424
  }
1686
2425
  let parsed;
1687
2426
  try {
1688
2427
  parsed = JSON.parse(jsonMatch[0]);
1689
2428
  } catch {
1690
- logger9.warn("Failed to parse JSON from LLM", { raw: jsonMatch[0].slice(0, 200) });
2429
+ logger12.warn("Failed to parse JSON from LLM", { raw: jsonMatch[0].slice(0, 200) });
1691
2430
  return null;
1692
2431
  }
1693
2432
  if (parsed.intent === null || parsed.intent === "null") return null;
1694
2433
  const intent = String(parsed.intent);
1695
2434
  if (!VALID_INTENTS.includes(intent)) {
1696
- logger9.warn("Invalid intent from LLM", { intent });
2435
+ logger12.warn("Invalid intent from LLM", { intent });
1697
2436
  return null;
1698
2437
  }
1699
2438
  const result = { intent };
@@ -1735,7 +2474,7 @@ ${userComment}`;
1735
2474
  child.on("close", (code) => {
1736
2475
  clearTimeout(timer);
1737
2476
  if (code !== 0) {
1738
- logger9.warn("LLM process exited with non-zero code", { code, stderr: stderr.slice(0, 300) });
2477
+ logger12.warn("LLM process exited with non-zero code", { code, stderr: stderr.slice(0, 300) });
1739
2478
  }
1740
2479
  resolve(stdout);
1741
2480
  });
@@ -1750,7 +2489,7 @@ ${userComment}`;
1750
2489
  };
1751
2490
 
1752
2491
  // src/webhook/WebhookServer.ts
1753
- var logger10 = logger.child("WebhookServer");
2492
+ var logger13 = logger.child("WebhookServer");
1754
2493
  var WebhookServer = class {
1755
2494
  app;
1756
2495
  server = null;
@@ -1768,7 +2507,8 @@ var WebhookServer = class {
1768
2507
  supplementStore: deps.supplementStore,
1769
2508
  gongfeng: deps.gongfeng,
1770
2509
  config: deps.config,
1771
- intentRecognizer
2510
+ intentRecognizer,
2511
+ poller: deps.poller
1772
2512
  };
1773
2513
  this.app = express2();
1774
2514
  this.app.use(express2.json());
@@ -1780,7 +2520,7 @@ var WebhookServer = class {
1780
2520
  start() {
1781
2521
  return new Promise((resolve) => {
1782
2522
  this.server = this.app.listen(this.port, () => {
1783
- logger10.info(`Webhook server listening on port ${this.port}`);
2523
+ logger13.info(`Webhook server listening on port ${this.port}`);
1784
2524
  resolve();
1785
2525
  });
1786
2526
  });
@@ -1789,7 +2529,7 @@ var WebhookServer = class {
1789
2529
  if (this.server) {
1790
2530
  this.server.close();
1791
2531
  this.server = null;
1792
- logger10.info("Webhook server stopped");
2532
+ logger13.info("Webhook server stopped");
1793
2533
  }
1794
2534
  }
1795
2535
  };
@@ -1797,7 +2537,7 @@ var WebhookServer = class {
1797
2537
  // src/web/AgentLogStore.ts
1798
2538
  import fs4 from "fs";
1799
2539
  import path5 from "path";
1800
- var logger11 = logger.child("AgentLogStore");
2540
+ var logger14 = logger.child("AgentLogStore");
1801
2541
  var MAX_LOGS_PER_ISSUE = 2e4;
1802
2542
  var DEBUG_EVENT_TYPES = /* @__PURE__ */ new Set([
1803
2543
  "thinking",
@@ -1824,7 +2564,7 @@ var AgentLogStore = class {
1824
2564
  eventBus.on("pipeline:progress", (payload) => {
1825
2565
  this.handlePipelineProgress(payload);
1826
2566
  });
1827
- logger11.info("AgentLogStore listening for events");
2567
+ logger14.info("AgentLogStore listening for events");
1828
2568
  }
1829
2569
  getLogs(issueIid) {
1830
2570
  const filePath = this.logFilePath(issueIid);
@@ -1834,7 +2574,7 @@ var AgentLogStore = class {
1834
2574
  if (!raw) return [];
1835
2575
  return raw.split("\n").map((line) => JSON.parse(line));
1836
2576
  } catch (err) {
1837
- logger11.warn("Failed to read agent logs", { issueIid, error: err.message });
2577
+ logger14.warn("Failed to read agent logs", { issueIid, error: err.message });
1838
2578
  return [];
1839
2579
  }
1840
2580
  }
@@ -1854,7 +2594,7 @@ var AgentLogStore = class {
1854
2594
  `, "utf-8");
1855
2595
  this.trimIfNeeded(issueIid, filePath);
1856
2596
  } catch (err) {
1857
- logger11.warn("Failed to write agent log", { issueIid, error: err.message });
2597
+ logger14.warn("Failed to write agent log", { issueIid, error: err.message });
1858
2598
  }
1859
2599
  }
1860
2600
  trimIfNeeded(issueIid, filePath) {
@@ -1866,7 +2606,7 @@ var AgentLogStore = class {
1866
2606
  const trimmed = lines.slice(-MAX_LOGS_PER_ISSUE);
1867
2607
  fs4.writeFileSync(filePath, `${trimmed.join("\n")}
1868
2608
  `, "utf-8");
1869
- logger11.info("Agent logs trimmed", { issueIid, from: lines.length, to: trimmed.length });
2609
+ logger14.info("Agent logs trimmed", { issueIid, from: lines.length, to: trimmed.length });
1870
2610
  }
1871
2611
  } catch {
1872
2612
  }
@@ -1936,14 +2676,317 @@ var AgentLogStore = class {
1936
2676
  }
1937
2677
  };
1938
2678
 
1939
- // src/index.ts
2679
+ // src/updater/VersionChecker.ts
2680
+ import { readFileSync, existsSync } from "fs";
1940
2681
  import path6 from "path";
1941
2682
  import { fileURLToPath as fileURLToPath2 } from "url";
1942
2683
  var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
2684
+ var logger15 = logger.child("VersionChecker");
2685
+ function compareSemver(a, b) {
2686
+ const pa = a.split(".").map(Number);
2687
+ const pb = b.split(".").map(Number);
2688
+ for (let i = 0; i < 3; i++) {
2689
+ const va = pa[i] ?? 0;
2690
+ const vb = pb[i] ?? 0;
2691
+ if (va > vb) return 1;
2692
+ if (va < vb) return -1;
2693
+ }
2694
+ return 0;
2695
+ }
2696
+ var VersionChecker = class {
2697
+ registry;
2698
+ packageName;
2699
+ currentVersion;
2700
+ constructor(registry) {
2701
+ this.registry = registry.replace(/\/$/, "");
2702
+ const pkg = this.readPackageJson();
2703
+ this.packageName = pkg.name;
2704
+ this.currentVersion = pkg.version;
2705
+ }
2706
+ getCurrentVersion() {
2707
+ return this.currentVersion;
2708
+ }
2709
+ getPackageName() {
2710
+ return this.packageName;
2711
+ }
2712
+ async check() {
2713
+ const latestVersion = await this.fetchLatestVersion();
2714
+ const hasUpdate = compareSemver(latestVersion, this.currentVersion) > 0;
2715
+ logger15.info("Version check completed", {
2716
+ current: this.currentVersion,
2717
+ latest: latestVersion,
2718
+ hasUpdate
2719
+ });
2720
+ return {
2721
+ currentVersion: this.currentVersion,
2722
+ latestVersion,
2723
+ hasUpdate
2724
+ };
2725
+ }
2726
+ async fetchLatestVersion() {
2727
+ const url = `${this.registry}/${encodeURIComponent(this.packageName)}`;
2728
+ const response = await fetch(url, {
2729
+ signal: AbortSignal.timeout(15e3),
2730
+ headers: { Accept: "application/json" }
2731
+ });
2732
+ if (!response.ok) {
2733
+ throw new Error(`Registry returned HTTP ${response.status} for ${this.packageName}`);
2734
+ }
2735
+ const data = await response.json();
2736
+ const latest = data["dist-tags"]?.latest;
2737
+ if (!latest) {
2738
+ throw new Error(`No latest version found in dist-tags for ${this.packageName}`);
2739
+ }
2740
+ return latest;
2741
+ }
2742
+ readPackageJson() {
2743
+ for (let dir = __dirname2; dir !== path6.dirname(dir); dir = path6.dirname(dir)) {
2744
+ const candidate = path6.join(dir, "package.json");
2745
+ if (existsSync(candidate)) {
2746
+ try {
2747
+ const content = JSON.parse(readFileSync(candidate, "utf-8"));
2748
+ if (content.name && content.version) return content;
2749
+ } catch {
2750
+ }
2751
+ }
2752
+ }
2753
+ throw new Error("Could not find package.json");
2754
+ }
2755
+ };
2756
+
2757
+ // src/updater/UpdateExecutor.ts
2758
+ import { execFile } from "child_process";
2759
+ import { cpSync, existsSync as existsSync2, rmSync } from "fs";
2760
+ import path7 from "path";
2761
+ import { fileURLToPath as fileURLToPath3 } from "url";
2762
+ var __dirname3 = path7.dirname(fileURLToPath3(import.meta.url));
2763
+ var logger16 = logger.child("UpdateExecutor");
2764
+ function findProjectRoot() {
2765
+ for (let dir = __dirname3; dir !== path7.dirname(dir); dir = path7.dirname(dir)) {
2766
+ if (existsSync2(path7.join(dir, "package.json"))) {
2767
+ return dir;
2768
+ }
2769
+ }
2770
+ throw new Error("Could not find project root");
2771
+ }
2772
+ function run(cmd, args, cwd, timeoutMs) {
2773
+ return new Promise((resolve, reject) => {
2774
+ execFile(cmd, args, { cwd, timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
2775
+ if (err) {
2776
+ reject(new Error(`${cmd} ${args.join(" ")} failed: ${stderr || err.message}`));
2777
+ return;
2778
+ }
2779
+ resolve(stdout);
2780
+ });
2781
+ });
2782
+ }
2783
+ var UpdateExecutor = class {
2784
+ packageName;
2785
+ projectRoot;
2786
+ constructor(packageName) {
2787
+ this.packageName = packageName;
2788
+ this.projectRoot = findProjectRoot();
2789
+ }
2790
+ async execute(targetVersion) {
2791
+ const distDir = path7.join(this.projectRoot, "dist");
2792
+ const backupDir = path7.join(this.projectRoot, "dist.backup");
2793
+ logger16.info("Backing up dist directory...");
2794
+ if (existsSync2(backupDir)) {
2795
+ rmSync(backupDir, { recursive: true, force: true });
2796
+ }
2797
+ if (existsSync2(distDir)) {
2798
+ cpSync(distDir, backupDir, { recursive: true });
2799
+ }
2800
+ try {
2801
+ logger16.info("Running pnpm update...", { package: this.packageName, targetVersion });
2802
+ await run("pnpm", ["update", this.packageName], this.projectRoot, 12e4);
2803
+ logger16.info("Running pnpm build...");
2804
+ await run("pnpm", ["build"], this.projectRoot, 12e4);
2805
+ } catch (err) {
2806
+ logger16.error("Update failed, rolling back dist...", { error: err.message });
2807
+ if (existsSync2(backupDir)) {
2808
+ if (existsSync2(distDir)) {
2809
+ rmSync(distDir, { recursive: true, force: true });
2810
+ }
2811
+ cpSync(backupDir, distDir, { recursive: true });
2812
+ rmSync(backupDir, { recursive: true, force: true });
2813
+ }
2814
+ throw err;
2815
+ }
2816
+ logger16.info("Reloading via PM2...");
2817
+ try {
2818
+ await run("pm2", ["reload", "issue-auto-finish"], this.projectRoot, 3e4);
2819
+ } catch (err) {
2820
+ logger16.warn("PM2 reload failed, the process may need manual restart", {
2821
+ error: err.message
2822
+ });
2823
+ throw err;
2824
+ }
2825
+ if (existsSync2(backupDir)) {
2826
+ rmSync(backupDir, { recursive: true, force: true });
2827
+ }
2828
+ logger16.info("Update completed successfully", { version: targetVersion });
2829
+ }
2830
+ };
2831
+
2832
+ // src/updater/AutoUpdater.ts
2833
+ var logger17 = logger.child("AutoUpdater");
2834
+ var INITIAL_DELAY_MS = 3e4;
2835
+ var AutoUpdater = class {
2836
+ config;
2837
+ poller;
2838
+ versionChecker;
2839
+ updateExecutor;
2840
+ state = "idle";
2841
+ latestVersion = null;
2842
+ lastCheckAt = null;
2843
+ lastError = null;
2844
+ updateInProgress = false;
2845
+ checkTimer = null;
2846
+ initialTimer = null;
2847
+ constructor(config, poller) {
2848
+ this.config = config;
2849
+ this.poller = poller;
2850
+ this.versionChecker = new VersionChecker(config.autoUpdate.registry);
2851
+ this.updateExecutor = new UpdateExecutor(this.versionChecker.getPackageName());
2852
+ }
2853
+ start() {
2854
+ if (!this.config.autoUpdate.enabled) {
2855
+ logger17.info("Auto-update is disabled");
2856
+ return;
2857
+ }
2858
+ logger17.info("Auto-updater starting", {
2859
+ intervalMs: this.config.autoUpdate.intervalMs,
2860
+ registry: this.config.autoUpdate.registry
2861
+ });
2862
+ this.initialTimer = setTimeout(() => {
2863
+ this.initialTimer = null;
2864
+ this.performCheck();
2865
+ this.checkTimer = setInterval(() => this.performCheck(), this.config.autoUpdate.intervalMs);
2866
+ }, INITIAL_DELAY_MS);
2867
+ }
2868
+ stop() {
2869
+ if (this.initialTimer) {
2870
+ clearTimeout(this.initialTimer);
2871
+ this.initialTimer = null;
2872
+ }
2873
+ if (this.checkTimer) {
2874
+ clearInterval(this.checkTimer);
2875
+ this.checkTimer = null;
2876
+ }
2877
+ logger17.info("Auto-updater stopped");
2878
+ }
2879
+ getStatus() {
2880
+ return {
2881
+ state: this.state,
2882
+ enabled: this.config.autoUpdate.enabled,
2883
+ currentVersion: this.versionChecker.getCurrentVersion(),
2884
+ latestVersion: this.latestVersion,
2885
+ lastCheckAt: this.lastCheckAt,
2886
+ lastError: this.lastError
2887
+ };
2888
+ }
2889
+ async checkForUpdate() {
2890
+ this.state = "checking";
2891
+ eventBus.emitTyped("update:checking", {});
2892
+ try {
2893
+ const result = await this.versionChecker.check();
2894
+ this.latestVersion = result.latestVersion;
2895
+ this.lastCheckAt = (/* @__PURE__ */ new Date()).toISOString();
2896
+ this.lastError = null;
2897
+ if (result.hasUpdate) {
2898
+ this.state = "update-available";
2899
+ eventBus.emitTyped("update:available", {
2900
+ currentVersion: result.currentVersion,
2901
+ latestVersion: result.latestVersion
2902
+ });
2903
+ } else {
2904
+ this.state = "idle";
2905
+ }
2906
+ return result;
2907
+ } catch (err) {
2908
+ const message = err.message;
2909
+ this.lastError = message;
2910
+ this.state = "failed";
2911
+ eventBus.emitTyped("update:failed", { error: message });
2912
+ throw err;
2913
+ }
2914
+ }
2915
+ async triggerUpdate() {
2916
+ if (this.updateInProgress) {
2917
+ logger17.warn("Update already in progress, skipping");
2918
+ return;
2919
+ }
2920
+ this.updateInProgress = true;
2921
+ try {
2922
+ if (!this.latestVersion) {
2923
+ const result = await this.checkForUpdate();
2924
+ if (!result.hasUpdate) {
2925
+ logger17.info("No update available");
2926
+ return;
2927
+ }
2928
+ }
2929
+ const targetVersion = this.latestVersion;
2930
+ this.state = "draining";
2931
+ logger17.info("Draining active issues before update...");
2932
+ this.poller.pauseDiscovery();
2933
+ const drainStart = Date.now();
2934
+ const drainTimeout = this.config.autoUpdate.drainTimeoutMs;
2935
+ while (this.poller.getActiveCount() > 0 && Date.now() - drainStart < drainTimeout) {
2936
+ logger17.info("Waiting for active issues to complete...", {
2937
+ active: this.poller.getActiveCount()
2938
+ });
2939
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
2940
+ }
2941
+ if (this.poller.getActiveCount() > 0) {
2942
+ logger17.warn("Drain timeout reached, proceeding with update", {
2943
+ active: this.poller.getActiveCount()
2944
+ });
2945
+ }
2946
+ this.state = "updating";
2947
+ eventBus.emitTyped("update:downloading", { version: targetVersion });
2948
+ logger17.info("Executing update...", { targetVersion });
2949
+ await this.updateExecutor.execute(targetVersion);
2950
+ this.state = "completed";
2951
+ eventBus.emitTyped("update:completed", { version: targetVersion });
2952
+ logger17.info("Update completed", { version: targetVersion });
2953
+ } catch (err) {
2954
+ const message = err.message;
2955
+ this.lastError = message;
2956
+ this.state = "failed";
2957
+ eventBus.emitTyped("update:failed", { error: message });
2958
+ logger17.error("Update failed", { error: message });
2959
+ this.poller.resumeDiscovery();
2960
+ } finally {
2961
+ this.updateInProgress = false;
2962
+ }
2963
+ }
2964
+ async performCheck() {
2965
+ if (this.updateInProgress) return;
2966
+ try {
2967
+ const result = await this.checkForUpdate();
2968
+ if (result.hasUpdate) {
2969
+ logger17.info("New version available, starting update", {
2970
+ current: result.currentVersion,
2971
+ latest: result.latestVersion
2972
+ });
2973
+ await this.triggerUpdate();
2974
+ }
2975
+ } catch (err) {
2976
+ logger17.warn("Periodic version check failed", { error: err.message });
2977
+ }
2978
+ }
2979
+ };
2980
+
2981
+ // src/index.ts
2982
+ import path8 from "path";
2983
+ import { fileURLToPath as fileURLToPath4 } from "url";
2984
+ var __dirname4 = path8.dirname(fileURLToPath4(import.meta.url));
1943
2985
  async function main() {
1944
2986
  logger.info("Issue Auto-Finish service starting...");
1945
2987
  const config = loadConfig();
1946
2988
  setLocale(config.locale);
2989
+ loadKnowledge(config.knowledgePath);
1947
2990
  const allAiPhaseNames = [
1948
2991
  ...CLASSIC_PIPELINE.phases.filter((p) => p.kind === "ai").map((p) => p.name),
1949
2992
  ...PLAN_MODE_PIPELINE.phases.filter((p) => p.kind === "ai").map((p) => p.name)
@@ -1952,7 +2995,7 @@ async function main() {
1952
2995
  const gongfeng = new GongfengClient(config.gongfeng);
1953
2996
  const git = new GitOperations(config.project.gitRootDir);
1954
2997
  const aiRunner = createAIRunner(config.ai);
1955
- const dataDir = path6.resolve(__dirname2, "../data");
2998
+ const dataDir = path8.resolve(__dirname4, "../data");
1956
2999
  const tracker = new IssueTracker(dataDir);
1957
3000
  const supplementStore = new SupplementStore(dataDir);
1958
3001
  const agentLogStore = new AgentLogStore(dataDir);
@@ -1960,25 +3003,53 @@ async function main() {
1960
3003
  const orchestrator = new PipelineOrchestrator(config, gongfeng, git, aiRunner, tracker, supplementStore);
1961
3004
  const poller = new IssuePoller(config, gongfeng, tracker, orchestrator);
1962
3005
  let webServer = null;
3006
+ const autoUpdater = new AutoUpdater(config, poller);
1963
3007
  if (config.web.enabled) {
1964
- webServer = new WebServer({ tracker, config, agentLogStore, orchestrator, gongfeng, supplementStore, mainGit: git });
3008
+ webServer = new WebServer({ tracker, config, agentLogStore, orchestrator, gongfeng, supplementStore, mainGit: git, autoUpdater, poller });
1965
3009
  await webServer.start();
1966
3010
  }
1967
3011
  let webhookServer = null;
1968
3012
  if (config.webhook.enabled) {
1969
- webhookServer = new WebhookServer({ tracker, config, orchestrator, gongfeng, supplementStore });
3013
+ webhookServer = new WebhookServer({ tracker, config, orchestrator, gongfeng, supplementStore, poller });
1970
3014
  await webhookServer.start();
1971
3015
  }
1972
- const shutdown = () => {
1973
- logger.info("Shutting down...");
3016
+ const recoveredCount = tracker.recoverInterruptedIssues();
3017
+ if (recoveredCount > 0) {
3018
+ logger.info("Recovered interrupted issues on startup", { count: recoveredCount });
3019
+ }
3020
+ await orchestrator.cleanupStaleState();
3021
+ let shutdownInProgress = false;
3022
+ const shutdown = async () => {
3023
+ if (shutdownInProgress) return;
3024
+ shutdownInProgress = true;
3025
+ logger.info("Graceful shutdown initiated");
3026
+ setShuttingDown();
3027
+ autoUpdater.stop();
1974
3028
  poller.stop();
3029
+ aiRunner.killAll();
3030
+ orchestrator.getDevServerManager().stopAll();
3031
+ const drainStart = Date.now();
3032
+ const DRAIN_TIMEOUT_MS = 5e3;
3033
+ while (poller.getActiveCount() > 0 && Date.now() - drainStart < DRAIN_TIMEOUT_MS) {
3034
+ logger.info("Waiting for active issues to drain...", { active: poller.getActiveCount() });
3035
+ await new Promise((resolve) => setTimeout(resolve, 500));
3036
+ }
3037
+ if (poller.getActiveCount() > 0) {
3038
+ logger.warn("Drain timeout reached, proceeding with shutdown", { active: poller.getActiveCount() });
3039
+ }
1975
3040
  webServer?.stop();
1976
3041
  webhookServer?.stop();
3042
+ logger.info("Graceful shutdown complete");
1977
3043
  process.exit(0);
1978
3044
  };
1979
- process.on("SIGINT", shutdown);
1980
- process.on("SIGTERM", shutdown);
3045
+ process.on("SIGINT", () => {
3046
+ shutdown();
3047
+ });
3048
+ process.on("SIGTERM", () => {
3049
+ shutdown();
3050
+ });
1981
3051
  poller.start();
3052
+ autoUpdater.start();
1982
3053
  logger.info("Issue Auto-Finish service started", {
1983
3054
  projectPath: config.gongfeng.projectPath,
1984
3055
  workDir: config.project.workDir,
@@ -1992,4 +3063,4 @@ async function main() {
1992
3063
  export {
1993
3064
  main
1994
3065
  };
1995
- //# sourceMappingURL=chunk-IDUKWCC2.js.map
3066
+ //# sourceMappingURL=chunk-C6ZJVIPZ.js.map