@covro/covro 0.2.1-dev.3 → 0.2.1-dev.5
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 +8 -1
- package/README.zh-CN.md +8 -1
- package/dist/codex/adapter.js +8 -1
- package/dist/codex/adapter.js.map +2 -2
- package/dist/drizzle/0005_session_workspaces.sql +24 -0
- package/dist/drizzle/meta/0005_snapshot.json +480 -0
- package/dist/drizzle/meta/_journal.json +7 -0
- package/dist/server/db/operations.js +14 -1
- package/dist/server/db/operations.js.map +2 -2
- package/dist/server/db/schema.js +9 -1
- package/dist/server/db/schema.js.map +2 -2
- package/dist/server/http.js +41 -0
- package/dist/server/http.js.map +2 -2
- package/dist/server/local-files.js +2 -2
- package/dist/server/local-files.js.map +2 -2
- package/dist/server/main.js +1 -0
- package/dist/server/main.js.map +2 -2
- package/dist/server/project-clones.js +296 -0
- package/dist/server/project-clones.js.map +7 -0
- package/dist/server/service.js +116 -51
- package/dist/server/service.js.map +2 -2
- package/dist/server/workspaces.js +142 -0
- package/dist/server/workspaces.js.map +7 -0
- package/dist/shared/validation.js +5 -0
- package/dist/shared/validation.js.map +2 -2
- package/dist/shared/workspace-scope.js +9 -0
- package/dist/shared/workspace-scope.js.map +7 -0
- package/dist/web/assets/{abnfDiagram-WEYOIA3C-6n1mVJ0V.js → abnfDiagram-WEYOIA3C-CwLX554E.js} +1 -1
- package/dist/web/assets/{arc-DFIzIYgQ.js → arc-DAaDyyqc.js} +1 -1
- package/dist/web/assets/{architecture-WOLXFQ4H-CoZpoP0F.js → architecture-WOLXFQ4H-y6MnEqN0.js} +1 -1
- package/dist/web/assets/{architectureDiagram-ZHVYKT53-LcqVhgRh.js → architectureDiagram-ZHVYKT53-_B_4k-Pp.js} +1 -1
- package/dist/web/assets/{blockDiagram-RJAE7QA3-CnVQsmM8.js → blockDiagram-RJAE7QA3-C9jZ7FHy.js} +1 -1
- package/dist/web/assets/{c4Diagram-XDPPPE54-C38UO-rV.js → c4Diagram-XDPPPE54-DQAtMvqN.js} +1 -1
- package/dist/web/assets/channel-ByzMCsOB.js +1 -0
- package/dist/web/assets/{chunk-2E7WHVMD-SlpXCVVC.js → chunk-2E7WHVMD-Bu427yab.js} +1 -1
- package/dist/web/assets/{chunk-4EMNKR2T-BPJa0lfJ.js → chunk-4EMNKR2T-BrGrBG7B.js} +1 -1
- package/dist/web/assets/{chunk-55XCU76K-CkP_cE5N.js → chunk-55XCU76K-l4f0OZWA.js} +1 -1
- package/dist/web/assets/{chunk-7S6XSTFO-BnrWU32l.js → chunk-7S6XSTFO-D7ixF313.js} +1 -1
- package/dist/web/assets/{chunk-AQDTSKZB-J2M9tBY-.js → chunk-AQDTSKZB-Cesd-BF2.js} +1 -1
- package/dist/web/assets/{chunk-DSNLSHJZ-ClfNpgVO.js → chunk-DSNLSHJZ-B7dim5uT.js} +1 -1
- package/dist/web/assets/{chunk-HAGFOTED-k7XcZqWB.js → chunk-HAGFOTED-B-f2I7TA.js} +1 -1
- package/dist/web/assets/{chunk-HNE4Q36P-DHHenyG4.js → chunk-HNE4Q36P-CoIvaeX6.js} +1 -1
- package/dist/web/assets/{chunk-NQOKRVIF-CkA3TLYc.js → chunk-NQOKRVIF-BybRxR7n.js} +1 -1
- package/dist/web/assets/{chunk-UHW7PFBP-DSLKZJoJ.js → chunk-UHW7PFBP-CV5WG4tq.js} +1 -1
- package/dist/web/assets/{chunk-VQ5W62QI-BBkSWILl.js → chunk-VQ5W62QI-BO4AjXi1.js} +1 -1
- package/dist/web/assets/{chunk-WQQL4CDK-BeKgda0U.js → chunk-WQQL4CDK-CADNDGXO.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-BC-ozzjn.js → chunk-XXDRQBXY-C6QZT7KW.js} +1 -1
- package/dist/web/assets/{chunk-YZO67HFG-C19e3AP5.js → chunk-YZO67HFG-Cm6eGReX.js} +1 -1
- package/dist/web/assets/{chunk-ZAUX2LTX-C03NsQfr.js → chunk-ZAUX2LTX-CUBoGbKg.js} +1 -1
- package/dist/web/assets/{chunk-ZCHMQSOR-CrXzPEWh.js → chunk-ZCHMQSOR-B0ImOOtq.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-4MYWR3UA-DCbxTUm6.js → classDiagram-v2-4MYWR3UA-D5NWZ9vm.js} +1 -1
- package/dist/web/assets/{cose-bilkent-JH36ORCC-CQZjK1BR.js → cose-bilkent-JH36ORCC-BzckEyjA.js} +1 -1
- package/dist/web/assets/{cynefin-EF2NZ3EQ-i1jWxeqI.js → cynefin-EF2NZ3EQ-Da_J6-Jb.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-WF24UDRP-DfVYiPE9.js → cynefinDiagram-WF24UDRP-b4g_B1A-.js} +1 -1
- package/dist/web/assets/{dagre-NK4GIZVX-OhkyZBvK.js → dagre-NK4GIZVX-89H2mfs9.js} +1 -1
- package/dist/web/assets/{diagram-CBK3MWTC-CXdVEIne.js → diagram-CBK3MWTC-CW4f_P0l.js} +1 -1
- package/dist/web/assets/{diagram-I5XIIV2D-BjrxVPW0.js → diagram-I5XIIV2D-Cv5Qndgc.js} +1 -1
- package/dist/web/assets/{diagram-ODIL2UCW-C5BpAqPG.js → diagram-ODIL2UCW-LrXxeoCt.js} +1 -1
- package/dist/web/assets/{diagram-PFBEJUXE-3yWOE5ZP.js → diagram-PFBEJUXE-uJV2PooB.js} +1 -1
- package/dist/web/assets/{diagram-RJA3I4VV-DdRZojn9.js → diagram-RJA3I4VV-DlY0PGRs.js} +1 -1
- package/dist/web/assets/{diagram-TYUNFSNR-m_gnI0-3.js → diagram-TYUNFSNR-B1KoXBEE.js} +1 -1
- package/dist/web/assets/{dist-DMGebNoK.js → dist-B4eX4BCP.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-V4P3TTPT-CVwc9K-O.js → ebnfDiagram-V4P3TTPT-xCvQVbdj.js} +1 -1
- package/dist/web/assets/{elk-QONTBCTQ-BDC-J8E3.js → elk-QONTBCTQ-Cl8Hu-Uy.js} +1 -1
- package/dist/web/assets/{erDiagram-KYQIGQAA-D-dqUb0a.js → erDiagram-KYQIGQAA-Xp-AvSwE.js} +1 -1
- package/dist/web/assets/{eventmodeling-K75KTNOO-ByZ_MfSZ.js → eventmodeling-K75KTNOO-C0truZGU.js} +1 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-T67jQ-T_.js +1 -0
- package/dist/web/assets/{ganttDiagram-R7GPNTJY-BHzyPnqV.js → ganttDiagram-R7GPNTJY-rPaYp-D7.js} +1 -1
- package/dist/web/assets/{gitGraph-VSP46ZUC-DQhxrj3o.js → gitGraph-VSP46ZUC-Bgfpho41.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-8M2oNXTx.js → gitGraphDiagram-PDYL7XHY-kDcmuv6k.js} +1 -1
- package/dist/web/assets/index-3DUP8YCe.js +142 -0
- package/dist/web/assets/index-B7tnf28H.css +1 -0
- package/dist/web/assets/{info-OHQRW6UA-717A6Kfa.js → info-OHQRW6UA-CAWg5k9R.js} +1 -1
- package/dist/web/assets/{infoDiagram-MVDRKAX5-D-fxOvE-.js → infoDiagram-MVDRKAX5-Cd08ebhn.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-CKrXOGrR.js → ishikawaDiagram-HPEHV2GE-tAXBLt9k.js} +1 -1
- package/dist/web/assets/{journeyDiagram-BOSHW7O7-BLD4lmI6.js → journeyDiagram-BOSHW7O7-ZgnWqXkg.js} +1 -1
- package/dist/web/assets/{kanban-definition-FNSA4HC5-Mj1GI5Fb.js → kanban-definition-FNSA4HC5-BUjtjd-h.js} +1 -1
- package/dist/web/assets/{line-DotNyRbu.js → line-DG48HRVK.js} +1 -1
- package/dist/web/assets/{linear-DQ0Uvp2K.js → linear-CqnXVCVw.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-BtFC_UVn.js → mermaid-parser.core-gIvgB9WK.js} +3 -3
- package/dist/web/assets/{mermaid.core-DHL14mKq.js → mermaid.core-C_R7Y1XB.js} +4 -4
- package/dist/web/assets/{mindmap-definition-6OY57UG3-CMCguBhk.js → mindmap-definition-6OY57UG3-CwUEEl_y.js} +1 -1
- package/dist/web/assets/{packet-JDAUHWVQ-CGyEqUmo.js → packet-JDAUHWVQ-BrXmpRQ3.js} +1 -1
- package/dist/web/assets/{pegDiagram-ECIUJNBX-DPgLxTHJ.js → pegDiagram-ECIUJNBX-lLApO85Q.js} +1 -1
- package/dist/web/assets/{pie-XZMESJXO-Ddsl6geS.js → pie-XZMESJXO-C_8kR5WX.js} +1 -1
- package/dist/web/assets/{pieDiagram-GNMPY74T-BJdkqrU_.js → pieDiagram-GNMPY74T-CawGmJZn.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-D57K4MC3-47l23gov.js → quadrantDiagram-D57K4MC3-Dg6Yq3s_.js} +1 -1
- package/dist/web/assets/{radar-ABXABTNO-DAyB1li0.js → radar-ABXABTNO-Bs1xY09D.js} +1 -1
- package/dist/web/assets/{railroad-I3PHUGI6-BZYuBfPR.js → railroad-I3PHUGI6-Bw_qS6e3.js} +1 -1
- package/dist/web/assets/{railroad-abnf-LNEFI6M7-PBkoxIWP.js → railroad-abnf-LNEFI6M7-CQr33CuI.js} +1 -1
- package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-Bl_rTgpR.js → railroad-ebnf-DWYD2UWJ-BY9nk5pg.js} +1 -1
- package/dist/web/assets/{railroad-peg-JR7BVNR7-U9kj7aP3.js → railroad-peg-JR7BVNR7-CcFsAEI4.js} +1 -1
- package/dist/web/assets/{railroadDiagram-Y72NRBL7-Dpq4us5_.js → railroadDiagram-Y72NRBL7-rkZbchmc.js} +1 -1
- package/dist/web/assets/{requirementDiagram-EQYSW5RA-CCqQbcTz.js → requirementDiagram-EQYSW5RA-CSOcijj5.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-SY24JHO5-DDwytZM3.js → sankeyDiagram-SY24JHO5-E0uo0rRp.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-BLEHNHQU-C-z57Y06.js → sequenceDiagram-BLEHNHQU-BKWAAcRu.js} +1 -1
- package/dist/web/assets/{src-CK51kAyQ.js → src-F6L8HcEC.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-DgTp0O6g.js → stateDiagram-v2-A6IESKWZ-BBk2tpPh.js} +1 -1
- package/dist/web/assets/{swimlanes-B3ENRKFQ-BiyS7wH3.js → swimlanes-B3ENRKFQ-CAUrhGwF.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BCnJymLL.js +8 -0
- package/dist/web/assets/{timeline-definition-JXYKGLGB-D_PAPA8Z.js → timeline-definition-JXYKGLGB-D0FWj9fa.js} +1 -1
- package/dist/web/assets/{treeView-D4JQ5SDB-m1fbOydD.js → treeView-D4JQ5SDB-CkJOmf93.js} +1 -1
- package/dist/web/assets/{treemap-SAJKECNS-Bxt7Udu9.js → treemap-SAJKECNS-BSFsardB.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-WPOLBNNX-BTrMcoPL.js → usecaseDiagram-WPOLBNNX-B2U-2o4g.js} +1 -1
- package/dist/web/assets/{vennDiagram-Q3Y3IDCW-C_kOOxPJ.js → vennDiagram-Q3Y3IDCW-BijTH0fT.js} +1 -1
- package/dist/web/assets/{wardley-7MLQ67FV-C33rdYbB.js → wardley-7MLQ67FV-DDOa5RzL.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-IVAIDKWA-DpG2rl09.js → wardleyDiagram-IVAIDKWA-mbb5GvW5.js} +1 -1
- package/dist/web/assets/{xychartDiagram-4URNHDU4-5a7LnsSZ.js → xychartDiagram-4URNHDU4-DHd60D_Y.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -3
- package/dist/web/assets/channel-CV6Myu73.js +0 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-CiUTloqN.js +0 -1
- package/dist/web/assets/index-DYwdlB-_.js +0 -142
- package/dist/web/assets/index-TP24ydrn.css +0 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-DJ90Jhqq.js +0 -8
package/dist/server/service.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ShellService } from "./shell.js";
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
|
+
import { cp } from "node:fs/promises";
|
|
4
|
+
import { WorkspaceRegistry } from "./workspaces.js";
|
|
5
|
+
import { UNASSIGNED_SCOPE } from "../shared/workspace-scope.js";
|
|
3
6
|
import { AppError } from "../shared/errors.js";
|
|
4
7
|
import { threadNameError } from "../shared/thread-name.js";
|
|
5
8
|
import {
|
|
@@ -13,6 +16,7 @@ import {
|
|
|
13
16
|
import { EventHub } from "./events.js";
|
|
14
17
|
import { TurnUsageStore } from "./db/turn-usage.js";
|
|
15
18
|
import { Projection, isActive, isTerminal } from "./projection.js";
|
|
19
|
+
import { ProjectClones } from "./project-clones.js";
|
|
16
20
|
import { renderThreadExport } from "./thread-export.js";
|
|
17
21
|
import { readLocalImage } from "./images.js";
|
|
18
22
|
import { temporaryFileRoots } from "./local-files.js";
|
|
@@ -24,6 +28,20 @@ class GatewayService {
|
|
|
24
28
|
this.store = options.store;
|
|
25
29
|
this.turnUsage = new TurnUsageStore(this.store.database);
|
|
26
30
|
this.projects = options.projects;
|
|
31
|
+
this.workspaces = new WorkspaceRegistry(
|
|
32
|
+
this.store.database,
|
|
33
|
+
this.projects,
|
|
34
|
+
options.workspacesRoot
|
|
35
|
+
);
|
|
36
|
+
this.projectClones = new ProjectClones(
|
|
37
|
+
(input) => {
|
|
38
|
+
this.workspaces.assertProjectDirectory(input.path);
|
|
39
|
+
const project = this.projects.add(input);
|
|
40
|
+
this.events.publishProjects(this.projects.list());
|
|
41
|
+
return project;
|
|
42
|
+
},
|
|
43
|
+
(path) => this.projects.list().some((project) => project.canonicalRoot === path)
|
|
44
|
+
);
|
|
27
45
|
this.codex = options.codex;
|
|
28
46
|
this.shell = new ShellService(this.codex);
|
|
29
47
|
this.events = new EventHub(options.limits?.eventBytes);
|
|
@@ -33,12 +51,14 @@ class GatewayService {
|
|
|
33
51
|
this.removeListener = this.codex.onEvent((event) => this.consume(event));
|
|
34
52
|
}
|
|
35
53
|
options;
|
|
54
|
+
projectClones;
|
|
36
55
|
shell;
|
|
37
56
|
events;
|
|
38
57
|
projection;
|
|
39
58
|
store;
|
|
40
59
|
turnUsage;
|
|
41
60
|
projects;
|
|
61
|
+
workspaces;
|
|
42
62
|
codex;
|
|
43
63
|
removeListener;
|
|
44
64
|
fileTemporaryRoots = temporaryFileRoots();
|
|
@@ -47,6 +67,7 @@ class GatewayService {
|
|
|
47
67
|
currentEpoch;
|
|
48
68
|
confirmedCompatibility;
|
|
49
69
|
archiveChanges = /* @__PURE__ */ new Set();
|
|
70
|
+
archiveRevisions = /* @__PURE__ */ new Map();
|
|
50
71
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
51
72
|
renameChanges = /* @__PURE__ */ new Set();
|
|
52
73
|
reads = /* @__PURE__ */ new Map();
|
|
@@ -86,6 +107,7 @@ class GatewayService {
|
|
|
86
107
|
this.maintenance = value;
|
|
87
108
|
}
|
|
88
109
|
prepareUpdate() {
|
|
110
|
+
if (this.projectClones.hasActive) return { ready: false, reason: "\u7B49\u5F85\u9879\u76EE\u514B\u9686\u5B8C\u6210\u6216\u53D6\u6D88" };
|
|
89
111
|
if (this.shell.active()) return { ready: false, reason: "\u7B49\u5F85 Shell \u547D\u4EE4\u7ED3\u675F" };
|
|
90
112
|
if (this.inFlightOperations.size || this.resumes.size || this.pendingThreadCreations.size)
|
|
91
113
|
return { ready: false, reason: "\u7B49\u5F85\u6B63\u5728\u63D0\u4EA4\u6216\u6062\u590D\u7684\u64CD\u4F5C\u5B8C\u6210" };
|
|
@@ -99,10 +121,18 @@ class GatewayService {
|
|
|
99
121
|
return { ready: true };
|
|
100
122
|
}
|
|
101
123
|
addProject(input) {
|
|
124
|
+
this.workspaces.assertProjectDirectory(input.path);
|
|
125
|
+
this.projectClones.assertIdlePath(input.path);
|
|
102
126
|
const project = this.projects.add(input);
|
|
103
127
|
this.events.publishProjects(this.projects.list());
|
|
104
128
|
return project;
|
|
105
129
|
}
|
|
130
|
+
cloneProject(input) {
|
|
131
|
+
if (this.maintenance || !this.accepting)
|
|
132
|
+
throw new AppError("GATEWAY_STOPPING", "\u670D\u52A1\u6B63\u5728\u5347\u7EA7\u6216\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", 503);
|
|
133
|
+
this.workspaces.assertProjectDirectory(input.path);
|
|
134
|
+
return this.projectClones.start(input);
|
|
135
|
+
}
|
|
106
136
|
renameProject(projectId, input) {
|
|
107
137
|
const project = this.projects.rename(projectId, input);
|
|
108
138
|
this.events.publishProjects(this.projects.list());
|
|
@@ -177,12 +207,12 @@ class GatewayService {
|
|
|
177
207
|
});
|
|
178
208
|
}
|
|
179
209
|
async readRuntimeSettings(projectId) {
|
|
180
|
-
const
|
|
181
|
-
return this.codex.readRuntimeSettings(
|
|
210
|
+
const cwd = projectId === null ? this.workspaces.defaultsDirectory() : this.projects.get(projectId).canonicalRoot;
|
|
211
|
+
return this.codex.readRuntimeSettings(cwd);
|
|
182
212
|
}
|
|
183
213
|
async saveModelDefaults(projectId, input) {
|
|
184
|
-
return this.perform("config.modelDefaults", projectId, input, async () => {
|
|
185
|
-
this.projects.get(projectId);
|
|
214
|
+
return this.perform("config.modelDefaults", projectId ?? UNASSIGNED_SCOPE, input, async () => {
|
|
215
|
+
if (projectId !== null) this.projects.get(projectId);
|
|
186
216
|
this.ensureReady();
|
|
187
217
|
this.ensureCompatibilityConfirmed();
|
|
188
218
|
this.store.associateProject(input.clientOperationId, projectId);
|
|
@@ -191,28 +221,43 @@ class GatewayService {
|
|
|
191
221
|
});
|
|
192
222
|
}
|
|
193
223
|
async listThreads(projectId, cursor, archived = false) {
|
|
194
|
-
const project = this.projects.get(projectId);
|
|
224
|
+
const project = projectId === null ? void 0 : this.projects.get(projectId);
|
|
225
|
+
const directories = project ? project.canonicalRoot : this.workspaces.allocations().map((row) => row.canonicalRoot);
|
|
195
226
|
const titleReads = new Map(
|
|
196
227
|
this.projection.values().filter((thread) => thread.projectId === projectId).map((thread) => [thread.id, this.beginTitleRead(thread.id)])
|
|
197
228
|
);
|
|
198
|
-
const
|
|
199
|
-
|
|
229
|
+
const archiveReads = new Map(
|
|
230
|
+
this.projection.values().filter((thread) => thread.projectId === projectId).map((thread) => [thread.id, this.archiveRevisions.get(thread.id) ?? 0])
|
|
231
|
+
);
|
|
232
|
+
const page = Array.isArray(directories) && !directories.length ? { data: [], nextCursor: null } : await this.codex.listThreads(directories, cursor, archived);
|
|
233
|
+
if (projectId !== null) this.projects.get(projectId);
|
|
200
234
|
const data = [];
|
|
201
235
|
for (const thread of page.data) {
|
|
202
236
|
try {
|
|
237
|
+
if (projectId === null && thread.source === "appServer" && page.data.filter((entry) => entry.cwd === thread.cwd).length === 1) {
|
|
238
|
+
const operationId = this.workspaces.recoverCreation(thread.id, thread.cwd);
|
|
239
|
+
if (operationId && !this.inFlightOperations.has(operationId)) {
|
|
240
|
+
const recovered = this.store.confirmWorkspaceCreation(operationId, thread.id);
|
|
241
|
+
this.events.publish("operation", this.operation(recovered));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
203
244
|
const normalized = this.normalize(thread, projectId);
|
|
204
245
|
normalized.hasMessages ??= true;
|
|
205
246
|
const cached = this.projection.get(thread.id);
|
|
206
247
|
if (cached && normalized.hasMessages) cached.hasMessages = true;
|
|
207
248
|
const titleRead = titleReads.get(thread.id);
|
|
208
|
-
if (cached &&
|
|
209
|
-
|
|
210
|
-
|
|
249
|
+
if (cached && archiveReads.get(thread.id) === (this.archiveRevisions.get(thread.id) ?? 0))
|
|
250
|
+
this.updateArchiveState(thread.id, Boolean(normalized.archived));
|
|
251
|
+
if (cached && titleRead !== void 0)
|
|
211
252
|
this.updateThreadTitle(thread.id, normalized.title, titleRead);
|
|
212
|
-
}
|
|
213
253
|
data.push(this.threadView(cached ?? normalized));
|
|
214
254
|
} catch (error) {
|
|
215
|
-
if (!(error instanceof AppError) || ![
|
|
255
|
+
if (!(error instanceof AppError) || ![
|
|
256
|
+
"PROJECT_OWNERSHIP",
|
|
257
|
+
"PROJECT_UNAVAILABLE",
|
|
258
|
+
"PROJECT_NOT_FOUND",
|
|
259
|
+
"WORKSPACE_UNAVAILABLE"
|
|
260
|
+
].includes(error.code))
|
|
216
261
|
throw error;
|
|
217
262
|
}
|
|
218
263
|
}
|
|
@@ -239,7 +284,7 @@ class GatewayService {
|
|
|
239
284
|
(thread) => (thread.managed || this.store.activeForThread(thread.id)) && thread.projectId === projectId && this.historyVisible(thread)
|
|
240
285
|
).sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0, 30);
|
|
241
286
|
for (const thread of managed) {
|
|
242
|
-
this.
|
|
287
|
+
this.workspaces.assertOwnership(projectId, thread.cwd, thread.id);
|
|
243
288
|
if (!data.some((entry) => entry.id === thread.id)) data.unshift(this.threadView(thread));
|
|
244
289
|
}
|
|
245
290
|
}
|
|
@@ -264,7 +309,7 @@ class GatewayService {
|
|
|
264
309
|
this.archiveChanges.add(threadId);
|
|
265
310
|
try {
|
|
266
311
|
const actual = await this.codex.readThreadMetadata(threadId);
|
|
267
|
-
this.
|
|
312
|
+
this.workspaces.assertOwnership(thread.projectId, actual.cwd, thread.id);
|
|
268
313
|
const current = this.projection.get(threadId);
|
|
269
314
|
if (input.archived && (this.resumes.has(threadId) || this.store.activeForThread(threadId) || current.managed && current.status === "unknown" || [actual, current].some(
|
|
270
315
|
(entry) => isActive(entry.status) || entry.turns.some((turn) => isActive(turn.status) || turn.status === "unknown") || entry.actions.some((action) => ["pending", "responding"].includes(action.status))
|
|
@@ -328,7 +373,7 @@ class GatewayService {
|
|
|
328
373
|
actual = await this.codex.readThreadMetadata(threadId);
|
|
329
374
|
if (actual.id !== threadId || this.codex.epoch !== epoch || !this.codex.ready)
|
|
330
375
|
throw new Error("\u4F1A\u8BDD\u6216 Codex \u5B9E\u4F8B\u5DF2\u53D8\u5316");
|
|
331
|
-
this.
|
|
376
|
+
this.workspaces.assertOwnership(thread.projectId, actual.cwd, thread.id);
|
|
332
377
|
} catch (cause) {
|
|
333
378
|
throw new Error("\u91CD\u547D\u540D\u5DF2\u53D1\u9001\uFF0C\u4F46\u65E0\u6CD5\u786E\u8BA4\u4F1A\u8BDD\u540D\u79F0\uFF0C\u8BF7\u6838\u5BF9\u63D0\u4EA4", { cause });
|
|
334
379
|
}
|
|
@@ -376,6 +421,7 @@ class GatewayService {
|
|
|
376
421
|
const thread = this.projection.get(threadId);
|
|
377
422
|
if (!thread || Boolean(thread.archived) === archived) return;
|
|
378
423
|
thread.archived = archived;
|
|
424
|
+
this.archiveRevisions.set(threadId, (this.archiveRevisions.get(threadId) ?? 0) + 1);
|
|
379
425
|
if (archived) this.releaseThreadOwnership(threadId);
|
|
380
426
|
this.projection.touch(threadId);
|
|
381
427
|
if (!this.archiveChanges.has(threadId)) this.publishThread(threadId);
|
|
@@ -383,9 +429,9 @@ class GatewayService {
|
|
|
383
429
|
async getThreadMetadata(threadId) {
|
|
384
430
|
const cached = this.projection.get(threadId);
|
|
385
431
|
if (cached) {
|
|
386
|
-
this.
|
|
432
|
+
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
387
433
|
if (this.codex.ready) await this.refreshThreadMetadata(threadId);
|
|
388
|
-
this.
|
|
434
|
+
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
389
435
|
return { ...this.threadView(cached), turns: [], actions: [] };
|
|
390
436
|
}
|
|
391
437
|
const metadata = this.threadView(this.normalize(await this.codex.readThreadMetadata(threadId)));
|
|
@@ -396,7 +442,7 @@ class GatewayService {
|
|
|
396
442
|
await this.loadThread(threadId);
|
|
397
443
|
if (cached && this.codex.ready) await this.refreshThreadMetadata(threadId);
|
|
398
444
|
const thread = this.projection.get(threadId);
|
|
399
|
-
this.
|
|
445
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
400
446
|
return this.threadView(thread);
|
|
401
447
|
}
|
|
402
448
|
async getThreadHistory(threadId, cursor) {
|
|
@@ -404,11 +450,11 @@ class GatewayService {
|
|
|
404
450
|
throw new AppError("VALIDATION_FAILED", "Missing or invalid history cursor", 400);
|
|
405
451
|
this.ensureReady();
|
|
406
452
|
const metadata = this.normalize(await this.codex.readThreadMetadata(threadId));
|
|
407
|
-
this.
|
|
453
|
+
this.workspaces.assertOwnership(metadata.projectId, metadata.cwd, metadata.id);
|
|
408
454
|
if (!this.codex.readThreadPage)
|
|
409
455
|
throw new AppError("HISTORY_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u5386\u53F2\u5206\u9875", 409);
|
|
410
456
|
const page = this.normalize(await this.codex.readThreadPage(threadId, cursor));
|
|
411
|
-
this.
|
|
457
|
+
this.workspaces.assertOwnership(page.projectId, page.cwd, page.id);
|
|
412
458
|
return page;
|
|
413
459
|
}
|
|
414
460
|
async exportThread(threadId) {
|
|
@@ -427,8 +473,8 @@ class GatewayService {
|
|
|
427
473
|
409
|
|
428
474
|
);
|
|
429
475
|
const link = this.store.getThreadLink(threadId);
|
|
430
|
-
if (link) this.
|
|
431
|
-
else this.
|
|
476
|
+
if (link) this.workspaces.assertOwnership(link.projectId, thread.cwd, thread.id);
|
|
477
|
+
else this.workspaces.findOwner(thread.cwd, thread.id);
|
|
432
478
|
return renderThreadExport(thread, {
|
|
433
479
|
executionUncertain: Boolean(this.store.activeForThread(threadId)),
|
|
434
480
|
historyChanged: revision !== this.projection.revision(threadId) || thread.updatedAt !== latest.updatedAt || thread.status !== latest.status
|
|
@@ -449,21 +495,23 @@ class GatewayService {
|
|
|
449
495
|
this.normalize(await this.codex.readThreadMetadata(threadId));
|
|
450
496
|
link = this.store.getThreadLink(threadId);
|
|
451
497
|
}
|
|
452
|
-
const
|
|
498
|
+
const ownership = this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
|
|
499
|
+
const { cwd } = ownership;
|
|
453
500
|
const file = await read(path, {
|
|
454
501
|
cwd,
|
|
502
|
+
excludedRoots: this.workspaces.allocations().filter((row) => row.threadId !== threadId).map((row) => row.canonicalRoot),
|
|
455
503
|
roots: [
|
|
456
|
-
{ canonicalRoot:
|
|
457
|
-
...this.fileTemporaryRoots
|
|
504
|
+
{ canonicalRoot: ownership.canonicalRoot, aliases: [ownership.rootPath] },
|
|
505
|
+
...link.projectId === null ? [] : this.fileTemporaryRoots
|
|
458
506
|
]
|
|
459
507
|
});
|
|
460
|
-
this.
|
|
508
|
+
this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
|
|
461
509
|
return file;
|
|
462
510
|
}
|
|
463
511
|
async loadThread(threadId) {
|
|
464
512
|
const cached = this.projection.get(threadId);
|
|
465
513
|
if (cached) {
|
|
466
|
-
this.
|
|
514
|
+
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
467
515
|
const marker = this.store.activeForThread(threadId);
|
|
468
516
|
if (marker?.turnId && this.codex.ready) {
|
|
469
517
|
await this.refreshExecution(marker);
|
|
@@ -498,18 +546,26 @@ class GatewayService {
|
|
|
498
546
|
}
|
|
499
547
|
}
|
|
500
548
|
async createThread(projectId, input) {
|
|
501
|
-
return this.perform("thread.create", projectId, input, async () => {
|
|
502
|
-
const project = this.projects.get(projectId);
|
|
549
|
+
return this.perform("thread.create", projectId ?? UNASSIGNED_SCOPE, input, async () => {
|
|
503
550
|
this.ensureReady();
|
|
504
551
|
this.ensureCompatibilityConfirmed();
|
|
552
|
+
const cwd = projectId === null ? this.workspaces.allocate(input.clientOperationId).cwd : this.projects.get(projectId).canonicalRoot;
|
|
553
|
+
if (projectId !== null) this.workspaces.assertProjectDirectory(cwd);
|
|
505
554
|
this.store.associateProject(input.clientOperationId, projectId);
|
|
506
555
|
this.store.dispatch(input.clientOperationId);
|
|
507
|
-
this.pendingThreadCreations.set(input.clientOperationId,
|
|
556
|
+
this.pendingThreadCreations.set(input.clientOperationId, cwd);
|
|
508
557
|
const epoch = this.codex.epoch;
|
|
509
558
|
const settingsSequence = this.runtimeSettingsSequence;
|
|
510
|
-
const thread = await this.codex.createThread(
|
|
559
|
+
const thread = await this.codex.createThread(cwd, input.settings);
|
|
511
560
|
if (this.codex.epoch !== epoch || !this.codex.ready)
|
|
512
561
|
throw new Error("\u521B\u5EFA\u8BF7\u6C42\u5DF2\u53D1\u9001\uFF0C\u4F46 Codex \u5B9E\u4F8B\u5DF2\u53D8\u5316\uFF0C\u8BF7\u6838\u5BF9\u5386\u53F2");
|
|
562
|
+
if (projectId === null) {
|
|
563
|
+
try {
|
|
564
|
+
this.workspaces.bind(input.clientOperationId, thread.id, thread.cwd);
|
|
565
|
+
} catch (cause) {
|
|
566
|
+
throw new Error("\u521B\u5EFA\u8BF7\u6C42\u5DF2\u53D1\u9001\uFF0C\u4F46\u8FD4\u56DE\u7684\u5DE5\u4F5C\u76EE\u5F55\u65E0\u6CD5\u786E\u8BA4\uFF0C\u8BF7\u6838\u5BF9\u5386\u53F2", { cause });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
513
569
|
const normalized = this.normalize(thread, projectId, true);
|
|
514
570
|
this.projection.thread(
|
|
515
571
|
normalized,
|
|
@@ -526,7 +582,7 @@ class GatewayService {
|
|
|
526
582
|
this.ensureReady();
|
|
527
583
|
this.ensureArchiveAvailable(thread);
|
|
528
584
|
if (thread.archived) throw new AppError("THREAD_ARCHIVED", "\u8BF7\u5148\u53D6\u6D88\u5F52\u6863\u540E\u518D\u6062\u590D\u4F1A\u8BDD", 409);
|
|
529
|
-
this.
|
|
585
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
530
586
|
this.store.associateProject(input.clientOperationId, thread.projectId);
|
|
531
587
|
const link = this.store.getThreadLink(threadId);
|
|
532
588
|
if (link?.managedEpoch === this.codex.epoch) {
|
|
@@ -540,7 +596,7 @@ class GatewayService {
|
|
|
540
596
|
operationId: marker.operationId,
|
|
541
597
|
threadId
|
|
542
598
|
});
|
|
543
|
-
const ownership = this.
|
|
599
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
544
600
|
this.ensureCompatibilityConfirmed();
|
|
545
601
|
this.store.dispatch(input.clientOperationId);
|
|
546
602
|
let pending = this.resumes.get(threadId);
|
|
@@ -585,21 +641,29 @@ class GatewayService {
|
|
|
585
641
|
this.ensureReady();
|
|
586
642
|
const source = this.projection.get(threadId) ?? await this.codex.readThread(threadId);
|
|
587
643
|
const link = this.store.getThreadLink(threadId);
|
|
588
|
-
const ownership = link ? this.
|
|
644
|
+
const ownership = link ? this.workspaces.assertOwnership(link.projectId, source.cwd, threadId) : this.workspaces.findOwner(source.cwd, source.id);
|
|
589
645
|
this.ensureCompatibilityConfirmed();
|
|
590
646
|
const epoch = this.codex.epoch;
|
|
591
|
-
|
|
647
|
+
let forkCwd = ownership.cwd;
|
|
648
|
+
if (ownership.projectId === null) {
|
|
649
|
+
forkCwd = this.workspaces.allocate(input.clientOperationId).cwd;
|
|
650
|
+
await cp(ownership.cwd, forkCwd, { recursive: true, verbatimSymlinks: true });
|
|
651
|
+
this.workspaces.assertOwnership(null, source.cwd, source.id);
|
|
652
|
+
}
|
|
653
|
+
this.store.associateProject(input.clientOperationId, ownership.projectId);
|
|
592
654
|
this.store.dispatch(input.clientOperationId);
|
|
593
|
-
this.pendingThreadCreations.set(input.clientOperationId,
|
|
655
|
+
this.pendingThreadCreations.set(input.clientOperationId, forkCwd);
|
|
594
656
|
const settingsSequence = this.runtimeSettingsSequence;
|
|
595
|
-
const forked = await this.codex.forkThread(threadId,
|
|
657
|
+
const forked = await this.codex.forkThread(threadId, forkCwd, input.settings);
|
|
596
658
|
if (forked.id === threadId || this.codex.epoch !== epoch || !this.codex.ready)
|
|
597
659
|
throw new Error("\u5206\u53C9\u8BF7\u6C42\u5DF2\u53D1\u9001\uFF0C\u4F46\u8FD4\u56DE\u7684\u65B0\u4F1A\u8BDD\u65E0\u6CD5\u786E\u8BA4\uFF0C\u8BF7\u6838\u5BF9\u5386\u53F2");
|
|
598
660
|
let normalized;
|
|
599
661
|
try {
|
|
662
|
+
if (ownership.projectId === null)
|
|
663
|
+
this.workspaces.bind(input.clientOperationId, forked.id, forked.cwd);
|
|
600
664
|
normalized = this.normalize(
|
|
601
665
|
{ ...forked, forkedFromId: threadId },
|
|
602
|
-
ownership.
|
|
666
|
+
ownership.projectId,
|
|
603
667
|
true
|
|
604
668
|
);
|
|
605
669
|
} catch (error) {
|
|
@@ -618,7 +682,7 @@ class GatewayService {
|
|
|
618
682
|
return this.perform("turn.start", threadId, input, async () => {
|
|
619
683
|
const thread = await this.loadThread(threadId);
|
|
620
684
|
this.ensureManaged(thread);
|
|
621
|
-
const ownership = this.
|
|
685
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
622
686
|
if (isActive(thread.status))
|
|
623
687
|
throw new AppError("THREAD_BUSY", "\u4F1A\u8BDD\u5DF2\u6709\u6D3B\u52A8\u4EFB\u52A1\uFF0C\u8BF7\u8FFD\u52A0\u8981\u6C42\u6216\u4E2D\u65AD", 409, { threadId });
|
|
624
688
|
this.ensureCompatibilityConfirmed();
|
|
@@ -651,7 +715,7 @@ class GatewayService {
|
|
|
651
715
|
return this.perform("thread.compact", threadId, input, async () => {
|
|
652
716
|
const thread = await this.loadThread(threadId);
|
|
653
717
|
this.ensureManaged(thread);
|
|
654
|
-
const ownership = this.
|
|
718
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
655
719
|
if (isActive(thread.status) || thread.turns.some((turn) => isActive(turn.status)))
|
|
656
720
|
throw new AppError("THREAD_BUSY", "\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u518D\u538B\u7F29\u4E0A\u4E0B\u6587", 409);
|
|
657
721
|
if (!thread.turns.length)
|
|
@@ -675,7 +739,7 @@ class GatewayService {
|
|
|
675
739
|
return this.perform("review.start", threadId, input, async () => {
|
|
676
740
|
const thread = await this.loadThread(threadId);
|
|
677
741
|
this.ensureManaged(thread);
|
|
678
|
-
const ownership = this.
|
|
742
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
679
743
|
if (isActive(thread.status) || thread.turns.some((turn2) => isActive(turn2.status)))
|
|
680
744
|
throw new AppError("THREAD_BUSY", "\u4F1A\u8BDD\u5DF2\u6709\u6D3B\u52A8\u4EFB\u52A1\uFF0C\u7ED3\u675F\u540E\u624D\u80FD\u5F00\u59CB\u4EE3\u7801\u5BA1\u67E5", 409, {
|
|
681
745
|
threadId
|
|
@@ -774,8 +838,8 @@ class GatewayService {
|
|
|
774
838
|
try {
|
|
775
839
|
const actual = await this.codex.readThread(marker.threadId);
|
|
776
840
|
const link = this.store.getThreadLink(marker.threadId);
|
|
777
|
-
if (link) this.
|
|
778
|
-
else this.
|
|
841
|
+
if (link) this.workspaces.assertOwnership(link.projectId, actual.cwd, actual.id);
|
|
842
|
+
else this.workspaces.findOwner(actual.cwd, actual.id);
|
|
779
843
|
if (this.markerIsActive(actual, marker.turnId))
|
|
780
844
|
throw new AppError("THREAD_BUSY", "\u4E0A\u6E38\u4ECD\u62A5\u544A\u8BE5\u4EFB\u52A1\u6D3B\u52A8\uFF0C\u4E0D\u80FD\u89E3\u9664\u5360\u7528", 409, {
|
|
781
845
|
threadId: marker.threadId
|
|
@@ -810,6 +874,7 @@ class GatewayService {
|
|
|
810
874
|
}
|
|
811
875
|
async shutdown() {
|
|
812
876
|
this.accepting = false;
|
|
877
|
+
await this.projectClones.close();
|
|
813
878
|
this.store.recover();
|
|
814
879
|
this.projection.runtimeReset();
|
|
815
880
|
this.removeListener();
|
|
@@ -870,14 +935,14 @@ class GatewayService {
|
|
|
870
935
|
}
|
|
871
936
|
normalize(thread, projectId, managed = false) {
|
|
872
937
|
const link = this.store.getThreadLink(thread.id);
|
|
873
|
-
const ownership = link ? this.
|
|
874
|
-
if (projectId && ownership.
|
|
938
|
+
const ownership = link ? this.workspaces.assertOwnership(link.projectId, thread.cwd, thread.id) : managed && projectId !== void 0 ? this.workspaces.assertOwnership(projectId, thread.cwd, thread.id) : this.workspaces.findOwner(thread.cwd, thread.id);
|
|
939
|
+
if (projectId !== void 0 && ownership.projectId !== projectId)
|
|
875
940
|
throw new AppError("PROJECT_OWNERSHIP", "\u4F1A\u8BDD\u5C5E\u4E8E\u5176\u4ED6\u9879\u76EE\uFF0C\u4E0D\u80FD\u6539\u53D8\u9879\u76EE\u5F52\u5C5E", 403);
|
|
876
941
|
const managedEpoch = thread.archived ? null : managed ? this.codex.epoch : link?.managedEpoch ?? null;
|
|
877
942
|
const result = {
|
|
878
943
|
...thread,
|
|
879
944
|
turns: this.turnUsage.restore(thread.id, thread.turns),
|
|
880
|
-
projectId: ownership.
|
|
945
|
+
projectId: ownership.projectId,
|
|
881
946
|
cwd: ownership.cwd,
|
|
882
947
|
managed: managedEpoch === this.codex.epoch,
|
|
883
948
|
resumeError: managed ? void 0 : this.projection.get(thread.id)?.resumeError,
|
|
@@ -887,7 +952,7 @@ class GatewayService {
|
|
|
887
952
|
};
|
|
888
953
|
this.store.linkThread({
|
|
889
954
|
threadId: thread.id,
|
|
890
|
-
projectId: ownership.
|
|
955
|
+
projectId: ownership.projectId,
|
|
891
956
|
cwd: ownership.cwd,
|
|
892
957
|
source: thread.source,
|
|
893
958
|
managedEpoch
|
|
@@ -922,7 +987,7 @@ class GatewayService {
|
|
|
922
987
|
if (!this.codex.ready || this.codex.epoch !== epoch || actual.id !== marker.threadId || latest?.operationId !== marker.operationId || latest.turnId !== marker.turnId || latest.codexEpoch !== marker.codexEpoch || this.projection.revision(marker.threadId) !== revision)
|
|
923
988
|
return;
|
|
924
989
|
const link = this.store.getThreadLink(marker.threadId);
|
|
925
|
-
const ownership = link ? this.
|
|
990
|
+
const ownership = link ? this.workspaces.assertOwnership(link.projectId, actual.cwd, actual.id) : this.workspaces.findOwner(actual.cwd, actual.id);
|
|
926
991
|
if (ownership.workspaceKey !== marker.workspaceKey) return;
|
|
927
992
|
const turn = actual.turns.find((entry) => entry.id === marker.turnId);
|
|
928
993
|
if (turn && isActive(turn.status) && latest.state === "active" && this.projection.get(marker.threadId)?.status === "unknown") {
|
|
@@ -1072,7 +1137,7 @@ class GatewayService {
|
|
|
1072
1137
|
const encoded = JSON.stringify(summary);
|
|
1073
1138
|
if (this.summaries.get(threadId) === encoded) return;
|
|
1074
1139
|
try {
|
|
1075
|
-
this.
|
|
1140
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
1076
1141
|
} catch {
|
|
1077
1142
|
return;
|
|
1078
1143
|
}
|
|
@@ -1120,7 +1185,7 @@ class GatewayService {
|
|
|
1120
1185
|
const thread = await this.codex.readThreadMetadata(threadId);
|
|
1121
1186
|
const current = this.projection.get(threadId);
|
|
1122
1187
|
if (!current || thread.id !== threadId) return;
|
|
1123
|
-
this.
|
|
1188
|
+
this.workspaces.assertOwnership(current.projectId, thread.cwd, thread.id);
|
|
1124
1189
|
const settings = thread.runtimeSettings ? { ...current.runtimeSettings, ...thread.runtimeSettings } : void 0;
|
|
1125
1190
|
if (this.titleReads.get(threadId) === sequence && this.projection.revision(threadId) === revision && settings && JSON.stringify(current.runtimeSettings) !== JSON.stringify(settings)) {
|
|
1126
1191
|
current.runtimeSettings = settings;
|
|
@@ -1225,7 +1290,7 @@ class GatewayService {
|
|
|
1225
1290
|
this.projection.expireTurn(thread.id, event.turn.id);
|
|
1226
1291
|
if (newCompletion) {
|
|
1227
1292
|
try {
|
|
1228
|
-
this.
|
|
1293
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
1229
1294
|
this.events.publishCompletion({
|
|
1230
1295
|
threadId: thread.id,
|
|
1231
1296
|
turnId: event.turn.id,
|