@xdevops/issue-auto-finish 1.0.85 → 1.0.87

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 (129) hide show
  1. package/dist/AIRunnerRegistry-II3WWSFN.js +31 -0
  2. package/dist/PtyRunner-6UGI5STW.js +22 -0
  3. package/dist/TerminalManager-RT2N7N5R.js +8 -0
  4. package/dist/ai-runner/AIRunner.d.ts +9 -1
  5. package/dist/ai-runner/AIRunner.d.ts.map +1 -1
  6. package/dist/ai-runner/AIRunnerRegistry.d.ts +37 -1
  7. package/dist/ai-runner/AIRunnerRegistry.d.ts.map +1 -1
  8. package/dist/ai-runner/PtyRunner.d.ts +114 -0
  9. package/dist/ai-runner/PtyRunner.d.ts.map +1 -0
  10. package/dist/ai-runner/index.d.ts +3 -1
  11. package/dist/ai-runner/index.d.ts.map +1 -1
  12. package/dist/{ai-runner-SVUNA3FX.js → ai-runner-HLA44WI6.js} +12 -3
  13. package/dist/{analyze-SXXPE5XL.js → analyze-ZIXNC5GN.js} +10 -8
  14. package/dist/{analyze-SXXPE5XL.js.map → analyze-ZIXNC5GN.js.map} +1 -1
  15. package/dist/{braindump-4E5SDMSZ.js → braindump-56WAY2RD.js} +10 -8
  16. package/dist/{braindump-4E5SDMSZ.js.map → braindump-56WAY2RD.js.map} +1 -1
  17. package/dist/{chunk-JINMYD56.js → chunk-2MESXJEZ.js} +3 -3
  18. package/dist/{chunk-P4O4ZXEC.js → chunk-2YQHKXLL.js} +40 -19
  19. package/dist/chunk-2YQHKXLL.js.map +1 -0
  20. package/dist/chunk-AVGZH64A.js +211 -0
  21. package/dist/chunk-AVGZH64A.js.map +1 -0
  22. package/dist/{chunk-5UPYA6KH.js → chunk-IP3QTP5A.js} +1028 -763
  23. package/dist/chunk-IP3QTP5A.js.map +1 -0
  24. package/dist/chunk-KC5S66OZ.js +177 -0
  25. package/dist/chunk-KC5S66OZ.js.map +1 -0
  26. package/dist/{chunk-4QV6D34Y.js → chunk-M5C2WILQ.js} +8 -6
  27. package/dist/{chunk-4QV6D34Y.js.map → chunk-M5C2WILQ.js.map} +1 -1
  28. package/dist/{chunk-FWEW5E3B.js → chunk-NZHKAPU6.js} +35 -5
  29. package/dist/chunk-NZHKAPU6.js.map +1 -0
  30. package/dist/{chunk-KTYPZTF4.js → chunk-O3WEV5W3.js} +10 -2
  31. package/dist/chunk-O3WEV5W3.js.map +1 -0
  32. package/dist/{chunk-K2OTLYJI.js → chunk-QZZGIZWC.js} +457 -202
  33. package/dist/chunk-QZZGIZWC.js.map +1 -0
  34. package/dist/{chunk-4LFNFRCL.js → chunk-SAMTXC4A.js} +91 -214
  35. package/dist/chunk-SAMTXC4A.js.map +1 -0
  36. package/dist/chunk-U237JSLB.js +1 -0
  37. package/dist/chunk-U237JSLB.js.map +1 -0
  38. package/dist/chunk-U6GWFTKA.js +657 -0
  39. package/dist/chunk-U6GWFTKA.js.map +1 -0
  40. package/dist/{chunk-HOFYJEJ4.js → chunk-UBQLXQ7I.js} +11 -11
  41. package/dist/cli/setup/env-metadata.d.ts.map +1 -1
  42. package/dist/cli.js +8 -7
  43. package/dist/cli.js.map +1 -1
  44. package/dist/{config-QLINHCHD.js → config-WTRSZLOC.js} +4 -3
  45. package/dist/config-WTRSZLOC.js.map +1 -0
  46. package/dist/config-schema.d.ts +17 -1
  47. package/dist/config-schema.d.ts.map +1 -1
  48. package/dist/config.d.ts +20 -0
  49. package/dist/config.d.ts.map +1 -1
  50. package/dist/errors/PhaseAbortedError.d.ts +3 -3
  51. package/dist/errors/PhaseAbortedError.d.ts.map +1 -1
  52. package/dist/errors-S3BWYA4I.js +43 -0
  53. package/dist/errors-S3BWYA4I.js.map +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +14 -11
  56. package/dist/{init-TDKDC6YP.js → init-QQDXGTPB.js} +7 -6
  57. package/dist/{init-TDKDC6YP.js.map → init-QQDXGTPB.js.map} +1 -1
  58. package/dist/lib.js +9 -7
  59. package/dist/lib.js.map +1 -1
  60. package/dist/orchestrator/IssueProcessingContext.d.ts +39 -21
  61. package/dist/orchestrator/IssueProcessingContext.d.ts.map +1 -1
  62. package/dist/orchestrator/PipelineOrchestrator.d.ts +10 -1
  63. package/dist/orchestrator/PipelineOrchestrator.d.ts.map +1 -1
  64. package/dist/orchestrator/steps/PhaseLoopStep.d.ts +1 -1
  65. package/dist/orchestrator/steps/PhaseLoopStep.d.ts.map +1 -1
  66. package/dist/orchestrator/steps/SetupStep.d.ts.map +1 -1
  67. package/dist/persistence/PlanPersistence.d.ts +7 -1
  68. package/dist/persistence/PlanPersistence.d.ts.map +1 -1
  69. package/dist/phases/BasePhase.d.ts +31 -42
  70. package/dist/phases/BasePhase.d.ts.map +1 -1
  71. package/dist/phases/BuildPhase.d.ts.map +1 -1
  72. package/dist/phases/PhaseFactory.d.ts +2 -3
  73. package/dist/phases/PhaseFactory.d.ts.map +1 -1
  74. package/dist/phases/PhaseOutcome.d.ts +42 -0
  75. package/dist/phases/PhaseOutcome.d.ts.map +1 -0
  76. package/dist/phases/PlanPhase.d.ts +1 -1
  77. package/dist/phases/PlanPhase.d.ts.map +1 -1
  78. package/dist/phases/ReleasePhase.d.ts +8 -18
  79. package/dist/phases/ReleasePhase.d.ts.map +1 -1
  80. package/dist/phases/UatPhase.d.ts +7 -24
  81. package/dist/phases/UatPhase.d.ts.map +1 -1
  82. package/dist/phases/VerifyPhase.d.ts +4 -4
  83. package/dist/phases/VerifyPhase.d.ts.map +1 -1
  84. package/dist/poller/IssuePoller.d.ts.map +1 -1
  85. package/dist/prompts/release-templates.d.ts.map +1 -1
  86. package/dist/prompts/templates.d.ts.map +1 -1
  87. package/dist/{restart-RNXGTDWZ.js → restart-BMILTP5X.js} +6 -5
  88. package/dist/{restart-RNXGTDWZ.js.map → restart-BMILTP5X.js.map} +1 -1
  89. package/dist/run.js +14 -11
  90. package/dist/run.js.map +1 -1
  91. package/dist/settings/ExperimentalSettings.d.ts +1 -1
  92. package/dist/settings/ExperimentalSettings.d.ts.map +1 -1
  93. package/dist/start-6QRW6IJI.js +15 -0
  94. package/dist/start-6QRW6IJI.js.map +1 -0
  95. package/dist/terminal/TerminalManager.d.ts +62 -0
  96. package/dist/terminal/TerminalManager.d.ts.map +1 -0
  97. package/dist/terminal/TerminalWebSocket.d.ts +9 -0
  98. package/dist/terminal/TerminalWebSocket.d.ts.map +1 -0
  99. package/dist/tracker/ExecutableTask.d.ts +4 -2
  100. package/dist/tracker/ExecutableTask.d.ts.map +1 -1
  101. package/dist/tracker/IssueState.d.ts +11 -1
  102. package/dist/tracker/IssueState.d.ts.map +1 -1
  103. package/dist/tracker/IssueTracker.d.ts +19 -1
  104. package/dist/tracker/IssueTracker.d.ts.map +1 -1
  105. package/dist/web/WebServer.d.ts +4 -0
  106. package/dist/web/WebServer.d.ts.map +1 -1
  107. package/dist/web/routes/terminal.d.ts +11 -0
  108. package/dist/web/routes/terminal.d.ts.map +1 -0
  109. package/dist/webhook/CommandExecutor.d.ts.map +1 -1
  110. package/package.json +7 -1
  111. package/src/web/frontend/dist/assets/index-COYziOhv.css +1 -0
  112. package/src/web/frontend/dist/assets/index-D_oTMuJU.js +151 -0
  113. package/src/web/frontend/dist/index.html +2 -2
  114. package/dist/chunk-4LFNFRCL.js.map +0 -1
  115. package/dist/chunk-5UPYA6KH.js.map +0 -1
  116. package/dist/chunk-DADQSKPL.js +0 -1
  117. package/dist/chunk-FWEW5E3B.js.map +0 -1
  118. package/dist/chunk-K2OTLYJI.js.map +0 -1
  119. package/dist/chunk-KTYPZTF4.js.map +0 -1
  120. package/dist/chunk-P4O4ZXEC.js.map +0 -1
  121. package/dist/start-27GRO4DP.js +0 -14
  122. package/src/web/frontend/dist/assets/index-C4NXoH9S.js +0 -133
  123. package/src/web/frontend/dist/assets/index-C7lorIa0.css +0 -1
  124. /package/dist/{ai-runner-SVUNA3FX.js.map → AIRunnerRegistry-II3WWSFN.js.map} +0 -0
  125. /package/dist/{chunk-DADQSKPL.js.map → PtyRunner-6UGI5STW.js.map} +0 -0
  126. /package/dist/{config-QLINHCHD.js.map → TerminalManager-RT2N7N5R.js.map} +0 -0
  127. /package/dist/{start-27GRO4DP.js.map → ai-runner-HLA44WI6.js.map} +0 -0
  128. /package/dist/{chunk-JINMYD56.js.map → chunk-2MESXJEZ.js.map} +0 -0
  129. /package/dist/{chunk-HOFYJEJ4.js.map → chunk-UBQLXQ7I.js.map} +0 -0
@@ -1,16 +1,19 @@
1
1
  import {
2
2
  BraindumpOrchestrator,
3
3
  BraindumpTracker
4
- } from "./chunk-4QV6D34Y.js";
4
+ } from "./chunk-M5C2WILQ.js";
5
5
  import {
6
6
  createSetupRouter
7
- } from "./chunk-HOFYJEJ4.js";
7
+ } from "./chunk-UBQLXQ7I.js";
8
8
  import {
9
9
  buildLockNoteBody,
10
10
  buildReleaseNoteBody,
11
11
  findAllLockNotes,
12
12
  findLockNote
13
13
  } from "./chunk-GXFG4JU6.js";
14
+ import {
15
+ TerminalManager
16
+ } from "./chunk-KC5S66OZ.js";
14
17
  import {
15
18
  BrainstormService,
16
19
  GongfengClient,
@@ -33,7 +36,7 @@ import {
33
36
  setE2eOverride,
34
37
  setNoteSyncOverride,
35
38
  validatePhaseRegistry
36
- } from "./chunk-5UPYA6KH.js";
39
+ } from "./chunk-IP3QTP5A.js";
37
40
  import {
38
41
  AsyncMutex,
39
42
  BaseTracker,
@@ -42,12 +45,15 @@ import {
42
45
  getExternalId,
43
46
  getIid,
44
47
  getTitle
45
- } from "./chunk-P4O4ZXEC.js";
48
+ } from "./chunk-2YQHKXLL.js";
46
49
  import {
47
50
  IwikiImporter,
48
51
  getProjectKnowledge,
49
52
  loadKnowledge
50
53
  } from "./chunk-ACVOOHAR.js";
54
+ import {
55
+ collectStaticInfo
56
+ } from "./chunk-B7TVVODN.js";
51
57
  import {
52
58
  setLocale,
53
59
  t
@@ -55,23 +61,15 @@ import {
55
61
  import {
56
62
  loadConfig,
57
63
  reloadConfig
58
- } from "./chunk-FWEW5E3B.js";
64
+ } from "./chunk-NZHKAPU6.js";
59
65
  import {
60
66
  resolveDisplayHost
61
67
  } from "./chunk-AKXDQH25.js";
62
- import {
63
- collectStaticInfo
64
- } from "./chunk-B7TVVODN.js";
65
68
  import {
66
69
  ensureDir,
67
70
  resolveDataDir
68
71
  } from "./chunk-TN2SYADO.js";
69
72
  import {
70
- KnowledgeStore
71
- } from "./chunk-DAX3FD2O.js";
72
- import {
73
- SessionLimitError,
74
- SessionNotFoundError,
75
73
  createAIRunner,
76
74
  getDefaultBinary,
77
75
  isBinaryAvailable,
@@ -80,7 +78,14 @@ import {
80
78
  resolveModelForRunner,
81
79
  setShuttingDown,
82
80
  validateRunnerRegistry
83
- } from "./chunk-4LFNFRCL.js";
81
+ } from "./chunk-SAMTXC4A.js";
82
+ import {
83
+ SessionLimitError,
84
+ SessionNotFoundError
85
+ } from "./chunk-AVGZH64A.js";
86
+ import {
87
+ KnowledgeStore
88
+ } from "./chunk-DAX3FD2O.js";
84
89
  import {
85
90
  logger
86
91
  } from "./chunk-GF2RRYHB.js";
@@ -170,6 +175,7 @@ ${sections.join("\n\n")}`;
170
175
  };
171
176
 
172
177
  // src/poller/IssuePoller.ts
178
+ import crypto from "crypto";
173
179
  var logger3 = logger.child("IssuePoller");
174
180
  var AUTO_FINISH_LABEL = "auto-finish";
175
181
  var AUTO_APPROVE_CHECK_INTERVAL_MS = 3e4;
@@ -225,6 +231,7 @@ var IssuePoller = class {
225
231
  this.activeIssues.delete(issueIid);
226
232
  logger3.info("Force-released issue from activeIssues", { issueIid });
227
233
  }
234
+ this.tracker.clearProcessingLock(issueIid);
228
235
  return had;
229
236
  }
230
237
  pauseDiscovery() {
@@ -287,8 +294,14 @@ var IssuePoller = class {
287
294
  max: maxConcurrent
288
295
  });
289
296
  for (const record of batch) {
290
- this.activeIssues.add(getIid(record));
291
- this.processInBackground(record);
297
+ const iid = getIid(record);
298
+ const correlationId = crypto.randomUUID();
299
+ if (!this.tracker.acquireProcessingLock(iid, correlationId)) {
300
+ logger3.warn("Failed to acquire processing lock, skipping", { issueIid: iid });
301
+ continue;
302
+ }
303
+ this.activeIssues.add(iid);
304
+ this.processInBackground(record, correlationId);
292
305
  }
293
306
  }
294
307
  maybeAutoApproveWaiting() {
@@ -331,7 +344,7 @@ var IssuePoller = class {
331
344
  }
332
345
  }
333
346
  }
334
- async processInBackground(record) {
347
+ async processInBackground(record, correlationId) {
335
348
  const iid = getIid(record);
336
349
  try {
337
350
  const issue = await this.resolveIssue(getExternalId(record));
@@ -367,6 +380,7 @@ var IssuePoller = class {
367
380
  });
368
381
  } finally {
369
382
  this.activeIssues.delete(iid);
383
+ this.tracker.releaseProcessingLock(iid, correlationId);
370
384
  }
371
385
  }
372
386
  async resolveIssue(issueId) {
@@ -681,7 +695,7 @@ function generateNodeId() {
681
695
  }
682
696
 
683
697
  // src/web/WebServer.ts
684
- import express12 from "express";
698
+ import express13 from "express";
685
699
  import fs6 from "fs";
686
700
  import path8 from "path";
687
701
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -869,7 +883,7 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
869
883
  res.status(404).json({ error: "Issue not found" });
870
884
  return;
871
885
  }
872
- const progress = await readProgress(iid, cfg, tracker, git);
886
+ const progress = record.phaseProgress ? { phases: record.phaseProgress, currentPhase: record.currentPhase } : await readProgress(iid, cfg, tracker, git);
873
887
  const preview = buildPreviewInfo(iid, orch);
874
888
  res.json({ ...record, progress, preview });
875
889
  });
@@ -898,6 +912,11 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
898
912
  return;
899
913
  }
900
914
  if (filename.endsWith(".json")) {
915
+ if (req.query.format === "html") {
916
+ const html = await marked("```json\n" + JSON.stringify(JSON.parse(content), null, 2) + "\n```");
917
+ res.type("html").send(html);
918
+ return;
919
+ }
901
920
  res.json(JSON.parse(content));
902
921
  return;
903
922
  }
@@ -957,8 +976,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
957
976
  await claimer.releaseClaim(getExternalId(record), iid, "cancelled");
958
977
  }
959
978
  }
960
- poller?.forceReleaseIssue(iid);
961
979
  await orch.restartIssue(iid);
980
+ poller?.forceReleaseIssue(iid);
962
981
  res.json({ success: true, message: `Issue #${iid} restarted` });
963
982
  } catch (err) {
964
983
  const msg = err.message;
@@ -977,6 +996,7 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
977
996
  return;
978
997
  }
979
998
  try {
999
+ poller?.forceReleaseIssue(iid);
980
1000
  orch.retryFromPhase(iid, phase);
981
1001
  res.json({ success: true, message: `Issue #${iid} reset to phase: ${phase}` });
982
1002
  } catch (err) {
@@ -1390,7 +1410,7 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
1390
1410
  });
1391
1411
  router.put("/api/system/feature-toggle", (req, res) => {
1392
1412
  const { feature, enabled } = req.body;
1393
- const validFeatures = ["brainstorm", "chat", "braindump", "knowledge", "distill"];
1413
+ const validFeatures = ["brainstorm", "chat", "braindump", "knowledge", "distill", "terminal"];
1394
1414
  if (!feature || !validFeatures.includes(feature) || typeof enabled !== "boolean") {
1395
1415
  res.status(400).json({ error: "Invalid feature or enabled value" });
1396
1416
  return;
@@ -1440,6 +1460,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
1440
1460
  braindumpEnabled: getFeatureEnabled("braindump", cfg),
1441
1461
  knowledgeEnabled: getFeatureEnabled("knowledge", cfg),
1442
1462
  distillEnabled: getFeatureEnabled("distill", cfg),
1463
+ terminalEnabled: getFeatureEnabled("terminal", cfg),
1464
+ terminalWorkDir: cfg.project.workDir,
1443
1465
  knowledgeSyncEnabled: true,
1444
1466
  multiRepoEnabled: wsConfig ? isMultiRepo(wsConfig) : false,
1445
1467
  linkedRepos: wsConfig?.associates.map((a) => ({
@@ -1706,7 +1728,7 @@ data: ${JSON.stringify({ time: (/* @__PURE__ */ new Date()).toISOString() })}
1706
1728
  const scenariosDir = path3.join(outputsDir, "scenarios");
1707
1729
  let scenarios = [];
1708
1730
  if (fs3.existsSync(scenariosDir) && fs3.statSync(scenariosDir).isDirectory()) {
1709
- scenarios = collectArtifactFiles(scenariosDir, scenariosDir);
1731
+ scenarios = collectArtifactFiles(scenariosDir, scenariosDir).filter((f) => !f.name.startsWith("config.") && !f.name.startsWith("config-"));
1710
1732
  }
1711
1733
  res.json({ configured: true, runs, scenarios });
1712
1734
  } catch (err) {
@@ -2404,7 +2426,7 @@ function createKnowledgeRouter(deps) {
2404
2426
  heartbeat = setInterval(() => {
2405
2427
  sendProgress({ step: "analyzing", current: 2, total, message: "AI \u5206\u6790\u4E2D...", elapsed: Date.now() - aiStart });
2406
2428
  }, 3e3);
2407
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2429
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2408
2430
  const runner = createAIRunner2(config.ai);
2409
2431
  const { analyze } = await import("./KnowledgeAnalyzer-MTTTSSHX.js");
2410
2432
  const knowledge = await analyze({ workDir, aiRunner: runner, syncToProject: config.sync.knowledgeToProject });
@@ -2449,7 +2471,7 @@ function createKnowledgeRouter(deps) {
2449
2471
  (async () => {
2450
2472
  try {
2451
2473
  sendProgress({ step: "collecting", current: 1, total, message: "\u68C0\u6D4B\u53D8\u66F4\u5E76\u6536\u96C6\u4FE1\u606F..." });
2452
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2474
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2453
2475
  const { analyzeIncremental } = await import("./KnowledgeAnalyzer-MTTTSSHX.js");
2454
2476
  const runner = createAIRunner2(config.ai);
2455
2477
  sendProgress({ step: "analyzing", current: 2, total, message: "\u589E\u91CF\u5206\u6790\u4E2D..." });
@@ -2733,7 +2755,7 @@ function createDomainModelRouter(deps) {
2733
2755
  }
2734
2756
  (async () => {
2735
2757
  try {
2736
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2758
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2737
2759
  const runner = createAIRunner2(config.ai);
2738
2760
  const model = await analyzer.analyze({
2739
2761
  workDir: config.project.workDir,
@@ -2947,7 +2969,7 @@ function createSystemUseCaseRouter(deps) {
2947
2969
  }
2948
2970
  (async () => {
2949
2971
  try {
2950
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2972
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2951
2973
  const runner = createAIRunner2(config.ai);
2952
2974
  const model = await analyzer.analyze({
2953
2975
  workDir: config.project.workDir,
@@ -3137,7 +3159,7 @@ function createSystemUseCaseRouter(deps) {
3137
3159
  res.status(404).json({ error: "No domain model loaded \u2014 run domain analysis first" });
3138
3160
  return;
3139
3161
  }
3140
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
3162
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
3141
3163
  const runner = createAIRunner2(config.ai);
3142
3164
  const associations = await analyzer.suggestDomainAssociations(
3143
3165
  useCaseModel,
@@ -3968,6 +3990,79 @@ function createAnalyticsRouter(deps) {
3968
3990
  return router;
3969
3991
  }
3970
3992
 
3993
+ // src/web/routes/terminal.ts
3994
+ import express12 from "express";
3995
+ var { Router: Router12 } = express12;
3996
+ var logger21 = logger.child("TerminalRoutes");
3997
+ function createTerminalRouter(deps) {
3998
+ const router = Router12();
3999
+ const { terminalManager, config } = deps;
4000
+ router.use("/api/terminal", (_req, res, next) => {
4001
+ if (!getFeatureEnabled("terminal", config)) {
4002
+ res.status(503).json({ error: "Terminal feature is disabled" });
4003
+ return;
4004
+ }
4005
+ next();
4006
+ });
4007
+ router.get("/api/terminal/sessions", (_req, res) => {
4008
+ try {
4009
+ const sessions = terminalManager.list();
4010
+ res.json({ success: true, sessions });
4011
+ } catch (err) {
4012
+ logger21.error("Failed to list terminal sessions", { error: err.message });
4013
+ res.status(500).json({ error: err.message });
4014
+ }
4015
+ });
4016
+ router.post("/api/terminal/sessions", (req, res) => {
4017
+ try {
4018
+ const { workDir, issueIid, cols, rows } = req.body;
4019
+ if (!workDir?.trim()) {
4020
+ res.status(400).json({ error: "workDir is required" });
4021
+ return;
4022
+ }
4023
+ const session = terminalManager.create({
4024
+ workDir: workDir.trim(),
4025
+ issueIid,
4026
+ cols,
4027
+ rows
4028
+ });
4029
+ res.json({ success: true, session });
4030
+ } catch (err) {
4031
+ logger21.error("Failed to create terminal session", { error: err.message });
4032
+ res.status(500).json({ error: err.message });
4033
+ }
4034
+ });
4035
+ router.get("/api/terminal/sessions/by-issue/:iid", (req, res) => {
4036
+ try {
4037
+ const iid = parseInt(req.params.iid, 10);
4038
+ if (isNaN(iid)) {
4039
+ res.status(400).json({ error: "Invalid issue IID" });
4040
+ return;
4041
+ }
4042
+ const sessions = terminalManager.list().filter((s) => s.issueIid === iid);
4043
+ res.json({ success: true, sessions });
4044
+ } catch (err) {
4045
+ logger21.error("Failed to find terminal sessions by issue", { error: err.message });
4046
+ res.status(500).json({ error: err.message });
4047
+ }
4048
+ });
4049
+ router.delete("/api/terminal/sessions/:id", (req, res) => {
4050
+ try {
4051
+ const session = terminalManager.get(req.params.id);
4052
+ if (!session) {
4053
+ res.status(404).json({ error: "Session not found" });
4054
+ return;
4055
+ }
4056
+ terminalManager.destroy(req.params.id);
4057
+ res.json({ success: true });
4058
+ } catch (err) {
4059
+ logger21.error("Failed to destroy terminal session", { error: err.message });
4060
+ res.status(500).json({ error: err.message });
4061
+ }
4062
+ });
4063
+ return router;
4064
+ }
4065
+
3971
4066
  // src/knowledge/DomainModelAnalyzer.ts
3972
4067
  import fs5 from "fs";
3973
4068
  import path6 from "path";
@@ -4097,7 +4192,7 @@ ${feedback}
4097
4192
  }
4098
4193
 
4099
4194
  // src/knowledge/DomainModelAnalyzer.ts
4100
- var logger21 = logger.child("DomainModelAnalyzer");
4195
+ var logger22 = logger.child("DomainModelAnalyzer");
4101
4196
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
4102
4197
  "node_modules",
4103
4198
  "dist",
@@ -4314,7 +4409,7 @@ var DomainModelAnalyzer = class {
4314
4409
  }
4315
4410
  this.currentModel = model;
4316
4411
  emit({ type: "complete", data: model });
4317
- logger21.info("Domain model analysis complete", {
4412
+ logger22.info("Domain model analysis complete", {
4318
4413
  contexts: model.boundedContexts.length,
4319
4414
  elements: model.elements.length,
4320
4415
  relationships: model.relationships.length
@@ -4340,7 +4435,7 @@ var DomainModelAnalyzer = class {
4340
4435
  tags: ["domain-model", "auto-generated"]
4341
4436
  });
4342
4437
  }
4343
- logger21.info("Domain model confirmed to knowledge store");
4438
+ logger22.info("Domain model confirmed to knowledge store");
4344
4439
  }
4345
4440
  /**
4346
4441
  * Load a previously stored domain model from KnowledgeStore.
@@ -4355,7 +4450,7 @@ var DomainModelAnalyzer = class {
4355
4450
  this.currentModel = model;
4356
4451
  return model;
4357
4452
  } catch {
4358
- logger21.warn("Failed to parse stored domain model");
4453
+ logger22.warn("Failed to parse stored domain model");
4359
4454
  return null;
4360
4455
  }
4361
4456
  }
@@ -4579,7 +4674,7 @@ ${domainModelJson}
4579
4674
  }
4580
4675
 
4581
4676
  // src/knowledge/SystemUseCaseAnalyzer.ts
4582
- var logger22 = logger.child("SystemUseCaseAnalyzer");
4677
+ var logger23 = logger.child("SystemUseCaseAnalyzer");
4583
4678
  function parseJsonFromOutput2(output) {
4584
4679
  const jsonBlockMatch = output.match(/```json\s*\n([\s\S]*?)```/);
4585
4680
  if (jsonBlockMatch) {
@@ -4704,7 +4799,7 @@ var SystemUseCaseAnalyzer = class {
4704
4799
  }
4705
4800
  this.currentModel = model;
4706
4801
  emit({ type: "complete", data: model });
4707
- logger22.info("Use case analysis complete", {
4802
+ logger23.info("Use case analysis complete", {
4708
4803
  useCases: model.useCases.length,
4709
4804
  relationships: model.relationships.length
4710
4805
  });
@@ -4725,12 +4820,12 @@ var SystemUseCaseAnalyzer = class {
4725
4820
  });
4726
4821
  const associations = /* @__PURE__ */ new Map();
4727
4822
  if (!result.success) {
4728
- logger22.warn("AI domain association failed", { output: result.output.slice(0, 200) });
4823
+ logger23.warn("AI domain association failed", { output: result.output.slice(0, 200) });
4729
4824
  return associations;
4730
4825
  }
4731
4826
  const parsed = parseJsonFromOutput2(result.output);
4732
4827
  if (!parsed || !parsed.associations) {
4733
- logger22.warn("Failed to parse domain association output");
4828
+ logger23.warn("Failed to parse domain association output");
4734
4829
  return associations;
4735
4830
  }
4736
4831
  const rawAssociations = parsed.associations;
@@ -4760,7 +4855,7 @@ var SystemUseCaseAnalyzer = class {
4760
4855
  tags: ["system-usecase", "auto-generated"]
4761
4856
  });
4762
4857
  }
4763
- logger22.info("Use case model confirmed to knowledge store");
4858
+ logger23.info("Use case model confirmed to knowledge store");
4764
4859
  }
4765
4860
  /**
4766
4861
  * Load a previously stored use case model from KnowledgeStore.
@@ -4775,7 +4870,7 @@ var SystemUseCaseAnalyzer = class {
4775
4870
  this.currentModel = model;
4776
4871
  return model;
4777
4872
  } catch {
4778
- logger22.warn("Failed to parse stored use case model");
4873
+ logger23.warn("Failed to parse stored use case model");
4779
4874
  return null;
4780
4875
  }
4781
4876
  }
@@ -4842,7 +4937,7 @@ ${knowledgeSection}${entriesSection}
4842
4937
  }
4843
4938
 
4844
4939
  // src/services/ChatService.ts
4845
- var logger23 = logger.child("Chat");
4940
+ var logger24 = logger.child("Chat");
4846
4941
  function agentConfigToAIConfig(agentCfg, timeoutMs) {
4847
4942
  return {
4848
4943
  mode: agentCfg.mode,
@@ -4878,7 +4973,7 @@ var ChatService = class {
4878
4973
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4879
4974
  };
4880
4975
  this.sessions.set(session.id, session);
4881
- logger23.info("Created chat session", { sessionId: session.id });
4976
+ logger24.info("Created chat session", { sessionId: session.id });
4882
4977
  return session;
4883
4978
  }
4884
4979
  getSession(id) {
@@ -4923,7 +5018,7 @@ var ChatService = class {
4923
5018
  ${r.content.slice(0, 500)}`).join("\n\n");
4924
5019
  }
4925
5020
  } catch (err) {
4926
- logger23.debug("Vector search failed, continuing without", { error: err.message });
5021
+ logger24.debug("Vector search failed, continuing without", { error: err.message });
4927
5022
  }
4928
5023
  }
4929
5024
  const prompt = isFirstMessage ? `${buildChatSystemPrompt(getProjectKnowledge(), knowledgeEntries)}${vectorContext}
@@ -5004,14 +5099,108 @@ ${r.content.slice(0, 500)}`).join("\n\n");
5004
5099
  }
5005
5100
  };
5006
5101
 
5102
+ // src/terminal/TerminalWebSocket.ts
5103
+ import { WebSocketServer, WebSocket } from "ws";
5104
+ var logger25 = logger.child("TerminalWebSocket");
5105
+ function setupTerminalWebSocket(opts) {
5106
+ const { httpServer, terminalManager, isEnabled } = opts;
5107
+ const wss = new WebSocketServer({ noServer: true });
5108
+ httpServer.on("upgrade", (request, socket, head) => {
5109
+ const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
5110
+ if (!url.pathname.startsWith("/ws/terminal")) return;
5111
+ if (!isEnabled()) {
5112
+ socket.write("HTTP/1.1 503 Service Unavailable\r\n\r\n");
5113
+ socket.destroy();
5114
+ return;
5115
+ }
5116
+ wss.handleUpgrade(request, socket, head, (ws) => {
5117
+ wss.emit("connection", ws, request);
5118
+ });
5119
+ });
5120
+ wss.on("connection", (ws, request) => {
5121
+ const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
5122
+ let sessionId = null;
5123
+ try {
5124
+ if (url.pathname === "/ws/terminal/new") {
5125
+ const workDir = url.searchParams.get("workDir") ?? "";
5126
+ const issueIidStr = url.searchParams.get("issueIid");
5127
+ const issueIid = issueIidStr ? parseInt(issueIidStr, 10) : void 0;
5128
+ if (!workDir) {
5129
+ ws.close(1008, "workDir is required");
5130
+ return;
5131
+ }
5132
+ const info = terminalManager.create({
5133
+ workDir,
5134
+ issueIid: issueIid && !isNaN(issueIid) ? issueIid : void 0
5135
+ });
5136
+ sessionId = info.id;
5137
+ ws.send(JSON.stringify({ type: "session", sessionId: info.id }));
5138
+ } else {
5139
+ sessionId = url.searchParams.get("sessionId");
5140
+ if (!sessionId || !terminalManager.get(sessionId)) {
5141
+ ws.close(1008, "Invalid or missing sessionId");
5142
+ return;
5143
+ }
5144
+ }
5145
+ terminalManager.onData(sessionId, (data) => {
5146
+ if (ws.readyState === WebSocket.OPEN) {
5147
+ try {
5148
+ ws.send(data);
5149
+ } catch {
5150
+ }
5151
+ }
5152
+ });
5153
+ terminalManager.onExit(sessionId, () => {
5154
+ if (ws.readyState === WebSocket.OPEN) {
5155
+ ws.close(1e3, "PTY process exited");
5156
+ }
5157
+ });
5158
+ ws.on("message", (msg) => {
5159
+ if (!sessionId) return;
5160
+ const str = msg.toString();
5161
+ try {
5162
+ const parsed = JSON.parse(str);
5163
+ if (parsed.type === "resize" && parsed.cols && parsed.rows) {
5164
+ terminalManager.resize(sessionId, parsed.cols, parsed.rows);
5165
+ return;
5166
+ }
5167
+ } catch {
5168
+ }
5169
+ terminalManager.write(sessionId, str);
5170
+ });
5171
+ ws.on("close", () => {
5172
+ if (sessionId) {
5173
+ const session = terminalManager.get(sessionId);
5174
+ if (session?.managed) {
5175
+ logger25.info("WebSocket detached from managed session (kept alive)", { sessionId });
5176
+ } else {
5177
+ logger25.info("WebSocket closed, destroying terminal session", { sessionId });
5178
+ terminalManager.destroy(sessionId);
5179
+ }
5180
+ }
5181
+ });
5182
+ ws.on("error", (err) => {
5183
+ logger25.warn("Terminal WebSocket error", { sessionId, error: err.message });
5184
+ });
5185
+ logger25.info("Terminal WebSocket connected", { sessionId });
5186
+ } catch (err) {
5187
+ logger25.error("Failed to handle terminal WebSocket connection", { error: err.message });
5188
+ ws.close(1011, err.message);
5189
+ }
5190
+ });
5191
+ logger25.info("Terminal WebSocket handler registered");
5192
+ }
5193
+
5007
5194
  // src/web/WebServer.ts
5008
5195
  var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
5009
- var logger24 = logger.child("WebServer");
5196
+ var logger26 = logger.child("WebServer");
5010
5197
  var WebServer = class _WebServer {
5011
5198
  app;
5012
5199
  server = null;
5013
5200
  host;
5014
5201
  port;
5202
+ terminalManager;
5203
+ config;
5015
5204
  constructor(trackerOrDeps, config, agentLogStore, orchestrator, mainGit) {
5016
5205
  let apiDeps;
5017
5206
  if (trackerOrDeps instanceof IssueTracker) {
@@ -5049,8 +5238,18 @@ var WebServer = class _WebServer {
5049
5238
  previewReaper: deps.previewReaper
5050
5239
  };
5051
5240
  }
5052
- this.app = express12();
5053
- this.app.use(express12.json());
5241
+ this.app = express13();
5242
+ this.app.use(express13.json());
5243
+ const resolvedConfig = trackerOrDeps instanceof IssueTracker ? config : trackerOrDeps.config;
5244
+ this.config = resolvedConfig;
5245
+ this.terminalManager = (trackerOrDeps instanceof IssueTracker ? void 0 : trackerOrDeps.terminalManager) ?? new TerminalManager({
5246
+ idleTimeoutMs: resolvedConfig.terminal.idleTimeoutMs,
5247
+ maxSessions: resolvedConfig.terminal.maxSessions,
5248
+ allowedBaseDirs: [
5249
+ resolvedConfig.project.worktreeBaseDir ?? resolvedConfig.project.workDir,
5250
+ resolvedConfig.project.workDir
5251
+ ].filter(Boolean)
5252
+ });
5054
5253
  this.app.get("/metrics", (_req, res) => {
5055
5254
  res.set("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
5056
5255
  res.send(metrics.serialize());
@@ -5074,12 +5273,12 @@ var WebServer = class _WebServer {
5074
5273
  if (fs6.existsSync(projectKnowledgeIndex) && !fs6.existsSync(globalKnowledgeIndex)) {
5075
5274
  try {
5076
5275
  _WebServer.migrateKnowledgeDir(projectKnowledgeDir, globalKnowledgeDataDir);
5077
- logger24.info("Migrated knowledge entries from project to global DATA_DIR", {
5276
+ logger26.info("Migrated knowledge entries from project to global DATA_DIR", {
5078
5277
  src: projectKnowledgeDir,
5079
5278
  dest: globalKnowledgeDataDir
5080
5279
  });
5081
5280
  } catch (err) {
5082
- logger24.warn("Failed to migrate knowledge entries to global DATA_DIR", {
5281
+ logger26.warn("Failed to migrate knowledge entries to global DATA_DIR", {
5083
5282
  error: err.message
5084
5283
  });
5085
5284
  }
@@ -5097,12 +5296,12 @@ var WebServer = class _WebServer {
5097
5296
  if (fs6.existsSync(legacyKnowledgePath) && !fs6.existsSync(dataDirKnowledgePath)) {
5098
5297
  try {
5099
5298
  fs6.copyFileSync(legacyKnowledgePath, dataDirKnowledgePath);
5100
- logger24.info("Migrated knowledge.json from project to DATA_DIR", {
5299
+ logger26.info("Migrated knowledge.json from project to DATA_DIR", {
5101
5300
  src: legacyKnowledgePath,
5102
5301
  dest: dataDirKnowledgePath
5103
5302
  });
5104
5303
  } catch (err) {
5105
- logger24.warn("Failed to migrate knowledge.json to DATA_DIR", {
5304
+ logger26.warn("Failed to migrate knowledge.json to DATA_DIR", {
5106
5305
  error: err.message
5107
5306
  });
5108
5307
  }
@@ -5159,8 +5358,13 @@ var WebServer = class _WebServer {
5159
5358
  });
5160
5359
  this.app.use(analyticsRouter);
5161
5360
  }
5361
+ const terminalRouter = createTerminalRouter({
5362
+ terminalManager: this.terminalManager,
5363
+ config: apiDeps.config
5364
+ });
5365
+ this.app.use(terminalRouter);
5162
5366
  const publicDir = apiDeps.config.web.frontendDistDir;
5163
- this.app.use(express12.static(publicDir));
5367
+ this.app.use(express13.static(publicDir));
5164
5368
  this.app.get("/{*splat}", (_req, res) => {
5165
5369
  res.sendFile("index.html", { root: publicDir });
5166
5370
  });
@@ -5168,16 +5372,22 @@ var WebServer = class _WebServer {
5168
5372
  start() {
5169
5373
  return new Promise((resolve) => {
5170
5374
  this.server = this.app.listen(this.port, this.host, () => {
5171
- logger24.info(`Web UI available at http://${resolveDisplayHost(this.host)}:${this.port}`);
5375
+ setupTerminalWebSocket({
5376
+ httpServer: this.server,
5377
+ terminalManager: this.terminalManager,
5378
+ isEnabled: () => getFeatureEnabled("terminal", this.config)
5379
+ });
5380
+ logger26.info(`Web UI available at http://${resolveDisplayHost(this.host)}:${this.port}`);
5172
5381
  resolve();
5173
5382
  });
5174
5383
  });
5175
5384
  }
5176
5385
  stop() {
5386
+ this.terminalManager.destroyAll();
5177
5387
  if (this.server) {
5178
5388
  this.server.close();
5179
5389
  this.server = null;
5180
- logger24.info("Web server stopped");
5390
+ logger26.info("Web server stopped");
5181
5391
  }
5182
5392
  }
5183
5393
  /**
@@ -5213,10 +5423,10 @@ var WebServer = class _WebServer {
5213
5423
  };
5214
5424
 
5215
5425
  // src/webhook/WebhookServer.ts
5216
- import express14 from "express";
5426
+ import express15 from "express";
5217
5427
 
5218
5428
  // src/webhook/WebhookHandler.ts
5219
- import express13 from "express";
5429
+ import express14 from "express";
5220
5430
 
5221
5431
  // src/webhook/CommandParser.ts
5222
5432
  var TRIGGERS = ["@issue-auto", "@iaf"];
@@ -5419,7 +5629,7 @@ function parseExact(commandText) {
5419
5629
  // src/webhook/CommandExecutor.ts
5420
5630
  import path9 from "path";
5421
5631
  import fs7 from "fs";
5422
- var logger25 = logger.child("CommandExecutor");
5632
+ var logger27 = logger.child("CommandExecutor");
5423
5633
  var CommandExecutor = class {
5424
5634
  tracker;
5425
5635
  orchestrator;
@@ -5438,13 +5648,13 @@ var CommandExecutor = class {
5438
5648
  this.claimer = deps.claimer;
5439
5649
  }
5440
5650
  async execute(issueIid, issueId, command) {
5441
- logger25.info("Executing webhook command", { issueIid, command });
5651
+ logger27.info("Executing webhook command", { issueIid, command });
5442
5652
  let result;
5443
5653
  try {
5444
5654
  result = await this.dispatch(issueIid, command);
5445
5655
  } catch (err) {
5446
5656
  const msg = err.message;
5447
- logger25.error("Webhook command failed", { issueIid, error: msg });
5657
+ logger27.error("Webhook command failed", { issueIid, error: msg });
5448
5658
  result = { success: false, message: msg };
5449
5659
  }
5450
5660
  await this.replyToIssue(issueId, result);
@@ -5502,6 +5712,7 @@ var CommandExecutor = class {
5502
5712
  if (!phase) return this.fail("Phase is required for retry-from (e.g. design, implement)");
5503
5713
  if (ctx) this.saveSupplement(iid, ctx);
5504
5714
  try {
5715
+ this.poller?.forceReleaseIssue(iid);
5505
5716
  this.orchestrator.retryFromPhase(iid, phase);
5506
5717
  } catch (err) {
5507
5718
  return this.fail(err.message);
@@ -5575,7 +5786,6 @@ var CommandExecutor = class {
5575
5786
  }
5576
5787
  async handleRestart(iid) {
5577
5788
  try {
5578
- this.poller?.forceReleaseIssue(iid);
5579
5789
  if (this.claimer) {
5580
5790
  const record = this.tracker.get(iid);
5581
5791
  if (record) {
@@ -5584,6 +5794,7 @@ var CommandExecutor = class {
5584
5794
  }
5585
5795
  }
5586
5796
  await this.orchestrator.restartIssue(iid);
5797
+ this.poller?.forceReleaseIssue(iid);
5587
5798
  } catch (err) {
5588
5799
  return this.fail(err.message);
5589
5800
  }
@@ -5599,7 +5810,7 @@ var CommandExecutor = class {
5599
5810
  const externalId = getExternalId(record);
5600
5811
  await this.claimer.releaseClaim(externalId, iid, "cancelled");
5601
5812
  } catch (err) {
5602
- logger25.warn("Failed to release lock on cancel", { iid, error: err.message });
5813
+ logger27.warn("Failed to release lock on cancel", { iid, error: err.message });
5603
5814
  }
5604
5815
  }
5605
5816
  await this.orchestrator.cancelIssue(iid);
@@ -5699,7 +5910,7 @@ var CommandExecutor = class {
5699
5910
  return this.fail(t("conflict.noMr", { iid }));
5700
5911
  }
5701
5912
  this.orchestrator.resolveConflict(iid).catch((err) => {
5702
- logger25.error("Async conflict resolution failed", { iid, error: err.message });
5913
+ logger27.error("Async conflict resolution failed", { iid, error: err.message });
5703
5914
  });
5704
5915
  return this.ok(t("conflict.startedMsg"));
5705
5916
  }
@@ -5741,7 +5952,7 @@ ${context}` : context;
5741
5952
  const prefix = result.success ? "" : "> **Failed**\n>\n> ";
5742
5953
  await this.gongfeng.createIssueNote(issueId, `${prefix}${result.message}`);
5743
5954
  } catch (err) {
5744
- logger25.warn("Failed to reply", { issueId, error: err.message });
5955
+ logger27.warn("Failed to reply", { issueId, error: err.message });
5745
5956
  }
5746
5957
  }
5747
5958
  ok(message) {
@@ -5796,10 +6007,10 @@ var NoteDeduplicator = class {
5796
6007
  };
5797
6008
 
5798
6009
  // src/webhook/WebhookHandler.ts
5799
- var { Router: Router12 } = express13;
5800
- var logger26 = logger.child("WebhookHandler");
6010
+ var { Router: Router13 } = express14;
6011
+ var logger28 = logger.child("WebhookHandler");
5801
6012
  function createWebhookRouter(deps) {
5802
- const router = Router12();
6013
+ const router = Router13();
5803
6014
  const executor = new CommandExecutor(deps);
5804
6015
  const dedup = new NoteDeduplicator();
5805
6016
  const { config, intentRecognizer } = deps;
@@ -5812,7 +6023,7 @@ function createWebhookRouter(deps) {
5812
6023
  if (config.webhook.secret) {
5813
6024
  const token = req.headers["x-gitlab-token"];
5814
6025
  if (token !== config.webhook.secret) {
5815
- logger26.warn("Webhook token mismatch");
6026
+ logger28.warn("Webhook token mismatch");
5816
6027
  res.status(401).json({ error: "Invalid token" });
5817
6028
  return;
5818
6029
  }
@@ -5829,7 +6040,7 @@ function createWebhookRouter(deps) {
5829
6040
  }
5830
6041
  const noteId = event.object_attributes.id;
5831
6042
  if (dedup.isDuplicate(noteId)) {
5832
- logger26.debug("Duplicate note, skipping", { noteId });
6043
+ logger28.debug("Duplicate note, skipping", { noteId });
5833
6044
  res.json({ ignored: true, reason: "duplicate" });
5834
6045
  return;
5835
6046
  }
@@ -5841,7 +6052,7 @@ function createWebhookRouter(deps) {
5841
6052
  return;
5842
6053
  }
5843
6054
  if (!issueId) {
5844
- logger26.info("Webhook received with null issue id (likely a test ping)", { issueIid });
6055
+ logger28.info("Webhook received with null issue id (likely a test ping)", { issueIid });
5845
6056
  res.json({ accepted: true, noteId, issueIid, test: true });
5846
6057
  return;
5847
6058
  }
@@ -5881,19 +6092,19 @@ async function processCommandAsync(noteBody, issueIid, issueId, executor, intent
5881
6092
  if (!command && intentRecognizer && config.webhook.llmFallback) {
5882
6093
  const record = tracker.get(issueIid);
5883
6094
  const state = record?.state;
5884
- logger26.info("Falling back to LLM intent recognition", { issueIid });
6095
+ logger28.info("Falling back to LLM intent recognition", { issueIid });
5885
6096
  command = await intentRecognizer.recognize(cmdText, state);
5886
6097
  }
5887
6098
  if (!command) {
5888
- logger26.info("Could not parse command from note", { issueIid, text: cmdText.slice(0, 100) });
6099
+ logger28.info("Could not parse command from note", { issueIid, text: cmdText.slice(0, 100) });
5889
6100
  await gongfeng.createIssueNote(issueId, HELP_TEXT_FUNC()).catch(
5890
- (e) => logger26.warn("Failed to reply help text", { error: e.message })
6101
+ (e) => logger28.warn("Failed to reply help text", { error: e.message })
5891
6102
  );
5892
6103
  return;
5893
6104
  }
5894
6105
  await executor.execute(issueIid, issueId, command);
5895
6106
  } catch (err) {
5896
- logger26.error("Failed to process webhook command", {
6107
+ logger28.error("Failed to process webhook command", {
5897
6108
  issueIid,
5898
6109
  error: err.message
5899
6110
  });
@@ -5913,7 +6124,7 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5913
6124
  const command = parseExact(cmdText);
5914
6125
  if (!command) {
5915
6126
  await gongfeng.createMergeRequestNote(mr.iid, HELP_TEXT_FUNC()).catch(
5916
- (e) => logger26.warn("Failed to reply help text on MR", { error: e.message })
6127
+ (e) => logger28.warn("Failed to reply help text on MR", { error: e.message })
5917
6128
  );
5918
6129
  return;
5919
6130
  }
@@ -5922,7 +6133,7 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5922
6133
  mr.iid,
5923
6134
  `\`${command.intent}\` command is not supported on MR comments. Please use Issue comments instead.`
5924
6135
  ).catch(
5925
- (e) => logger26.warn("Failed to reply on MR", { error: e.message })
6136
+ (e) => logger28.warn("Failed to reply on MR", { error: e.message })
5926
6137
  );
5927
6138
  return;
5928
6139
  }
@@ -5933,7 +6144,7 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5933
6144
  mr.iid,
5934
6145
  `Could not find a tracked issue for branch \`${mr.source_branch}\`.`
5935
6146
  ).catch(
5936
- (e) => logger26.warn("Failed to reply on MR", { error: e.message })
6147
+ (e) => logger28.warn("Failed to reply on MR", { error: e.message })
5937
6148
  );
5938
6149
  return;
5939
6150
  }
@@ -5942,10 +6153,10 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5942
6153
  const prefix = result.success ? "" : "> **Failed**\n>\n> ";
5943
6154
  await gongfeng.createMergeRequestNote(mr.iid, `${prefix}${result.message}`);
5944
6155
  } catch (err) {
5945
- logger26.warn("Failed to reply on MR", { mrIid: mr.iid, error: err.message });
6156
+ logger28.warn("Failed to reply on MR", { mrIid: mr.iid, error: err.message });
5946
6157
  }
5947
6158
  } catch (err) {
5948
- logger26.error("Failed to process MR webhook command", {
6159
+ logger28.error("Failed to process MR webhook command", {
5949
6160
  mrIid: mr.iid,
5950
6161
  error: err.message
5951
6162
  });
@@ -5957,7 +6168,7 @@ function isSelfNote(_event, _selfToken) {
5957
6168
 
5958
6169
  // src/webhook/IntentRecognizer.ts
5959
6170
  import { spawn } from "child_process";
5960
- var logger27 = logger.child("IntentRecognizer");
6171
+ var logger29 = logger.child("IntentRecognizer");
5961
6172
  var VALID_INTENTS = [
5962
6173
  "retry",
5963
6174
  "retry-from",
@@ -5999,7 +6210,7 @@ ${userComment}`;
5999
6210
  const rawOutput = await this.runLLM(userPrompt);
6000
6211
  return this.parseResponse(rawOutput);
6001
6212
  } catch (err) {
6002
- logger27.error("Intent recognition failed", { error: err.message });
6213
+ logger29.error("Intent recognition failed", { error: err.message });
6003
6214
  return null;
6004
6215
  }
6005
6216
  }
@@ -6007,20 +6218,20 @@ ${userComment}`;
6007
6218
  parseResponse(raw) {
6008
6219
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6009
6220
  if (!jsonMatch) {
6010
- logger27.warn("No JSON found in LLM response", { raw: raw.slice(0, 200) });
6221
+ logger29.warn("No JSON found in LLM response", { raw: raw.slice(0, 200) });
6011
6222
  return null;
6012
6223
  }
6013
6224
  let parsed;
6014
6225
  try {
6015
6226
  parsed = JSON.parse(jsonMatch[0]);
6016
6227
  } catch {
6017
- logger27.warn("Failed to parse JSON from LLM", { raw: jsonMatch[0].slice(0, 200) });
6228
+ logger29.warn("Failed to parse JSON from LLM", { raw: jsonMatch[0].slice(0, 200) });
6018
6229
  return null;
6019
6230
  }
6020
6231
  if (parsed.intent === null || parsed.intent === "null") return null;
6021
6232
  const intent = String(parsed.intent);
6022
6233
  if (!VALID_INTENTS.includes(intent)) {
6023
- logger27.warn("Invalid intent from LLM", { intent });
6234
+ logger29.warn("Invalid intent from LLM", { intent });
6024
6235
  return null;
6025
6236
  }
6026
6237
  const result = { intent };
@@ -6062,7 +6273,7 @@ ${userComment}`;
6062
6273
  child.on("close", (code) => {
6063
6274
  clearTimeout(timer);
6064
6275
  if (code !== 0) {
6065
- logger27.warn("LLM process exited with non-zero code", { code, stderr: stderr.slice(0, 300) });
6276
+ logger29.warn("LLM process exited with non-zero code", { code, stderr: stderr.slice(0, 300) });
6066
6277
  }
6067
6278
  resolve(stdout);
6068
6279
  });
@@ -6077,7 +6288,7 @@ ${userComment}`;
6077
6288
  };
6078
6289
 
6079
6290
  // src/webhook/WebhookServer.ts
6080
- var logger28 = logger.child("WebhookServer");
6291
+ var logger30 = logger.child("WebhookServer");
6081
6292
  var WebhookServer = class {
6082
6293
  app;
6083
6294
  server = null;
@@ -6101,8 +6312,8 @@ var WebhookServer = class {
6101
6312
  poller: deps.poller,
6102
6313
  claimer: deps.claimer
6103
6314
  };
6104
- this.app = express14();
6105
- this.app.use(express14.json());
6315
+ this.app = express15();
6316
+ this.app.use(express15.json());
6106
6317
  this.app.use(createWebhookRouter(handlerDeps));
6107
6318
  this.app.get("/health", (_req, res) => {
6108
6319
  res.json({ status: "ok", service: "webhook" });
@@ -6111,7 +6322,7 @@ var WebhookServer = class {
6111
6322
  start() {
6112
6323
  return new Promise((resolve) => {
6113
6324
  this.server = this.app.listen(this.port, this.host, () => {
6114
- logger28.info(`Webhook server listening on http://${resolveDisplayHost(this.host)}:${this.port}`);
6325
+ logger30.info(`Webhook server listening on http://${resolveDisplayHost(this.host)}:${this.port}`);
6115
6326
  resolve();
6116
6327
  });
6117
6328
  });
@@ -6120,7 +6331,7 @@ var WebhookServer = class {
6120
6331
  if (this.server) {
6121
6332
  this.server.close();
6122
6333
  this.server = null;
6123
- logger28.info("Webhook server stopped");
6334
+ logger30.info("Webhook server stopped");
6124
6335
  }
6125
6336
  }
6126
6337
  };
@@ -6128,7 +6339,7 @@ var WebhookServer = class {
6128
6339
  // src/web/AgentLogStore.ts
6129
6340
  import fs8 from "fs";
6130
6341
  import path10 from "path";
6131
- var logger29 = logger.child("AgentLogStore");
6342
+ var logger31 = logger.child("AgentLogStore");
6132
6343
  var MAX_LOGS_PER_ISSUE = 2e4;
6133
6344
  var DEBUG_EVENT_TYPES = /* @__PURE__ */ new Set([
6134
6345
  "thinking",
@@ -6157,7 +6368,7 @@ var AgentLogStore = class {
6157
6368
  eventBus.on("pipeline:progress", (payload) => {
6158
6369
  this.handlePipelineProgress(payload);
6159
6370
  });
6160
- logger29.info("AgentLogStore listening for events");
6371
+ logger31.info("AgentLogStore listening for events");
6161
6372
  }
6162
6373
  getLogs(issueIid) {
6163
6374
  const filePath = this.logFilePath(issueIid);
@@ -6167,7 +6378,7 @@ var AgentLogStore = class {
6167
6378
  if (!raw) return [];
6168
6379
  return raw.split("\n").map((line) => JSON.parse(line));
6169
6380
  } catch (err) {
6170
- logger29.warn("Failed to read agent logs", { issueIid, error: err.message });
6381
+ logger31.warn("Failed to read agent logs", { issueIid, error: err.message });
6171
6382
  return [];
6172
6383
  }
6173
6384
  }
@@ -6187,7 +6398,7 @@ var AgentLogStore = class {
6187
6398
  `, "utf-8");
6188
6399
  this.trimIfNeeded(issueIid, filePath);
6189
6400
  } catch (err) {
6190
- logger29.warn("Failed to write agent log", { issueIid, error: err.message });
6401
+ logger31.warn("Failed to write agent log", { issueIid, error: err.message });
6191
6402
  }
6192
6403
  }
6193
6404
  trimIfNeeded(issueIid, filePath) {
@@ -6199,7 +6410,7 @@ var AgentLogStore = class {
6199
6410
  const trimmed = lines.slice(-MAX_LOGS_PER_ISSUE);
6200
6411
  fs8.writeFileSync(filePath, `${trimmed.join("\n")}
6201
6412
  `, "utf-8");
6202
- logger29.info("Agent logs trimmed", { issueIid, from: lines.length, to: trimmed.length });
6413
+ logger31.info("Agent logs trimmed", { issueIid, from: lines.length, to: trimmed.length });
6203
6414
  }
6204
6415
  } catch {
6205
6416
  }
@@ -6274,7 +6485,7 @@ import { readFileSync, existsSync } from "fs";
6274
6485
  import path11 from "path";
6275
6486
  import { fileURLToPath as fileURLToPath3 } from "url";
6276
6487
  var __dirname3 = path11.dirname(fileURLToPath3(import.meta.url));
6277
- var logger30 = logger.child("VersionChecker");
6488
+ var logger32 = logger.child("VersionChecker");
6278
6489
  function compareSemver(a, b) {
6279
6490
  const pa = a.split(".").map(Number);
6280
6491
  const pb = b.split(".").map(Number);
@@ -6305,7 +6516,7 @@ var VersionChecker = class {
6305
6516
  async check() {
6306
6517
  const latestVersion = await this.fetchLatestVersion();
6307
6518
  const hasUpdate = compareSemver(latestVersion, this.currentVersion) > 0;
6308
- logger30.info("Version check completed", {
6519
+ logger32.info("Version check completed", {
6309
6520
  current: this.currentVersion,
6310
6521
  latest: latestVersion,
6311
6522
  hasUpdate
@@ -6353,7 +6564,7 @@ import { cpSync, existsSync as existsSync2, rmSync } from "fs";
6353
6564
  import path12 from "path";
6354
6565
  import { fileURLToPath as fileURLToPath4 } from "url";
6355
6566
  var __dirname4 = path12.dirname(fileURLToPath4(import.meta.url));
6356
- var logger31 = logger.child("UpdateExecutor");
6567
+ var logger33 = logger.child("UpdateExecutor");
6357
6568
  function findProjectRoot() {
6358
6569
  for (let dir = __dirname4; dir !== path12.dirname(dir); dir = path12.dirname(dir)) {
6359
6570
  if (existsSync2(path12.join(dir, "package.json"))) {
@@ -6383,7 +6594,7 @@ var UpdateExecutor = class {
6383
6594
  async execute(targetVersion) {
6384
6595
  const distDir = path12.join(this.projectRoot, "dist");
6385
6596
  const backupDir = path12.join(this.projectRoot, "dist.backup");
6386
- logger31.info("Backing up dist directory...");
6597
+ logger33.info("Backing up dist directory...");
6387
6598
  if (existsSync2(backupDir)) {
6388
6599
  rmSync(backupDir, { recursive: true, force: true });
6389
6600
  }
@@ -6391,12 +6602,12 @@ var UpdateExecutor = class {
6391
6602
  cpSync(distDir, backupDir, { recursive: true });
6392
6603
  }
6393
6604
  try {
6394
- logger31.info("Running package update...", { package: this.packageName, targetVersion, bin: "npm" });
6605
+ logger33.info("Running package update...", { package: this.packageName, targetVersion, bin: "npm" });
6395
6606
  await run("npm", ["update", this.packageName], this.projectRoot, 12e4);
6396
- logger31.info("Running build...", { bin: "npm" });
6607
+ logger33.info("Running build...", { bin: "npm" });
6397
6608
  await run("npm", ["run", "build"], this.projectRoot, 12e4);
6398
6609
  } catch (err) {
6399
- logger31.error("Update failed, rolling back dist...", { error: err.message });
6610
+ logger33.error("Update failed, rolling back dist...", { error: err.message });
6400
6611
  if (existsSync2(backupDir)) {
6401
6612
  if (existsSync2(distDir)) {
6402
6613
  rmSync(distDir, { recursive: true, force: true });
@@ -6409,21 +6620,21 @@ var UpdateExecutor = class {
6409
6620
  rmSync(backupDir, { recursive: true, force: true });
6410
6621
  }
6411
6622
  }
6412
- logger31.info("Reloading via PM2...");
6623
+ logger33.info("Reloading via PM2...");
6413
6624
  try {
6414
6625
  await run("pm2", ["reload", "issue-auto-finish"], this.projectRoot, 3e4);
6415
6626
  } catch (err) {
6416
- logger31.warn("PM2 reload failed, the process may need manual restart", {
6627
+ logger33.warn("PM2 reload failed, the process may need manual restart", {
6417
6628
  error: err.message
6418
6629
  });
6419
6630
  throw err;
6420
6631
  }
6421
- logger31.info("Update completed successfully", { version: targetVersion });
6632
+ logger33.info("Update completed successfully", { version: targetVersion });
6422
6633
  }
6423
6634
  };
6424
6635
 
6425
6636
  // src/updater/AutoUpdater.ts
6426
- var logger32 = logger.child("AutoUpdater");
6637
+ var logger34 = logger.child("AutoUpdater");
6427
6638
  var INITIAL_DELAY_MS = 3e4;
6428
6639
  var AutoUpdater = class {
6429
6640
  config;
@@ -6445,10 +6656,10 @@ var AutoUpdater = class {
6445
6656
  }
6446
6657
  start() {
6447
6658
  if (!this.config.autoUpdate.enabled) {
6448
- logger32.info("Auto-update is disabled");
6659
+ logger34.info("Auto-update is disabled");
6449
6660
  return;
6450
6661
  }
6451
- logger32.info("Auto-updater starting", {
6662
+ logger34.info("Auto-updater starting", {
6452
6663
  intervalMs: this.config.autoUpdate.intervalMs,
6453
6664
  registry: this.config.autoUpdate.registry
6454
6665
  });
@@ -6467,7 +6678,7 @@ var AutoUpdater = class {
6467
6678
  clearInterval(this.checkTimer);
6468
6679
  this.checkTimer = null;
6469
6680
  }
6470
- logger32.info("Auto-updater stopped");
6681
+ logger34.info("Auto-updater stopped");
6471
6682
  }
6472
6683
  getStatus() {
6473
6684
  return {
@@ -6507,7 +6718,7 @@ var AutoUpdater = class {
6507
6718
  }
6508
6719
  async triggerUpdate() {
6509
6720
  if (this.updateInProgress) {
6510
- logger32.warn("Update already in progress, skipping");
6721
+ logger34.warn("Update already in progress, skipping");
6511
6722
  return;
6512
6723
  }
6513
6724
  this.updateInProgress = true;
@@ -6515,40 +6726,40 @@ var AutoUpdater = class {
6515
6726
  if (!this.latestVersion) {
6516
6727
  const result = await this.checkForUpdate();
6517
6728
  if (!result.hasUpdate) {
6518
- logger32.info("No update available");
6729
+ logger34.info("No update available");
6519
6730
  return;
6520
6731
  }
6521
6732
  }
6522
6733
  const targetVersion = this.latestVersion;
6523
6734
  this.state = "draining";
6524
- logger32.info("Draining active issues before update...");
6735
+ logger34.info("Draining active issues before update...");
6525
6736
  this.poller.pauseDiscovery();
6526
6737
  const drainStart = Date.now();
6527
6738
  const drainTimeout = this.config.autoUpdate.drainTimeoutMs;
6528
6739
  while (this.poller.getActiveCount() > 0 && Date.now() - drainStart < drainTimeout) {
6529
- logger32.info("Waiting for active issues to complete...", {
6740
+ logger34.info("Waiting for active issues to complete...", {
6530
6741
  active: this.poller.getActiveCount()
6531
6742
  });
6532
6743
  await new Promise((resolve) => setTimeout(resolve, 5e3));
6533
6744
  }
6534
6745
  if (this.poller.getActiveCount() > 0) {
6535
- logger32.warn("Drain timeout reached, proceeding with update", {
6746
+ logger34.warn("Drain timeout reached, proceeding with update", {
6536
6747
  active: this.poller.getActiveCount()
6537
6748
  });
6538
6749
  }
6539
6750
  this.state = "updating";
6540
6751
  eventBus.emitTyped("update:downloading", { version: targetVersion });
6541
- logger32.info("Executing update...", { targetVersion });
6752
+ logger34.info("Executing update...", { targetVersion });
6542
6753
  await this.updateExecutor.execute(targetVersion);
6543
6754
  this.state = "completed";
6544
6755
  eventBus.emitTyped("update:completed", { version: targetVersion });
6545
- logger32.info("Update completed", { version: targetVersion });
6756
+ logger34.info("Update completed", { version: targetVersion });
6546
6757
  } catch (err) {
6547
6758
  const message = err.message;
6548
6759
  this.lastError = message;
6549
6760
  this.state = "failed";
6550
6761
  eventBus.emitTyped("update:failed", { error: message });
6551
- logger32.error("Update failed", { error: message });
6762
+ logger34.error("Update failed", { error: message });
6552
6763
  this.poller.resumeDiscovery();
6553
6764
  } finally {
6554
6765
  this.updateInProgress = false;
@@ -6559,20 +6770,20 @@ var AutoUpdater = class {
6559
6770
  try {
6560
6771
  const result = await this.checkForUpdate();
6561
6772
  if (result.hasUpdate) {
6562
- logger32.info("New version available, starting update", {
6773
+ logger34.info("New version available, starting update", {
6563
6774
  current: result.currentVersion,
6564
6775
  latest: result.latestVersion
6565
6776
  });
6566
6777
  await this.triggerUpdate();
6567
6778
  }
6568
6779
  } catch (err) {
6569
- logger32.warn("Periodic version check failed", { error: err.message });
6780
+ logger34.warn("Periodic version check failed", { error: err.message });
6570
6781
  }
6571
6782
  }
6572
6783
  };
6573
6784
 
6574
6785
  // src/config/ConfigReloader.ts
6575
- var logger33 = logger.child("ConfigReloader");
6786
+ var logger35 = logger.child("ConfigReloader");
6576
6787
  var ConfigReloader = class {
6577
6788
  config;
6578
6789
  poller;
@@ -6588,11 +6799,11 @@ var ConfigReloader = class {
6588
6799
  }
6589
6800
  async reload(opts) {
6590
6801
  const timeoutMs = opts?.waitTimeoutMs ?? 6e4;
6591
- logger33.info("Config reload requested", { timeoutMs });
6802
+ logger35.info("Config reload requested", { timeoutMs });
6592
6803
  this.poller.pauseDiscovery();
6593
6804
  const drained = await this.waitForDrain(timeoutMs);
6594
6805
  if (!drained) {
6595
- logger33.warn("Active issues did not drain within timeout, aborting reload", {
6806
+ logger35.warn("Active issues did not drain within timeout, aborting reload", {
6596
6807
  active: this.poller.getActiveCount(),
6597
6808
  timeoutMs
6598
6809
  });
@@ -6607,7 +6818,7 @@ var ConfigReloader = class {
6607
6818
  this.orchestrator.setAIRunner(newRunner);
6608
6819
  this.options.onAIRunnerChanged?.(newRunner);
6609
6820
  this.gongfeng.updateConfig(this.config.gongfeng);
6610
- logger33.info("Config reloaded successfully", {
6821
+ logger35.info("Config reloaded successfully", {
6611
6822
  aiMode: this.config.ai.mode,
6612
6823
  aiModel: this.config.ai.model,
6613
6824
  discoveryIntervalMs: this.config.poll.discoveryIntervalMs
@@ -6628,7 +6839,7 @@ var ConfigReloader = class {
6628
6839
 
6629
6840
  // src/distill/DiaryCollector.ts
6630
6841
  import { randomUUID as randomUUID2 } from "crypto";
6631
- var logger34 = logger.child("DiaryCollector");
6842
+ var logger36 = logger.child("DiaryCollector");
6632
6843
  var DiaryCollector = class {
6633
6844
  tracker;
6634
6845
  diaryStore;
@@ -6646,21 +6857,21 @@ var DiaryCollector = class {
6646
6857
  if (this.stateChangedHandler) return;
6647
6858
  this.stateChangedHandler = (payload) => {
6648
6859
  this.handleStateChanged(payload).catch((err) => {
6649
- logger34.warn("DiaryCollector failed to handle stateChanged event", {
6860
+ logger36.warn("DiaryCollector failed to handle stateChanged event", {
6650
6861
  error: err.message
6651
6862
  });
6652
6863
  });
6653
6864
  };
6654
6865
  this.failedHandler = (payload) => {
6655
6866
  this.handleFailed(payload).catch((err) => {
6656
- logger34.warn("DiaryCollector failed to handle failed event", {
6867
+ logger36.warn("DiaryCollector failed to handle failed event", {
6657
6868
  error: err.message
6658
6869
  });
6659
6870
  });
6660
6871
  };
6661
6872
  eventBus.on("issue:stateChanged", this.stateChangedHandler);
6662
6873
  eventBus.on("issue:failed", this.failedHandler);
6663
- logger34.info("DiaryCollector started");
6874
+ logger36.info("DiaryCollector started");
6664
6875
  }
6665
6876
  /** 停止监听事件 */
6666
6877
  stop() {
@@ -6672,7 +6883,7 @@ var DiaryCollector = class {
6672
6883
  eventBus.off("issue:failed", this.failedHandler);
6673
6884
  this.failedHandler = null;
6674
6885
  }
6675
- logger34.info("DiaryCollector stopped");
6886
+ logger36.info("DiaryCollector stopped");
6676
6887
  }
6677
6888
  /** 处理 issue:stateChanged 事件 */
6678
6889
  async handleStateChanged(payload) {
@@ -6683,7 +6894,7 @@ var DiaryCollector = class {
6683
6894
  if (!issueIid) return;
6684
6895
  const existing = this.diaryStore.getByIssueIid(issueIid);
6685
6896
  if (existing.some((d) => d.outcome === "completed")) {
6686
- logger34.debug("Diary already exists for completed issue", { issueIid });
6897
+ logger36.debug("Diary already exists for completed issue", { issueIid });
6687
6898
  return;
6688
6899
  }
6689
6900
  await this.collectDiary(issueIid, "completed");
@@ -6700,7 +6911,7 @@ var DiaryCollector = class {
6700
6911
  try {
6701
6912
  const record = this.tracker.get(issueIid);
6702
6913
  if (!record) {
6703
- logger34.warn("Cannot collect diary: issue record not found", { issueIid });
6914
+ logger36.warn("Cannot collect diary: issue record not found", { issueIid });
6704
6915
  return null;
6705
6916
  }
6706
6917
  const plan = this.createPlanPersistence?.(issueIid);
@@ -6724,10 +6935,10 @@ var DiaryCollector = class {
6724
6935
  };
6725
6936
  this.diaryStore.create(diary);
6726
6937
  eventBus.emitTyped("distill:diary:created", { issueIid, diaryId: diary.id, outcome });
6727
- logger34.info("Diary collected", { issueIid, diaryId: diary.id, outcome });
6938
+ logger36.info("Diary collected", { issueIid, diaryId: diary.id, outcome });
6728
6939
  return diary;
6729
6940
  } catch (err) {
6730
- logger34.warn("Failed to collect diary", {
6941
+ logger36.warn("Failed to collect diary", {
6731
6942
  issueIid,
6732
6943
  outcome,
6733
6944
  error: err.message
@@ -6950,7 +7161,7 @@ ${memorySection}
6950
7161
  }
6951
7162
 
6952
7163
  // src/distill/MemoryDistiller.ts
6953
- var logger35 = logger.child("MemoryDistiller");
7164
+ var logger37 = logger.child("MemoryDistiller");
6954
7165
  var MemoryDistiller = class {
6955
7166
  aiRunner;
6956
7167
  diaryStore;
@@ -6975,17 +7186,17 @@ var MemoryDistiller = class {
6975
7186
  async distill(options) {
6976
7187
  const undistilled = this.diaryStore.getUndistilled();
6977
7188
  if (!options?.force && undistilled.length < this.minDiariesForDistill) {
6978
- logger35.info("Not enough undistilled diaries, skipping memory distillation", {
7189
+ logger37.info("Not enough undistilled diaries, skipping memory distillation", {
6979
7190
  count: undistilled.length,
6980
7191
  threshold: this.minDiariesForDistill
6981
7192
  });
6982
7193
  return { processedDiaries: 0, actions: 0 };
6983
7194
  }
6984
7195
  if (undistilled.length === 0) {
6985
- logger35.info("No undistilled diaries available");
7196
+ logger37.info("No undistilled diaries available");
6986
7197
  return { processedDiaries: 0, actions: 0 };
6987
7198
  }
6988
- logger35.info("Starting memory distillation", { diaryCount: undistilled.length });
7199
+ logger37.info("Starting memory distillation", { diaryCount: undistilled.length });
6989
7200
  const existingMemories = this.loadExistingMemories();
6990
7201
  const prompt = buildMemoryDistillPrompt(undistilled, existingMemories);
6991
7202
  const result = await this.aiRunner.run({
@@ -6994,12 +7205,12 @@ var MemoryDistiller = class {
6994
7205
  timeoutMs: this.timeoutMs
6995
7206
  });
6996
7207
  if (!result.success) {
6997
- logger35.error("AI distillation failed", { output: result.output.slice(0, 500) });
7208
+ logger37.error("AI distillation failed", { output: result.output.slice(0, 500) });
6998
7209
  throw new Error(`Memory distillation AI call failed: ${result.output.slice(0, 200)}`);
6999
7210
  }
7000
7211
  const actions = this.parseActions(result.output);
7001
7212
  if (actions.length === 0) {
7002
- logger35.info("No distillation actions returned by AI");
7213
+ logger37.info("No distillation actions returned by AI");
7003
7214
  this.diaryStore.markDistilled(undistilled.map((d) => d.id));
7004
7215
  return { processedDiaries: undistilled.length, actions: 0 };
7005
7216
  }
@@ -7009,14 +7220,14 @@ var MemoryDistiller = class {
7009
7220
  this.executeAction(action, existingMemories);
7010
7221
  actionCount++;
7011
7222
  } catch (err) {
7012
- logger35.warn("Failed to execute distill action", {
7223
+ logger37.warn("Failed to execute distill action", {
7013
7224
  action: action.type,
7014
7225
  error: err.message
7015
7226
  });
7016
7227
  }
7017
7228
  }
7018
7229
  this.diaryStore.markDistilled(undistilled.map((d) => d.id));
7019
- logger35.info("Memory distillation complete", {
7230
+ logger37.info("Memory distillation complete", {
7020
7231
  processedDiaries: undistilled.length,
7021
7232
  actions: actionCount
7022
7233
  });
@@ -7040,18 +7251,18 @@ var MemoryDistiller = class {
7040
7251
  try {
7041
7252
  const jsonMatch = output.match(/```json\s*([\s\S]*?)```/) ?? output.match(/\{[\s\S]*"actions"[\s\S]*\}/);
7042
7253
  if (!jsonMatch) {
7043
- logger35.warn("No JSON found in AI output");
7254
+ logger37.warn("No JSON found in AI output");
7044
7255
  return [];
7045
7256
  }
7046
7257
  const jsonStr = jsonMatch[1] ?? jsonMatch[0];
7047
7258
  const parsed = JSON.parse(jsonStr);
7048
7259
  if (!Array.isArray(parsed.actions)) {
7049
- logger35.warn("Invalid actions format in AI output");
7260
+ logger37.warn("Invalid actions format in AI output");
7050
7261
  return [];
7051
7262
  }
7052
7263
  return parsed.actions;
7053
7264
  } catch (err) {
7054
- logger35.warn("Failed to parse AI distillation output", {
7265
+ logger37.warn("Failed to parse AI distillation output", {
7055
7266
  error: err.message,
7056
7267
  output: output.slice(0, 300)
7057
7268
  });
@@ -7071,7 +7282,7 @@ var MemoryDistiller = class {
7071
7282
  this.supersedeMemory(action, existingMemories);
7072
7283
  break;
7073
7284
  default:
7074
- logger35.warn("Unknown distill action type", { type: action.type });
7285
+ logger37.warn("Unknown distill action type", { type: action.type });
7075
7286
  }
7076
7287
  }
7077
7288
  /** 创建新 memory */
@@ -7101,13 +7312,13 @@ var MemoryDistiller = class {
7101
7312
  action: "created",
7102
7313
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7103
7314
  });
7104
- logger35.info("Created new memory", { id: memoryEntry.id, theme: action.theme, title: action.title });
7315
+ logger37.info("Created new memory", { id: memoryEntry.id, theme: action.theme, title: action.title });
7105
7316
  }
7106
7317
  /** 合并新证据到已有 memory */
7107
7318
  mergeMemory(action, existingMemories) {
7108
7319
  const existing = existingMemories.find((m) => m.id === action.memoryId);
7109
7320
  if (!existing) {
7110
- logger35.warn("Cannot merge: memory not found", { memoryId: action.memoryId });
7321
+ logger37.warn("Cannot merge: memory not found", { memoryId: action.memoryId });
7111
7322
  return;
7112
7323
  }
7113
7324
  existing.evidence = [.../* @__PURE__ */ new Set([...existing.evidence, ...action.newEvidence])];
@@ -7140,7 +7351,7 @@ var MemoryDistiller = class {
7140
7351
  reason: `Merged ${action.newEvidence.length} new evidence(s)`,
7141
7352
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7142
7353
  });
7143
- logger35.info("Merged memory", { id: existing.id, newEvidence: action.newEvidence.length });
7354
+ logger37.info("Merged memory", { id: existing.id, newEvidence: action.newEvidence.length });
7144
7355
  }
7145
7356
  /** 用新 memory 替代旧 memory */
7146
7357
  supersedeMemory(action, existingMemories) {
@@ -7182,7 +7393,7 @@ var MemoryDistiller = class {
7182
7393
  reason: `Supersedes ${action.oldMemoryId}`,
7183
7394
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7184
7395
  });
7185
- logger35.info("Superseded memory", {
7396
+ logger37.info("Superseded memory", {
7186
7397
  oldId: action.oldMemoryId,
7187
7398
  newId: newMemory.id,
7188
7399
  theme: action.theme
@@ -7284,7 +7495,7 @@ ${ruleSection}
7284
7495
  }
7285
7496
 
7286
7497
  // src/distill/AgentRuleDistiller.ts
7287
- var logger36 = logger.child("AgentRuleDistiller");
7498
+ var logger38 = logger.child("AgentRuleDistiller");
7288
7499
  var AgentRuleDistiller = class {
7289
7500
  aiRunner;
7290
7501
  knowledgeStore;
@@ -7313,11 +7524,11 @@ var AgentRuleDistiller = class {
7313
7524
  async distill() {
7314
7525
  const matureMemories = this.getMatureMemories();
7315
7526
  if (matureMemories.length === 0) {
7316
- logger36.info("No mature memories ready for rule distillation");
7527
+ logger38.info("No mature memories ready for rule distillation");
7317
7528
  return { processedMemories: 0, actions: 0 };
7318
7529
  }
7319
7530
  const existingRules = this.loadExistingRules();
7320
- logger36.info("Starting rule distillation", {
7531
+ logger38.info("Starting rule distillation", {
7321
7532
  matureMemories: matureMemories.length,
7322
7533
  existingRules: existingRules.length
7323
7534
  });
@@ -7328,12 +7539,12 @@ var AgentRuleDistiller = class {
7328
7539
  timeoutMs: this.timeoutMs
7329
7540
  });
7330
7541
  if (!result.success) {
7331
- logger36.error("AI rule distillation failed", { output: result.output.slice(0, 500) });
7542
+ logger38.error("AI rule distillation failed", { output: result.output.slice(0, 500) });
7332
7543
  throw new Error(`Rule distillation AI call failed: ${result.output.slice(0, 200)}`);
7333
7544
  }
7334
7545
  const actions = this.parseActions(result.output);
7335
7546
  if (actions.length === 0) {
7336
- logger36.info("No rule distillation actions returned by AI");
7547
+ logger38.info("No rule distillation actions returned by AI");
7337
7548
  return { processedMemories: matureMemories.length, actions: 0 };
7338
7549
  }
7339
7550
  let actionCount = 0;
@@ -7342,13 +7553,13 @@ var AgentRuleDistiller = class {
7342
7553
  this.executeAction(action, existingRules);
7343
7554
  actionCount++;
7344
7555
  } catch (err) {
7345
- logger36.warn("Failed to execute rule distill action", {
7556
+ logger38.warn("Failed to execute rule distill action", {
7346
7557
  action: action.type,
7347
7558
  error: err.message
7348
7559
  });
7349
7560
  }
7350
7561
  }
7351
- logger36.info("Rule distillation complete", {
7562
+ logger38.info("Rule distillation complete", {
7352
7563
  processedMemories: matureMemories.length,
7353
7564
  actions: actionCount
7354
7565
  });
@@ -7402,7 +7613,7 @@ var AgentRuleDistiller = class {
7402
7613
  try {
7403
7614
  const jsonMatch = output.match(/```json\s*([\s\S]*?)```/) ?? output.match(/\{[\s\S]*"actions"[\s\S]*\}/);
7404
7615
  if (!jsonMatch) {
7405
- logger36.warn("No JSON found in AI rule distill output");
7616
+ logger38.warn("No JSON found in AI rule distill output");
7406
7617
  return [];
7407
7618
  }
7408
7619
  const jsonStr = jsonMatch[1] ?? jsonMatch[0];
@@ -7410,7 +7621,7 @@ var AgentRuleDistiller = class {
7410
7621
  if (!Array.isArray(parsed.actions)) return [];
7411
7622
  return parsed.actions;
7412
7623
  } catch (err) {
7413
- logger36.warn("Failed to parse AI rule distill output", {
7624
+ logger38.warn("Failed to parse AI rule distill output", {
7414
7625
  error: err.message
7415
7626
  });
7416
7627
  return [];
@@ -7429,7 +7640,7 @@ var AgentRuleDistiller = class {
7429
7640
  this.deprecateRule(action, existingRules);
7430
7641
  break;
7431
7642
  default:
7432
- logger36.warn("Unknown rule distill action type", { type: action.type });
7643
+ logger38.warn("Unknown rule distill action type", { type: action.type });
7433
7644
  }
7434
7645
  }
7435
7646
  /** 创建新规则 */
@@ -7462,13 +7673,13 @@ var AgentRuleDistiller = class {
7462
7673
  action: "created",
7463
7674
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7464
7675
  });
7465
- logger36.info("Created agent rule", { id: ruleEntry.id, ruleName: action.ruleName });
7676
+ logger38.info("Created agent rule", { id: ruleEntry.id, ruleName: action.ruleName });
7466
7677
  }
7467
7678
  /** 更新已有规则 */
7468
7679
  updateRule(action, existingRules) {
7469
7680
  const existing = existingRules.find((r) => r.id === action.ruleId);
7470
7681
  if (!existing) {
7471
- logger36.warn("Cannot update: rule not found", { ruleId: action.ruleId });
7682
+ logger38.warn("Cannot update: rule not found", { ruleId: action.ruleId });
7472
7683
  return;
7473
7684
  }
7474
7685
  existing.content = action.content;
@@ -7498,13 +7709,13 @@ var AgentRuleDistiller = class {
7498
7709
  action: "merged",
7499
7710
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7500
7711
  });
7501
- logger36.info("Updated agent rule", { id: existing.id, ruleName: existing.ruleName });
7712
+ logger38.info("Updated agent rule", { id: existing.id, ruleName: existing.ruleName });
7502
7713
  }
7503
7714
  /** 废弃规则 */
7504
7715
  deprecateRule(action, existingRules) {
7505
7716
  const existing = existingRules.find((r) => r.id === action.ruleId);
7506
7717
  if (!existing) {
7507
- logger36.warn("Cannot deprecate: rule not found", { ruleId: action.ruleId });
7718
+ logger38.warn("Cannot deprecate: rule not found", { ruleId: action.ruleId });
7508
7719
  return;
7509
7720
  }
7510
7721
  existing.deprecated = true;
@@ -7533,7 +7744,7 @@ var AgentRuleDistiller = class {
7533
7744
  reason: action.reason,
7534
7745
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7535
7746
  });
7536
- logger36.info("Deprecated agent rule", { id: existing.id, reason: action.reason });
7747
+ logger38.info("Deprecated agent rule", { id: existing.id, reason: action.reason });
7537
7748
  }
7538
7749
  /** 同步规则到 DATA_DIR/rules/ MDC 文件,可选同步到项目 */
7539
7750
  syncMdcFile(rule) {
@@ -7549,9 +7760,9 @@ var AgentRuleDistiller = class {
7549
7760
  ensureDir(this.rulesDir);
7550
7761
  const filePath = path13.join(this.rulesDir, `distilled-${rule.ruleName}.mdc`);
7551
7762
  fs9.writeFileSync(filePath, mdcContent, "utf-8");
7552
- logger36.debug("Synced MDC file", { path: filePath });
7763
+ logger38.debug("Synced MDC file", { path: filePath });
7553
7764
  } catch (err) {
7554
- logger36.warn("Failed to sync MDC file", {
7765
+ logger38.warn("Failed to sync MDC file", {
7555
7766
  ruleName: rule.ruleName,
7556
7767
  error: err.message
7557
7768
  });
@@ -7561,9 +7772,9 @@ var AgentRuleDistiller = class {
7561
7772
  ensureDir(this.projectRulesDir);
7562
7773
  const projectFilePath = path13.join(this.projectRulesDir, `distilled-${rule.ruleName}.mdc`);
7563
7774
  fs9.writeFileSync(projectFilePath, mdcContent, "utf-8");
7564
- logger36.debug("Synced MDC file to project", { path: projectFilePath });
7775
+ logger38.debug("Synced MDC file to project", { path: projectFilePath });
7565
7776
  } catch (err) {
7566
- logger36.warn("Failed to sync MDC file to project", {
7777
+ logger38.warn("Failed to sync MDC file to project", {
7567
7778
  ruleName: rule.ruleName,
7568
7779
  error: err.message
7569
7780
  });
@@ -7577,10 +7788,10 @@ var AgentRuleDistiller = class {
7577
7788
  const filePath = path13.join(this.rulesDir, fileName);
7578
7789
  if (fs9.existsSync(filePath)) {
7579
7790
  fs9.unlinkSync(filePath);
7580
- logger36.debug("Removed MDC file", { path: filePath });
7791
+ logger38.debug("Removed MDC file", { path: filePath });
7581
7792
  }
7582
7793
  } catch (err) {
7583
- logger36.warn("Failed to remove MDC file", {
7794
+ logger38.warn("Failed to remove MDC file", {
7584
7795
  ruleName,
7585
7796
  error: err.message
7586
7797
  });
@@ -7590,10 +7801,10 @@ var AgentRuleDistiller = class {
7590
7801
  const projectFilePath = path13.join(this.projectRulesDir, fileName);
7591
7802
  if (fs9.existsSync(projectFilePath)) {
7592
7803
  fs9.unlinkSync(projectFilePath);
7593
- logger36.debug("Removed MDC file from project", { path: projectFilePath });
7804
+ logger38.debug("Removed MDC file from project", { path: projectFilePath });
7594
7805
  }
7595
7806
  } catch (err) {
7596
- logger36.warn("Failed to remove MDC file from project", {
7807
+ logger38.warn("Failed to remove MDC file from project", {
7597
7808
  ruleName,
7598
7809
  error: err.message
7599
7810
  });
@@ -7624,7 +7835,7 @@ var AgentRuleDistiller = class {
7624
7835
  // src/distill/VectorStore.ts
7625
7836
  import fs10 from "fs";
7626
7837
  import path14 from "path";
7627
- var logger37 = logger.child("VectorStore");
7838
+ var logger39 = logger.child("VectorStore");
7628
7839
  var STOP_WORDS = /* @__PURE__ */ new Set([
7629
7840
  // 英文
7630
7841
  "the",
@@ -7811,7 +8022,7 @@ var VectorStore = class {
7811
8022
  async reindex() {
7812
8023
  this.rebuildDocumentFreqs();
7813
8024
  this.save();
7814
- logger37.info("Vector index rebuilt", { count: this.data.vectors.length });
8025
+ logger39.info("Vector index rebuilt", { count: this.data.vectors.length });
7815
8026
  }
7816
8027
  /** 获取索引条目数 */
7817
8028
  count() {
@@ -7888,7 +8099,7 @@ var VectorStore = class {
7888
8099
  return JSON.parse(raw);
7889
8100
  }
7890
8101
  } catch (err) {
7891
- logger37.warn("Failed to load vector store", { error: err.message });
8102
+ logger39.warn("Failed to load vector store", { error: err.message });
7892
8103
  }
7893
8104
  return { documentFreqs: {}, vectors: [], totalDocs: 0 };
7894
8105
  }
@@ -7906,7 +8117,7 @@ var VectorStore = class {
7906
8117
  // src/distill/VersionStore.ts
7907
8118
  import fs11 from "fs";
7908
8119
  import path15 from "path";
7909
- var logger38 = logger.child("VersionStore");
8120
+ var logger40 = logger.child("VersionStore");
7910
8121
  var VersionStore = class {
7911
8122
  filePath;
7912
8123
  data;
@@ -7938,7 +8149,7 @@ var VersionStore = class {
7938
8149
  return JSON.parse(raw);
7939
8150
  }
7940
8151
  } catch (err) {
7941
- logger38.warn("Failed to load version store", { error: err.message });
8152
+ logger40.warn("Failed to load version store", { error: err.message });
7942
8153
  }
7943
8154
  return { records: [] };
7944
8155
  }
@@ -7954,7 +8165,7 @@ var VersionStore = class {
7954
8165
  };
7955
8166
 
7956
8167
  // src/distill/DistillScheduler.ts
7957
- var logger39 = logger.child("DistillScheduler");
8168
+ var logger41 = logger.child("DistillScheduler");
7958
8169
  var DistillScheduler = class {
7959
8170
  diaryStore;
7960
8171
  memoryDistiller;
@@ -7980,29 +8191,29 @@ var DistillScheduler = class {
7980
8191
  if (this.timer) return;
7981
8192
  this.timer = setInterval(() => {
7982
8193
  this.runDistill().catch((err) => {
7983
- logger39.error("Scheduled distillation failed", { error: err.message });
8194
+ logger41.error("Scheduled distillation failed", { error: err.message });
7984
8195
  });
7985
8196
  }, this.intervalMs);
7986
- logger39.info("DistillScheduler started", { intervalMs: this.intervalMs });
8197
+ logger41.info("DistillScheduler started", { intervalMs: this.intervalMs });
7987
8198
  }
7988
8199
  /** 停止定时调度 */
7989
8200
  stop() {
7990
8201
  if (this.timer) {
7991
8202
  clearInterval(this.timer);
7992
8203
  this.timer = null;
7993
- logger39.info("DistillScheduler stopped");
8204
+ logger41.info("DistillScheduler stopped");
7994
8205
  }
7995
8206
  }
7996
8207
  /** 手动触发蒸馏 */
7997
8208
  async runDistill(options) {
7998
8209
  if (this.running) {
7999
- logger39.info("Distillation already in progress, skipping");
8210
+ logger41.info("Distillation already in progress, skipping");
8000
8211
  return { memory: { processedDiaries: 0, actions: 0 }, rule: { processedMemories: 0, actions: 0 }, vectorIndexed: 0 };
8001
8212
  }
8002
8213
  this.running = true;
8003
8214
  eventBus.emitTyped("distill:started", {});
8004
8215
  try {
8005
- logger39.info("Starting distillation pipeline");
8216
+ logger41.info("Starting distillation pipeline");
8006
8217
  const memoryResult = await this.memoryDistiller.distill({ force: options?.force });
8007
8218
  if (memoryResult.actions > 0) {
8008
8219
  eventBus.emitTyped("distill:memory:updated", {
@@ -8027,7 +8238,7 @@ var DistillScheduler = class {
8027
8238
  rule: ruleResult,
8028
8239
  vectorIndexed
8029
8240
  });
8030
- logger39.info("Distillation pipeline complete", {
8241
+ logger41.info("Distillation pipeline complete", {
8031
8242
  memoryActions: memoryResult.actions,
8032
8243
  ruleActions: ruleResult.actions,
8033
8244
  vectorIndexed
@@ -8095,13 +8306,13 @@ ${content}`
8095
8306
  });
8096
8307
  indexed++;
8097
8308
  }
8098
- logger39.info("Vector indexing complete", { indexed });
8309
+ logger41.info("Vector indexing complete", { indexed });
8099
8310
  return indexed;
8100
8311
  }
8101
8312
  };
8102
8313
 
8103
8314
  // src/deploy/PreviewReaper.ts
8104
- var logger40 = logger.child("PreviewReaper");
8315
+ var logger42 = logger.child("PreviewReaper");
8105
8316
  var TERMINAL_STATES = /* @__PURE__ */ new Set(["completed" /* Completed */, "failed" /* Failed */, "deployed" /* Deployed */]);
8106
8317
  var PreviewReaper = class {
8107
8318
  tracker;
@@ -8124,16 +8335,16 @@ var PreviewReaper = class {
8124
8335
  if (this.timer) return;
8125
8336
  this.timer = setInterval(() => {
8126
8337
  this.reap().catch((err) => {
8127
- logger40.error("Scheduled preview reap failed", { error: err.message });
8338
+ logger42.error("Scheduled preview reap failed", { error: err.message });
8128
8339
  });
8129
8340
  }, this.intervalMs);
8130
- logger40.info("PreviewReaper started", { intervalMs: this.intervalMs, ttlMs: this.ttlMs });
8341
+ logger42.info("PreviewReaper started", { intervalMs: this.intervalMs, ttlMs: this.ttlMs });
8131
8342
  }
8132
8343
  stop() {
8133
8344
  if (this.timer) {
8134
8345
  clearInterval(this.timer);
8135
8346
  this.timer = null;
8136
- logger40.info("PreviewReaper stopped");
8347
+ logger42.info("PreviewReaper stopped");
8137
8348
  }
8138
8349
  }
8139
8350
  async reap() {
@@ -8155,13 +8366,13 @@ var PreviewReaper = class {
8155
8366
  try {
8156
8367
  this.orchestrator.stopPreviewServers(iid);
8157
8368
  reaped.push(iid);
8158
- logger40.info(t("reaper.reaped", { iid, hours }));
8369
+ logger42.info(t("reaper.reaped", { iid, hours }));
8159
8370
  } catch (err) {
8160
- logger40.warn("Failed to reap preview", { iid, error: err.message });
8371
+ logger42.warn("Failed to reap preview", { iid, error: err.message });
8161
8372
  }
8162
8373
  }
8163
8374
  if (reaped.length > 0) {
8164
- logger40.info(t("reaper.summary", { count: reaped.length }));
8375
+ logger42.info(t("reaper.summary", { count: reaped.length }));
8165
8376
  this.bus.emitTyped("preview:reaped", { iids: reaped, count: reaped.length });
8166
8377
  }
8167
8378
  this.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -8190,7 +8401,7 @@ import { randomUUID as randomUUID5 } from "crypto";
8190
8401
  import { execFile as execFile2 } from "child_process";
8191
8402
  import { promisify } from "util";
8192
8403
  var execFileAsync = promisify(execFile2);
8193
- var logger41 = logger.child("OrphanBranchManager");
8404
+ var logger43 = logger.child("OrphanBranchManager");
8194
8405
  var SYNC_MANIFEST = [
8195
8406
  "knowledge/index.json",
8196
8407
  "knowledge/knowledge.json",
@@ -8261,7 +8472,7 @@ var OrphanBranchManager = class {
8261
8472
  await this.gitIn(tmpDir, ["add", "-A"]);
8262
8473
  const status = await this.gitIn(tmpDir, ["status", "--porcelain"]);
8263
8474
  if (!status.trim()) {
8264
- logger41.info("No changes to publish");
8475
+ logger43.info("No changes to publish");
8265
8476
  return { commitSha: await this.gitIn(tmpDir, ["rev-parse", "HEAD"]) };
8266
8477
  }
8267
8478
  await this.gitIn(tmpDir, [
@@ -8272,7 +8483,7 @@ var OrphanBranchManager = class {
8272
8483
  ]);
8273
8484
  const commitSha = await this.gitIn(tmpDir, ["rev-parse", "HEAD"]);
8274
8485
  await this.pushWithRetry(tmpDir);
8275
- logger41.info("Knowledge published to orphan branch", {
8486
+ logger43.info("Knowledge published to orphan branch", {
8276
8487
  branch: this.branch,
8277
8488
  commitSha: commitSha.slice(0, 8),
8278
8489
  entriesCount: copied
@@ -8339,7 +8550,7 @@ var OrphanBranchManager = class {
8339
8550
  } catch {
8340
8551
  }
8341
8552
  }
8342
- logger41.info("Knowledge restored from orphan branch", {
8553
+ logger43.info("Knowledge restored from orphan branch", {
8343
8554
  branch: this.branch,
8344
8555
  entriesCount
8345
8556
  });
@@ -8376,7 +8587,7 @@ var OrphanBranchManager = class {
8376
8587
  // Private helpers
8377
8588
  // -----------------------------------------------------------------------
8378
8589
  async git(args) {
8379
- logger41.debug("git exec (main)", { args });
8590
+ logger43.debug("git exec (main)", { args });
8380
8591
  const { stdout } = await execFileAsync("git", args, {
8381
8592
  cwd: this.gitRootDir,
8382
8593
  maxBuffer: 10 * 1024 * 1024,
@@ -8385,7 +8596,7 @@ var OrphanBranchManager = class {
8385
8596
  return stdout.trim();
8386
8597
  }
8387
8598
  async gitIn(cwd, args) {
8388
- logger41.debug("git exec (worktree)", { cwd: path16.basename(cwd), args });
8599
+ logger43.debug("git exec (worktree)", { cwd: path16.basename(cwd), args });
8389
8600
  const { stdout } = await execFileAsync("git", args, {
8390
8601
  cwd,
8391
8602
  maxBuffer: 10 * 1024 * 1024,
@@ -8485,7 +8696,7 @@ var OrphanBranchManager = class {
8485
8696
  `Failed to push to orphan branch after ${MAX_PUSH_RETRIES} attempts: ${err.message}`
8486
8697
  );
8487
8698
  }
8488
- logger41.warn("Push failed, pulling and retrying", {
8699
+ logger43.warn("Push failed, pulling and retrying", {
8489
8700
  attempt,
8490
8701
  error: err.message
8491
8702
  });
@@ -8498,7 +8709,7 @@ var OrphanBranchManager = class {
8498
8709
  try {
8499
8710
  await this.git(["worktree", "remove", wtDir, "--force"]);
8500
8711
  } catch {
8501
- logger41.debug("Worktree cleanup skipped (may not exist)", { dir: wtDir });
8712
+ logger43.debug("Worktree cleanup skipped (may not exist)", { dir: wtDir });
8502
8713
  }
8503
8714
  try {
8504
8715
  await this.git(["worktree", "prune"]);
@@ -8515,7 +8726,7 @@ import fs14 from "fs";
8515
8726
  import { z } from "zod";
8516
8727
  import fs13 from "fs";
8517
8728
  import path17 from "path";
8518
- var logger42 = logger.child("TenantConfig");
8729
+ var logger44 = logger.child("TenantConfig");
8519
8730
  var tenantGongfengSchema = z.object({
8520
8731
  apiUrl: z.string().url("apiUrl must be a valid URL"),
8521
8732
  privateToken: z.string().min(1, "privateToken is required"),
@@ -8542,7 +8753,7 @@ var DEFAULT_TENANT_ID = "default";
8542
8753
  function loadTenantsConfig(configPath) {
8543
8754
  if (!configPath) return null;
8544
8755
  if (!fs13.existsSync(configPath)) {
8545
- logger42.warn("Tenants config file not found, falling back to single-tenant mode", {
8756
+ logger44.warn("Tenants config file not found, falling back to single-tenant mode", {
8546
8757
  path: configPath
8547
8758
  });
8548
8759
  return null;
@@ -8553,14 +8764,14 @@ function loadTenantsConfig(configPath) {
8553
8764
  const result = tenantsConfigSchema.safeParse(json);
8554
8765
  if (!result.success) {
8555
8766
  const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
8556
- logger42.error(`Tenants config validation failed:
8767
+ logger44.error(`Tenants config validation failed:
8557
8768
  ${issues}`);
8558
8769
  return null;
8559
8770
  }
8560
8771
  const ids = result.data.tenants.map((t2) => t2.id);
8561
8772
  const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
8562
8773
  if (dupes.length > 0) {
8563
- logger42.error("Duplicate tenant IDs found", { duplicates: [...new Set(dupes)] });
8774
+ logger44.error("Duplicate tenant IDs found", { duplicates: [...new Set(dupes)] });
8564
8775
  return null;
8565
8776
  }
8566
8777
  const baseDir = path17.dirname(configPath);
@@ -8569,12 +8780,12 @@ ${issues}`);
8569
8780
  tenant.workspace = path17.resolve(baseDir, tenant.workspace);
8570
8781
  }
8571
8782
  }
8572
- logger42.info("Tenants config loaded", {
8783
+ logger44.info("Tenants config loaded", {
8573
8784
  tenants: result.data.tenants.map((t2) => t2.id)
8574
8785
  });
8575
8786
  return result.data;
8576
8787
  } catch (err) {
8577
- logger42.error("Failed to parse tenants config", {
8788
+ logger44.error("Failed to parse tenants config", {
8578
8789
  path: configPath,
8579
8790
  error: err.message
8580
8791
  });
@@ -8637,6 +8848,49 @@ async function main() {
8637
8848
  const allAiPhaseNames = [...new Set(
8638
8849
  [...lifecycleManagers.values()].flatMap((lm) => lm.getExecutablePhaseNames())
8639
8850
  )];
8851
+ let sharedTerminalManager;
8852
+ if (config.ai.mode === "pty") {
8853
+ const { TerminalManager: TerminalManager2 } = await import("./TerminalManager-RT2N7N5R.js");
8854
+ const { PtyRunner } = await import("./PtyRunner-6UGI5STW.js");
8855
+ const { registerAIRunner: regRunner, getPtyProfile } = await import("./AIRunnerRegistry-II3WWSFN.js");
8856
+ const allPtyAgents = /* @__PURE__ */ new Set([
8857
+ config.pty.defaultAgent,
8858
+ ...Object.values(config.pty.phaseAgents)
8859
+ ]);
8860
+ for (const agent of allPtyAgents) {
8861
+ if (!getPtyProfile(agent)) {
8862
+ throw new Error(
8863
+ `PTY agent "${agent}" has no PtyProfile. Supported agents: claude-internal, codebuddy, cursor-agent`
8864
+ );
8865
+ }
8866
+ }
8867
+ sharedTerminalManager = new TerminalManager2({
8868
+ idleTimeoutMs: config.terminal.idleTimeoutMs,
8869
+ maxSessions: config.terminal.maxSessions,
8870
+ allowedBaseDirs: [
8871
+ config.project.worktreeBaseDir ?? config.project.workDir,
8872
+ config.project.workDir
8873
+ ].filter(Boolean)
8874
+ });
8875
+ const tm = sharedTerminalManager;
8876
+ regRunner("pty", {
8877
+ factoryFn: (_binary, nvmNodeVersion) => new PtyRunner(
8878
+ nvmNodeVersion,
8879
+ tm,
8880
+ config.pty.defaultAgent,
8881
+ config.pty.phaseAgents,
8882
+ config.ai.model,
8883
+ config.pty.idleDetectMs
8884
+ ),
8885
+ defaultBinary: "claude",
8886
+ binaryEnvKey: "CLAUDE_BINARY",
8887
+ capabilities: { nativePlanMode: false }
8888
+ });
8889
+ logger.info("PTY runner registered (multi-agent)", {
8890
+ defaultAgent: config.pty.defaultAgent,
8891
+ phaseAgents: config.pty.phaseAgents
8892
+ });
8893
+ }
8640
8894
  validatePhaseRegistry(allAiPhaseNames);
8641
8895
  validateRunnerRegistry([config.ai.mode]);
8642
8896
  const aiRunner = createAIRunner(config.ai);
@@ -8894,7 +9148,8 @@ async function main() {
8894
9148
  configReloader,
8895
9149
  orphanManager,
8896
9150
  wsConfig: primaryTenant.wsConfig,
8897
- previewReaper
9151
+ previewReaper,
9152
+ terminalManager: sharedTerminalManager
8898
9153
  });
8899
9154
  await webServer.start();
8900
9155
  }
@@ -9005,4 +9260,4 @@ function migrateKnowledgeDir(srcDir, destDir) {
9005
9260
  export {
9006
9261
  main
9007
9262
  };
9008
- //# sourceMappingURL=chunk-K2OTLYJI.js.map
9263
+ //# sourceMappingURL=chunk-QZZGIZWC.js.map