@covro/covro 0.5.1-dev.2 → 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 (106) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  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/http.js +14 -0
  12. package/dist/server/http.js.map +2 -2
  13. package/dist/server/projection.js +10 -3
  14. package/dist/server/projection.js.map +2 -2
  15. package/dist/server/service.js +134 -5
  16. package/dist/server/service.js.map +2 -2
  17. package/dist/shared/turn-errors.js +25 -0
  18. package/dist/shared/turn-errors.js.map +7 -0
  19. package/dist/shared/validation.js +2 -0
  20. package/dist/shared/validation.js.map +2 -2
  21. package/dist/web/assets/{abnfDiagram-WEYOIA3C-DK1b1m5g.js → abnfDiagram-WEYOIA3C-ATf52olP.js} +1 -1
  22. package/dist/web/assets/{arc-0hKPa6cH.js → arc-ChLnTnZI.js} +1 -1
  23. package/dist/web/assets/{architecture-WOLXFQ4H-DUa2ELbW.js → architecture-WOLXFQ4H-B2oEg329.js} +1 -1
  24. package/dist/web/assets/{architectureDiagram-ZHVYKT53-DfpAMcHI.js → architectureDiagram-ZHVYKT53-BzSXU9d0.js} +1 -1
  25. package/dist/web/assets/{blockDiagram-RJAE7QA3-CD8LlPAk.js → blockDiagram-RJAE7QA3-xhCZ7iUm.js} +1 -1
  26. package/dist/web/assets/{c4Diagram-XDPPPE54-KVKGUBPP.js → c4Diagram-XDPPPE54-DZ8j79Mx.js} +1 -1
  27. package/dist/web/assets/channel-COC5jFhc.js +1 -0
  28. package/dist/web/assets/{chunk-2E7WHVMD-CPcfaznm.js → chunk-2E7WHVMD-y5rSOViW.js} +1 -1
  29. package/dist/web/assets/{chunk-4EMNKR2T-Ba4ZkZos.js → chunk-4EMNKR2T-B7WVoonr.js} +1 -1
  30. package/dist/web/assets/{chunk-55XCU76K-COU6aQoV.js → chunk-55XCU76K-D-tlN44X.js} +1 -1
  31. package/dist/web/assets/{chunk-7S6XSTFO-mof6va94.js → chunk-7S6XSTFO-31xOZx-Y.js} +1 -1
  32. package/dist/web/assets/{chunk-AQDTSKZB-B9S7XD0k.js → chunk-AQDTSKZB-CeCjUBmu.js} +1 -1
  33. package/dist/web/assets/{chunk-DSNLSHJZ-DiTTjo21.js → chunk-DSNLSHJZ-DgzXTo3_.js} +1 -1
  34. package/dist/web/assets/{chunk-HAGFOTED-bBaCv2Gr.js → chunk-HAGFOTED-Dm22rez0.js} +1 -1
  35. package/dist/web/assets/{chunk-HNE4Q36P-COubV5SQ.js → chunk-HNE4Q36P-CnFLQjbZ.js} +1 -1
  36. package/dist/web/assets/{chunk-NQOKRVIF-D7mCDLmx.js → chunk-NQOKRVIF-7DXXp2d_.js} +1 -1
  37. package/dist/web/assets/{chunk-UHW7PFBP-7E_Y7QMq.js → chunk-UHW7PFBP-Bw9-oVzV.js} +1 -1
  38. package/dist/web/assets/{chunk-VQ5W62QI-DIe2sxBO.js → chunk-VQ5W62QI-C9PHAHIJ.js} +1 -1
  39. package/dist/web/assets/{chunk-WQQL4CDK-sGc70NW0.js → chunk-WQQL4CDK-C9nKxc0i.js} +1 -1
  40. package/dist/web/assets/{chunk-XXDRQBXY-CnTJfCp_.js → chunk-XXDRQBXY-DjdTeNXE.js} +1 -1
  41. package/dist/web/assets/{chunk-YZO67HFG-D1uBxw_D.js → chunk-YZO67HFG-C1z77_dC.js} +1 -1
  42. package/dist/web/assets/{chunk-ZAUX2LTX-quytYPeh.js → chunk-ZAUX2LTX-C7e41NBP.js} +1 -1
  43. package/dist/web/assets/{chunk-ZCHMQSOR-CebhdtBu.js → chunk-ZCHMQSOR-Gv41Qgvd.js} +1 -1
  44. package/dist/web/assets/{classDiagram-v2-4MYWR3UA-_NujQwSa.js → classDiagram-v2-4MYWR3UA-PjZDyaSs.js} +1 -1
  45. package/dist/web/assets/{cose-bilkent-JH36ORCC-DltmODTL.js → cose-bilkent-JH36ORCC-CvwZK3HN.js} +1 -1
  46. package/dist/web/assets/{cynefin-EF2NZ3EQ-CyNUtvfu.js → cynefin-EF2NZ3EQ-Yn6p93cE.js} +1 -1
  47. package/dist/web/assets/{cynefinDiagram-WF24UDRP-DH1m77PD.js → cynefinDiagram-WF24UDRP-BXa5Guxe.js} +1 -1
  48. package/dist/web/assets/{dagre-NK4GIZVX-DiCwjat3.js → dagre-NK4GIZVX-Cnskqb30.js} +1 -1
  49. package/dist/web/assets/{diagram-CBK3MWTC-DnoBkry8.js → diagram-CBK3MWTC-K8WEazkZ.js} +1 -1
  50. package/dist/web/assets/{diagram-I5XIIV2D-CIeRHNwj.js → diagram-I5XIIV2D-CLzouqUh.js} +1 -1
  51. package/dist/web/assets/{diagram-ODIL2UCW-BiyJagHP.js → diagram-ODIL2UCW-B_9ysyHU.js} +1 -1
  52. package/dist/web/assets/{diagram-PFBEJUXE-B6xGLENr.js → diagram-PFBEJUXE-D1ge5Ebx.js} +1 -1
  53. package/dist/web/assets/{diagram-RJA3I4VV-DCbGrXsR.js → diagram-RJA3I4VV-DWF6V26r.js} +1 -1
  54. package/dist/web/assets/{diagram-TYUNFSNR-BjYXlBMa.js → diagram-TYUNFSNR-BNM1pCS4.js} +1 -1
  55. package/dist/web/assets/{dist-C9Qd1YEe.js → dist-_uWZFvFF.js} +1 -1
  56. package/dist/web/assets/{ebnfDiagram-V4P3TTPT-CpRXdAa6.js → ebnfDiagram-V4P3TTPT-Cge9YHFL.js} +1 -1
  57. package/dist/web/assets/{elk-QONTBCTQ-BZa9Vnbc.js → elk-QONTBCTQ-CY_393oL.js} +1 -1
  58. package/dist/web/assets/{erDiagram-KYQIGQAA-7AaYG_a9.js → erDiagram-KYQIGQAA-CCOr67SS.js} +1 -1
  59. package/dist/web/assets/{eventmodeling-K75KTNOO-BzIg1KNf.js → eventmodeling-K75KTNOO-BzNNWwRQ.js} +1 -1
  60. package/dist/web/assets/flowDiagram-IUGLLWBU-C8dLq0uV.js +1 -0
  61. package/dist/web/assets/{ganttDiagram-R7GPNTJY-DeNg-7K2.js → ganttDiagram-R7GPNTJY-8BewtZrJ.js} +1 -1
  62. package/dist/web/assets/{gitGraph-VSP46ZUC-BR-A7hdM.js → gitGraph-VSP46ZUC-CrqJbic_.js} +1 -1
  63. package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-LMyLpeqK.js → gitGraphDiagram-PDYL7XHY-Ddr5Q6Is.js} +1 -1
  64. package/dist/web/assets/{index-CEM8pkOB.css → index-C_i_PNi6.css} +1 -1
  65. package/dist/web/assets/{index-D2dQZgkr.js → index-DHENTsqN.js} +16 -15
  66. package/dist/web/assets/{info-OHQRW6UA-CRbCGNnm.js → info-OHQRW6UA-DgPvuC2J.js} +1 -1
  67. package/dist/web/assets/{infoDiagram-MVDRKAX5-DTUWae1O.js → infoDiagram-MVDRKAX5-zlubFwKS.js} +1 -1
  68. package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-CE4r0jTM.js → ishikawaDiagram-HPEHV2GE-DdQfvqVi.js} +1 -1
  69. package/dist/web/assets/{journeyDiagram-BOSHW7O7-KcSuVnTV.js → journeyDiagram-BOSHW7O7-DZ4XswBd.js} +1 -1
  70. package/dist/web/assets/{kanban-definition-FNSA4HC5-I0QF1Ftr.js → kanban-definition-FNSA4HC5-B1BQdpKc.js} +1 -1
  71. package/dist/web/assets/{line-DLSy1-se.js → line-jQDqs207.js} +1 -1
  72. package/dist/web/assets/{linear-XWNSUdmR.js → linear-DbYz4fU8.js} +1 -1
  73. package/dist/web/assets/{mermaid-parser.core-BcMfaJSH.js → mermaid-parser.core-CY5CVNe8.js} +3 -3
  74. package/dist/web/assets/{mermaid.core-CPl3cZ55.js → mermaid.core-mETL7nBW.js} +4 -4
  75. package/dist/web/assets/{mindmap-definition-6OY57UG3-BIB4ahHf.js → mindmap-definition-6OY57UG3-CxgWFLxa.js} +1 -1
  76. package/dist/web/assets/{packet-JDAUHWVQ-UZicf9-F.js → packet-JDAUHWVQ-Czsr9kk7.js} +1 -1
  77. package/dist/web/assets/{pegDiagram-ECIUJNBX-ckOqIfTt.js → pegDiagram-ECIUJNBX-BqYq-SA-.js} +1 -1
  78. package/dist/web/assets/{pie-XZMESJXO-DErhKWJX.js → pie-XZMESJXO-CRpxJzCy.js} +1 -1
  79. package/dist/web/assets/{pieDiagram-GNMPY74T-CrnS_2qe.js → pieDiagram-GNMPY74T-Bt2wKk8z.js} +1 -1
  80. package/dist/web/assets/{quadrantDiagram-D57K4MC3-UK4DsDJz.js → quadrantDiagram-D57K4MC3-VOKqYTgn.js} +1 -1
  81. package/dist/web/assets/{radar-ABXABTNO-BYgJm4I9.js → radar-ABXABTNO-wXmArQH-.js} +1 -1
  82. package/dist/web/assets/{railroad-I3PHUGI6-2n6yUb7T.js → railroad-I3PHUGI6-CKPK4tQV.js} +1 -1
  83. package/dist/web/assets/{railroad-abnf-LNEFI6M7-CGAFqTzs.js → railroad-abnf-LNEFI6M7-BLEs3xqV.js} +1 -1
  84. package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-gJx_FXYW.js → railroad-ebnf-DWYD2UWJ-DS5QZgky.js} +1 -1
  85. package/dist/web/assets/{railroad-peg-JR7BVNR7-Dm0kEaxK.js → railroad-peg-JR7BVNR7-DHWXNOSl.js} +1 -1
  86. package/dist/web/assets/{railroadDiagram-Y72NRBL7-CqzySn4U.js → railroadDiagram-Y72NRBL7-Cy4eWKH1.js} +1 -1
  87. package/dist/web/assets/{requirementDiagram-EQYSW5RA-B33Hrc0L.js → requirementDiagram-EQYSW5RA-Bvbk8wwe.js} +1 -1
  88. package/dist/web/assets/{sankeyDiagram-SY24JHO5-BA9Smc0j.js → sankeyDiagram-SY24JHO5-B4jGjMzu.js} +1 -1
  89. package/dist/web/assets/{sequenceDiagram-BLEHNHQU-CUA-rKot.js → sequenceDiagram-BLEHNHQU-BpgUogv_.js} +1 -1
  90. package/dist/web/assets/{src-htchAh5W.js → src-DigPeGm7.js} +1 -1
  91. package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-kIYMkX3_.js → stateDiagram-v2-A6IESKWZ-tHq7e40Q.js} +1 -1
  92. package/dist/web/assets/{swimlanes-B3ENRKFQ-C8Lzsqig.js → swimlanes-B3ENRKFQ-DVUPAaDR.js} +1 -1
  93. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-jJ6zDGAv.js +8 -0
  94. package/dist/web/assets/{timeline-definition-JXYKGLGB-C3uOHb1e.js → timeline-definition-JXYKGLGB-BHMM9AHV.js} +1 -1
  95. package/dist/web/assets/{treeView-D4JQ5SDB-Dq-ug1SE.js → treeView-D4JQ5SDB-DZ7k_TTS.js} +1 -1
  96. package/dist/web/assets/{treemap-SAJKECNS-mX01fnXB.js → treemap-SAJKECNS-C1wGfVb4.js} +1 -1
  97. package/dist/web/assets/{usecaseDiagram-WPOLBNNX-DMBep2-D.js → usecaseDiagram-WPOLBNNX-BTg0K_GO.js} +1 -1
  98. package/dist/web/assets/{vennDiagram-Q3Y3IDCW-CUardqBP.js → vennDiagram-Q3Y3IDCW-BTKShPx1.js} +1 -1
  99. package/dist/web/assets/{wardley-7MLQ67FV-CIOqSz29.js → wardley-7MLQ67FV-DnYPM5OB.js} +1 -1
  100. package/dist/web/assets/{wardleyDiagram-IVAIDKWA-DtfU2fzi.js → wardleyDiagram-IVAIDKWA-Dzb67Boa.js} +1 -1
  101. package/dist/web/assets/{xychartDiagram-4URNHDU4-Cb3se94q.js → xychartDiagram-4URNHDU4-qijltKIO.js} +1 -1
  102. package/dist/web/index.html +2 -2
  103. package/package.json +3 -3
  104. package/dist/web/assets/channel-CpEW0zog.js +0 -1
  105. package/dist/web/assets/flowDiagram-IUGLLWBU-FDG_7_Jf.js +0 -1
  106. package/dist/web/assets/swimlanesDiagram-AYQRV4AK-CWrPdxiH.js +0 -8
@@ -1,3 +1,5 @@
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";
3
5
  import { readGithubRepository } from "./github-repository.js";
@@ -49,11 +51,16 @@ class GatewayService {
49
51
  this.shell = new ShellService(this.codex);
50
52
  this.events = new EventHub(options.limits?.eventBytes);
51
53
  this.projection = new Projection(options.limits?.itemBytes);
54
+ this.capacityRetries = new CapacityRetries(
55
+ (job) => this.dispatchCapacityRetry(job),
56
+ (id) => this.publishThread(id)
57
+ );
52
58
  this.currentEpoch = this.codex.epoch;
53
59
  this.store.recover();
54
60
  this.removeListener = this.codex.onEvent((event) => this.consume(event));
55
61
  }
56
62
  options;
63
+ capacityRetries;
57
64
  projectClones;
58
65
  shell;
59
66
  events;
@@ -86,6 +93,7 @@ class GatewayService {
86
93
  runtimeSettingsUpdates = /* @__PURE__ */ new Map();
87
94
  inFlightOperations = /* @__PURE__ */ new Set();
88
95
  pendingThreadCreations = /* @__PURE__ */ new Map();
96
+ capacityLinks = /* @__PURE__ */ new Map();
89
97
  listProjects() {
90
98
  return this.projects.list();
91
99
  }
@@ -132,6 +140,8 @@ class GatewayService {
132
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);
133
141
  }
134
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" };
135
145
  if (this.projectClones.hasActive) return { ready: false, reason: "\u7B49\u5F85\u9879\u76EE\u514B\u9686\u5B8C\u6210\u6216\u53D6\u6D88" };
136
146
  if (this.shell.active()) return { ready: false, reason: "\u7B49\u5F85 Shell \u547D\u4EE4\u7ED3\u675F" };
137
147
  if (this.inFlightOperations.size || this.resumes.size || this.pendingThreadCreations.size)
@@ -179,7 +189,7 @@ class GatewayService {
179
189
  return link ? link.projectId === projectId : entry.workspaceKey === project.workspaceKey;
180
190
  });
181
191
  const active = this.projection.values().find(
182
- (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)))
183
193
  );
184
194
  if (pending || marker || active || this.shell.active(projectId))
185
195
  throw new AppError(
@@ -453,6 +463,7 @@ class GatewayService {
453
463
  updateArchiveState(threadId, archived) {
454
464
  const thread = this.projection.get(threadId);
455
465
  if (!thread || Boolean(thread.archived) === archived) return;
466
+ if (archived) this.capacityRetries.clear(threadId);
456
467
  thread.archived = archived;
457
468
  this.archiveRevisions.set(threadId, (this.archiveRevisions.get(threadId) ?? 0) + 1);
458
469
  if (archived) this.releaseThreadOwnership(threadId);
@@ -494,6 +505,11 @@ class GatewayService {
494
505
  throw new AppError("HISTORY_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u5386\u53F2\u5206\u9875", 409);
495
506
  const page = this.normalize(await this.codex.readThreadPage(threadId, cursor));
496
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
+ }
497
513
  return page;
498
514
  }
499
515
  async exportThread(threadId) {
@@ -620,6 +636,7 @@ class GatewayService {
620
636
  }
621
637
  async resumeThread(threadId, input) {
622
638
  return this.perform("thread.resume", threadId, input, async () => {
639
+ this.capacityRetries.clear(threadId);
623
640
  const thread = await this.loadThread(threadId);
624
641
  this.ensureReady();
625
642
  this.ensureArchiveAvailable(thread);
@@ -721,11 +738,21 @@ class GatewayService {
721
738
  });
722
739
  }
723
740
  async startTurn(threadId, input) {
741
+ return this.dispatchTurn(threadId, input);
742
+ }
743
+ async dispatchTurn(threadId, input, retry, expectedTurnId) {
724
744
  return this.perform("turn.start", threadId, input, async () => {
745
+ if (!retry && !expectedTurnId && this.capacityRetries.view(threadId)?.status === "waiting")
746
+ this.capacityRetries.clear(threadId);
725
747
  const thread = await this.loadThread(threadId);
726
748
  this.ensureManaged(thread);
727
749
  const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
728
- 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))
729
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 });
730
757
  this.ensureCompatibilityConfirmed();
731
758
  this.store.dispatch(input.clientOperationId, {
@@ -733,6 +760,15 @@ class GatewayService {
733
760
  workspaceKey: ownership.workspaceKey,
734
761
  codexEpoch: this.codex.epoch
735
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);
736
772
  this.turnBaselines.set(input.clientOperationId, new Set(thread.turns.map((turn2) => turn2.id)));
737
773
  this.publishThread(threadId);
738
774
  const revision = this.projection.revision(threadId);
@@ -746,15 +782,70 @@ class GatewayService {
746
782
  this.store.attachTurn(input.clientOperationId, turn.id);
747
783
  this.turnBaselines.delete(input.clientOperationId);
748
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);
749
788
  const projectedTurn = this.projection.get(threadId)?.turns.find((entry) => entry.id === turn.id);
750
789
  if (projectedTurn && isTerminal(projectedTurn.status))
751
790
  this.store.finishTurn(threadId, turn.id, this.codex.epoch);
752
791
  this.publishThread(threadId);
753
- return { result: { threadId, turnId: turn.id }, turnId: turn.id };
792
+ return {
793
+ result: { threadId, turnId: turn.id, ...retryOf ? { retryOf } : {} },
794
+ turnId: turn.id
795
+ };
754
796
  });
755
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
+ }
756
846
  async compactThread(threadId, input) {
757
847
  return this.perform("thread.compact", threadId, input, async () => {
848
+ this.capacityRetries.clear(threadId);
758
849
  const thread = await this.loadThread(threadId);
759
850
  this.ensureManaged(thread);
760
851
  const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
@@ -779,6 +870,7 @@ class GatewayService {
779
870
  }
780
871
  async startReview(threadId, input) {
781
872
  return this.perform("review.start", threadId, input, async () => {
873
+ this.capacityRetries.clear(threadId);
782
874
  const thread = await this.loadThread(threadId);
783
875
  this.ensureManaged(thread);
784
876
  const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
@@ -916,6 +1008,7 @@ class GatewayService {
916
1008
  }
917
1009
  async shutdown() {
918
1010
  this.accepting = false;
1011
+ this.capacityRetries.clearAll();
919
1012
  await this.projectClones.close();
920
1013
  this.store.recover();
921
1014
  this.projection.runtimeReset();
@@ -957,6 +1050,8 @@ class GatewayService {
957
1050
  const action = this.projection.resolveAction(target, "pending");
958
1051
  if (action?.threadId) this.publishThread(action.threadId);
959
1052
  }
1053
+ if (kind === "turn.start")
1054
+ this.capacityRetries.dispatchFailed(target, input.clientOperationId);
960
1055
  if (dispatched && kind === "turn.start") this.publishThread(target);
961
1056
  this.events.publish("operation", operation);
962
1057
  if (uncertain)
@@ -1190,8 +1285,25 @@ class GatewayService {
1190
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))
1191
1286
  );
1192
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
+ }
1193
1296
  threadView(thread) {
1194
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";
1195
1307
  result.archived = thread.archived ?? false;
1196
1308
  result.historyVisible = this.historyVisible(thread);
1197
1309
  const marker = this.store.activeForThread(thread.id);
@@ -1240,6 +1352,17 @@ class GatewayService {
1240
1352
  }
1241
1353
  }
1242
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
+ }
1243
1366
  if (event.type === "runtime") {
1244
1367
  this.compatibilityConfirmation(event.diagnostics);
1245
1368
  if (!event.diagnostics.ready || event.diagnostics.codexEpoch !== this.currentEpoch) {
@@ -1304,7 +1427,13 @@ class GatewayService {
1304
1427
  return;
1305
1428
  }
1306
1429
  if (event.type === "turnError" || event.type === "turnProgress") {
1307
- 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);
1308
1437
  if (changed) this.publishThread(thread.id);
1309
1438
  return;
1310
1439
  }
@@ -1338,7 +1467,7 @@ class GatewayService {
1338
1467
  if (isTerminal(event.turn.status)) {
1339
1468
  this.store.finishTurn(thread.id, event.turn.id, this.codex.epoch);
1340
1469
  this.projection.expireTurn(thread.id, event.turn.id);
1341
- if (newCompletion) {
1470
+ if (newCompletion && !this.capacityRetries.pending(thread.id)) {
1342
1471
  try {
1343
1472
  this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
1344
1473
  this.events.publishCompletion({