@covro/covro 0.5.1-dev.1 → 0.5.1-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +5 -1
  2. package/README.zh-CN.md +5 -1
  3. package/dist/codex/adapter.js +3 -1
  4. package/dist/codex/adapter.js.map +2 -2
  5. package/dist/codex/mapping.js +10 -1
  6. package/dist/codex/mapping.js.map +2 -2
  7. package/dist/server/capacity-retry.js +176 -0
  8. package/dist/server/capacity-retry.js.map +7 -0
  9. package/dist/server/db/operations.js +11 -0
  10. package/dist/server/db/operations.js.map +2 -2
  11. package/dist/server/github-repository.js +61 -0
  12. package/dist/server/github-repository.js.map +7 -0
  13. package/dist/server/http.js +22 -0
  14. package/dist/server/http.js.map +2 -2
  15. package/dist/server/projection.js +10 -3
  16. package/dist/server/projection.js.map +2 -2
  17. package/dist/server/service.js +152 -5
  18. package/dist/server/service.js.map +2 -2
  19. package/dist/shared/turn-errors.js +25 -0
  20. package/dist/shared/turn-errors.js.map +7 -0
  21. package/dist/shared/validation.js +2 -0
  22. package/dist/shared/validation.js.map +2 -2
  23. package/dist/web/assets/{abnfDiagram-WEYOIA3C-B7ZgLRcz.js → abnfDiagram-WEYOIA3C-ATf52olP.js} +1 -1
  24. package/dist/web/assets/{arc-l2Y0qpaA.js → arc-ChLnTnZI.js} +1 -1
  25. package/dist/web/assets/{architecture-WOLXFQ4H-CPMJhOrI.js → architecture-WOLXFQ4H-B2oEg329.js} +1 -1
  26. package/dist/web/assets/{architectureDiagram-ZHVYKT53-BGyNOKZB.js → architectureDiagram-ZHVYKT53-BzSXU9d0.js} +1 -1
  27. package/dist/web/assets/{blockDiagram-RJAE7QA3-CFTsFM2C.js → blockDiagram-RJAE7QA3-xhCZ7iUm.js} +1 -1
  28. package/dist/web/assets/{c4Diagram-XDPPPE54-DePVq6hL.js → c4Diagram-XDPPPE54-DZ8j79Mx.js} +1 -1
  29. package/dist/web/assets/channel-COC5jFhc.js +1 -0
  30. package/dist/web/assets/{chunk-2E7WHVMD-BjwXDHKa.js → chunk-2E7WHVMD-y5rSOViW.js} +1 -1
  31. package/dist/web/assets/{chunk-4EMNKR2T-DfazpkKZ.js → chunk-4EMNKR2T-B7WVoonr.js} +1 -1
  32. package/dist/web/assets/{chunk-55XCU76K-C8SPf4XM.js → chunk-55XCU76K-D-tlN44X.js} +1 -1
  33. package/dist/web/assets/{chunk-7S6XSTFO-4drJ38MC.js → chunk-7S6XSTFO-31xOZx-Y.js} +1 -1
  34. package/dist/web/assets/{chunk-AQDTSKZB-CT94xwkm.js → chunk-AQDTSKZB-CeCjUBmu.js} +1 -1
  35. package/dist/web/assets/{chunk-DSNLSHJZ-Do8zQFMz.js → chunk-DSNLSHJZ-DgzXTo3_.js} +1 -1
  36. package/dist/web/assets/{chunk-HAGFOTED-5XuJjFhz.js → chunk-HAGFOTED-Dm22rez0.js} +1 -1
  37. package/dist/web/assets/{chunk-HNE4Q36P-DlG6wQNV.js → chunk-HNE4Q36P-CnFLQjbZ.js} +1 -1
  38. package/dist/web/assets/{chunk-NQOKRVIF-D-7GGebB.js → chunk-NQOKRVIF-7DXXp2d_.js} +1 -1
  39. package/dist/web/assets/{chunk-UHW7PFBP-BTJa6uY3.js → chunk-UHW7PFBP-Bw9-oVzV.js} +1 -1
  40. package/dist/web/assets/{chunk-VQ5W62QI-DtWEqWoe.js → chunk-VQ5W62QI-C9PHAHIJ.js} +1 -1
  41. package/dist/web/assets/{chunk-WQQL4CDK-qNZoyGqM.js → chunk-WQQL4CDK-C9nKxc0i.js} +1 -1
  42. package/dist/web/assets/{chunk-XXDRQBXY-DGCXD5QG.js → chunk-XXDRQBXY-DjdTeNXE.js} +1 -1
  43. package/dist/web/assets/{chunk-YZO67HFG-DwHNq7O3.js → chunk-YZO67HFG-C1z77_dC.js} +1 -1
  44. package/dist/web/assets/{chunk-ZAUX2LTX-1lvpL_7X.js → chunk-ZAUX2LTX-C7e41NBP.js} +1 -1
  45. package/dist/web/assets/{chunk-ZCHMQSOR-Dt3EEGC_.js → chunk-ZCHMQSOR-Gv41Qgvd.js} +1 -1
  46. package/dist/web/assets/{classDiagram-v2-4MYWR3UA-DiM_A5E4.js → classDiagram-v2-4MYWR3UA-PjZDyaSs.js} +1 -1
  47. package/dist/web/assets/{cose-bilkent-JH36ORCC-B5yeBESI.js → cose-bilkent-JH36ORCC-CvwZK3HN.js} +1 -1
  48. package/dist/web/assets/{cynefin-EF2NZ3EQ-DepIuM8k.js → cynefin-EF2NZ3EQ-Yn6p93cE.js} +1 -1
  49. package/dist/web/assets/{cynefinDiagram-WF24UDRP-9NThILAR.js → cynefinDiagram-WF24UDRP-BXa5Guxe.js} +1 -1
  50. package/dist/web/assets/{dagre-NK4GIZVX-C3KdtDhG.js → dagre-NK4GIZVX-Cnskqb30.js} +1 -1
  51. package/dist/web/assets/{diagram-CBK3MWTC-v3bh0eI-.js → diagram-CBK3MWTC-K8WEazkZ.js} +1 -1
  52. package/dist/web/assets/{diagram-I5XIIV2D-B-ICpu9I.js → diagram-I5XIIV2D-CLzouqUh.js} +1 -1
  53. package/dist/web/assets/{diagram-ODIL2UCW-DpmbfYP-.js → diagram-ODIL2UCW-B_9ysyHU.js} +1 -1
  54. package/dist/web/assets/{diagram-PFBEJUXE-DjCVTHlN.js → diagram-PFBEJUXE-D1ge5Ebx.js} +1 -1
  55. package/dist/web/assets/{diagram-RJA3I4VV-BgwHDpSh.js → diagram-RJA3I4VV-DWF6V26r.js} +1 -1
  56. package/dist/web/assets/{diagram-TYUNFSNR-BSQV5_eI.js → diagram-TYUNFSNR-BNM1pCS4.js} +1 -1
  57. package/dist/web/assets/{dist-DnjG1c62.js → dist-_uWZFvFF.js} +1 -1
  58. package/dist/web/assets/{ebnfDiagram-V4P3TTPT-CpfF488c.js → ebnfDiagram-V4P3TTPT-Cge9YHFL.js} +1 -1
  59. package/dist/web/assets/{elk-QONTBCTQ-DpD_nrIC.js → elk-QONTBCTQ-CY_393oL.js} +1 -1
  60. package/dist/web/assets/{erDiagram-KYQIGQAA-17q4UWrx.js → erDiagram-KYQIGQAA-CCOr67SS.js} +1 -1
  61. package/dist/web/assets/{eventmodeling-K75KTNOO-CuFiu_4h.js → eventmodeling-K75KTNOO-BzNNWwRQ.js} +1 -1
  62. package/dist/web/assets/flowDiagram-IUGLLWBU-C8dLq0uV.js +1 -0
  63. package/dist/web/assets/{ganttDiagram-R7GPNTJY-9ZLwR83Q.js → ganttDiagram-R7GPNTJY-8BewtZrJ.js} +1 -1
  64. package/dist/web/assets/{gitGraph-VSP46ZUC-DjHWMo_n.js → gitGraph-VSP46ZUC-CrqJbic_.js} +1 -1
  65. package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-qoPQTUpT.js → gitGraphDiagram-PDYL7XHY-Ddr5Q6Is.js} +1 -1
  66. package/dist/web/assets/index-C_i_PNi6.css +1 -0
  67. package/dist/web/assets/index-DHENTsqN.js +148 -0
  68. package/dist/web/assets/{info-OHQRW6UA-B0YOBALB.js → info-OHQRW6UA-DgPvuC2J.js} +1 -1
  69. package/dist/web/assets/{infoDiagram-MVDRKAX5-DWLetMhi.js → infoDiagram-MVDRKAX5-zlubFwKS.js} +1 -1
  70. package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-D7fHGOrW.js → ishikawaDiagram-HPEHV2GE-DdQfvqVi.js} +1 -1
  71. package/dist/web/assets/{journeyDiagram-BOSHW7O7-DJJ-UZbG.js → journeyDiagram-BOSHW7O7-DZ4XswBd.js} +1 -1
  72. package/dist/web/assets/{kanban-definition-FNSA4HC5-zWaq8RKK.js → kanban-definition-FNSA4HC5-B1BQdpKc.js} +1 -1
  73. package/dist/web/assets/{line-BggZ3vsX.js → line-jQDqs207.js} +1 -1
  74. package/dist/web/assets/{linear-Cz_-flHq.js → linear-DbYz4fU8.js} +1 -1
  75. package/dist/web/assets/{mermaid-parser.core-C7Kpq-Lh.js → mermaid-parser.core-CY5CVNe8.js} +3 -3
  76. package/dist/web/assets/{mermaid.core-B-AKvFuX.js → mermaid.core-mETL7nBW.js} +4 -4
  77. package/dist/web/assets/{mindmap-definition-6OY57UG3-JGOdyl4n.js → mindmap-definition-6OY57UG3-CxgWFLxa.js} +1 -1
  78. package/dist/web/assets/{packet-JDAUHWVQ-DKCNvhvF.js → packet-JDAUHWVQ-Czsr9kk7.js} +1 -1
  79. package/dist/web/assets/{pegDiagram-ECIUJNBX-BKKUVsiJ.js → pegDiagram-ECIUJNBX-BqYq-SA-.js} +1 -1
  80. package/dist/web/assets/{pie-XZMESJXO-Cj1U8HAg.js → pie-XZMESJXO-CRpxJzCy.js} +1 -1
  81. package/dist/web/assets/{pieDiagram-GNMPY74T-B4gWYmj5.js → pieDiagram-GNMPY74T-Bt2wKk8z.js} +1 -1
  82. package/dist/web/assets/{quadrantDiagram-D57K4MC3-CzKFFFWA.js → quadrantDiagram-D57K4MC3-VOKqYTgn.js} +1 -1
  83. package/dist/web/assets/{radar-ABXABTNO-BvW6txnl.js → radar-ABXABTNO-wXmArQH-.js} +1 -1
  84. package/dist/web/assets/{railroad-I3PHUGI6-CQFUQhrP.js → railroad-I3PHUGI6-CKPK4tQV.js} +1 -1
  85. package/dist/web/assets/{railroad-abnf-LNEFI6M7-Dp31IQhK.js → railroad-abnf-LNEFI6M7-BLEs3xqV.js} +1 -1
  86. package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-DDYlK9R4.js → railroad-ebnf-DWYD2UWJ-DS5QZgky.js} +1 -1
  87. package/dist/web/assets/{railroad-peg-JR7BVNR7-C8-c26bu.js → railroad-peg-JR7BVNR7-DHWXNOSl.js} +1 -1
  88. package/dist/web/assets/{railroadDiagram-Y72NRBL7-6FaSHHCa.js → railroadDiagram-Y72NRBL7-Cy4eWKH1.js} +1 -1
  89. package/dist/web/assets/{requirementDiagram-EQYSW5RA-C6Ry03Cr.js → requirementDiagram-EQYSW5RA-Bvbk8wwe.js} +1 -1
  90. package/dist/web/assets/{sankeyDiagram-SY24JHO5-Cwnb3Qeu.js → sankeyDiagram-SY24JHO5-B4jGjMzu.js} +1 -1
  91. package/dist/web/assets/{sequenceDiagram-BLEHNHQU-CRvwFUCU.js → sequenceDiagram-BLEHNHQU-BpgUogv_.js} +1 -1
  92. package/dist/web/assets/{src-DVW4doAe.js → src-DigPeGm7.js} +1 -1
  93. package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-wwkCw23o.js → stateDiagram-v2-A6IESKWZ-tHq7e40Q.js} +1 -1
  94. package/dist/web/assets/{swimlanes-B3ENRKFQ-ChNg0Cbk.js → swimlanes-B3ENRKFQ-DVUPAaDR.js} +1 -1
  95. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-jJ6zDGAv.js +8 -0
  96. package/dist/web/assets/{timeline-definition-JXYKGLGB-BVfl26_t.js → timeline-definition-JXYKGLGB-BHMM9AHV.js} +1 -1
  97. package/dist/web/assets/{treeView-D4JQ5SDB-DJFvd6yo.js → treeView-D4JQ5SDB-DZ7k_TTS.js} +1 -1
  98. package/dist/web/assets/{treemap-SAJKECNS-BDiJSu2k.js → treemap-SAJKECNS-C1wGfVb4.js} +1 -1
  99. package/dist/web/assets/{usecaseDiagram-WPOLBNNX-Bq_3KTOD.js → usecaseDiagram-WPOLBNNX-BTg0K_GO.js} +1 -1
  100. package/dist/web/assets/{vennDiagram-Q3Y3IDCW-6YGgOzny.js → vennDiagram-Q3Y3IDCW-BTKShPx1.js} +1 -1
  101. package/dist/web/assets/{wardley-7MLQ67FV-4cm0j7qc.js → wardley-7MLQ67FV-DnYPM5OB.js} +1 -1
  102. package/dist/web/assets/{wardleyDiagram-IVAIDKWA-BMqtyCjx.js → wardleyDiagram-IVAIDKWA-Dzb67Boa.js} +1 -1
  103. package/dist/web/assets/{xychartDiagram-4URNHDU4-CFsu67vz.js → xychartDiagram-4URNHDU4-qijltKIO.js} +1 -1
  104. package/dist/web/index.html +2 -2
  105. package/package.json +3 -3
  106. package/dist/web/assets/channel-DiiJLG5R.js +0 -1
  107. package/dist/web/assets/flowDiagram-IUGLLWBU-D63MFFYq.js +0 -1
  108. package/dist/web/assets/index-CGAKLE5y.js +0 -147
  109. package/dist/web/assets/index-QZirxrGa.css +0 -1
  110. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-Dww2HRCa.js +0 -8
@@ -1,5 +1,8 @@
1
+ import { CapacityRetries } from "./capacity-retry.js";
2
+ import { isModelCapacityError } from "../shared/turn-errors.js";
1
3
  import { attentionKey, pendingAttention } from "../shared/thread-attention.js";
2
4
  import { ShellService } from "./shell.js";
5
+ import { readGithubRepository } from "./github-repository.js";
3
6
  import { realpathSync } from "node:fs";
4
7
  import { cp } from "node:fs/promises";
5
8
  import { WorkspaceRegistry } from "./workspaces.js";
@@ -48,11 +51,16 @@ class GatewayService {
48
51
  this.shell = new ShellService(this.codex);
49
52
  this.events = new EventHub(options.limits?.eventBytes);
50
53
  this.projection = new Projection(options.limits?.itemBytes);
54
+ this.capacityRetries = new CapacityRetries(
55
+ (job) => this.dispatchCapacityRetry(job),
56
+ (id) => this.publishThread(id)
57
+ );
51
58
  this.currentEpoch = this.codex.epoch;
52
59
  this.store.recover();
53
60
  this.removeListener = this.codex.onEvent((event) => this.consume(event));
54
61
  }
55
62
  options;
63
+ capacityRetries;
56
64
  projectClones;
57
65
  shell;
58
66
  events;
@@ -85,9 +93,27 @@ class GatewayService {
85
93
  runtimeSettingsUpdates = /* @__PURE__ */ new Map();
86
94
  inFlightOperations = /* @__PURE__ */ new Set();
87
95
  pendingThreadCreations = /* @__PURE__ */ new Map();
96
+ capacityLinks = /* @__PURE__ */ new Map();
88
97
  listProjects() {
89
98
  return this.projects.list();
90
99
  }
100
+ async getProjectGithubRepository(projectId) {
101
+ const project = this.projects.get(projectId);
102
+ const url = await readGithubRepository(project.canonicalRoot);
103
+ this.projects.get(projectId);
104
+ return { url };
105
+ }
106
+ async getThreadGithubRepository(threadId) {
107
+ let link = this.store.getThreadLink(threadId);
108
+ if (!link) {
109
+ this.normalize(await this.codex.readThreadMetadata(threadId));
110
+ link = this.store.getThreadLink(threadId);
111
+ }
112
+ const { cwd } = this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
113
+ const url = await readGithubRepository(cwd);
114
+ this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
115
+ return { url };
116
+ }
91
117
  startShell(projectId, input) {
92
118
  return this.perform("shell.start", projectId, input, async () => {
93
119
  this.ensureCompatibilityConfirmed();
@@ -114,6 +140,8 @@ class GatewayService {
114
140
  return this.maintenanceReason === "backup" ? new AppError("BACKUP_MAINTENANCE", "\u6B63\u5728\u5907\u4EFD\uFF0C\u8BF7\u5B8C\u6210\u540E\u91CD\u8BD5\u3002", 503) : new AppError("UPDATE_MAINTENANCE", "\u6B63\u5728\u5347\u7EA7\uFF0C\u8BF7\u7B49\u5F85\u670D\u52A1\u91CD\u65B0\u8FDE\u63A5", 503);
115
141
  }
116
142
  prepareUpdate(reason = "update") {
143
+ if (this.capacityRetries.active())
144
+ return { ready: false, reason: "\u7B49\u5F85\u6A21\u578B\u5BB9\u91CF\u91CD\u8BD5\u7ED3\u675F\u6216\u53D6\u6D88" };
117
145
  if (this.projectClones.hasActive) return { ready: false, reason: "\u7B49\u5F85\u9879\u76EE\u514B\u9686\u5B8C\u6210\u6216\u53D6\u6D88" };
118
146
  if (this.shell.active()) return { ready: false, reason: "\u7B49\u5F85 Shell \u547D\u4EE4\u7ED3\u675F" };
119
147
  if (this.inFlightOperations.size || this.resumes.size || this.pendingThreadCreations.size)
@@ -161,7 +189,7 @@ class GatewayService {
161
189
  return link ? link.projectId === projectId : entry.workspaceKey === project.workspaceKey;
162
190
  });
163
191
  const active = this.projection.values().find(
164
- (thread) => thread.projectId === projectId && (isActive(thread.status) || thread.actions.some((action) => ["pending", "responding"].includes(action.status)))
192
+ (thread) => thread.projectId === projectId && (isActive(thread.status) || this.capacityRetries.pending(thread.id) || thread.actions.some((action) => ["pending", "responding"].includes(action.status)))
165
193
  );
166
194
  if (pending || marker || active || this.shell.active(projectId))
167
195
  throw new AppError(
@@ -435,6 +463,7 @@ class GatewayService {
435
463
  updateArchiveState(threadId, archived) {
436
464
  const thread = this.projection.get(threadId);
437
465
  if (!thread || Boolean(thread.archived) === archived) return;
466
+ if (archived) this.capacityRetries.clear(threadId);
438
467
  thread.archived = archived;
439
468
  this.archiveRevisions.set(threadId, (this.archiveRevisions.get(threadId) ?? 0) + 1);
440
469
  if (archived) this.releaseThreadOwnership(threadId);
@@ -476,6 +505,11 @@ class GatewayService {
476
505
  throw new AppError("HISTORY_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u5386\u53F2\u5206\u9875", 409);
477
506
  const page = this.normalize(await this.codex.readThreadPage(threadId, cursor));
478
507
  this.workspaces.assertOwnership(page.projectId, page.cwd, page.id);
508
+ const links = this.retryLinks(threadId);
509
+ for (const turn of page.turns) {
510
+ const root = links.get(turn.id);
511
+ if (root) turn.retryOf = root;
512
+ }
479
513
  return page;
480
514
  }
481
515
  async exportThread(threadId) {
@@ -602,6 +636,7 @@ class GatewayService {
602
636
  }
603
637
  async resumeThread(threadId, input) {
604
638
  return this.perform("thread.resume", threadId, input, async () => {
639
+ this.capacityRetries.clear(threadId);
605
640
  const thread = await this.loadThread(threadId);
606
641
  this.ensureReady();
607
642
  this.ensureArchiveAvailable(thread);
@@ -703,11 +738,21 @@ class GatewayService {
703
738
  });
704
739
  }
705
740
  async startTurn(threadId, input) {
741
+ return this.dispatchTurn(threadId, input);
742
+ }
743
+ async dispatchTurn(threadId, input, retry, expectedTurnId) {
706
744
  return this.perform("turn.start", threadId, input, async () => {
745
+ if (!retry && !expectedTurnId && this.capacityRetries.view(threadId)?.status === "waiting")
746
+ this.capacityRetries.clear(threadId);
707
747
  const thread = await this.loadThread(threadId);
708
748
  this.ensureManaged(thread);
709
749
  const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
710
- if (isActive(thread.status))
750
+ if (expectedTurnId) {
751
+ const last = thread.turns.at(-1);
752
+ if (last?.id !== expectedTurnId || last.status !== "failed" || !isModelCapacityError(last.error ?? "", last.errorCode))
753
+ throw new AppError("TURN_CONFLICT", "\u76EE\u6807\u5BB9\u91CF\u5931\u8D25\u5DF2\u6539\u53D8\uFF0C\u8BF7\u5237\u65B0\u540E\u7EE7\u7EED", 409);
754
+ }
755
+ if (isActive(this.projection.get(threadId)?.status ?? thread.status))
711
756
  throw new AppError("THREAD_BUSY", "\u4F1A\u8BDD\u5DF2\u6709\u6D3B\u52A8\u4EFB\u52A1\uFF0C\u8BF7\u8FFD\u52A0\u8981\u6C42\u6216\u4E2D\u65AD", 409, { threadId });
712
757
  this.ensureCompatibilityConfirmed();
713
758
  this.store.dispatch(input.clientOperationId, {
@@ -715,6 +760,15 @@ class GatewayService {
715
760
  workspaceKey: ownership.workspaceKey,
716
761
  codexEpoch: this.codex.epoch
717
762
  });
763
+ if (retry && !this.capacityRetries.owns(retry))
764
+ throw new AppError("TURN_CONFLICT", "\u5BB9\u91CF\u91CD\u8BD5\u5DF2\u53D6\u6D88\u6216\u88AB\u66FF\u4EE3", 409);
765
+ const candidate = retry ?? this.capacityRetries.begin(
766
+ threadId,
767
+ input.clientOperationId,
768
+ this.retrySettings(thread.runtimeSettings, input.settings),
769
+ this.codex.epoch
770
+ );
771
+ const retryOf = retry?.rootTurnId ?? (expectedTurnId ? this.retryLinks(threadId).get(expectedTurnId) ?? expectedTurnId : void 0);
718
772
  this.turnBaselines.set(input.clientOperationId, new Set(thread.turns.map((turn2) => turn2.id)));
719
773
  this.publishThread(threadId);
720
774
  const revision = this.projection.revision(threadId);
@@ -728,15 +782,70 @@ class GatewayService {
728
782
  this.store.attachTurn(input.clientOperationId, turn.id);
729
783
  this.turnBaselines.delete(input.clientOperationId);
730
784
  this.projection.turn(threadId, turn, this.projection.revision(threadId) !== revision);
785
+ this.capacityRetries.observe({ type: "turn", threadId, turn }, input.clientOperationId);
786
+ if (retryOf) this.retryLinks(threadId).set(turn.id, retryOf);
787
+ if (candidate.cancelled && retry) await this.interruptCancelledCapacity(candidate, turn.id);
731
788
  const projectedTurn = this.projection.get(threadId)?.turns.find((entry) => entry.id === turn.id);
732
789
  if (projectedTurn && isTerminal(projectedTurn.status))
733
790
  this.store.finishTurn(threadId, turn.id, this.codex.epoch);
734
791
  this.publishThread(threadId);
735
- return { result: { threadId, turnId: turn.id }, turnId: turn.id };
792
+ return {
793
+ result: { threadId, turnId: turn.id, ...retryOf ? { retryOf } : {} },
794
+ turnId: turn.id
795
+ };
736
796
  });
737
797
  }
798
+ retrySettings(runtime, input = {}) {
799
+ return {
800
+ ...runtime?.model ? { model: runtime.model } : {},
801
+ ...runtime?.reasoningEffort ? { reasoningEffort: runtime.reasoningEffort } : {},
802
+ ...runtime?.serviceTier === "fast" || runtime?.serviceTier === null ? { serviceTier: runtime.serviceTier } : {},
803
+ ...input
804
+ };
805
+ }
806
+ async dispatchCapacityRetry(job) {
807
+ if (!this.capacityRetries.owns(job) || !this.accepting || this.maintenance || !this.codex.ready || this.codex.epoch !== job.epoch)
808
+ throw new AppError("TURN_CONFLICT", "\u5BB9\u91CF\u91CD\u8BD5\u7684\u8FD0\u884C\u73AF\u5883\u5DF2\u6539\u53D8", 409);
809
+ const thread = this.projection.get(job.threadId);
810
+ const last = thread?.turns.at(-1);
811
+ if (!thread || !last || last.id !== job.turnId || last.status !== "failed" || this.store.activeForThread(job.threadId) || !job.previousOperationId || this.store.get(job.previousOperationId)?.state !== "acknowledged" || this.inFlightOperations.has(job.previousOperationId))
812
+ throw new AppError("TURN_CONFLICT", "\u539F\u4EFB\u52A1\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u68C0\u67E5\u540E\u7EE7\u7EED", 409);
813
+ await this.dispatchTurn(
814
+ job.threadId,
815
+ {
816
+ clientOperationId: job.operationId,
817
+ text: "",
818
+ settings: job.settings
819
+ },
820
+ job
821
+ );
822
+ }
823
+ async cancelCapacityRetry(threadId, input) {
824
+ return this.perform("turn.retry.cancel", threadId, input, async () => {
825
+ const thread = await this.loadThread(threadId);
826
+ this.ensureManaged(thread);
827
+ const job = this.capacityRetries.current(threadId);
828
+ if (!job?.view || job.rootTurnId !== input.expectedTurnId)
829
+ throw new AppError("TURN_CONFLICT", "\u5BB9\u91CF\u91CD\u8BD5\u5DF2\u7ED3\u675F\u6216\u88AB\u66FF\u4EE3", 409);
830
+ this.store.dispatch(input.clientOperationId);
831
+ this.capacityRetries.cancel(job);
832
+ if (job.attempt > 0 && job.turnId) await this.interruptCancelledCapacity(job, job.turnId);
833
+ return { result: { threadId, cancelled: true } };
834
+ });
835
+ }
836
+ async interruptCancelledCapacity(job, turnId) {
837
+ const turn = this.projection.get(job.threadId)?.turns.find((entry) => entry.id === turnId);
838
+ if (!job.cancelled || job.interruptSent || !turn || !isActive(turn.status) || this.codex.epoch !== job.epoch || !this.codex.ready)
839
+ return;
840
+ job.interruptSent = true;
841
+ await this.codex.interruptTurn(job.threadId, turnId);
842
+ }
843
+ async retryTurn(threadId, input) {
844
+ return this.dispatchTurn(threadId, { ...input, text: "" }, void 0, input.expectedTurnId);
845
+ }
738
846
  async compactThread(threadId, input) {
739
847
  return this.perform("thread.compact", threadId, input, async () => {
848
+ this.capacityRetries.clear(threadId);
740
849
  const thread = await this.loadThread(threadId);
741
850
  this.ensureManaged(thread);
742
851
  const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
@@ -761,6 +870,7 @@ class GatewayService {
761
870
  }
762
871
  async startReview(threadId, input) {
763
872
  return this.perform("review.start", threadId, input, async () => {
873
+ this.capacityRetries.clear(threadId);
764
874
  const thread = await this.loadThread(threadId);
765
875
  this.ensureManaged(thread);
766
876
  const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
@@ -898,6 +1008,7 @@ class GatewayService {
898
1008
  }
899
1009
  async shutdown() {
900
1010
  this.accepting = false;
1011
+ this.capacityRetries.clearAll();
901
1012
  await this.projectClones.close();
902
1013
  this.store.recover();
903
1014
  this.projection.runtimeReset();
@@ -939,6 +1050,8 @@ class GatewayService {
939
1050
  const action = this.projection.resolveAction(target, "pending");
940
1051
  if (action?.threadId) this.publishThread(action.threadId);
941
1052
  }
1053
+ if (kind === "turn.start")
1054
+ this.capacityRetries.dispatchFailed(target, input.clientOperationId);
942
1055
  if (dispatched && kind === "turn.start") this.publishThread(target);
943
1056
  this.events.publish("operation", operation);
944
1057
  if (uncertain)
@@ -1172,8 +1285,25 @@ class GatewayService {
1172
1285
  thread.hasMessages || thread.archived || thread.turns.length || this.store.activeForThread(thread.id) || isActive(thread.status) || thread.actions.some((action) => ["pending", "responding"].includes(action.status))
1173
1286
  );
1174
1287
  }
1288
+ retryLinks(threadId) {
1289
+ let links = this.capacityLinks.get(threadId);
1290
+ if (!links) {
1291
+ links = this.store.capacityRetryTurns(threadId);
1292
+ this.capacityLinks.set(threadId, links);
1293
+ }
1294
+ return links;
1295
+ }
1175
1296
  threadView(thread) {
1176
1297
  const result = structuredClone(thread);
1298
+ const links = this.capacityLinks.get(thread.id) ?? (thread.turns.some((turn) => !turn.items.some((item) => item.type === "userMessage")) ? this.retryLinks(thread.id) : void 0);
1299
+ for (const turn of result.turns) {
1300
+ const root = links?.get(turn.id);
1301
+ if (root) turn.retryOf = root;
1302
+ }
1303
+ const retry = this.capacityRetries.view(thread.id);
1304
+ if (retry) result.capacityRetry = structuredClone(retry);
1305
+ else delete result.capacityRetry;
1306
+ if (this.capacityRetries.pending(thread.id)) result.status = "running";
1177
1307
  result.archived = thread.archived ?? false;
1178
1308
  result.historyVisible = this.historyVisible(thread);
1179
1309
  const marker = this.store.activeForThread(thread.id);
@@ -1222,6 +1352,17 @@ class GatewayService {
1222
1352
  }
1223
1353
  }
1224
1354
  consume(event) {
1355
+ if (event.type === "runtime" && (!event.diagnostics.ready || event.diagnostics.codexEpoch !== this.currentEpoch))
1356
+ this.capacityRetries.clearAll();
1357
+ const eventThread = event.type === "action" ? event.action.threadId : "threadId" in event ? event.threadId : void 0;
1358
+ const eventMarker = eventThread ? this.store.activeForThread(eventThread) : void 0;
1359
+ const matchesDispatch = event.type !== "turn" || eventMarker?.turnId === event.turn.id || eventMarker?.turnId === null && this.turnBaselines.get(eventMarker.operationId)?.has(event.turn.id) === false;
1360
+ this.capacityRetries.observe(event, matchesDispatch ? eventMarker?.operationId : void 0);
1361
+ if (event.type === "turn") {
1362
+ const job = this.capacityRetries.current(event.threadId);
1363
+ if (job?.attempt && job.turnId === event.turn.id && job.rootTurnId)
1364
+ this.retryLinks(event.threadId).set(event.turn.id, job.rootTurnId);
1365
+ }
1225
1366
  if (event.type === "runtime") {
1226
1367
  this.compatibilityConfirmation(event.diagnostics);
1227
1368
  if (!event.diagnostics.ready || event.diagnostics.codexEpoch !== this.currentEpoch) {
@@ -1286,7 +1427,13 @@ class GatewayService {
1286
1427
  return;
1287
1428
  }
1288
1429
  if (event.type === "turnError" || event.type === "turnProgress") {
1289
- const changed = event.type === "turnError" ? this.projection.turnError(thread.id, event.turnId, event.message, event.willRetry) : this.projection.turnProgress(thread.id, event.turnId);
1430
+ const changed = event.type === "turnError" ? this.projection.turnError(
1431
+ thread.id,
1432
+ event.turnId,
1433
+ event.message,
1434
+ event.willRetry,
1435
+ event
1436
+ ) : this.projection.turnProgress(thread.id, event.turnId);
1290
1437
  if (changed) this.publishThread(thread.id);
1291
1438
  return;
1292
1439
  }
@@ -1320,7 +1467,7 @@ class GatewayService {
1320
1467
  if (isTerminal(event.turn.status)) {
1321
1468
  this.store.finishTurn(thread.id, event.turn.id, this.codex.epoch);
1322
1469
  this.projection.expireTurn(thread.id, event.turn.id);
1323
- if (newCompletion) {
1470
+ if (newCompletion && !this.capacityRetries.pending(thread.id)) {
1324
1471
  try {
1325
1472
  this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
1326
1473
  this.events.publishCompletion({