@covro/covro 0.5.1-dev.2 → 0.5.1-dev.4
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.
- package/README.md +11 -1
- package/README.zh-CN.md +11 -1
- package/dist/codex/adapter.js +65 -5
- package/dist/codex/adapter.js.map +2 -2
- package/dist/codex/collaboration.js +28 -0
- package/dist/codex/collaboration.js.map +7 -0
- package/dist/codex/guardian-denials.js +67 -0
- package/dist/codex/guardian-denials.js.map +7 -0
- package/dist/codex/mapping.js +10 -1
- package/dist/codex/mapping.js.map +2 -2
- package/dist/codex/thread-goals.js +15 -0
- package/dist/codex/thread-goals.js.map +7 -0
- package/dist/codex/thread-titles.js +135 -60
- package/dist/codex/thread-titles.js.map +2 -2
- package/dist/server/capacity-retry.js +176 -0
- package/dist/server/capacity-retry.js.map +7 -0
- package/dist/server/db/operations.js +11 -0
- package/dist/server/db/operations.js.map +2 -2
- package/dist/server/http.js +51 -0
- package/dist/server/http.js.map +2 -2
- package/dist/server/project-init.js +28 -0
- package/dist/server/project-init.js.map +7 -0
- package/dist/server/projection.js +11 -3
- package/dist/server/projection.js.map +2 -2
- package/dist/server/service.js +315 -8
- package/dist/server/service.js.map +2 -2
- package/dist/server/thread-recap.js +34 -0
- package/dist/server/thread-recap.js.map +7 -0
- package/dist/server/workspace-diff.js +149 -0
- package/dist/server/workspace-diff.js.map +7 -0
- package/dist/shared/guardian-denials.js +1 -0
- package/dist/shared/guardian-denials.js.map +7 -0
- package/dist/shared/thread-goal.js +1 -0
- package/dist/shared/thread-goal.js.map +7 -0
- package/dist/shared/turn-errors.js +25 -0
- package/dist/shared/turn-errors.js.map +7 -0
- package/dist/shared/validation.js +53 -3
- package/dist/shared/validation.js.map +2 -2
- package/dist/shared/workspace-diff.js +1 -0
- package/dist/shared/workspace-diff.js.map +7 -0
- package/dist/web/assets/{abnfDiagram-WEYOIA3C-DK1b1m5g.js → abnfDiagram-WEYOIA3C-2_00Zp4O.js} +1 -1
- package/dist/web/assets/{arc-0hKPa6cH.js → arc-DAyuVDVU.js} +1 -1
- package/dist/web/assets/{architecture-WOLXFQ4H-DUa2ELbW.js → architecture-WOLXFQ4H-CgR1C4gR.js} +1 -1
- package/dist/web/assets/{architectureDiagram-ZHVYKT53-DfpAMcHI.js → architectureDiagram-ZHVYKT53-Dkb6AWjz.js} +1 -1
- package/dist/web/assets/{blockDiagram-RJAE7QA3-CD8LlPAk.js → blockDiagram-RJAE7QA3-NtOGZdkX.js} +1 -1
- package/dist/web/assets/{c4Diagram-XDPPPE54-KVKGUBPP.js → c4Diagram-XDPPPE54-C1dqWKb1.js} +1 -1
- package/dist/web/assets/channel-Co9_v2dk.js +1 -0
- package/dist/web/assets/{chunk-2E7WHVMD-CPcfaznm.js → chunk-2E7WHVMD-ClzuEiGk.js} +1 -1
- package/dist/web/assets/{chunk-4EMNKR2T-Ba4ZkZos.js → chunk-4EMNKR2T-DyTpkWag.js} +1 -1
- package/dist/web/assets/{chunk-55XCU76K-COU6aQoV.js → chunk-55XCU76K-DXBA4ib1.js} +1 -1
- package/dist/web/assets/{chunk-7S6XSTFO-mof6va94.js → chunk-7S6XSTFO-DA_aq0zc.js} +1 -1
- package/dist/web/assets/{chunk-AQDTSKZB-B9S7XD0k.js → chunk-AQDTSKZB-BWTN3SC_.js} +1 -1
- package/dist/web/assets/{chunk-DSNLSHJZ-DiTTjo21.js → chunk-DSNLSHJZ-DJnLTeE9.js} +1 -1
- package/dist/web/assets/{chunk-HAGFOTED-bBaCv2Gr.js → chunk-HAGFOTED-cDP-g1Eh.js} +1 -1
- package/dist/web/assets/{chunk-HNE4Q36P-COubV5SQ.js → chunk-HNE4Q36P-B3TTIYd_.js} +1 -1
- package/dist/web/assets/{chunk-NQOKRVIF-D7mCDLmx.js → chunk-NQOKRVIF-D8mmhZHq.js} +1 -1
- package/dist/web/assets/{chunk-UHW7PFBP-7E_Y7QMq.js → chunk-UHW7PFBP-DWIIg_jq.js} +1 -1
- package/dist/web/assets/{chunk-VQ5W62QI-DIe2sxBO.js → chunk-VQ5W62QI-Bu3R2cpS.js} +1 -1
- package/dist/web/assets/{chunk-WQQL4CDK-sGc70NW0.js → chunk-WQQL4CDK-BxsR1mXs.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-CnTJfCp_.js → chunk-XXDRQBXY-Ds0JNdm-.js} +1 -1
- package/dist/web/assets/{chunk-YZO67HFG-D1uBxw_D.js → chunk-YZO67HFG-COpqyQaD.js} +1 -1
- package/dist/web/assets/{chunk-ZAUX2LTX-quytYPeh.js → chunk-ZAUX2LTX-BUrGjDuS.js} +1 -1
- package/dist/web/assets/{chunk-ZCHMQSOR-CebhdtBu.js → chunk-ZCHMQSOR-DYMM9mEK.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-4MYWR3UA-_NujQwSa.js → classDiagram-v2-4MYWR3UA-DfJvks0C.js} +1 -1
- package/dist/web/assets/{cose-bilkent-JH36ORCC-DltmODTL.js → cose-bilkent-JH36ORCC-CN5mpYIb.js} +1 -1
- package/dist/web/assets/{cynefin-EF2NZ3EQ-CyNUtvfu.js → cynefin-EF2NZ3EQ-DmlCHkY-.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-WF24UDRP-DH1m77PD.js → cynefinDiagram-WF24UDRP-C0aUblC2.js} +1 -1
- package/dist/web/assets/{dagre-NK4GIZVX-DiCwjat3.js → dagre-NK4GIZVX-Ba7RcAgP.js} +1 -1
- package/dist/web/assets/{diagram-CBK3MWTC-DnoBkry8.js → diagram-CBK3MWTC-BYQDjB9i.js} +1 -1
- package/dist/web/assets/{diagram-I5XIIV2D-CIeRHNwj.js → diagram-I5XIIV2D-EV79hoob.js} +1 -1
- package/dist/web/assets/{diagram-ODIL2UCW-BiyJagHP.js → diagram-ODIL2UCW-BvQdwmgA.js} +1 -1
- package/dist/web/assets/{diagram-PFBEJUXE-B6xGLENr.js → diagram-PFBEJUXE-hxwI3F00.js} +1 -1
- package/dist/web/assets/{diagram-RJA3I4VV-DCbGrXsR.js → diagram-RJA3I4VV-DwaHE2iB.js} +1 -1
- package/dist/web/assets/{diagram-TYUNFSNR-BjYXlBMa.js → diagram-TYUNFSNR-BxiOemA0.js} +1 -1
- package/dist/web/assets/{dist-C9Qd1YEe.js → dist-nfRX7L9k.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-V4P3TTPT-CpRXdAa6.js → ebnfDiagram-V4P3TTPT-CzQv5iDM.js} +1 -1
- package/dist/web/assets/{elk-QONTBCTQ-BZa9Vnbc.js → elk-QONTBCTQ-DNR9HWcl.js} +1 -1
- package/dist/web/assets/{erDiagram-KYQIGQAA-7AaYG_a9.js → erDiagram-KYQIGQAA-aJlTdb9w.js} +1 -1
- package/dist/web/assets/{eventmodeling-K75KTNOO-BzIg1KNf.js → eventmodeling-K75KTNOO-D4J-ujy-.js} +1 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-Bq7dmPlS.js +1 -0
- package/dist/web/assets/{ganttDiagram-R7GPNTJY-DeNg-7K2.js → ganttDiagram-R7GPNTJY-DgytSfqO.js} +1 -1
- package/dist/web/assets/{gitGraph-VSP46ZUC-BR-A7hdM.js → gitGraph-VSP46ZUC-BtjueFHB.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-LMyLpeqK.js → gitGraphDiagram-PDYL7XHY-WP0Hjdh9.js} +1 -1
- package/dist/web/assets/index-BesPYgjq.js +156 -0
- package/dist/web/assets/{index-CEM8pkOB.css → index-qYBdGqKM.css} +1 -1
- package/dist/web/assets/{info-OHQRW6UA-CRbCGNnm.js → info-OHQRW6UA-iuP0WTfn.js} +1 -1
- package/dist/web/assets/{infoDiagram-MVDRKAX5-DTUWae1O.js → infoDiagram-MVDRKAX5-CpAtMyKS.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-CE4r0jTM.js → ishikawaDiagram-HPEHV2GE-JTnItv7N.js} +1 -1
- package/dist/web/assets/{journeyDiagram-BOSHW7O7-KcSuVnTV.js → journeyDiagram-BOSHW7O7-B333k1QJ.js} +1 -1
- package/dist/web/assets/{kanban-definition-FNSA4HC5-I0QF1Ftr.js → kanban-definition-FNSA4HC5-DpCk5ZH0.js} +1 -1
- package/dist/web/assets/{line-DLSy1-se.js → line-CLs-qFxt.js} +1 -1
- package/dist/web/assets/{linear-XWNSUdmR.js → linear-E9AWqBNs.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-BcMfaJSH.js → mermaid-parser.core-D1rEzlb-.js} +3 -3
- package/dist/web/assets/{mermaid.core-CPl3cZ55.js → mermaid.core-D6TNGGsX.js} +4 -4
- package/dist/web/assets/{mindmap-definition-6OY57UG3-BIB4ahHf.js → mindmap-definition-6OY57UG3-DrpuGWuc.js} +1 -1
- package/dist/web/assets/{packet-JDAUHWVQ-UZicf9-F.js → packet-JDAUHWVQ-BpBr3aKk.js} +1 -1
- package/dist/web/assets/{pegDiagram-ECIUJNBX-ckOqIfTt.js → pegDiagram-ECIUJNBX-BrZX1cdv.js} +1 -1
- package/dist/web/assets/{pie-XZMESJXO-DErhKWJX.js → pie-XZMESJXO-CcTpis-K.js} +1 -1
- package/dist/web/assets/{pieDiagram-GNMPY74T-CrnS_2qe.js → pieDiagram-GNMPY74T-CR2em875.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-D57K4MC3-UK4DsDJz.js → quadrantDiagram-D57K4MC3-D-ET5xbH.js} +1 -1
- package/dist/web/assets/{radar-ABXABTNO-BYgJm4I9.js → radar-ABXABTNO-CTFFPCi_.js} +1 -1
- package/dist/web/assets/{railroad-I3PHUGI6-2n6yUb7T.js → railroad-I3PHUGI6-S6MHolw_.js} +1 -1
- package/dist/web/assets/{railroad-abnf-LNEFI6M7-CGAFqTzs.js → railroad-abnf-LNEFI6M7-NWxYLYMN.js} +1 -1
- package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-gJx_FXYW.js → railroad-ebnf-DWYD2UWJ-CRnaPl26.js} +1 -1
- package/dist/web/assets/{railroad-peg-JR7BVNR7-Dm0kEaxK.js → railroad-peg-JR7BVNR7-CB85ffhO.js} +1 -1
- package/dist/web/assets/{railroadDiagram-Y72NRBL7-CqzySn4U.js → railroadDiagram-Y72NRBL7-Ba2xIF7q.js} +1 -1
- package/dist/web/assets/{requirementDiagram-EQYSW5RA-B33Hrc0L.js → requirementDiagram-EQYSW5RA-B7faqgDT.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-SY24JHO5-BA9Smc0j.js → sankeyDiagram-SY24JHO5-IjJVI58S.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-BLEHNHQU-CUA-rKot.js → sequenceDiagram-BLEHNHQU-D-h5jxwQ.js} +1 -1
- package/dist/web/assets/{src-htchAh5W.js → src-hBdSvoFJ.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-kIYMkX3_.js → stateDiagram-v2-A6IESKWZ-rlZaPFqZ.js} +1 -1
- package/dist/web/assets/{swimlanes-B3ENRKFQ-C8Lzsqig.js → swimlanes-B3ENRKFQ-CqgKwSbP.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BGvWQ2Hp.js +8 -0
- package/dist/web/assets/{timeline-definition-JXYKGLGB-C3uOHb1e.js → timeline-definition-JXYKGLGB-DD76ZtY0.js} +1 -1
- package/dist/web/assets/{treeView-D4JQ5SDB-Dq-ug1SE.js → treeView-D4JQ5SDB-CcwWh4Bw.js} +1 -1
- package/dist/web/assets/{treemap-SAJKECNS-mX01fnXB.js → treemap-SAJKECNS-8lde6V5O.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-WPOLBNNX-DMBep2-D.js → usecaseDiagram-WPOLBNNX-CWowobZv.js} +1 -1
- package/dist/web/assets/{vennDiagram-Q3Y3IDCW-CUardqBP.js → vennDiagram-Q3Y3IDCW-U11W8pHe.js} +1 -1
- package/dist/web/assets/{wardley-7MLQ67FV-CIOqSz29.js → wardley-7MLQ67FV-Dp2EaBmo.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-IVAIDKWA-DtfU2fzi.js → wardleyDiagram-IVAIDKWA-7Qnos8ak.js} +1 -1
- package/dist/web/assets/{xychartDiagram-4URNHDU4-Cb3se94q.js → xychartDiagram-4URNHDU4-BwfHNtsG.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/channel-CpEW0zog.js +0 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-FDG_7_Jf.js +0 -1
- package/dist/web/assets/index-D2dQZgkr.js +0 -147
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-CWrPdxiH.js +0 -8
package/dist/server/service.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { CapacityRetries } from "./capacity-retry.js";
|
|
2
|
+
import { isModelCapacityError } from "../shared/turn-errors.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
1
4
|
import { attentionKey, pendingAttention } from "../shared/thread-attention.js";
|
|
2
5
|
import { ShellService } from "./shell.js";
|
|
6
|
+
import { assertProjectInstructionsMissing, PROJECT_INIT_PROMPT } from "./project-init.js";
|
|
7
|
+
import { recapInput } from "./thread-recap.js";
|
|
3
8
|
import { readGithubRepository } from "./github-repository.js";
|
|
9
|
+
import { readWorkspaceDiff } from "./workspace-diff.js";
|
|
4
10
|
import { realpathSync } from "node:fs";
|
|
5
11
|
import { cp } from "node:fs/promises";
|
|
6
12
|
import { WorkspaceRegistry } from "./workspaces.js";
|
|
@@ -13,6 +19,7 @@ import {
|
|
|
13
19
|
} from "../shared/elicitation.js";
|
|
14
20
|
import { toThreadSummary } from "../shared/thread-summary.js";
|
|
15
21
|
import {
|
|
22
|
+
requestDigest,
|
|
16
23
|
StoreConflict
|
|
17
24
|
} from "./db/operations.js";
|
|
18
25
|
import { EventHub } from "./events.js";
|
|
@@ -49,11 +56,16 @@ class GatewayService {
|
|
|
49
56
|
this.shell = new ShellService(this.codex);
|
|
50
57
|
this.events = new EventHub(options.limits?.eventBytes);
|
|
51
58
|
this.projection = new Projection(options.limits?.itemBytes);
|
|
59
|
+
this.capacityRetries = new CapacityRetries(
|
|
60
|
+
(job) => this.dispatchCapacityRetry(job),
|
|
61
|
+
(id) => this.publishThread(id)
|
|
62
|
+
);
|
|
52
63
|
this.currentEpoch = this.codex.epoch;
|
|
53
64
|
this.store.recover();
|
|
54
65
|
this.removeListener = this.codex.onEvent((event) => this.consume(event));
|
|
55
66
|
}
|
|
56
67
|
options;
|
|
68
|
+
capacityRetries;
|
|
57
69
|
projectClones;
|
|
58
70
|
shell;
|
|
59
71
|
events;
|
|
@@ -74,6 +86,7 @@ class GatewayService {
|
|
|
74
86
|
archiveRevisions = /* @__PURE__ */ new Map();
|
|
75
87
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
76
88
|
renameChanges = /* @__PURE__ */ new Set();
|
|
89
|
+
goalWrites = /* @__PURE__ */ new Set();
|
|
77
90
|
reads = /* @__PURE__ */ new Map();
|
|
78
91
|
resumes = /* @__PURE__ */ new Map();
|
|
79
92
|
executionReads = /* @__PURE__ */ new Map();
|
|
@@ -86,6 +99,7 @@ class GatewayService {
|
|
|
86
99
|
runtimeSettingsUpdates = /* @__PURE__ */ new Map();
|
|
87
100
|
inFlightOperations = /* @__PURE__ */ new Set();
|
|
88
101
|
pendingThreadCreations = /* @__PURE__ */ new Map();
|
|
102
|
+
capacityLinks = /* @__PURE__ */ new Map();
|
|
89
103
|
listProjects() {
|
|
90
104
|
return this.projects.list();
|
|
91
105
|
}
|
|
@@ -95,6 +109,29 @@ class GatewayService {
|
|
|
95
109
|
this.projects.get(projectId);
|
|
96
110
|
return { url };
|
|
97
111
|
}
|
|
112
|
+
async getProjectDiff(projectId) {
|
|
113
|
+
const project = this.projects.get(projectId);
|
|
114
|
+
const result = await readWorkspaceDiff(
|
|
115
|
+
project.canonicalRoot,
|
|
116
|
+
this.workspaces.allocations().map((row) => row.canonicalRoot)
|
|
117
|
+
);
|
|
118
|
+
this.projects.get(projectId);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
async getThreadDiff(threadId) {
|
|
122
|
+
let link = this.store.getThreadLink(threadId);
|
|
123
|
+
if (!link) {
|
|
124
|
+
this.normalize(await this.codex.readThreadMetadata(threadId));
|
|
125
|
+
link = this.store.getThreadLink(threadId);
|
|
126
|
+
}
|
|
127
|
+
const { cwd } = this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
|
|
128
|
+
const result = await readWorkspaceDiff(
|
|
129
|
+
cwd,
|
|
130
|
+
this.workspaces.allocations().filter((row) => row.threadId !== threadId).map((row) => row.canonicalRoot)
|
|
131
|
+
);
|
|
132
|
+
this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
98
135
|
async getThreadGithubRepository(threadId) {
|
|
99
136
|
let link = this.store.getThreadLink(threadId);
|
|
100
137
|
if (!link) {
|
|
@@ -132,6 +169,8 @@ class GatewayService {
|
|
|
132
169
|
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
170
|
}
|
|
134
171
|
prepareUpdate(reason = "update") {
|
|
172
|
+
if (this.capacityRetries.active())
|
|
173
|
+
return { ready: false, reason: "\u7B49\u5F85\u6A21\u578B\u5BB9\u91CF\u91CD\u8BD5\u7ED3\u675F\u6216\u53D6\u6D88" };
|
|
135
174
|
if (this.projectClones.hasActive) return { ready: false, reason: "\u7B49\u5F85\u9879\u76EE\u514B\u9686\u5B8C\u6210\u6216\u53D6\u6D88" };
|
|
136
175
|
if (this.shell.active()) return { ready: false, reason: "\u7B49\u5F85 Shell \u547D\u4EE4\u7ED3\u675F" };
|
|
137
176
|
if (this.inFlightOperations.size || this.resumes.size || this.pendingThreadCreations.size)
|
|
@@ -179,7 +218,7 @@ class GatewayService {
|
|
|
179
218
|
return link ? link.projectId === projectId : entry.workspaceKey === project.workspaceKey;
|
|
180
219
|
});
|
|
181
220
|
const active = this.projection.values().find(
|
|
182
|
-
(thread) => thread.projectId === projectId && (isActive(thread.status) || thread.actions.some((action) => ["pending", "responding"].includes(action.status)))
|
|
221
|
+
(thread) => thread.projectId === projectId && (isActive(thread.status) || this.capacityRetries.pending(thread.id) || thread.actions.some((action) => ["pending", "responding"].includes(action.status)))
|
|
183
222
|
);
|
|
184
223
|
if (pending || marker || active || this.shell.active(projectId))
|
|
185
224
|
throw new AppError(
|
|
@@ -453,21 +492,34 @@ class GatewayService {
|
|
|
453
492
|
updateArchiveState(threadId, archived) {
|
|
454
493
|
const thread = this.projection.get(threadId);
|
|
455
494
|
if (!thread || Boolean(thread.archived) === archived) return;
|
|
495
|
+
if (archived) this.capacityRetries.clear(threadId);
|
|
456
496
|
thread.archived = archived;
|
|
457
497
|
this.archiveRevisions.set(threadId, (this.archiveRevisions.get(threadId) ?? 0) + 1);
|
|
458
498
|
if (archived) this.releaseThreadOwnership(threadId);
|
|
459
499
|
this.projection.touch(threadId);
|
|
460
500
|
if (!this.archiveChanges.has(threadId)) this.publishThread(threadId);
|
|
461
501
|
}
|
|
502
|
+
async refreshGoal(thread) {
|
|
503
|
+
if (thread.goal !== void 0 || !this.codex.ready || !this.codex.getGoal) return;
|
|
504
|
+
try {
|
|
505
|
+
const goal = await this.codex.getGoal(thread.id);
|
|
506
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
507
|
+
if (thread.goal === void 0) thread.goal = goal;
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
}
|
|
462
511
|
async getThreadMetadata(threadId) {
|
|
463
512
|
const cached = this.projection.get(threadId);
|
|
464
513
|
if (cached) {
|
|
465
514
|
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
466
515
|
if (this.codex.ready) await this.refreshThreadMetadata(threadId);
|
|
467
516
|
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
517
|
+
await this.refreshGoal(cached);
|
|
468
518
|
return { ...this.threadView(cached), turns: [], actions: [] };
|
|
469
519
|
}
|
|
470
520
|
const metadata = this.threadView(this.normalize(await this.codex.readThreadMetadata(threadId)));
|
|
521
|
+
await this.refreshGoal(metadata);
|
|
522
|
+
metadata.historyVisible = this.historyVisible(metadata);
|
|
471
523
|
return { ...metadata, turns: [], actions: [] };
|
|
472
524
|
}
|
|
473
525
|
async getThread(threadId) {
|
|
@@ -476,6 +528,7 @@ class GatewayService {
|
|
|
476
528
|
if (cached && this.codex.ready) await this.refreshThreadMetadata(threadId);
|
|
477
529
|
const thread = this.projection.get(threadId);
|
|
478
530
|
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
531
|
+
await this.refreshGoal(thread);
|
|
479
532
|
return this.threadView(thread);
|
|
480
533
|
}
|
|
481
534
|
getThreadAttention(threadId) {
|
|
@@ -494,6 +547,11 @@ class GatewayService {
|
|
|
494
547
|
throw new AppError("HISTORY_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u5386\u53F2\u5206\u9875", 409);
|
|
495
548
|
const page = this.normalize(await this.codex.readThreadPage(threadId, cursor));
|
|
496
549
|
this.workspaces.assertOwnership(page.projectId, page.cwd, page.id);
|
|
550
|
+
const links = this.retryLinks(threadId);
|
|
551
|
+
for (const turn of page.turns) {
|
|
552
|
+
const root = links.get(turn.id);
|
|
553
|
+
if (root) turn.retryOf = root;
|
|
554
|
+
}
|
|
497
555
|
return page;
|
|
498
556
|
}
|
|
499
557
|
async exportThread(threadId) {
|
|
@@ -620,6 +678,7 @@ class GatewayService {
|
|
|
620
678
|
}
|
|
621
679
|
async resumeThread(threadId, input) {
|
|
622
680
|
return this.perform("thread.resume", threadId, input, async () => {
|
|
681
|
+
this.capacityRetries.clear(threadId);
|
|
623
682
|
const thread = await this.loadThread(threadId);
|
|
624
683
|
this.ensureReady();
|
|
625
684
|
this.ensureArchiveAvailable(thread);
|
|
@@ -721,40 +780,218 @@ class GatewayService {
|
|
|
721
780
|
});
|
|
722
781
|
}
|
|
723
782
|
async startTurn(threadId, input) {
|
|
783
|
+
return this.dispatchTurn(threadId, input);
|
|
784
|
+
}
|
|
785
|
+
async dispatchTurn(threadId, input, retry, expectedTurnId) {
|
|
724
786
|
return this.perform("turn.start", threadId, input, async () => {
|
|
787
|
+
if (!retry && !expectedTurnId && this.capacityRetries.view(threadId)?.status === "waiting")
|
|
788
|
+
this.capacityRetries.clear(threadId);
|
|
725
789
|
const thread = await this.loadThread(threadId);
|
|
726
790
|
this.ensureManaged(thread);
|
|
727
791
|
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
728
|
-
if (
|
|
792
|
+
if (expectedTurnId) {
|
|
793
|
+
const last = thread.turns.at(-1);
|
|
794
|
+
if (last?.id !== expectedTurnId || last.status !== "failed" || !isModelCapacityError(last.error ?? "", last.errorCode))
|
|
795
|
+
throw new AppError("TURN_CONFLICT", "\u76EE\u6807\u5BB9\u91CF\u5931\u8D25\u5DF2\u6539\u53D8\uFF0C\u8BF7\u5237\u65B0\u540E\u7EE7\u7EED", 409);
|
|
796
|
+
}
|
|
797
|
+
if (isActive(this.projection.get(threadId)?.status ?? thread.status))
|
|
729
798
|
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
799
|
this.ensureCompatibilityConfirmed();
|
|
800
|
+
if (input.taskKind === "init") await assertProjectInstructionsMissing(ownership.cwd);
|
|
731
801
|
this.store.dispatch(input.clientOperationId, {
|
|
732
802
|
threadId,
|
|
733
803
|
workspaceKey: ownership.workspaceKey,
|
|
734
804
|
codexEpoch: this.codex.epoch
|
|
735
805
|
});
|
|
806
|
+
if (retry && !this.capacityRetries.owns(retry))
|
|
807
|
+
throw new AppError("TURN_CONFLICT", "\u5BB9\u91CF\u91CD\u8BD5\u5DF2\u53D6\u6D88\u6216\u88AB\u66FF\u4EE3", 409);
|
|
808
|
+
const candidate = retry ?? this.capacityRetries.begin(
|
|
809
|
+
threadId,
|
|
810
|
+
input.clientOperationId,
|
|
811
|
+
this.retrySettings(thread.runtimeSettings, input.settings),
|
|
812
|
+
this.codex.epoch
|
|
813
|
+
);
|
|
814
|
+
const retryOf = retry?.rootTurnId ?? (expectedTurnId ? this.retryLinks(threadId).get(expectedTurnId) ?? expectedTurnId : void 0);
|
|
736
815
|
this.turnBaselines.set(input.clientOperationId, new Set(thread.turns.map((turn2) => turn2.id)));
|
|
737
816
|
this.publishThread(threadId);
|
|
738
817
|
const revision = this.projection.revision(threadId);
|
|
739
818
|
const turn = await this.codex.startTurn(
|
|
740
819
|
threadId,
|
|
741
820
|
ownership.cwd,
|
|
742
|
-
input.text,
|
|
821
|
+
input.taskKind === "init" ? PROJECT_INIT_PROMPT : input.text,
|
|
743
822
|
input.settings,
|
|
744
823
|
input.images
|
|
745
824
|
);
|
|
746
825
|
this.store.attachTurn(input.clientOperationId, turn.id);
|
|
747
826
|
this.turnBaselines.delete(input.clientOperationId);
|
|
748
827
|
this.projection.turn(threadId, turn, this.projection.revision(threadId) !== revision);
|
|
828
|
+
this.capacityRetries.observe({ type: "turn", threadId, turn }, input.clientOperationId);
|
|
829
|
+
if (retryOf) this.retryLinks(threadId).set(turn.id, retryOf);
|
|
830
|
+
if (candidate.cancelled && retry) await this.interruptCancelledCapacity(candidate, turn.id);
|
|
749
831
|
const projectedTurn = this.projection.get(threadId)?.turns.find((entry) => entry.id === turn.id);
|
|
750
832
|
if (projectedTurn && isTerminal(projectedTurn.status))
|
|
751
833
|
this.store.finishTurn(threadId, turn.id, this.codex.epoch);
|
|
752
834
|
this.publishThread(threadId);
|
|
753
|
-
return {
|
|
835
|
+
return {
|
|
836
|
+
result: { threadId, turnId: turn.id, ...retryOf ? { retryOf } : {} },
|
|
837
|
+
turnId: turn.id
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
retrySettings(runtime, input = {}) {
|
|
842
|
+
return {
|
|
843
|
+
...runtime?.model ? { model: runtime.model } : {},
|
|
844
|
+
...runtime?.reasoningEffort ? { reasoningEffort: runtime.reasoningEffort } : {},
|
|
845
|
+
...runtime?.serviceTier === "fast" || runtime?.serviceTier === null ? { serviceTier: runtime.serviceTier } : {},
|
|
846
|
+
...input
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
async dispatchCapacityRetry(job) {
|
|
850
|
+
if (!this.capacityRetries.owns(job) || !this.accepting || this.maintenance || !this.codex.ready || this.codex.epoch !== job.epoch)
|
|
851
|
+
throw new AppError("TURN_CONFLICT", "\u5BB9\u91CF\u91CD\u8BD5\u7684\u8FD0\u884C\u73AF\u5883\u5DF2\u6539\u53D8", 409);
|
|
852
|
+
const thread = this.projection.get(job.threadId);
|
|
853
|
+
const last = thread?.turns.at(-1);
|
|
854
|
+
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))
|
|
855
|
+
throw new AppError("TURN_CONFLICT", "\u539F\u4EFB\u52A1\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u68C0\u67E5\u540E\u7EE7\u7EED", 409);
|
|
856
|
+
await this.dispatchTurn(
|
|
857
|
+
job.threadId,
|
|
858
|
+
{
|
|
859
|
+
clientOperationId: job.operationId,
|
|
860
|
+
text: "",
|
|
861
|
+
settings: job.settings
|
|
862
|
+
},
|
|
863
|
+
job
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
async cancelCapacityRetry(threadId, input) {
|
|
867
|
+
return this.perform("turn.retry.cancel", threadId, input, async () => {
|
|
868
|
+
const thread = await this.loadThread(threadId);
|
|
869
|
+
this.ensureManaged(thread);
|
|
870
|
+
const job = this.capacityRetries.current(threadId);
|
|
871
|
+
if (!job?.view || job.rootTurnId !== input.expectedTurnId)
|
|
872
|
+
throw new AppError("TURN_CONFLICT", "\u5BB9\u91CF\u91CD\u8BD5\u5DF2\u7ED3\u675F\u6216\u88AB\u66FF\u4EE3", 409);
|
|
873
|
+
this.store.dispatch(input.clientOperationId);
|
|
874
|
+
this.capacityRetries.cancel(job);
|
|
875
|
+
if (job.attempt > 0 && job.turnId) await this.interruptCancelledCapacity(job, job.turnId);
|
|
876
|
+
return { result: { threadId, cancelled: true } };
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
async interruptCancelledCapacity(job, turnId) {
|
|
880
|
+
const turn = this.projection.get(job.threadId)?.turns.find((entry) => entry.id === turnId);
|
|
881
|
+
if (!job.cancelled || job.interruptSent || !turn || !isActive(turn.status) || this.codex.epoch !== job.epoch || !this.codex.ready)
|
|
882
|
+
return;
|
|
883
|
+
job.interruptSent = true;
|
|
884
|
+
await this.codex.interruptTurn(job.threadId, turnId);
|
|
885
|
+
}
|
|
886
|
+
async retryTurn(threadId, input) {
|
|
887
|
+
return this.dispatchTurn(threadId, { ...input, text: "" }, void 0, input.expectedTurnId);
|
|
888
|
+
}
|
|
889
|
+
async getGoal(threadId) {
|
|
890
|
+
const thread = await this.loadThread(threadId);
|
|
891
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
892
|
+
this.ensureReady();
|
|
893
|
+
if (!this.codex.getGoal) throw new AppError("GOAL_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u76EE\u6807", 409);
|
|
894
|
+
const goal = await this.codex.getGoal(threadId);
|
|
895
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
896
|
+
const current = this.projection.get(threadId);
|
|
897
|
+
if (current) {
|
|
898
|
+
current.goal = goal;
|
|
899
|
+
this.projection.touch(threadId);
|
|
900
|
+
this.publishThread(threadId);
|
|
901
|
+
}
|
|
902
|
+
return { goal, version: requestDigest(goal) };
|
|
903
|
+
}
|
|
904
|
+
async mutateGoal(threadId, input) {
|
|
905
|
+
return this.perform("goal.update", threadId, input, async () => {
|
|
906
|
+
if (this.goalWrites.has(threadId))
|
|
907
|
+
throw new AppError("GOAL_BUSY", "\u76EE\u6807\u66F4\u65B0\u6B63\u5728\u8FDB\u884C\uFF0C\u8BF7\u7B49\u5F85\u56DE\u6267", 409);
|
|
908
|
+
this.goalWrites.add(threadId);
|
|
909
|
+
try {
|
|
910
|
+
const thread = await this.loadThread(threadId);
|
|
911
|
+
this.ensureManaged(thread);
|
|
912
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
913
|
+
const stopping = input.action === "clear" || input.action === "set" && input.status !== "active" && input.status !== void 0 && input.objective === void 0 && input.tokenBudget === void 0;
|
|
914
|
+
if (!stopping && (thread.status === "unknown" || isActive(thread.status) || thread.turns.some((turn) => isActive(turn.status)) || this.store.activeForThread(threadId)))
|
|
915
|
+
throw new AppError("THREAD_BUSY", "\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u5E76\u6838\u5BF9\u72B6\u6001\u540E\u518D\u4FEE\u6539\u6216\u6062\u590D\u76EE\u6807", 409);
|
|
916
|
+
this.ensureCompatibilityConfirmed();
|
|
917
|
+
if (!this.codex.getGoal || !this.codex.mutateGoal)
|
|
918
|
+
throw new AppError("GOAL_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u76EE\u6807", 409);
|
|
919
|
+
const before = await this.getGoal(threadId);
|
|
920
|
+
if (before.version !== input.expectedVersion)
|
|
921
|
+
throw new AppError("GOAL_CONFLICT", "\u76EE\u6807\u5DF2\u53D1\u751F\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u540E\u64CD\u4F5C", 409);
|
|
922
|
+
this.store.dispatch(input.clientOperationId);
|
|
923
|
+
const mutation = input.action === "clear" ? { action: "clear" } : {
|
|
924
|
+
action: "set",
|
|
925
|
+
...input.objective === void 0 ? {} : { objective: input.objective },
|
|
926
|
+
...input.status === void 0 ? {} : { status: input.status },
|
|
927
|
+
...input.tokenBudget === void 0 ? {} : { tokenBudget: input.tokenBudget }
|
|
928
|
+
};
|
|
929
|
+
const previousGoal = this.projection.get(threadId)?.goal;
|
|
930
|
+
const goal = await this.codex.mutateGoal(threadId, mutation);
|
|
931
|
+
const current = this.projection.get(threadId);
|
|
932
|
+
if (current && current.goal === previousGoal) {
|
|
933
|
+
current.goal = goal;
|
|
934
|
+
this.projection.touch(threadId);
|
|
935
|
+
this.publishThread(threadId);
|
|
936
|
+
}
|
|
937
|
+
return { result: { goal } };
|
|
938
|
+
} finally {
|
|
939
|
+
this.goalWrites.delete(threadId);
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
async recapThread(threadId, input) {
|
|
944
|
+
return this.perform("thread.recap", threadId, input, async () => {
|
|
945
|
+
const thread = await this.loadThread(threadId);
|
|
946
|
+
this.ensureManaged(thread);
|
|
947
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
948
|
+
if (thread.status === "unknown" || isActive(thread.status) || thread.turns.some((turn) => isActive(turn.status)))
|
|
949
|
+
throw new AppError("THREAD_BUSY", "\u8BF7\u7B49\u5F85\u4EFB\u52A1\u7ED3\u675F\u5E76\u6838\u5BF9\u72B6\u6001\u540E\u518D\u751F\u6210\u6458\u8981", 409);
|
|
950
|
+
this.ensureCompatibilityConfirmed();
|
|
951
|
+
if (!this.codex.recapThread)
|
|
952
|
+
throw new AppError("METHOD_UNAVAILABLE", "Codex \u4E0D\u652F\u6301\u4F1A\u8BDD\u6458\u8981", 409);
|
|
953
|
+
const history = await (this.codex.readThreadPage?.(threadId) ?? this.codex.readThread(threadId));
|
|
954
|
+
this.workspaces.assertOwnership(thread.projectId, history.cwd, history.id);
|
|
955
|
+
const { prompt, partial } = recapInput(history, input.language);
|
|
956
|
+
if (!history.turns.some(
|
|
957
|
+
(turn) => turn.items.some(
|
|
958
|
+
(item) => (item.type === "userMessage" || item.type === "agentMessage") && item.text?.trim()
|
|
959
|
+
)
|
|
960
|
+
))
|
|
961
|
+
throw new AppError("EMPTY_THREAD", "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u53EF\u751F\u6210\u6458\u8981\u7684\u6D88\u606F", 409);
|
|
962
|
+
this.store.dispatch(input.clientOperationId);
|
|
963
|
+
const summary = await this.codex.recapThread(threadId, prompt);
|
|
964
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
965
|
+
return { result: { summary, partial, readAt: Date.now() } };
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
async getGuardianDenials(threadId) {
|
|
969
|
+
const thread = await this.loadThread(threadId);
|
|
970
|
+
this.ensureManaged(thread);
|
|
971
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
972
|
+
if (!this.codex.listGuardianDenials)
|
|
973
|
+
throw new AppError("METHOD_UNAVAILABLE", "Codex \u4E0D\u652F\u6301\u62D2\u7EDD\u8BB0\u5F55\u8BFB\u53D6", 409);
|
|
974
|
+
return { data: this.codex.listGuardianDenials(threadId) };
|
|
975
|
+
}
|
|
976
|
+
async approveGuardianDenial(threadId, input) {
|
|
977
|
+
return this.perform("guardian.approve", threadId, input, async () => {
|
|
978
|
+
const thread = await this.loadThread(threadId);
|
|
979
|
+
this.ensureManaged(thread);
|
|
980
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
981
|
+
if (thread.status === "unknown" || isActive(thread.status) || thread.turns.some((turn) => isActive(turn.status)))
|
|
982
|
+
throw new AppError("THREAD_BUSY", "\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u5E76\u786E\u8BA4\u72B6\u6001\u540E\u518D\u6388\u6743\u91CD\u8BD5", 409);
|
|
983
|
+
this.ensureCompatibilityConfirmed();
|
|
984
|
+
if (!this.codex.approveGuardianDenial || !this.codex.listGuardianDenials?.(threadId).some((entry) => entry.id === input.denialId))
|
|
985
|
+
throw new AppError("DENIAL_EXPIRED", "\u62D2\u7EDD\u8BB0\u5F55\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD", 409);
|
|
986
|
+
this.store.dispatch(input.clientOperationId);
|
|
987
|
+
await this.codex.approveGuardianDenial(threadId, input.denialId);
|
|
988
|
+
this.publishThread(threadId);
|
|
989
|
+
return { result: { threadId } };
|
|
754
990
|
});
|
|
755
991
|
}
|
|
756
992
|
async compactThread(threadId, input) {
|
|
757
993
|
return this.perform("thread.compact", threadId, input, async () => {
|
|
994
|
+
this.capacityRetries.clear(threadId);
|
|
758
995
|
const thread = await this.loadThread(threadId);
|
|
759
996
|
this.ensureManaged(thread);
|
|
760
997
|
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
@@ -779,6 +1016,7 @@ class GatewayService {
|
|
|
779
1016
|
}
|
|
780
1017
|
async startReview(threadId, input) {
|
|
781
1018
|
return this.perform("review.start", threadId, input, async () => {
|
|
1019
|
+
this.capacityRetries.clear(threadId);
|
|
782
1020
|
const thread = await this.loadThread(threadId);
|
|
783
1021
|
this.ensureManaged(thread);
|
|
784
1022
|
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
@@ -796,7 +1034,7 @@ class GatewayService {
|
|
|
796
1034
|
});
|
|
797
1035
|
const revision = this.projection.revision(threadId);
|
|
798
1036
|
this.turnBaselines.set(input.clientOperationId, new Set(thread.turns.map((turn2) => turn2.id)));
|
|
799
|
-
const turn = await this.codex.startReview(threadId);
|
|
1037
|
+
const turn = await this.codex.startReview(threadId, input.target);
|
|
800
1038
|
this.store.attachTurn(input.clientOperationId, turn.id);
|
|
801
1039
|
this.turnBaselines.delete(input.clientOperationId);
|
|
802
1040
|
this.projection.turn(threadId, turn, this.projection.revision(threadId) !== revision);
|
|
@@ -916,6 +1154,7 @@ class GatewayService {
|
|
|
916
1154
|
}
|
|
917
1155
|
async shutdown() {
|
|
918
1156
|
this.accepting = false;
|
|
1157
|
+
this.capacityRetries.clearAll();
|
|
919
1158
|
await this.projectClones.close();
|
|
920
1159
|
this.store.recover();
|
|
921
1160
|
this.projection.runtimeReset();
|
|
@@ -957,6 +1196,8 @@ class GatewayService {
|
|
|
957
1196
|
const action = this.projection.resolveAction(target, "pending");
|
|
958
1197
|
if (action?.threadId) this.publishThread(action.threadId);
|
|
959
1198
|
}
|
|
1199
|
+
if (kind === "turn.start")
|
|
1200
|
+
this.capacityRetries.dispatchFailed(target, input.clientOperationId);
|
|
960
1201
|
if (dispatched && kind === "turn.start") this.publishThread(target);
|
|
961
1202
|
this.events.publish("operation", operation);
|
|
962
1203
|
if (uncertain)
|
|
@@ -1187,11 +1428,28 @@ class GatewayService {
|
|
|
1187
1428
|
}
|
|
1188
1429
|
historyVisible(thread) {
|
|
1189
1430
|
return Boolean(
|
|
1190
|
-
thread.hasMessages || thread.archived || thread.turns.length || this.store.activeForThread(thread.id) || isActive(thread.status) || thread.actions.some((action) => ["pending", "responding"].includes(action.status))
|
|
1431
|
+
thread.hasMessages || thread.archived || thread.goal || thread.turns.length || this.store.activeForThread(thread.id) || isActive(thread.status) || thread.actions.some((action) => ["pending", "responding"].includes(action.status))
|
|
1191
1432
|
);
|
|
1192
1433
|
}
|
|
1434
|
+
retryLinks(threadId) {
|
|
1435
|
+
let links = this.capacityLinks.get(threadId);
|
|
1436
|
+
if (!links) {
|
|
1437
|
+
links = this.store.capacityRetryTurns(threadId);
|
|
1438
|
+
this.capacityLinks.set(threadId, links);
|
|
1439
|
+
}
|
|
1440
|
+
return links;
|
|
1441
|
+
}
|
|
1193
1442
|
threadView(thread) {
|
|
1194
1443
|
const result = structuredClone(thread);
|
|
1444
|
+
const links = this.capacityLinks.get(thread.id) ?? (thread.turns.some((turn) => !turn.items.some((item) => item.type === "userMessage")) ? this.retryLinks(thread.id) : void 0);
|
|
1445
|
+
for (const turn of result.turns) {
|
|
1446
|
+
const root = links?.get(turn.id);
|
|
1447
|
+
if (root) turn.retryOf = root;
|
|
1448
|
+
}
|
|
1449
|
+
const retry = this.capacityRetries.view(thread.id);
|
|
1450
|
+
if (retry) result.capacityRetry = structuredClone(retry);
|
|
1451
|
+
else delete result.capacityRetry;
|
|
1452
|
+
if (this.capacityRetries.pending(thread.id)) result.status = "running";
|
|
1195
1453
|
result.archived = thread.archived ?? false;
|
|
1196
1454
|
result.historyVisible = this.historyVisible(thread);
|
|
1197
1455
|
const marker = this.store.activeForThread(thread.id);
|
|
@@ -1240,6 +1498,17 @@ class GatewayService {
|
|
|
1240
1498
|
}
|
|
1241
1499
|
}
|
|
1242
1500
|
consume(event) {
|
|
1501
|
+
if (event.type === "runtime" && (!event.diagnostics.ready || event.diagnostics.codexEpoch !== this.currentEpoch))
|
|
1502
|
+
this.capacityRetries.clearAll();
|
|
1503
|
+
const eventThread = event.type === "action" ? event.action.threadId : "threadId" in event ? event.threadId : void 0;
|
|
1504
|
+
const eventMarker = eventThread ? this.store.activeForThread(eventThread) : void 0;
|
|
1505
|
+
const matchesDispatch = event.type !== "turn" || eventMarker?.turnId === event.turn.id || eventMarker?.turnId === null && this.turnBaselines.get(eventMarker.operationId)?.has(event.turn.id) === false;
|
|
1506
|
+
this.capacityRetries.observe(event, matchesDispatch ? eventMarker?.operationId : void 0);
|
|
1507
|
+
if (event.type === "turn") {
|
|
1508
|
+
const job = this.capacityRetries.current(event.threadId);
|
|
1509
|
+
if (job?.attempt && job.turnId === event.turn.id && job.rootTurnId)
|
|
1510
|
+
this.retryLinks(event.threadId).set(event.turn.id, job.rootTurnId);
|
|
1511
|
+
}
|
|
1243
1512
|
if (event.type === "runtime") {
|
|
1244
1513
|
this.compatibilityConfirmation(event.diagnostics);
|
|
1245
1514
|
if (!event.diagnostics.ready || event.diagnostics.codexEpoch !== this.currentEpoch) {
|
|
@@ -1295,6 +1564,12 @@ class GatewayService {
|
|
|
1295
1564
|
}
|
|
1296
1565
|
const thread = this.projection.get(event.threadId);
|
|
1297
1566
|
if (!thread) return;
|
|
1567
|
+
if (event.type === "goal") {
|
|
1568
|
+
thread.goal = event.goal;
|
|
1569
|
+
this.projection.touch(thread.id);
|
|
1570
|
+
this.publishThread(thread.id);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1298
1573
|
const previousAttention = new Set(
|
|
1299
1574
|
event.type === "item" || event.type === "turn" ? pendingAttention(thread).map(attentionKey) : []
|
|
1300
1575
|
);
|
|
@@ -1304,7 +1579,13 @@ class GatewayService {
|
|
|
1304
1579
|
return;
|
|
1305
1580
|
}
|
|
1306
1581
|
if (event.type === "turnError" || event.type === "turnProgress") {
|
|
1307
|
-
const changed = event.type === "turnError" ? this.projection.turnError(
|
|
1582
|
+
const changed = event.type === "turnError" ? this.projection.turnError(
|
|
1583
|
+
thread.id,
|
|
1584
|
+
event.turnId,
|
|
1585
|
+
event.message,
|
|
1586
|
+
event.willRetry,
|
|
1587
|
+
event
|
|
1588
|
+
) : this.projection.turnProgress(thread.id, event.turnId);
|
|
1308
1589
|
if (changed) this.publishThread(thread.id);
|
|
1309
1590
|
return;
|
|
1310
1591
|
}
|
|
@@ -1324,6 +1605,32 @@ class GatewayService {
|
|
|
1324
1605
|
this.projection.touch(thread.id);
|
|
1325
1606
|
} else if (event.type === "turn") {
|
|
1326
1607
|
const previous = thread.turns.find((turn) => turn.id === event.turn.id);
|
|
1608
|
+
if (thread.goal && !isTerminal(event.turn.status) && (!previous || !isTerminal(previous.status)) && !this.store.activeForThread(thread.id) && this.store.getThreadLink(thread.id)?.managedEpoch === this.codex.epoch) {
|
|
1609
|
+
try {
|
|
1610
|
+
const ownership = this.workspaces.assertOwnership(
|
|
1611
|
+
thread.projectId,
|
|
1612
|
+
thread.cwd,
|
|
1613
|
+
thread.id
|
|
1614
|
+
);
|
|
1615
|
+
const operationId = randomUUID();
|
|
1616
|
+
this.store.accept(operationId, "goal.continue", thread.id, {
|
|
1617
|
+
turnId: event.turn.id,
|
|
1618
|
+
codexEpoch: this.codex.epoch
|
|
1619
|
+
});
|
|
1620
|
+
this.store.dispatch(operationId, {
|
|
1621
|
+
threadId: thread.id,
|
|
1622
|
+
workspaceKey: ownership.workspaceKey,
|
|
1623
|
+
codexEpoch: this.codex.epoch
|
|
1624
|
+
});
|
|
1625
|
+
this.store.attachTurn(operationId, event.turn.id);
|
|
1626
|
+
this.store.acknowledge(
|
|
1627
|
+
operationId,
|
|
1628
|
+
{ threadId: thread.id, turnId: event.turn.id },
|
|
1629
|
+
event.turn.id
|
|
1630
|
+
);
|
|
1631
|
+
} catch {
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1327
1634
|
const newCompletion = !previous || !isTerminal(previous.status);
|
|
1328
1635
|
if (isTerminal(event.turn.status)) this.turnUsage.save(thread.id, event.turn);
|
|
1329
1636
|
this.projection.turn(thread.id, event.turn);
|
|
@@ -1338,7 +1645,7 @@ class GatewayService {
|
|
|
1338
1645
|
if (isTerminal(event.turn.status)) {
|
|
1339
1646
|
this.store.finishTurn(thread.id, event.turn.id, this.codex.epoch);
|
|
1340
1647
|
this.projection.expireTurn(thread.id, event.turn.id);
|
|
1341
|
-
if (newCompletion) {
|
|
1648
|
+
if (newCompletion && !this.capacityRetries.pending(thread.id)) {
|
|
1342
1649
|
try {
|
|
1343
1650
|
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
1344
1651
|
this.events.publishCompletion({
|