@xdevops/issue-auto-finish 1.0.86 → 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-ICXB2WP5.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-OUPJMHAL.js → chunk-IP3QTP5A.js} +1026 -764
  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-QO5VTSMI.js → chunk-QZZGIZWC.js} +455 -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-4NSHDOX3.js → restart-BMILTP5X.js} +6 -5
  88. package/dist/{restart-4NSHDOX3.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-DADQSKPL.js +0 -1
  116. package/dist/chunk-FWEW5E3B.js.map +0 -1
  117. package/dist/chunk-KTYPZTF4.js.map +0 -1
  118. package/dist/chunk-OUPJMHAL.js.map +0 -1
  119. package/dist/chunk-P4O4ZXEC.js.map +0 -1
  120. package/dist/chunk-QO5VTSMI.js.map +0 -1
  121. package/dist/start-XZIBPLC2.js +0 -14
  122. package/src/web/frontend/dist/assets/index-BWVpNmFm.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-XZIBPLC2.js.map → ai-runner-HLA44WI6.js.map} +0 -0
  128. /package/dist/{chunk-ICXB2WP5.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-OUPJMHAL.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;
@@ -1391,7 +1410,7 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
1391
1410
  });
1392
1411
  router.put("/api/system/feature-toggle", (req, res) => {
1393
1412
  const { feature, enabled } = req.body;
1394
- const validFeatures = ["brainstorm", "chat", "braindump", "knowledge", "distill"];
1413
+ const validFeatures = ["brainstorm", "chat", "braindump", "knowledge", "distill", "terminal"];
1395
1414
  if (!feature || !validFeatures.includes(feature) || typeof enabled !== "boolean") {
1396
1415
  res.status(400).json({ error: "Invalid feature or enabled value" });
1397
1416
  return;
@@ -1441,6 +1460,8 @@ function createApiRouter(trackerOrDeps, config, agentLogStore, orchestrator, mai
1441
1460
  braindumpEnabled: getFeatureEnabled("braindump", cfg),
1442
1461
  knowledgeEnabled: getFeatureEnabled("knowledge", cfg),
1443
1462
  distillEnabled: getFeatureEnabled("distill", cfg),
1463
+ terminalEnabled: getFeatureEnabled("terminal", cfg),
1464
+ terminalWorkDir: cfg.project.workDir,
1444
1465
  knowledgeSyncEnabled: true,
1445
1466
  multiRepoEnabled: wsConfig ? isMultiRepo(wsConfig) : false,
1446
1467
  linkedRepos: wsConfig?.associates.map((a) => ({
@@ -1707,7 +1728,7 @@ data: ${JSON.stringify({ time: (/* @__PURE__ */ new Date()).toISOString() })}
1707
1728
  const scenariosDir = path3.join(outputsDir, "scenarios");
1708
1729
  let scenarios = [];
1709
1730
  if (fs3.existsSync(scenariosDir) && fs3.statSync(scenariosDir).isDirectory()) {
1710
- scenarios = collectArtifactFiles(scenariosDir, scenariosDir);
1731
+ scenarios = collectArtifactFiles(scenariosDir, scenariosDir).filter((f) => !f.name.startsWith("config.") && !f.name.startsWith("config-"));
1711
1732
  }
1712
1733
  res.json({ configured: true, runs, scenarios });
1713
1734
  } catch (err) {
@@ -2405,7 +2426,7 @@ function createKnowledgeRouter(deps) {
2405
2426
  heartbeat = setInterval(() => {
2406
2427
  sendProgress({ step: "analyzing", current: 2, total, message: "AI \u5206\u6790\u4E2D...", elapsed: Date.now() - aiStart });
2407
2428
  }, 3e3);
2408
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2429
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2409
2430
  const runner = createAIRunner2(config.ai);
2410
2431
  const { analyze } = await import("./KnowledgeAnalyzer-MTTTSSHX.js");
2411
2432
  const knowledge = await analyze({ workDir, aiRunner: runner, syncToProject: config.sync.knowledgeToProject });
@@ -2450,7 +2471,7 @@ function createKnowledgeRouter(deps) {
2450
2471
  (async () => {
2451
2472
  try {
2452
2473
  sendProgress({ step: "collecting", current: 1, total, message: "\u68C0\u6D4B\u53D8\u66F4\u5E76\u6536\u96C6\u4FE1\u606F..." });
2453
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2474
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2454
2475
  const { analyzeIncremental } = await import("./KnowledgeAnalyzer-MTTTSSHX.js");
2455
2476
  const runner = createAIRunner2(config.ai);
2456
2477
  sendProgress({ step: "analyzing", current: 2, total, message: "\u589E\u91CF\u5206\u6790\u4E2D..." });
@@ -2734,7 +2755,7 @@ function createDomainModelRouter(deps) {
2734
2755
  }
2735
2756
  (async () => {
2736
2757
  try {
2737
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2758
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2738
2759
  const runner = createAIRunner2(config.ai);
2739
2760
  const model = await analyzer.analyze({
2740
2761
  workDir: config.project.workDir,
@@ -2948,7 +2969,7 @@ function createSystemUseCaseRouter(deps) {
2948
2969
  }
2949
2970
  (async () => {
2950
2971
  try {
2951
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
2972
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
2952
2973
  const runner = createAIRunner2(config.ai);
2953
2974
  const model = await analyzer.analyze({
2954
2975
  workDir: config.project.workDir,
@@ -3138,7 +3159,7 @@ function createSystemUseCaseRouter(deps) {
3138
3159
  res.status(404).json({ error: "No domain model loaded \u2014 run domain analysis first" });
3139
3160
  return;
3140
3161
  }
3141
- const { createAIRunner: createAIRunner2 } = await import("./ai-runner-SVUNA3FX.js");
3162
+ const { createAIRunner: createAIRunner2 } = await import("./ai-runner-HLA44WI6.js");
3142
3163
  const runner = createAIRunner2(config.ai);
3143
3164
  const associations = await analyzer.suggestDomainAssociations(
3144
3165
  useCaseModel,
@@ -3969,6 +3990,79 @@ function createAnalyticsRouter(deps) {
3969
3990
  return router;
3970
3991
  }
3971
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
+
3972
4066
  // src/knowledge/DomainModelAnalyzer.ts
3973
4067
  import fs5 from "fs";
3974
4068
  import path6 from "path";
@@ -4098,7 +4192,7 @@ ${feedback}
4098
4192
  }
4099
4193
 
4100
4194
  // src/knowledge/DomainModelAnalyzer.ts
4101
- var logger21 = logger.child("DomainModelAnalyzer");
4195
+ var logger22 = logger.child("DomainModelAnalyzer");
4102
4196
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
4103
4197
  "node_modules",
4104
4198
  "dist",
@@ -4315,7 +4409,7 @@ var DomainModelAnalyzer = class {
4315
4409
  }
4316
4410
  this.currentModel = model;
4317
4411
  emit({ type: "complete", data: model });
4318
- logger21.info("Domain model analysis complete", {
4412
+ logger22.info("Domain model analysis complete", {
4319
4413
  contexts: model.boundedContexts.length,
4320
4414
  elements: model.elements.length,
4321
4415
  relationships: model.relationships.length
@@ -4341,7 +4435,7 @@ var DomainModelAnalyzer = class {
4341
4435
  tags: ["domain-model", "auto-generated"]
4342
4436
  });
4343
4437
  }
4344
- logger21.info("Domain model confirmed to knowledge store");
4438
+ logger22.info("Domain model confirmed to knowledge store");
4345
4439
  }
4346
4440
  /**
4347
4441
  * Load a previously stored domain model from KnowledgeStore.
@@ -4356,7 +4450,7 @@ var DomainModelAnalyzer = class {
4356
4450
  this.currentModel = model;
4357
4451
  return model;
4358
4452
  } catch {
4359
- logger21.warn("Failed to parse stored domain model");
4453
+ logger22.warn("Failed to parse stored domain model");
4360
4454
  return null;
4361
4455
  }
4362
4456
  }
@@ -4580,7 +4674,7 @@ ${domainModelJson}
4580
4674
  }
4581
4675
 
4582
4676
  // src/knowledge/SystemUseCaseAnalyzer.ts
4583
- var logger22 = logger.child("SystemUseCaseAnalyzer");
4677
+ var logger23 = logger.child("SystemUseCaseAnalyzer");
4584
4678
  function parseJsonFromOutput2(output) {
4585
4679
  const jsonBlockMatch = output.match(/```json\s*\n([\s\S]*?)```/);
4586
4680
  if (jsonBlockMatch) {
@@ -4705,7 +4799,7 @@ var SystemUseCaseAnalyzer = class {
4705
4799
  }
4706
4800
  this.currentModel = model;
4707
4801
  emit({ type: "complete", data: model });
4708
- logger22.info("Use case analysis complete", {
4802
+ logger23.info("Use case analysis complete", {
4709
4803
  useCases: model.useCases.length,
4710
4804
  relationships: model.relationships.length
4711
4805
  });
@@ -4726,12 +4820,12 @@ var SystemUseCaseAnalyzer = class {
4726
4820
  });
4727
4821
  const associations = /* @__PURE__ */ new Map();
4728
4822
  if (!result.success) {
4729
- 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) });
4730
4824
  return associations;
4731
4825
  }
4732
4826
  const parsed = parseJsonFromOutput2(result.output);
4733
4827
  if (!parsed || !parsed.associations) {
4734
- logger22.warn("Failed to parse domain association output");
4828
+ logger23.warn("Failed to parse domain association output");
4735
4829
  return associations;
4736
4830
  }
4737
4831
  const rawAssociations = parsed.associations;
@@ -4761,7 +4855,7 @@ var SystemUseCaseAnalyzer = class {
4761
4855
  tags: ["system-usecase", "auto-generated"]
4762
4856
  });
4763
4857
  }
4764
- logger22.info("Use case model confirmed to knowledge store");
4858
+ logger23.info("Use case model confirmed to knowledge store");
4765
4859
  }
4766
4860
  /**
4767
4861
  * Load a previously stored use case model from KnowledgeStore.
@@ -4776,7 +4870,7 @@ var SystemUseCaseAnalyzer = class {
4776
4870
  this.currentModel = model;
4777
4871
  return model;
4778
4872
  } catch {
4779
- logger22.warn("Failed to parse stored use case model");
4873
+ logger23.warn("Failed to parse stored use case model");
4780
4874
  return null;
4781
4875
  }
4782
4876
  }
@@ -4843,7 +4937,7 @@ ${knowledgeSection}${entriesSection}
4843
4937
  }
4844
4938
 
4845
4939
  // src/services/ChatService.ts
4846
- var logger23 = logger.child("Chat");
4940
+ var logger24 = logger.child("Chat");
4847
4941
  function agentConfigToAIConfig(agentCfg, timeoutMs) {
4848
4942
  return {
4849
4943
  mode: agentCfg.mode,
@@ -4879,7 +4973,7 @@ var ChatService = class {
4879
4973
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4880
4974
  };
4881
4975
  this.sessions.set(session.id, session);
4882
- logger23.info("Created chat session", { sessionId: session.id });
4976
+ logger24.info("Created chat session", { sessionId: session.id });
4883
4977
  return session;
4884
4978
  }
4885
4979
  getSession(id) {
@@ -4924,7 +5018,7 @@ var ChatService = class {
4924
5018
  ${r.content.slice(0, 500)}`).join("\n\n");
4925
5019
  }
4926
5020
  } catch (err) {
4927
- logger23.debug("Vector search failed, continuing without", { error: err.message });
5021
+ logger24.debug("Vector search failed, continuing without", { error: err.message });
4928
5022
  }
4929
5023
  }
4930
5024
  const prompt = isFirstMessage ? `${buildChatSystemPrompt(getProjectKnowledge(), knowledgeEntries)}${vectorContext}
@@ -5005,14 +5099,108 @@ ${r.content.slice(0, 500)}`).join("\n\n");
5005
5099
  }
5006
5100
  };
5007
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
+
5008
5194
  // src/web/WebServer.ts
5009
5195
  var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
5010
- var logger24 = logger.child("WebServer");
5196
+ var logger26 = logger.child("WebServer");
5011
5197
  var WebServer = class _WebServer {
5012
5198
  app;
5013
5199
  server = null;
5014
5200
  host;
5015
5201
  port;
5202
+ terminalManager;
5203
+ config;
5016
5204
  constructor(trackerOrDeps, config, agentLogStore, orchestrator, mainGit) {
5017
5205
  let apiDeps;
5018
5206
  if (trackerOrDeps instanceof IssueTracker) {
@@ -5050,8 +5238,18 @@ var WebServer = class _WebServer {
5050
5238
  previewReaper: deps.previewReaper
5051
5239
  };
5052
5240
  }
5053
- this.app = express12();
5054
- 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
+ });
5055
5253
  this.app.get("/metrics", (_req, res) => {
5056
5254
  res.set("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
5057
5255
  res.send(metrics.serialize());
@@ -5075,12 +5273,12 @@ var WebServer = class _WebServer {
5075
5273
  if (fs6.existsSync(projectKnowledgeIndex) && !fs6.existsSync(globalKnowledgeIndex)) {
5076
5274
  try {
5077
5275
  _WebServer.migrateKnowledgeDir(projectKnowledgeDir, globalKnowledgeDataDir);
5078
- logger24.info("Migrated knowledge entries from project to global DATA_DIR", {
5276
+ logger26.info("Migrated knowledge entries from project to global DATA_DIR", {
5079
5277
  src: projectKnowledgeDir,
5080
5278
  dest: globalKnowledgeDataDir
5081
5279
  });
5082
5280
  } catch (err) {
5083
- logger24.warn("Failed to migrate knowledge entries to global DATA_DIR", {
5281
+ logger26.warn("Failed to migrate knowledge entries to global DATA_DIR", {
5084
5282
  error: err.message
5085
5283
  });
5086
5284
  }
@@ -5098,12 +5296,12 @@ var WebServer = class _WebServer {
5098
5296
  if (fs6.existsSync(legacyKnowledgePath) && !fs6.existsSync(dataDirKnowledgePath)) {
5099
5297
  try {
5100
5298
  fs6.copyFileSync(legacyKnowledgePath, dataDirKnowledgePath);
5101
- logger24.info("Migrated knowledge.json from project to DATA_DIR", {
5299
+ logger26.info("Migrated knowledge.json from project to DATA_DIR", {
5102
5300
  src: legacyKnowledgePath,
5103
5301
  dest: dataDirKnowledgePath
5104
5302
  });
5105
5303
  } catch (err) {
5106
- logger24.warn("Failed to migrate knowledge.json to DATA_DIR", {
5304
+ logger26.warn("Failed to migrate knowledge.json to DATA_DIR", {
5107
5305
  error: err.message
5108
5306
  });
5109
5307
  }
@@ -5160,8 +5358,13 @@ var WebServer = class _WebServer {
5160
5358
  });
5161
5359
  this.app.use(analyticsRouter);
5162
5360
  }
5361
+ const terminalRouter = createTerminalRouter({
5362
+ terminalManager: this.terminalManager,
5363
+ config: apiDeps.config
5364
+ });
5365
+ this.app.use(terminalRouter);
5163
5366
  const publicDir = apiDeps.config.web.frontendDistDir;
5164
- this.app.use(express12.static(publicDir));
5367
+ this.app.use(express13.static(publicDir));
5165
5368
  this.app.get("/{*splat}", (_req, res) => {
5166
5369
  res.sendFile("index.html", { root: publicDir });
5167
5370
  });
@@ -5169,16 +5372,22 @@ var WebServer = class _WebServer {
5169
5372
  start() {
5170
5373
  return new Promise((resolve) => {
5171
5374
  this.server = this.app.listen(this.port, this.host, () => {
5172
- 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}`);
5173
5381
  resolve();
5174
5382
  });
5175
5383
  });
5176
5384
  }
5177
5385
  stop() {
5386
+ this.terminalManager.destroyAll();
5178
5387
  if (this.server) {
5179
5388
  this.server.close();
5180
5389
  this.server = null;
5181
- logger24.info("Web server stopped");
5390
+ logger26.info("Web server stopped");
5182
5391
  }
5183
5392
  }
5184
5393
  /**
@@ -5214,10 +5423,10 @@ var WebServer = class _WebServer {
5214
5423
  };
5215
5424
 
5216
5425
  // src/webhook/WebhookServer.ts
5217
- import express14 from "express";
5426
+ import express15 from "express";
5218
5427
 
5219
5428
  // src/webhook/WebhookHandler.ts
5220
- import express13 from "express";
5429
+ import express14 from "express";
5221
5430
 
5222
5431
  // src/webhook/CommandParser.ts
5223
5432
  var TRIGGERS = ["@issue-auto", "@iaf"];
@@ -5420,7 +5629,7 @@ function parseExact(commandText) {
5420
5629
  // src/webhook/CommandExecutor.ts
5421
5630
  import path9 from "path";
5422
5631
  import fs7 from "fs";
5423
- var logger25 = logger.child("CommandExecutor");
5632
+ var logger27 = logger.child("CommandExecutor");
5424
5633
  var CommandExecutor = class {
5425
5634
  tracker;
5426
5635
  orchestrator;
@@ -5439,13 +5648,13 @@ var CommandExecutor = class {
5439
5648
  this.claimer = deps.claimer;
5440
5649
  }
5441
5650
  async execute(issueIid, issueId, command) {
5442
- logger25.info("Executing webhook command", { issueIid, command });
5651
+ logger27.info("Executing webhook command", { issueIid, command });
5443
5652
  let result;
5444
5653
  try {
5445
5654
  result = await this.dispatch(issueIid, command);
5446
5655
  } catch (err) {
5447
5656
  const msg = err.message;
5448
- logger25.error("Webhook command failed", { issueIid, error: msg });
5657
+ logger27.error("Webhook command failed", { issueIid, error: msg });
5449
5658
  result = { success: false, message: msg };
5450
5659
  }
5451
5660
  await this.replyToIssue(issueId, result);
@@ -5577,7 +5786,6 @@ var CommandExecutor = class {
5577
5786
  }
5578
5787
  async handleRestart(iid) {
5579
5788
  try {
5580
- this.poller?.forceReleaseIssue(iid);
5581
5789
  if (this.claimer) {
5582
5790
  const record = this.tracker.get(iid);
5583
5791
  if (record) {
@@ -5586,6 +5794,7 @@ var CommandExecutor = class {
5586
5794
  }
5587
5795
  }
5588
5796
  await this.orchestrator.restartIssue(iid);
5797
+ this.poller?.forceReleaseIssue(iid);
5589
5798
  } catch (err) {
5590
5799
  return this.fail(err.message);
5591
5800
  }
@@ -5601,7 +5810,7 @@ var CommandExecutor = class {
5601
5810
  const externalId = getExternalId(record);
5602
5811
  await this.claimer.releaseClaim(externalId, iid, "cancelled");
5603
5812
  } catch (err) {
5604
- 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 });
5605
5814
  }
5606
5815
  }
5607
5816
  await this.orchestrator.cancelIssue(iid);
@@ -5701,7 +5910,7 @@ var CommandExecutor = class {
5701
5910
  return this.fail(t("conflict.noMr", { iid }));
5702
5911
  }
5703
5912
  this.orchestrator.resolveConflict(iid).catch((err) => {
5704
- logger25.error("Async conflict resolution failed", { iid, error: err.message });
5913
+ logger27.error("Async conflict resolution failed", { iid, error: err.message });
5705
5914
  });
5706
5915
  return this.ok(t("conflict.startedMsg"));
5707
5916
  }
@@ -5743,7 +5952,7 @@ ${context}` : context;
5743
5952
  const prefix = result.success ? "" : "> **Failed**\n>\n> ";
5744
5953
  await this.gongfeng.createIssueNote(issueId, `${prefix}${result.message}`);
5745
5954
  } catch (err) {
5746
- logger25.warn("Failed to reply", { issueId, error: err.message });
5955
+ logger27.warn("Failed to reply", { issueId, error: err.message });
5747
5956
  }
5748
5957
  }
5749
5958
  ok(message) {
@@ -5798,10 +6007,10 @@ var NoteDeduplicator = class {
5798
6007
  };
5799
6008
 
5800
6009
  // src/webhook/WebhookHandler.ts
5801
- var { Router: Router12 } = express13;
5802
- var logger26 = logger.child("WebhookHandler");
6010
+ var { Router: Router13 } = express14;
6011
+ var logger28 = logger.child("WebhookHandler");
5803
6012
  function createWebhookRouter(deps) {
5804
- const router = Router12();
6013
+ const router = Router13();
5805
6014
  const executor = new CommandExecutor(deps);
5806
6015
  const dedup = new NoteDeduplicator();
5807
6016
  const { config, intentRecognizer } = deps;
@@ -5814,7 +6023,7 @@ function createWebhookRouter(deps) {
5814
6023
  if (config.webhook.secret) {
5815
6024
  const token = req.headers["x-gitlab-token"];
5816
6025
  if (token !== config.webhook.secret) {
5817
- logger26.warn("Webhook token mismatch");
6026
+ logger28.warn("Webhook token mismatch");
5818
6027
  res.status(401).json({ error: "Invalid token" });
5819
6028
  return;
5820
6029
  }
@@ -5831,7 +6040,7 @@ function createWebhookRouter(deps) {
5831
6040
  }
5832
6041
  const noteId = event.object_attributes.id;
5833
6042
  if (dedup.isDuplicate(noteId)) {
5834
- logger26.debug("Duplicate note, skipping", { noteId });
6043
+ logger28.debug("Duplicate note, skipping", { noteId });
5835
6044
  res.json({ ignored: true, reason: "duplicate" });
5836
6045
  return;
5837
6046
  }
@@ -5843,7 +6052,7 @@ function createWebhookRouter(deps) {
5843
6052
  return;
5844
6053
  }
5845
6054
  if (!issueId) {
5846
- 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 });
5847
6056
  res.json({ accepted: true, noteId, issueIid, test: true });
5848
6057
  return;
5849
6058
  }
@@ -5883,19 +6092,19 @@ async function processCommandAsync(noteBody, issueIid, issueId, executor, intent
5883
6092
  if (!command && intentRecognizer && config.webhook.llmFallback) {
5884
6093
  const record = tracker.get(issueIid);
5885
6094
  const state = record?.state;
5886
- logger26.info("Falling back to LLM intent recognition", { issueIid });
6095
+ logger28.info("Falling back to LLM intent recognition", { issueIid });
5887
6096
  command = await intentRecognizer.recognize(cmdText, state);
5888
6097
  }
5889
6098
  if (!command) {
5890
- 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) });
5891
6100
  await gongfeng.createIssueNote(issueId, HELP_TEXT_FUNC()).catch(
5892
- (e) => logger26.warn("Failed to reply help text", { error: e.message })
6101
+ (e) => logger28.warn("Failed to reply help text", { error: e.message })
5893
6102
  );
5894
6103
  return;
5895
6104
  }
5896
6105
  await executor.execute(issueIid, issueId, command);
5897
6106
  } catch (err) {
5898
- logger26.error("Failed to process webhook command", {
6107
+ logger28.error("Failed to process webhook command", {
5899
6108
  issueIid,
5900
6109
  error: err.message
5901
6110
  });
@@ -5915,7 +6124,7 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5915
6124
  const command = parseExact(cmdText);
5916
6125
  if (!command) {
5917
6126
  await gongfeng.createMergeRequestNote(mr.iid, HELP_TEXT_FUNC()).catch(
5918
- (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 })
5919
6128
  );
5920
6129
  return;
5921
6130
  }
@@ -5924,7 +6133,7 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5924
6133
  mr.iid,
5925
6134
  `\`${command.intent}\` command is not supported on MR comments. Please use Issue comments instead.`
5926
6135
  ).catch(
5927
- (e) => logger26.warn("Failed to reply on MR", { error: e.message })
6136
+ (e) => logger28.warn("Failed to reply on MR", { error: e.message })
5928
6137
  );
5929
6138
  return;
5930
6139
  }
@@ -5935,7 +6144,7 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5935
6144
  mr.iid,
5936
6145
  `Could not find a tracked issue for branch \`${mr.source_branch}\`.`
5937
6146
  ).catch(
5938
- (e) => logger26.warn("Failed to reply on MR", { error: e.message })
6147
+ (e) => logger28.warn("Failed to reply on MR", { error: e.message })
5939
6148
  );
5940
6149
  return;
5941
6150
  }
@@ -5944,10 +6153,10 @@ async function processMRCommandAsync(noteBody, mr, executor, tracker, gongfeng)
5944
6153
  const prefix = result.success ? "" : "> **Failed**\n>\n> ";
5945
6154
  await gongfeng.createMergeRequestNote(mr.iid, `${prefix}${result.message}`);
5946
6155
  } catch (err) {
5947
- 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 });
5948
6157
  }
5949
6158
  } catch (err) {
5950
- logger26.error("Failed to process MR webhook command", {
6159
+ logger28.error("Failed to process MR webhook command", {
5951
6160
  mrIid: mr.iid,
5952
6161
  error: err.message
5953
6162
  });
@@ -5959,7 +6168,7 @@ function isSelfNote(_event, _selfToken) {
5959
6168
 
5960
6169
  // src/webhook/IntentRecognizer.ts
5961
6170
  import { spawn } from "child_process";
5962
- var logger27 = logger.child("IntentRecognizer");
6171
+ var logger29 = logger.child("IntentRecognizer");
5963
6172
  var VALID_INTENTS = [
5964
6173
  "retry",
5965
6174
  "retry-from",
@@ -6001,7 +6210,7 @@ ${userComment}`;
6001
6210
  const rawOutput = await this.runLLM(userPrompt);
6002
6211
  return this.parseResponse(rawOutput);
6003
6212
  } catch (err) {
6004
- logger27.error("Intent recognition failed", { error: err.message });
6213
+ logger29.error("Intent recognition failed", { error: err.message });
6005
6214
  return null;
6006
6215
  }
6007
6216
  }
@@ -6009,20 +6218,20 @@ ${userComment}`;
6009
6218
  parseResponse(raw) {
6010
6219
  const jsonMatch = raw.match(/\{[\s\S]*\}/);
6011
6220
  if (!jsonMatch) {
6012
- 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) });
6013
6222
  return null;
6014
6223
  }
6015
6224
  let parsed;
6016
6225
  try {
6017
6226
  parsed = JSON.parse(jsonMatch[0]);
6018
6227
  } catch {
6019
- 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) });
6020
6229
  return null;
6021
6230
  }
6022
6231
  if (parsed.intent === null || parsed.intent === "null") return null;
6023
6232
  const intent = String(parsed.intent);
6024
6233
  if (!VALID_INTENTS.includes(intent)) {
6025
- logger27.warn("Invalid intent from LLM", { intent });
6234
+ logger29.warn("Invalid intent from LLM", { intent });
6026
6235
  return null;
6027
6236
  }
6028
6237
  const result = { intent };
@@ -6064,7 +6273,7 @@ ${userComment}`;
6064
6273
  child.on("close", (code) => {
6065
6274
  clearTimeout(timer);
6066
6275
  if (code !== 0) {
6067
- 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) });
6068
6277
  }
6069
6278
  resolve(stdout);
6070
6279
  });
@@ -6079,7 +6288,7 @@ ${userComment}`;
6079
6288
  };
6080
6289
 
6081
6290
  // src/webhook/WebhookServer.ts
6082
- var logger28 = logger.child("WebhookServer");
6291
+ var logger30 = logger.child("WebhookServer");
6083
6292
  var WebhookServer = class {
6084
6293
  app;
6085
6294
  server = null;
@@ -6103,8 +6312,8 @@ var WebhookServer = class {
6103
6312
  poller: deps.poller,
6104
6313
  claimer: deps.claimer
6105
6314
  };
6106
- this.app = express14();
6107
- this.app.use(express14.json());
6315
+ this.app = express15();
6316
+ this.app.use(express15.json());
6108
6317
  this.app.use(createWebhookRouter(handlerDeps));
6109
6318
  this.app.get("/health", (_req, res) => {
6110
6319
  res.json({ status: "ok", service: "webhook" });
@@ -6113,7 +6322,7 @@ var WebhookServer = class {
6113
6322
  start() {
6114
6323
  return new Promise((resolve) => {
6115
6324
  this.server = this.app.listen(this.port, this.host, () => {
6116
- 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}`);
6117
6326
  resolve();
6118
6327
  });
6119
6328
  });
@@ -6122,7 +6331,7 @@ var WebhookServer = class {
6122
6331
  if (this.server) {
6123
6332
  this.server.close();
6124
6333
  this.server = null;
6125
- logger28.info("Webhook server stopped");
6334
+ logger30.info("Webhook server stopped");
6126
6335
  }
6127
6336
  }
6128
6337
  };
@@ -6130,7 +6339,7 @@ var WebhookServer = class {
6130
6339
  // src/web/AgentLogStore.ts
6131
6340
  import fs8 from "fs";
6132
6341
  import path10 from "path";
6133
- var logger29 = logger.child("AgentLogStore");
6342
+ var logger31 = logger.child("AgentLogStore");
6134
6343
  var MAX_LOGS_PER_ISSUE = 2e4;
6135
6344
  var DEBUG_EVENT_TYPES = /* @__PURE__ */ new Set([
6136
6345
  "thinking",
@@ -6159,7 +6368,7 @@ var AgentLogStore = class {
6159
6368
  eventBus.on("pipeline:progress", (payload) => {
6160
6369
  this.handlePipelineProgress(payload);
6161
6370
  });
6162
- logger29.info("AgentLogStore listening for events");
6371
+ logger31.info("AgentLogStore listening for events");
6163
6372
  }
6164
6373
  getLogs(issueIid) {
6165
6374
  const filePath = this.logFilePath(issueIid);
@@ -6169,7 +6378,7 @@ var AgentLogStore = class {
6169
6378
  if (!raw) return [];
6170
6379
  return raw.split("\n").map((line) => JSON.parse(line));
6171
6380
  } catch (err) {
6172
- logger29.warn("Failed to read agent logs", { issueIid, error: err.message });
6381
+ logger31.warn("Failed to read agent logs", { issueIid, error: err.message });
6173
6382
  return [];
6174
6383
  }
6175
6384
  }
@@ -6189,7 +6398,7 @@ var AgentLogStore = class {
6189
6398
  `, "utf-8");
6190
6399
  this.trimIfNeeded(issueIid, filePath);
6191
6400
  } catch (err) {
6192
- logger29.warn("Failed to write agent log", { issueIid, error: err.message });
6401
+ logger31.warn("Failed to write agent log", { issueIid, error: err.message });
6193
6402
  }
6194
6403
  }
6195
6404
  trimIfNeeded(issueIid, filePath) {
@@ -6201,7 +6410,7 @@ var AgentLogStore = class {
6201
6410
  const trimmed = lines.slice(-MAX_LOGS_PER_ISSUE);
6202
6411
  fs8.writeFileSync(filePath, `${trimmed.join("\n")}
6203
6412
  `, "utf-8");
6204
- 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 });
6205
6414
  }
6206
6415
  } catch {
6207
6416
  }
@@ -6276,7 +6485,7 @@ import { readFileSync, existsSync } from "fs";
6276
6485
  import path11 from "path";
6277
6486
  import { fileURLToPath as fileURLToPath3 } from "url";
6278
6487
  var __dirname3 = path11.dirname(fileURLToPath3(import.meta.url));
6279
- var logger30 = logger.child("VersionChecker");
6488
+ var logger32 = logger.child("VersionChecker");
6280
6489
  function compareSemver(a, b) {
6281
6490
  const pa = a.split(".").map(Number);
6282
6491
  const pb = b.split(".").map(Number);
@@ -6307,7 +6516,7 @@ var VersionChecker = class {
6307
6516
  async check() {
6308
6517
  const latestVersion = await this.fetchLatestVersion();
6309
6518
  const hasUpdate = compareSemver(latestVersion, this.currentVersion) > 0;
6310
- logger30.info("Version check completed", {
6519
+ logger32.info("Version check completed", {
6311
6520
  current: this.currentVersion,
6312
6521
  latest: latestVersion,
6313
6522
  hasUpdate
@@ -6355,7 +6564,7 @@ import { cpSync, existsSync as existsSync2, rmSync } from "fs";
6355
6564
  import path12 from "path";
6356
6565
  import { fileURLToPath as fileURLToPath4 } from "url";
6357
6566
  var __dirname4 = path12.dirname(fileURLToPath4(import.meta.url));
6358
- var logger31 = logger.child("UpdateExecutor");
6567
+ var logger33 = logger.child("UpdateExecutor");
6359
6568
  function findProjectRoot() {
6360
6569
  for (let dir = __dirname4; dir !== path12.dirname(dir); dir = path12.dirname(dir)) {
6361
6570
  if (existsSync2(path12.join(dir, "package.json"))) {
@@ -6385,7 +6594,7 @@ var UpdateExecutor = class {
6385
6594
  async execute(targetVersion) {
6386
6595
  const distDir = path12.join(this.projectRoot, "dist");
6387
6596
  const backupDir = path12.join(this.projectRoot, "dist.backup");
6388
- logger31.info("Backing up dist directory...");
6597
+ logger33.info("Backing up dist directory...");
6389
6598
  if (existsSync2(backupDir)) {
6390
6599
  rmSync(backupDir, { recursive: true, force: true });
6391
6600
  }
@@ -6393,12 +6602,12 @@ var UpdateExecutor = class {
6393
6602
  cpSync(distDir, backupDir, { recursive: true });
6394
6603
  }
6395
6604
  try {
6396
- logger31.info("Running package update...", { package: this.packageName, targetVersion, bin: "npm" });
6605
+ logger33.info("Running package update...", { package: this.packageName, targetVersion, bin: "npm" });
6397
6606
  await run("npm", ["update", this.packageName], this.projectRoot, 12e4);
6398
- logger31.info("Running build...", { bin: "npm" });
6607
+ logger33.info("Running build...", { bin: "npm" });
6399
6608
  await run("npm", ["run", "build"], this.projectRoot, 12e4);
6400
6609
  } catch (err) {
6401
- logger31.error("Update failed, rolling back dist...", { error: err.message });
6610
+ logger33.error("Update failed, rolling back dist...", { error: err.message });
6402
6611
  if (existsSync2(backupDir)) {
6403
6612
  if (existsSync2(distDir)) {
6404
6613
  rmSync(distDir, { recursive: true, force: true });
@@ -6411,21 +6620,21 @@ var UpdateExecutor = class {
6411
6620
  rmSync(backupDir, { recursive: true, force: true });
6412
6621
  }
6413
6622
  }
6414
- logger31.info("Reloading via PM2...");
6623
+ logger33.info("Reloading via PM2...");
6415
6624
  try {
6416
6625
  await run("pm2", ["reload", "issue-auto-finish"], this.projectRoot, 3e4);
6417
6626
  } catch (err) {
6418
- logger31.warn("PM2 reload failed, the process may need manual restart", {
6627
+ logger33.warn("PM2 reload failed, the process may need manual restart", {
6419
6628
  error: err.message
6420
6629
  });
6421
6630
  throw err;
6422
6631
  }
6423
- logger31.info("Update completed successfully", { version: targetVersion });
6632
+ logger33.info("Update completed successfully", { version: targetVersion });
6424
6633
  }
6425
6634
  };
6426
6635
 
6427
6636
  // src/updater/AutoUpdater.ts
6428
- var logger32 = logger.child("AutoUpdater");
6637
+ var logger34 = logger.child("AutoUpdater");
6429
6638
  var INITIAL_DELAY_MS = 3e4;
6430
6639
  var AutoUpdater = class {
6431
6640
  config;
@@ -6447,10 +6656,10 @@ var AutoUpdater = class {
6447
6656
  }
6448
6657
  start() {
6449
6658
  if (!this.config.autoUpdate.enabled) {
6450
- logger32.info("Auto-update is disabled");
6659
+ logger34.info("Auto-update is disabled");
6451
6660
  return;
6452
6661
  }
6453
- logger32.info("Auto-updater starting", {
6662
+ logger34.info("Auto-updater starting", {
6454
6663
  intervalMs: this.config.autoUpdate.intervalMs,
6455
6664
  registry: this.config.autoUpdate.registry
6456
6665
  });
@@ -6469,7 +6678,7 @@ var AutoUpdater = class {
6469
6678
  clearInterval(this.checkTimer);
6470
6679
  this.checkTimer = null;
6471
6680
  }
6472
- logger32.info("Auto-updater stopped");
6681
+ logger34.info("Auto-updater stopped");
6473
6682
  }
6474
6683
  getStatus() {
6475
6684
  return {
@@ -6509,7 +6718,7 @@ var AutoUpdater = class {
6509
6718
  }
6510
6719
  async triggerUpdate() {
6511
6720
  if (this.updateInProgress) {
6512
- logger32.warn("Update already in progress, skipping");
6721
+ logger34.warn("Update already in progress, skipping");
6513
6722
  return;
6514
6723
  }
6515
6724
  this.updateInProgress = true;
@@ -6517,40 +6726,40 @@ var AutoUpdater = class {
6517
6726
  if (!this.latestVersion) {
6518
6727
  const result = await this.checkForUpdate();
6519
6728
  if (!result.hasUpdate) {
6520
- logger32.info("No update available");
6729
+ logger34.info("No update available");
6521
6730
  return;
6522
6731
  }
6523
6732
  }
6524
6733
  const targetVersion = this.latestVersion;
6525
6734
  this.state = "draining";
6526
- logger32.info("Draining active issues before update...");
6735
+ logger34.info("Draining active issues before update...");
6527
6736
  this.poller.pauseDiscovery();
6528
6737
  const drainStart = Date.now();
6529
6738
  const drainTimeout = this.config.autoUpdate.drainTimeoutMs;
6530
6739
  while (this.poller.getActiveCount() > 0 && Date.now() - drainStart < drainTimeout) {
6531
- logger32.info("Waiting for active issues to complete...", {
6740
+ logger34.info("Waiting for active issues to complete...", {
6532
6741
  active: this.poller.getActiveCount()
6533
6742
  });
6534
6743
  await new Promise((resolve) => setTimeout(resolve, 5e3));
6535
6744
  }
6536
6745
  if (this.poller.getActiveCount() > 0) {
6537
- logger32.warn("Drain timeout reached, proceeding with update", {
6746
+ logger34.warn("Drain timeout reached, proceeding with update", {
6538
6747
  active: this.poller.getActiveCount()
6539
6748
  });
6540
6749
  }
6541
6750
  this.state = "updating";
6542
6751
  eventBus.emitTyped("update:downloading", { version: targetVersion });
6543
- logger32.info("Executing update...", { targetVersion });
6752
+ logger34.info("Executing update...", { targetVersion });
6544
6753
  await this.updateExecutor.execute(targetVersion);
6545
6754
  this.state = "completed";
6546
6755
  eventBus.emitTyped("update:completed", { version: targetVersion });
6547
- logger32.info("Update completed", { version: targetVersion });
6756
+ logger34.info("Update completed", { version: targetVersion });
6548
6757
  } catch (err) {
6549
6758
  const message = err.message;
6550
6759
  this.lastError = message;
6551
6760
  this.state = "failed";
6552
6761
  eventBus.emitTyped("update:failed", { error: message });
6553
- logger32.error("Update failed", { error: message });
6762
+ logger34.error("Update failed", { error: message });
6554
6763
  this.poller.resumeDiscovery();
6555
6764
  } finally {
6556
6765
  this.updateInProgress = false;
@@ -6561,20 +6770,20 @@ var AutoUpdater = class {
6561
6770
  try {
6562
6771
  const result = await this.checkForUpdate();
6563
6772
  if (result.hasUpdate) {
6564
- logger32.info("New version available, starting update", {
6773
+ logger34.info("New version available, starting update", {
6565
6774
  current: result.currentVersion,
6566
6775
  latest: result.latestVersion
6567
6776
  });
6568
6777
  await this.triggerUpdate();
6569
6778
  }
6570
6779
  } catch (err) {
6571
- logger32.warn("Periodic version check failed", { error: err.message });
6780
+ logger34.warn("Periodic version check failed", { error: err.message });
6572
6781
  }
6573
6782
  }
6574
6783
  };
6575
6784
 
6576
6785
  // src/config/ConfigReloader.ts
6577
- var logger33 = logger.child("ConfigReloader");
6786
+ var logger35 = logger.child("ConfigReloader");
6578
6787
  var ConfigReloader = class {
6579
6788
  config;
6580
6789
  poller;
@@ -6590,11 +6799,11 @@ var ConfigReloader = class {
6590
6799
  }
6591
6800
  async reload(opts) {
6592
6801
  const timeoutMs = opts?.waitTimeoutMs ?? 6e4;
6593
- logger33.info("Config reload requested", { timeoutMs });
6802
+ logger35.info("Config reload requested", { timeoutMs });
6594
6803
  this.poller.pauseDiscovery();
6595
6804
  const drained = await this.waitForDrain(timeoutMs);
6596
6805
  if (!drained) {
6597
- logger33.warn("Active issues did not drain within timeout, aborting reload", {
6806
+ logger35.warn("Active issues did not drain within timeout, aborting reload", {
6598
6807
  active: this.poller.getActiveCount(),
6599
6808
  timeoutMs
6600
6809
  });
@@ -6609,7 +6818,7 @@ var ConfigReloader = class {
6609
6818
  this.orchestrator.setAIRunner(newRunner);
6610
6819
  this.options.onAIRunnerChanged?.(newRunner);
6611
6820
  this.gongfeng.updateConfig(this.config.gongfeng);
6612
- logger33.info("Config reloaded successfully", {
6821
+ logger35.info("Config reloaded successfully", {
6613
6822
  aiMode: this.config.ai.mode,
6614
6823
  aiModel: this.config.ai.model,
6615
6824
  discoveryIntervalMs: this.config.poll.discoveryIntervalMs
@@ -6630,7 +6839,7 @@ var ConfigReloader = class {
6630
6839
 
6631
6840
  // src/distill/DiaryCollector.ts
6632
6841
  import { randomUUID as randomUUID2 } from "crypto";
6633
- var logger34 = logger.child("DiaryCollector");
6842
+ var logger36 = logger.child("DiaryCollector");
6634
6843
  var DiaryCollector = class {
6635
6844
  tracker;
6636
6845
  diaryStore;
@@ -6648,21 +6857,21 @@ var DiaryCollector = class {
6648
6857
  if (this.stateChangedHandler) return;
6649
6858
  this.stateChangedHandler = (payload) => {
6650
6859
  this.handleStateChanged(payload).catch((err) => {
6651
- logger34.warn("DiaryCollector failed to handle stateChanged event", {
6860
+ logger36.warn("DiaryCollector failed to handle stateChanged event", {
6652
6861
  error: err.message
6653
6862
  });
6654
6863
  });
6655
6864
  };
6656
6865
  this.failedHandler = (payload) => {
6657
6866
  this.handleFailed(payload).catch((err) => {
6658
- logger34.warn("DiaryCollector failed to handle failed event", {
6867
+ logger36.warn("DiaryCollector failed to handle failed event", {
6659
6868
  error: err.message
6660
6869
  });
6661
6870
  });
6662
6871
  };
6663
6872
  eventBus.on("issue:stateChanged", this.stateChangedHandler);
6664
6873
  eventBus.on("issue:failed", this.failedHandler);
6665
- logger34.info("DiaryCollector started");
6874
+ logger36.info("DiaryCollector started");
6666
6875
  }
6667
6876
  /** 停止监听事件 */
6668
6877
  stop() {
@@ -6674,7 +6883,7 @@ var DiaryCollector = class {
6674
6883
  eventBus.off("issue:failed", this.failedHandler);
6675
6884
  this.failedHandler = null;
6676
6885
  }
6677
- logger34.info("DiaryCollector stopped");
6886
+ logger36.info("DiaryCollector stopped");
6678
6887
  }
6679
6888
  /** 处理 issue:stateChanged 事件 */
6680
6889
  async handleStateChanged(payload) {
@@ -6685,7 +6894,7 @@ var DiaryCollector = class {
6685
6894
  if (!issueIid) return;
6686
6895
  const existing = this.diaryStore.getByIssueIid(issueIid);
6687
6896
  if (existing.some((d) => d.outcome === "completed")) {
6688
- logger34.debug("Diary already exists for completed issue", { issueIid });
6897
+ logger36.debug("Diary already exists for completed issue", { issueIid });
6689
6898
  return;
6690
6899
  }
6691
6900
  await this.collectDiary(issueIid, "completed");
@@ -6702,7 +6911,7 @@ var DiaryCollector = class {
6702
6911
  try {
6703
6912
  const record = this.tracker.get(issueIid);
6704
6913
  if (!record) {
6705
- logger34.warn("Cannot collect diary: issue record not found", { issueIid });
6914
+ logger36.warn("Cannot collect diary: issue record not found", { issueIid });
6706
6915
  return null;
6707
6916
  }
6708
6917
  const plan = this.createPlanPersistence?.(issueIid);
@@ -6726,10 +6935,10 @@ var DiaryCollector = class {
6726
6935
  };
6727
6936
  this.diaryStore.create(diary);
6728
6937
  eventBus.emitTyped("distill:diary:created", { issueIid, diaryId: diary.id, outcome });
6729
- logger34.info("Diary collected", { issueIid, diaryId: diary.id, outcome });
6938
+ logger36.info("Diary collected", { issueIid, diaryId: diary.id, outcome });
6730
6939
  return diary;
6731
6940
  } catch (err) {
6732
- logger34.warn("Failed to collect diary", {
6941
+ logger36.warn("Failed to collect diary", {
6733
6942
  issueIid,
6734
6943
  outcome,
6735
6944
  error: err.message
@@ -6952,7 +7161,7 @@ ${memorySection}
6952
7161
  }
6953
7162
 
6954
7163
  // src/distill/MemoryDistiller.ts
6955
- var logger35 = logger.child("MemoryDistiller");
7164
+ var logger37 = logger.child("MemoryDistiller");
6956
7165
  var MemoryDistiller = class {
6957
7166
  aiRunner;
6958
7167
  diaryStore;
@@ -6977,17 +7186,17 @@ var MemoryDistiller = class {
6977
7186
  async distill(options) {
6978
7187
  const undistilled = this.diaryStore.getUndistilled();
6979
7188
  if (!options?.force && undistilled.length < this.minDiariesForDistill) {
6980
- logger35.info("Not enough undistilled diaries, skipping memory distillation", {
7189
+ logger37.info("Not enough undistilled diaries, skipping memory distillation", {
6981
7190
  count: undistilled.length,
6982
7191
  threshold: this.minDiariesForDistill
6983
7192
  });
6984
7193
  return { processedDiaries: 0, actions: 0 };
6985
7194
  }
6986
7195
  if (undistilled.length === 0) {
6987
- logger35.info("No undistilled diaries available");
7196
+ logger37.info("No undistilled diaries available");
6988
7197
  return { processedDiaries: 0, actions: 0 };
6989
7198
  }
6990
- logger35.info("Starting memory distillation", { diaryCount: undistilled.length });
7199
+ logger37.info("Starting memory distillation", { diaryCount: undistilled.length });
6991
7200
  const existingMemories = this.loadExistingMemories();
6992
7201
  const prompt = buildMemoryDistillPrompt(undistilled, existingMemories);
6993
7202
  const result = await this.aiRunner.run({
@@ -6996,12 +7205,12 @@ var MemoryDistiller = class {
6996
7205
  timeoutMs: this.timeoutMs
6997
7206
  });
6998
7207
  if (!result.success) {
6999
- logger35.error("AI distillation failed", { output: result.output.slice(0, 500) });
7208
+ logger37.error("AI distillation failed", { output: result.output.slice(0, 500) });
7000
7209
  throw new Error(`Memory distillation AI call failed: ${result.output.slice(0, 200)}`);
7001
7210
  }
7002
7211
  const actions = this.parseActions(result.output);
7003
7212
  if (actions.length === 0) {
7004
- logger35.info("No distillation actions returned by AI");
7213
+ logger37.info("No distillation actions returned by AI");
7005
7214
  this.diaryStore.markDistilled(undistilled.map((d) => d.id));
7006
7215
  return { processedDiaries: undistilled.length, actions: 0 };
7007
7216
  }
@@ -7011,14 +7220,14 @@ var MemoryDistiller = class {
7011
7220
  this.executeAction(action, existingMemories);
7012
7221
  actionCount++;
7013
7222
  } catch (err) {
7014
- logger35.warn("Failed to execute distill action", {
7223
+ logger37.warn("Failed to execute distill action", {
7015
7224
  action: action.type,
7016
7225
  error: err.message
7017
7226
  });
7018
7227
  }
7019
7228
  }
7020
7229
  this.diaryStore.markDistilled(undistilled.map((d) => d.id));
7021
- logger35.info("Memory distillation complete", {
7230
+ logger37.info("Memory distillation complete", {
7022
7231
  processedDiaries: undistilled.length,
7023
7232
  actions: actionCount
7024
7233
  });
@@ -7042,18 +7251,18 @@ var MemoryDistiller = class {
7042
7251
  try {
7043
7252
  const jsonMatch = output.match(/```json\s*([\s\S]*?)```/) ?? output.match(/\{[\s\S]*"actions"[\s\S]*\}/);
7044
7253
  if (!jsonMatch) {
7045
- logger35.warn("No JSON found in AI output");
7254
+ logger37.warn("No JSON found in AI output");
7046
7255
  return [];
7047
7256
  }
7048
7257
  const jsonStr = jsonMatch[1] ?? jsonMatch[0];
7049
7258
  const parsed = JSON.parse(jsonStr);
7050
7259
  if (!Array.isArray(parsed.actions)) {
7051
- logger35.warn("Invalid actions format in AI output");
7260
+ logger37.warn("Invalid actions format in AI output");
7052
7261
  return [];
7053
7262
  }
7054
7263
  return parsed.actions;
7055
7264
  } catch (err) {
7056
- logger35.warn("Failed to parse AI distillation output", {
7265
+ logger37.warn("Failed to parse AI distillation output", {
7057
7266
  error: err.message,
7058
7267
  output: output.slice(0, 300)
7059
7268
  });
@@ -7073,7 +7282,7 @@ var MemoryDistiller = class {
7073
7282
  this.supersedeMemory(action, existingMemories);
7074
7283
  break;
7075
7284
  default:
7076
- logger35.warn("Unknown distill action type", { type: action.type });
7285
+ logger37.warn("Unknown distill action type", { type: action.type });
7077
7286
  }
7078
7287
  }
7079
7288
  /** 创建新 memory */
@@ -7103,13 +7312,13 @@ var MemoryDistiller = class {
7103
7312
  action: "created",
7104
7313
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7105
7314
  });
7106
- 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 });
7107
7316
  }
7108
7317
  /** 合并新证据到已有 memory */
7109
7318
  mergeMemory(action, existingMemories) {
7110
7319
  const existing = existingMemories.find((m) => m.id === action.memoryId);
7111
7320
  if (!existing) {
7112
- logger35.warn("Cannot merge: memory not found", { memoryId: action.memoryId });
7321
+ logger37.warn("Cannot merge: memory not found", { memoryId: action.memoryId });
7113
7322
  return;
7114
7323
  }
7115
7324
  existing.evidence = [.../* @__PURE__ */ new Set([...existing.evidence, ...action.newEvidence])];
@@ -7142,7 +7351,7 @@ var MemoryDistiller = class {
7142
7351
  reason: `Merged ${action.newEvidence.length} new evidence(s)`,
7143
7352
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7144
7353
  });
7145
- logger35.info("Merged memory", { id: existing.id, newEvidence: action.newEvidence.length });
7354
+ logger37.info("Merged memory", { id: existing.id, newEvidence: action.newEvidence.length });
7146
7355
  }
7147
7356
  /** 用新 memory 替代旧 memory */
7148
7357
  supersedeMemory(action, existingMemories) {
@@ -7184,7 +7393,7 @@ var MemoryDistiller = class {
7184
7393
  reason: `Supersedes ${action.oldMemoryId}`,
7185
7394
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7186
7395
  });
7187
- logger35.info("Superseded memory", {
7396
+ logger37.info("Superseded memory", {
7188
7397
  oldId: action.oldMemoryId,
7189
7398
  newId: newMemory.id,
7190
7399
  theme: action.theme
@@ -7286,7 +7495,7 @@ ${ruleSection}
7286
7495
  }
7287
7496
 
7288
7497
  // src/distill/AgentRuleDistiller.ts
7289
- var logger36 = logger.child("AgentRuleDistiller");
7498
+ var logger38 = logger.child("AgentRuleDistiller");
7290
7499
  var AgentRuleDistiller = class {
7291
7500
  aiRunner;
7292
7501
  knowledgeStore;
@@ -7315,11 +7524,11 @@ var AgentRuleDistiller = class {
7315
7524
  async distill() {
7316
7525
  const matureMemories = this.getMatureMemories();
7317
7526
  if (matureMemories.length === 0) {
7318
- logger36.info("No mature memories ready for rule distillation");
7527
+ logger38.info("No mature memories ready for rule distillation");
7319
7528
  return { processedMemories: 0, actions: 0 };
7320
7529
  }
7321
7530
  const existingRules = this.loadExistingRules();
7322
- logger36.info("Starting rule distillation", {
7531
+ logger38.info("Starting rule distillation", {
7323
7532
  matureMemories: matureMemories.length,
7324
7533
  existingRules: existingRules.length
7325
7534
  });
@@ -7330,12 +7539,12 @@ var AgentRuleDistiller = class {
7330
7539
  timeoutMs: this.timeoutMs
7331
7540
  });
7332
7541
  if (!result.success) {
7333
- 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) });
7334
7543
  throw new Error(`Rule distillation AI call failed: ${result.output.slice(0, 200)}`);
7335
7544
  }
7336
7545
  const actions = this.parseActions(result.output);
7337
7546
  if (actions.length === 0) {
7338
- logger36.info("No rule distillation actions returned by AI");
7547
+ logger38.info("No rule distillation actions returned by AI");
7339
7548
  return { processedMemories: matureMemories.length, actions: 0 };
7340
7549
  }
7341
7550
  let actionCount = 0;
@@ -7344,13 +7553,13 @@ var AgentRuleDistiller = class {
7344
7553
  this.executeAction(action, existingRules);
7345
7554
  actionCount++;
7346
7555
  } catch (err) {
7347
- logger36.warn("Failed to execute rule distill action", {
7556
+ logger38.warn("Failed to execute rule distill action", {
7348
7557
  action: action.type,
7349
7558
  error: err.message
7350
7559
  });
7351
7560
  }
7352
7561
  }
7353
- logger36.info("Rule distillation complete", {
7562
+ logger38.info("Rule distillation complete", {
7354
7563
  processedMemories: matureMemories.length,
7355
7564
  actions: actionCount
7356
7565
  });
@@ -7404,7 +7613,7 @@ var AgentRuleDistiller = class {
7404
7613
  try {
7405
7614
  const jsonMatch = output.match(/```json\s*([\s\S]*?)```/) ?? output.match(/\{[\s\S]*"actions"[\s\S]*\}/);
7406
7615
  if (!jsonMatch) {
7407
- logger36.warn("No JSON found in AI rule distill output");
7616
+ logger38.warn("No JSON found in AI rule distill output");
7408
7617
  return [];
7409
7618
  }
7410
7619
  const jsonStr = jsonMatch[1] ?? jsonMatch[0];
@@ -7412,7 +7621,7 @@ var AgentRuleDistiller = class {
7412
7621
  if (!Array.isArray(parsed.actions)) return [];
7413
7622
  return parsed.actions;
7414
7623
  } catch (err) {
7415
- logger36.warn("Failed to parse AI rule distill output", {
7624
+ logger38.warn("Failed to parse AI rule distill output", {
7416
7625
  error: err.message
7417
7626
  });
7418
7627
  return [];
@@ -7431,7 +7640,7 @@ var AgentRuleDistiller = class {
7431
7640
  this.deprecateRule(action, existingRules);
7432
7641
  break;
7433
7642
  default:
7434
- logger36.warn("Unknown rule distill action type", { type: action.type });
7643
+ logger38.warn("Unknown rule distill action type", { type: action.type });
7435
7644
  }
7436
7645
  }
7437
7646
  /** 创建新规则 */
@@ -7464,13 +7673,13 @@ var AgentRuleDistiller = class {
7464
7673
  action: "created",
7465
7674
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7466
7675
  });
7467
- logger36.info("Created agent rule", { id: ruleEntry.id, ruleName: action.ruleName });
7676
+ logger38.info("Created agent rule", { id: ruleEntry.id, ruleName: action.ruleName });
7468
7677
  }
7469
7678
  /** 更新已有规则 */
7470
7679
  updateRule(action, existingRules) {
7471
7680
  const existing = existingRules.find((r) => r.id === action.ruleId);
7472
7681
  if (!existing) {
7473
- logger36.warn("Cannot update: rule not found", { ruleId: action.ruleId });
7682
+ logger38.warn("Cannot update: rule not found", { ruleId: action.ruleId });
7474
7683
  return;
7475
7684
  }
7476
7685
  existing.content = action.content;
@@ -7500,13 +7709,13 @@ var AgentRuleDistiller = class {
7500
7709
  action: "merged",
7501
7710
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7502
7711
  });
7503
- logger36.info("Updated agent rule", { id: existing.id, ruleName: existing.ruleName });
7712
+ logger38.info("Updated agent rule", { id: existing.id, ruleName: existing.ruleName });
7504
7713
  }
7505
7714
  /** 废弃规则 */
7506
7715
  deprecateRule(action, existingRules) {
7507
7716
  const existing = existingRules.find((r) => r.id === action.ruleId);
7508
7717
  if (!existing) {
7509
- logger36.warn("Cannot deprecate: rule not found", { ruleId: action.ruleId });
7718
+ logger38.warn("Cannot deprecate: rule not found", { ruleId: action.ruleId });
7510
7719
  return;
7511
7720
  }
7512
7721
  existing.deprecated = true;
@@ -7535,7 +7744,7 @@ var AgentRuleDistiller = class {
7535
7744
  reason: action.reason,
7536
7745
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7537
7746
  });
7538
- logger36.info("Deprecated agent rule", { id: existing.id, reason: action.reason });
7747
+ logger38.info("Deprecated agent rule", { id: existing.id, reason: action.reason });
7539
7748
  }
7540
7749
  /** 同步规则到 DATA_DIR/rules/ MDC 文件,可选同步到项目 */
7541
7750
  syncMdcFile(rule) {
@@ -7551,9 +7760,9 @@ var AgentRuleDistiller = class {
7551
7760
  ensureDir(this.rulesDir);
7552
7761
  const filePath = path13.join(this.rulesDir, `distilled-${rule.ruleName}.mdc`);
7553
7762
  fs9.writeFileSync(filePath, mdcContent, "utf-8");
7554
- logger36.debug("Synced MDC file", { path: filePath });
7763
+ logger38.debug("Synced MDC file", { path: filePath });
7555
7764
  } catch (err) {
7556
- logger36.warn("Failed to sync MDC file", {
7765
+ logger38.warn("Failed to sync MDC file", {
7557
7766
  ruleName: rule.ruleName,
7558
7767
  error: err.message
7559
7768
  });
@@ -7563,9 +7772,9 @@ var AgentRuleDistiller = class {
7563
7772
  ensureDir(this.projectRulesDir);
7564
7773
  const projectFilePath = path13.join(this.projectRulesDir, `distilled-${rule.ruleName}.mdc`);
7565
7774
  fs9.writeFileSync(projectFilePath, mdcContent, "utf-8");
7566
- logger36.debug("Synced MDC file to project", { path: projectFilePath });
7775
+ logger38.debug("Synced MDC file to project", { path: projectFilePath });
7567
7776
  } catch (err) {
7568
- logger36.warn("Failed to sync MDC file to project", {
7777
+ logger38.warn("Failed to sync MDC file to project", {
7569
7778
  ruleName: rule.ruleName,
7570
7779
  error: err.message
7571
7780
  });
@@ -7579,10 +7788,10 @@ var AgentRuleDistiller = class {
7579
7788
  const filePath = path13.join(this.rulesDir, fileName);
7580
7789
  if (fs9.existsSync(filePath)) {
7581
7790
  fs9.unlinkSync(filePath);
7582
- logger36.debug("Removed MDC file", { path: filePath });
7791
+ logger38.debug("Removed MDC file", { path: filePath });
7583
7792
  }
7584
7793
  } catch (err) {
7585
- logger36.warn("Failed to remove MDC file", {
7794
+ logger38.warn("Failed to remove MDC file", {
7586
7795
  ruleName,
7587
7796
  error: err.message
7588
7797
  });
@@ -7592,10 +7801,10 @@ var AgentRuleDistiller = class {
7592
7801
  const projectFilePath = path13.join(this.projectRulesDir, fileName);
7593
7802
  if (fs9.existsSync(projectFilePath)) {
7594
7803
  fs9.unlinkSync(projectFilePath);
7595
- logger36.debug("Removed MDC file from project", { path: projectFilePath });
7804
+ logger38.debug("Removed MDC file from project", { path: projectFilePath });
7596
7805
  }
7597
7806
  } catch (err) {
7598
- logger36.warn("Failed to remove MDC file from project", {
7807
+ logger38.warn("Failed to remove MDC file from project", {
7599
7808
  ruleName,
7600
7809
  error: err.message
7601
7810
  });
@@ -7626,7 +7835,7 @@ var AgentRuleDistiller = class {
7626
7835
  // src/distill/VectorStore.ts
7627
7836
  import fs10 from "fs";
7628
7837
  import path14 from "path";
7629
- var logger37 = logger.child("VectorStore");
7838
+ var logger39 = logger.child("VectorStore");
7630
7839
  var STOP_WORDS = /* @__PURE__ */ new Set([
7631
7840
  // 英文
7632
7841
  "the",
@@ -7813,7 +8022,7 @@ var VectorStore = class {
7813
8022
  async reindex() {
7814
8023
  this.rebuildDocumentFreqs();
7815
8024
  this.save();
7816
- logger37.info("Vector index rebuilt", { count: this.data.vectors.length });
8025
+ logger39.info("Vector index rebuilt", { count: this.data.vectors.length });
7817
8026
  }
7818
8027
  /** 获取索引条目数 */
7819
8028
  count() {
@@ -7890,7 +8099,7 @@ var VectorStore = class {
7890
8099
  return JSON.parse(raw);
7891
8100
  }
7892
8101
  } catch (err) {
7893
- logger37.warn("Failed to load vector store", { error: err.message });
8102
+ logger39.warn("Failed to load vector store", { error: err.message });
7894
8103
  }
7895
8104
  return { documentFreqs: {}, vectors: [], totalDocs: 0 };
7896
8105
  }
@@ -7908,7 +8117,7 @@ var VectorStore = class {
7908
8117
  // src/distill/VersionStore.ts
7909
8118
  import fs11 from "fs";
7910
8119
  import path15 from "path";
7911
- var logger38 = logger.child("VersionStore");
8120
+ var logger40 = logger.child("VersionStore");
7912
8121
  var VersionStore = class {
7913
8122
  filePath;
7914
8123
  data;
@@ -7940,7 +8149,7 @@ var VersionStore = class {
7940
8149
  return JSON.parse(raw);
7941
8150
  }
7942
8151
  } catch (err) {
7943
- logger38.warn("Failed to load version store", { error: err.message });
8152
+ logger40.warn("Failed to load version store", { error: err.message });
7944
8153
  }
7945
8154
  return { records: [] };
7946
8155
  }
@@ -7956,7 +8165,7 @@ var VersionStore = class {
7956
8165
  };
7957
8166
 
7958
8167
  // src/distill/DistillScheduler.ts
7959
- var logger39 = logger.child("DistillScheduler");
8168
+ var logger41 = logger.child("DistillScheduler");
7960
8169
  var DistillScheduler = class {
7961
8170
  diaryStore;
7962
8171
  memoryDistiller;
@@ -7982,29 +8191,29 @@ var DistillScheduler = class {
7982
8191
  if (this.timer) return;
7983
8192
  this.timer = setInterval(() => {
7984
8193
  this.runDistill().catch((err) => {
7985
- logger39.error("Scheduled distillation failed", { error: err.message });
8194
+ logger41.error("Scheduled distillation failed", { error: err.message });
7986
8195
  });
7987
8196
  }, this.intervalMs);
7988
- logger39.info("DistillScheduler started", { intervalMs: this.intervalMs });
8197
+ logger41.info("DistillScheduler started", { intervalMs: this.intervalMs });
7989
8198
  }
7990
8199
  /** 停止定时调度 */
7991
8200
  stop() {
7992
8201
  if (this.timer) {
7993
8202
  clearInterval(this.timer);
7994
8203
  this.timer = null;
7995
- logger39.info("DistillScheduler stopped");
8204
+ logger41.info("DistillScheduler stopped");
7996
8205
  }
7997
8206
  }
7998
8207
  /** 手动触发蒸馏 */
7999
8208
  async runDistill(options) {
8000
8209
  if (this.running) {
8001
- logger39.info("Distillation already in progress, skipping");
8210
+ logger41.info("Distillation already in progress, skipping");
8002
8211
  return { memory: { processedDiaries: 0, actions: 0 }, rule: { processedMemories: 0, actions: 0 }, vectorIndexed: 0 };
8003
8212
  }
8004
8213
  this.running = true;
8005
8214
  eventBus.emitTyped("distill:started", {});
8006
8215
  try {
8007
- logger39.info("Starting distillation pipeline");
8216
+ logger41.info("Starting distillation pipeline");
8008
8217
  const memoryResult = await this.memoryDistiller.distill({ force: options?.force });
8009
8218
  if (memoryResult.actions > 0) {
8010
8219
  eventBus.emitTyped("distill:memory:updated", {
@@ -8029,7 +8238,7 @@ var DistillScheduler = class {
8029
8238
  rule: ruleResult,
8030
8239
  vectorIndexed
8031
8240
  });
8032
- logger39.info("Distillation pipeline complete", {
8241
+ logger41.info("Distillation pipeline complete", {
8033
8242
  memoryActions: memoryResult.actions,
8034
8243
  ruleActions: ruleResult.actions,
8035
8244
  vectorIndexed
@@ -8097,13 +8306,13 @@ ${content}`
8097
8306
  });
8098
8307
  indexed++;
8099
8308
  }
8100
- logger39.info("Vector indexing complete", { indexed });
8309
+ logger41.info("Vector indexing complete", { indexed });
8101
8310
  return indexed;
8102
8311
  }
8103
8312
  };
8104
8313
 
8105
8314
  // src/deploy/PreviewReaper.ts
8106
- var logger40 = logger.child("PreviewReaper");
8315
+ var logger42 = logger.child("PreviewReaper");
8107
8316
  var TERMINAL_STATES = /* @__PURE__ */ new Set(["completed" /* Completed */, "failed" /* Failed */, "deployed" /* Deployed */]);
8108
8317
  var PreviewReaper = class {
8109
8318
  tracker;
@@ -8126,16 +8335,16 @@ var PreviewReaper = class {
8126
8335
  if (this.timer) return;
8127
8336
  this.timer = setInterval(() => {
8128
8337
  this.reap().catch((err) => {
8129
- logger40.error("Scheduled preview reap failed", { error: err.message });
8338
+ logger42.error("Scheduled preview reap failed", { error: err.message });
8130
8339
  });
8131
8340
  }, this.intervalMs);
8132
- logger40.info("PreviewReaper started", { intervalMs: this.intervalMs, ttlMs: this.ttlMs });
8341
+ logger42.info("PreviewReaper started", { intervalMs: this.intervalMs, ttlMs: this.ttlMs });
8133
8342
  }
8134
8343
  stop() {
8135
8344
  if (this.timer) {
8136
8345
  clearInterval(this.timer);
8137
8346
  this.timer = null;
8138
- logger40.info("PreviewReaper stopped");
8347
+ logger42.info("PreviewReaper stopped");
8139
8348
  }
8140
8349
  }
8141
8350
  async reap() {
@@ -8157,13 +8366,13 @@ var PreviewReaper = class {
8157
8366
  try {
8158
8367
  this.orchestrator.stopPreviewServers(iid);
8159
8368
  reaped.push(iid);
8160
- logger40.info(t("reaper.reaped", { iid, hours }));
8369
+ logger42.info(t("reaper.reaped", { iid, hours }));
8161
8370
  } catch (err) {
8162
- logger40.warn("Failed to reap preview", { iid, error: err.message });
8371
+ logger42.warn("Failed to reap preview", { iid, error: err.message });
8163
8372
  }
8164
8373
  }
8165
8374
  if (reaped.length > 0) {
8166
- logger40.info(t("reaper.summary", { count: reaped.length }));
8375
+ logger42.info(t("reaper.summary", { count: reaped.length }));
8167
8376
  this.bus.emitTyped("preview:reaped", { iids: reaped, count: reaped.length });
8168
8377
  }
8169
8378
  this.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -8192,7 +8401,7 @@ import { randomUUID as randomUUID5 } from "crypto";
8192
8401
  import { execFile as execFile2 } from "child_process";
8193
8402
  import { promisify } from "util";
8194
8403
  var execFileAsync = promisify(execFile2);
8195
- var logger41 = logger.child("OrphanBranchManager");
8404
+ var logger43 = logger.child("OrphanBranchManager");
8196
8405
  var SYNC_MANIFEST = [
8197
8406
  "knowledge/index.json",
8198
8407
  "knowledge/knowledge.json",
@@ -8263,7 +8472,7 @@ var OrphanBranchManager = class {
8263
8472
  await this.gitIn(tmpDir, ["add", "-A"]);
8264
8473
  const status = await this.gitIn(tmpDir, ["status", "--porcelain"]);
8265
8474
  if (!status.trim()) {
8266
- logger41.info("No changes to publish");
8475
+ logger43.info("No changes to publish");
8267
8476
  return { commitSha: await this.gitIn(tmpDir, ["rev-parse", "HEAD"]) };
8268
8477
  }
8269
8478
  await this.gitIn(tmpDir, [
@@ -8274,7 +8483,7 @@ var OrphanBranchManager = class {
8274
8483
  ]);
8275
8484
  const commitSha = await this.gitIn(tmpDir, ["rev-parse", "HEAD"]);
8276
8485
  await this.pushWithRetry(tmpDir);
8277
- logger41.info("Knowledge published to orphan branch", {
8486
+ logger43.info("Knowledge published to orphan branch", {
8278
8487
  branch: this.branch,
8279
8488
  commitSha: commitSha.slice(0, 8),
8280
8489
  entriesCount: copied
@@ -8341,7 +8550,7 @@ var OrphanBranchManager = class {
8341
8550
  } catch {
8342
8551
  }
8343
8552
  }
8344
- logger41.info("Knowledge restored from orphan branch", {
8553
+ logger43.info("Knowledge restored from orphan branch", {
8345
8554
  branch: this.branch,
8346
8555
  entriesCount
8347
8556
  });
@@ -8378,7 +8587,7 @@ var OrphanBranchManager = class {
8378
8587
  // Private helpers
8379
8588
  // -----------------------------------------------------------------------
8380
8589
  async git(args) {
8381
- logger41.debug("git exec (main)", { args });
8590
+ logger43.debug("git exec (main)", { args });
8382
8591
  const { stdout } = await execFileAsync("git", args, {
8383
8592
  cwd: this.gitRootDir,
8384
8593
  maxBuffer: 10 * 1024 * 1024,
@@ -8387,7 +8596,7 @@ var OrphanBranchManager = class {
8387
8596
  return stdout.trim();
8388
8597
  }
8389
8598
  async gitIn(cwd, args) {
8390
- logger41.debug("git exec (worktree)", { cwd: path16.basename(cwd), args });
8599
+ logger43.debug("git exec (worktree)", { cwd: path16.basename(cwd), args });
8391
8600
  const { stdout } = await execFileAsync("git", args, {
8392
8601
  cwd,
8393
8602
  maxBuffer: 10 * 1024 * 1024,
@@ -8487,7 +8696,7 @@ var OrphanBranchManager = class {
8487
8696
  `Failed to push to orphan branch after ${MAX_PUSH_RETRIES} attempts: ${err.message}`
8488
8697
  );
8489
8698
  }
8490
- logger41.warn("Push failed, pulling and retrying", {
8699
+ logger43.warn("Push failed, pulling and retrying", {
8491
8700
  attempt,
8492
8701
  error: err.message
8493
8702
  });
@@ -8500,7 +8709,7 @@ var OrphanBranchManager = class {
8500
8709
  try {
8501
8710
  await this.git(["worktree", "remove", wtDir, "--force"]);
8502
8711
  } catch {
8503
- logger41.debug("Worktree cleanup skipped (may not exist)", { dir: wtDir });
8712
+ logger43.debug("Worktree cleanup skipped (may not exist)", { dir: wtDir });
8504
8713
  }
8505
8714
  try {
8506
8715
  await this.git(["worktree", "prune"]);
@@ -8517,7 +8726,7 @@ import fs14 from "fs";
8517
8726
  import { z } from "zod";
8518
8727
  import fs13 from "fs";
8519
8728
  import path17 from "path";
8520
- var logger42 = logger.child("TenantConfig");
8729
+ var logger44 = logger.child("TenantConfig");
8521
8730
  var tenantGongfengSchema = z.object({
8522
8731
  apiUrl: z.string().url("apiUrl must be a valid URL"),
8523
8732
  privateToken: z.string().min(1, "privateToken is required"),
@@ -8544,7 +8753,7 @@ var DEFAULT_TENANT_ID = "default";
8544
8753
  function loadTenantsConfig(configPath) {
8545
8754
  if (!configPath) return null;
8546
8755
  if (!fs13.existsSync(configPath)) {
8547
- 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", {
8548
8757
  path: configPath
8549
8758
  });
8550
8759
  return null;
@@ -8555,14 +8764,14 @@ function loadTenantsConfig(configPath) {
8555
8764
  const result = tenantsConfigSchema.safeParse(json);
8556
8765
  if (!result.success) {
8557
8766
  const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
8558
- logger42.error(`Tenants config validation failed:
8767
+ logger44.error(`Tenants config validation failed:
8559
8768
  ${issues}`);
8560
8769
  return null;
8561
8770
  }
8562
8771
  const ids = result.data.tenants.map((t2) => t2.id);
8563
8772
  const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
8564
8773
  if (dupes.length > 0) {
8565
- logger42.error("Duplicate tenant IDs found", { duplicates: [...new Set(dupes)] });
8774
+ logger44.error("Duplicate tenant IDs found", { duplicates: [...new Set(dupes)] });
8566
8775
  return null;
8567
8776
  }
8568
8777
  const baseDir = path17.dirname(configPath);
@@ -8571,12 +8780,12 @@ ${issues}`);
8571
8780
  tenant.workspace = path17.resolve(baseDir, tenant.workspace);
8572
8781
  }
8573
8782
  }
8574
- logger42.info("Tenants config loaded", {
8783
+ logger44.info("Tenants config loaded", {
8575
8784
  tenants: result.data.tenants.map((t2) => t2.id)
8576
8785
  });
8577
8786
  return result.data;
8578
8787
  } catch (err) {
8579
- logger42.error("Failed to parse tenants config", {
8788
+ logger44.error("Failed to parse tenants config", {
8580
8789
  path: configPath,
8581
8790
  error: err.message
8582
8791
  });
@@ -8639,6 +8848,49 @@ async function main() {
8639
8848
  const allAiPhaseNames = [...new Set(
8640
8849
  [...lifecycleManagers.values()].flatMap((lm) => lm.getExecutablePhaseNames())
8641
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
+ }
8642
8894
  validatePhaseRegistry(allAiPhaseNames);
8643
8895
  validateRunnerRegistry([config.ai.mode]);
8644
8896
  const aiRunner = createAIRunner(config.ai);
@@ -8896,7 +9148,8 @@ async function main() {
8896
9148
  configReloader,
8897
9149
  orphanManager,
8898
9150
  wsConfig: primaryTenant.wsConfig,
8899
- previewReaper
9151
+ previewReaper,
9152
+ terminalManager: sharedTerminalManager
8900
9153
  });
8901
9154
  await webServer.start();
8902
9155
  }
@@ -9007,4 +9260,4 @@ function migrateKnowledgeDir(srcDir, destDir) {
9007
9260
  export {
9008
9261
  main
9009
9262
  };
9010
- //# sourceMappingURL=chunk-QO5VTSMI.js.map
9263
+ //# sourceMappingURL=chunk-QZZGIZWC.js.map