@covro/covro 0.2.1-dev.2 → 0.2.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 +9 -2
- package/README.zh-CN.md +9 -2
- 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 +25 -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/service.js +96 -51
- package/dist/server/service.js.map +2 -2
- package/dist/server/workspaces.js +138 -0
- package/dist/server/workspaces.js.map +7 -0
- package/dist/shared/workspace-scope.js +9 -0
- package/dist/shared/workspace-scope.js.map +7 -0
- package/dist/web/assets/abnfDiagram-WEYOIA3C-DTTcFQGL.js +1 -0
- package/dist/web/assets/arc-K2pKIwfu.js +1 -0
- package/dist/web/assets/architecture-WOLXFQ4H-DrxN_1CG.js +1 -0
- package/dist/web/assets/architectureDiagram-ZHVYKT53-CyyCrVAb.js +36 -0
- package/dist/web/assets/array-BifhSqXX.js +1 -0
- package/dist/web/assets/blockDiagram-RJAE7QA3-C8vl8QXS.js +140 -0
- package/dist/web/assets/c4Diagram-XDPPPE54-BtnIITFN.js +38 -0
- package/dist/web/assets/channel-CDExoPVe.js +1 -0
- package/dist/web/assets/chunk-2E7WHVMD-BssauSch.js +88 -0
- package/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/web/assets/chunk-4EMNKR2T-BZOo1RWg.js +1 -0
- package/dist/web/assets/chunk-55XCU76K-D8ZWazMQ.js +1 -0
- package/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/web/assets/chunk-7S6XSTFO-C8WqPzUl.js +2 -0
- package/dist/web/assets/chunk-AQDTSKZB-Coo6Axpg.js +72 -0
- package/dist/web/assets/chunk-DSNLSHJZ-5TwmEPWR.js +129 -0
- package/dist/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/dist/web/assets/chunk-HAGFOTED-Dgtj-Fc_.js +62 -0
- package/dist/web/assets/chunk-HNE4Q36P-COoS4UPL.js +1 -0
- package/dist/web/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/dist/web/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/web/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
- package/dist/web/assets/chunk-NQOKRVIF-tPhNkBBj.js +213 -0
- package/dist/web/assets/chunk-UHW7PFBP-BLk_BkCl.js +1 -0
- package/dist/web/assets/chunk-VQ5W62QI-HBeSVTU0.js +1 -0
- package/dist/web/assets/chunk-WQQL4CDK-LmZUm8If.js +10 -0
- package/dist/web/assets/chunk-XXDRQBXY-49rKU8Nk.js +1 -0
- package/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/web/assets/chunk-YZO67HFG-B_TgpaEG.js +1 -0
- package/dist/web/assets/chunk-ZAUX2LTX-f7-DSOrY.js +1 -0
- package/dist/web/assets/chunk-ZCHMQSOR-QhJOh7x0.js +2 -0
- package/dist/web/assets/classDiagram-v2-4MYWR3UA-CwF2HuoZ.js +217 -0
- package/dist/web/assets/cose-bilkent-JH36ORCC-CLvKOlx5.js +1 -0
- package/dist/web/assets/cynefin-EF2NZ3EQ-BNRVavZN.js +1 -0
- package/dist/web/assets/cynefinDiagram-WF24UDRP-DEd83kgI.js +62 -0
- package/dist/web/assets/cytoscape.esm-BegBNzOV.js +321 -0
- package/dist/web/assets/dagre-NK4GIZVX-C340UEI3.js +4 -0
- package/dist/web/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/web/assets/diagram-CBK3MWTC-CuN7ncUm.js +24 -0
- package/dist/web/assets/diagram-I5XIIV2D-D9mGAfA7.js +24 -0
- package/dist/web/assets/diagram-ODIL2UCW-CXtBv4bI.js +30 -0
- package/dist/web/assets/diagram-PFBEJUXE-B8qP5nGT.js +3 -0
- package/dist/web/assets/diagram-RJA3I4VV-BVrzsw2b.js +41 -0
- package/dist/web/assets/diagram-TYUNFSNR-Djfine3D.js +200 -0
- package/dist/web/assets/dist-D8kDnYqD.js +1 -0
- package/dist/web/assets/ebnfDiagram-V4P3TTPT-C69Oiekp.js +1 -0
- package/dist/web/assets/elk-QONTBCTQ-Bd3BP1h1.js +27 -0
- package/dist/web/assets/erDiagram-KYQIGQAA-Dxcl5jVl.js +99 -0
- package/dist/web/assets/eventmodeling-K75KTNOO-Do8p535i.js +1 -0
- package/dist/web/assets/flowDiagram-IUGLLWBU-X85uzgrG.js +1 -0
- package/dist/web/assets/ganttDiagram-R7GPNTJY-MBGUZbVy.js +292 -0
- package/dist/web/assets/gitGraph-VSP46ZUC-CHxRIbkg.js +1 -0
- package/dist/web/assets/gitGraphDiagram-PDYL7XHY-DbW0GeW4.js +106 -0
- package/dist/web/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/web/assets/index-Cz3AOJOw.js +142 -0
- package/dist/web/assets/index-DaMLk_sl.css +1 -0
- package/dist/web/assets/info-OHQRW6UA-K8BF1xZb.js +1 -0
- package/dist/web/assets/infoDiagram-MVDRKAX5-QaWV5-SY.js +2 -0
- package/dist/web/assets/init-C-OQMol4.js +1 -0
- package/dist/web/assets/ishikawaDiagram-HPEHV2GE-DpVz_sC0.js +70 -0
- package/dist/web/assets/journeyDiagram-BOSHW7O7-S9MzozV7.js +139 -0
- package/dist/web/assets/kanban-definition-FNSA4HC5-BiU8Ibw-.js +89 -0
- package/dist/web/assets/katex-B_JcIwqj.js +257 -0
- package/dist/web/assets/line-B1KaX5ms.js +1 -0
- package/dist/web/assets/linear-Cg1dwAtU.js +1 -0
- package/dist/web/assets/mermaid-parser.core-hzYHYxfI.js +7 -0
- package/dist/web/assets/mermaid.core-N8xYYvXy.js +44 -0
- package/dist/web/assets/mindmap-definition-6OY57UG3-BmIQCrEd.js +98 -0
- package/dist/web/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/web/assets/packet-JDAUHWVQ-DhOTX9De.js +1 -0
- package/dist/web/assets/path-fybaL0A-.js +1 -0
- package/dist/web/assets/pegDiagram-ECIUJNBX-CVfE6yUu.js +1 -0
- package/dist/web/assets/pie-XZMESJXO-BayfKVL8.js +1 -0
- package/dist/web/assets/pieDiagram-GNMPY74T-ClbMRve3.js +39 -0
- package/dist/web/assets/quadrantDiagram-D57K4MC3-DFp-P_EZ.js +7 -0
- package/dist/web/assets/radar-ABXABTNO-Bt1EAsxC.js +1 -0
- package/dist/web/assets/railroad-I3PHUGI6-CSos2Rm9.js +1 -0
- package/dist/web/assets/railroad-abnf-LNEFI6M7-CebY-3Kh.js +1 -0
- package/dist/web/assets/railroad-ebnf-DWYD2UWJ-DpA6-TBs.js +1 -0
- package/dist/web/assets/railroad-peg-JR7BVNR7-Bg3QPvi5.js +1 -0
- package/dist/web/assets/railroadDiagram-Y72NRBL7-BM4RAgqS.js +1 -0
- package/dist/web/assets/requirementDiagram-EQYSW5RA-CqP9bCS_.js +84 -0
- package/dist/web/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dist/web/assets/sankeyDiagram-SY24JHO5-BhS8f6-k.js +40 -0
- package/dist/web/assets/sequenceDiagram-BLEHNHQU-p3C1DgeC.js +169 -0
- package/dist/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/dist/web/assets/src-9XJAFibc.js +1 -0
- package/dist/web/assets/stateDiagram-v2-A6IESKWZ-DVVy8chY.js +291 -0
- package/dist/web/assets/swimlanes-B3ENRKFQ-CtMPO6Gw.js +1 -0
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-BRXJWgnO.js +8 -0
- package/dist/web/assets/timeline-definition-JXYKGLGB-DuTqpjLQ.js +120 -0
- package/dist/web/assets/treeView-D4JQ5SDB-qpOZEphu.js +1 -0
- package/dist/web/assets/treemap-SAJKECNS-Dm6e3TpG.js +1 -0
- package/dist/web/assets/usecaseDiagram-WPOLBNNX-DoCSdm5y.js +328 -0
- package/dist/web/assets/vennDiagram-Q3Y3IDCW-qzd4yvh5.js +34 -0
- package/dist/web/assets/wardley-7MLQ67FV-CkcgIQgQ.js +1 -0
- package/dist/web/assets/wardleyDiagram-IVAIDKWA-CE6OR9hA.js +78 -0
- package/dist/web/assets/xychartDiagram-4URNHDU4-OrFXHVc6.js +7 -0
- package/dist/web/index.html +2 -2
- package/package.json +4 -3
- package/dist/web/assets/index-D4yT2i6A.js +0 -140
- package/dist/web/assets/index-Du6go0-C.css +0 -1
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 {
|
|
@@ -24,6 +27,11 @@ class GatewayService {
|
|
|
24
27
|
this.store = options.store;
|
|
25
28
|
this.turnUsage = new TurnUsageStore(this.store.database);
|
|
26
29
|
this.projects = options.projects;
|
|
30
|
+
this.workspaces = new WorkspaceRegistry(
|
|
31
|
+
this.store.database,
|
|
32
|
+
this.projects,
|
|
33
|
+
options.workspacesRoot
|
|
34
|
+
);
|
|
27
35
|
this.codex = options.codex;
|
|
28
36
|
this.shell = new ShellService(this.codex);
|
|
29
37
|
this.events = new EventHub(options.limits?.eventBytes);
|
|
@@ -39,6 +47,7 @@ class GatewayService {
|
|
|
39
47
|
store;
|
|
40
48
|
turnUsage;
|
|
41
49
|
projects;
|
|
50
|
+
workspaces;
|
|
42
51
|
codex;
|
|
43
52
|
removeListener;
|
|
44
53
|
fileTemporaryRoots = temporaryFileRoots();
|
|
@@ -47,6 +56,7 @@ class GatewayService {
|
|
|
47
56
|
currentEpoch;
|
|
48
57
|
confirmedCompatibility;
|
|
49
58
|
archiveChanges = /* @__PURE__ */ new Set();
|
|
59
|
+
archiveRevisions = /* @__PURE__ */ new Map();
|
|
50
60
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
51
61
|
renameChanges = /* @__PURE__ */ new Set();
|
|
52
62
|
reads = /* @__PURE__ */ new Map();
|
|
@@ -99,6 +109,7 @@ class GatewayService {
|
|
|
99
109
|
return { ready: true };
|
|
100
110
|
}
|
|
101
111
|
addProject(input) {
|
|
112
|
+
this.workspaces.assertProjectDirectory(input.path);
|
|
102
113
|
const project = this.projects.add(input);
|
|
103
114
|
this.events.publishProjects(this.projects.list());
|
|
104
115
|
return project;
|
|
@@ -177,12 +188,12 @@ class GatewayService {
|
|
|
177
188
|
});
|
|
178
189
|
}
|
|
179
190
|
async readRuntimeSettings(projectId) {
|
|
180
|
-
const
|
|
181
|
-
return this.codex.readRuntimeSettings(
|
|
191
|
+
const cwd = projectId === null ? this.workspaces.defaultsDirectory() : this.projects.get(projectId).canonicalRoot;
|
|
192
|
+
return this.codex.readRuntimeSettings(cwd);
|
|
182
193
|
}
|
|
183
194
|
async saveModelDefaults(projectId, input) {
|
|
184
|
-
return this.perform("config.modelDefaults", projectId, input, async () => {
|
|
185
|
-
this.projects.get(projectId);
|
|
195
|
+
return this.perform("config.modelDefaults", projectId ?? UNASSIGNED_SCOPE, input, async () => {
|
|
196
|
+
if (projectId !== null) this.projects.get(projectId);
|
|
186
197
|
this.ensureReady();
|
|
187
198
|
this.ensureCompatibilityConfirmed();
|
|
188
199
|
this.store.associateProject(input.clientOperationId, projectId);
|
|
@@ -191,28 +202,43 @@ class GatewayService {
|
|
|
191
202
|
});
|
|
192
203
|
}
|
|
193
204
|
async listThreads(projectId, cursor, archived = false) {
|
|
194
|
-
const project = this.projects.get(projectId);
|
|
205
|
+
const project = projectId === null ? void 0 : this.projects.get(projectId);
|
|
206
|
+
const directories = project ? project.canonicalRoot : this.workspaces.allocations().map((row) => row.canonicalRoot);
|
|
195
207
|
const titleReads = new Map(
|
|
196
208
|
this.projection.values().filter((thread) => thread.projectId === projectId).map((thread) => [thread.id, this.beginTitleRead(thread.id)])
|
|
197
209
|
);
|
|
198
|
-
const
|
|
199
|
-
|
|
210
|
+
const archiveReads = new Map(
|
|
211
|
+
this.projection.values().filter((thread) => thread.projectId === projectId).map((thread) => [thread.id, this.archiveRevisions.get(thread.id) ?? 0])
|
|
212
|
+
);
|
|
213
|
+
const page = Array.isArray(directories) && !directories.length ? { data: [], nextCursor: null } : await this.codex.listThreads(directories, cursor, archived);
|
|
214
|
+
if (projectId !== null) this.projects.get(projectId);
|
|
200
215
|
const data = [];
|
|
201
216
|
for (const thread of page.data) {
|
|
202
217
|
try {
|
|
218
|
+
if (projectId === null && thread.source === "appServer" && page.data.filter((entry) => entry.cwd === thread.cwd).length === 1) {
|
|
219
|
+
const operationId = this.workspaces.recoverCreation(thread.id, thread.cwd);
|
|
220
|
+
if (operationId && !this.inFlightOperations.has(operationId)) {
|
|
221
|
+
const recovered = this.store.confirmWorkspaceCreation(operationId, thread.id);
|
|
222
|
+
this.events.publish("operation", this.operation(recovered));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
203
225
|
const normalized = this.normalize(thread, projectId);
|
|
204
226
|
normalized.hasMessages ??= true;
|
|
205
227
|
const cached = this.projection.get(thread.id);
|
|
206
228
|
if (cached && normalized.hasMessages) cached.hasMessages = true;
|
|
207
229
|
const titleRead = titleReads.get(thread.id);
|
|
208
|
-
if (cached &&
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
if (cached && archiveReads.get(thread.id) === (this.archiveRevisions.get(thread.id) ?? 0))
|
|
231
|
+
this.updateArchiveState(thread.id, Boolean(normalized.archived));
|
|
232
|
+
if (cached && titleRead !== void 0)
|
|
211
233
|
this.updateThreadTitle(thread.id, normalized.title, titleRead);
|
|
212
|
-
}
|
|
213
234
|
data.push(this.threadView(cached ?? normalized));
|
|
214
235
|
} catch (error) {
|
|
215
|
-
if (!(error instanceof AppError) || ![
|
|
236
|
+
if (!(error instanceof AppError) || ![
|
|
237
|
+
"PROJECT_OWNERSHIP",
|
|
238
|
+
"PROJECT_UNAVAILABLE",
|
|
239
|
+
"PROJECT_NOT_FOUND",
|
|
240
|
+
"WORKSPACE_UNAVAILABLE"
|
|
241
|
+
].includes(error.code))
|
|
216
242
|
throw error;
|
|
217
243
|
}
|
|
218
244
|
}
|
|
@@ -239,7 +265,7 @@ class GatewayService {
|
|
|
239
265
|
(thread) => (thread.managed || this.store.activeForThread(thread.id)) && thread.projectId === projectId && this.historyVisible(thread)
|
|
240
266
|
).sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0, 30);
|
|
241
267
|
for (const thread of managed) {
|
|
242
|
-
this.
|
|
268
|
+
this.workspaces.assertOwnership(projectId, thread.cwd, thread.id);
|
|
243
269
|
if (!data.some((entry) => entry.id === thread.id)) data.unshift(this.threadView(thread));
|
|
244
270
|
}
|
|
245
271
|
}
|
|
@@ -264,7 +290,7 @@ class GatewayService {
|
|
|
264
290
|
this.archiveChanges.add(threadId);
|
|
265
291
|
try {
|
|
266
292
|
const actual = await this.codex.readThreadMetadata(threadId);
|
|
267
|
-
this.
|
|
293
|
+
this.workspaces.assertOwnership(thread.projectId, actual.cwd, thread.id);
|
|
268
294
|
const current = this.projection.get(threadId);
|
|
269
295
|
if (input.archived && (this.resumes.has(threadId) || this.store.activeForThread(threadId) || current.managed && current.status === "unknown" || [actual, current].some(
|
|
270
296
|
(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 +354,7 @@ class GatewayService {
|
|
|
328
354
|
actual = await this.codex.readThreadMetadata(threadId);
|
|
329
355
|
if (actual.id !== threadId || this.codex.epoch !== epoch || !this.codex.ready)
|
|
330
356
|
throw new Error("\u4F1A\u8BDD\u6216 Codex \u5B9E\u4F8B\u5DF2\u53D8\u5316");
|
|
331
|
-
this.
|
|
357
|
+
this.workspaces.assertOwnership(thread.projectId, actual.cwd, thread.id);
|
|
332
358
|
} catch (cause) {
|
|
333
359
|
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
360
|
}
|
|
@@ -376,6 +402,7 @@ class GatewayService {
|
|
|
376
402
|
const thread = this.projection.get(threadId);
|
|
377
403
|
if (!thread || Boolean(thread.archived) === archived) return;
|
|
378
404
|
thread.archived = archived;
|
|
405
|
+
this.archiveRevisions.set(threadId, (this.archiveRevisions.get(threadId) ?? 0) + 1);
|
|
379
406
|
if (archived) this.releaseThreadOwnership(threadId);
|
|
380
407
|
this.projection.touch(threadId);
|
|
381
408
|
if (!this.archiveChanges.has(threadId)) this.publishThread(threadId);
|
|
@@ -383,9 +410,9 @@ class GatewayService {
|
|
|
383
410
|
async getThreadMetadata(threadId) {
|
|
384
411
|
const cached = this.projection.get(threadId);
|
|
385
412
|
if (cached) {
|
|
386
|
-
this.
|
|
413
|
+
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
387
414
|
if (this.codex.ready) await this.refreshThreadMetadata(threadId);
|
|
388
|
-
this.
|
|
415
|
+
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
389
416
|
return { ...this.threadView(cached), turns: [], actions: [] };
|
|
390
417
|
}
|
|
391
418
|
const metadata = this.threadView(this.normalize(await this.codex.readThreadMetadata(threadId)));
|
|
@@ -396,7 +423,7 @@ class GatewayService {
|
|
|
396
423
|
await this.loadThread(threadId);
|
|
397
424
|
if (cached && this.codex.ready) await this.refreshThreadMetadata(threadId);
|
|
398
425
|
const thread = this.projection.get(threadId);
|
|
399
|
-
this.
|
|
426
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
400
427
|
return this.threadView(thread);
|
|
401
428
|
}
|
|
402
429
|
async getThreadHistory(threadId, cursor) {
|
|
@@ -404,11 +431,11 @@ class GatewayService {
|
|
|
404
431
|
throw new AppError("VALIDATION_FAILED", "Missing or invalid history cursor", 400);
|
|
405
432
|
this.ensureReady();
|
|
406
433
|
const metadata = this.normalize(await this.codex.readThreadMetadata(threadId));
|
|
407
|
-
this.
|
|
434
|
+
this.workspaces.assertOwnership(metadata.projectId, metadata.cwd, metadata.id);
|
|
408
435
|
if (!this.codex.readThreadPage)
|
|
409
436
|
throw new AppError("HISTORY_UNAVAILABLE", "\u5F53\u524D Codex \u4E0D\u652F\u6301\u5386\u53F2\u5206\u9875", 409);
|
|
410
437
|
const page = this.normalize(await this.codex.readThreadPage(threadId, cursor));
|
|
411
|
-
this.
|
|
438
|
+
this.workspaces.assertOwnership(page.projectId, page.cwd, page.id);
|
|
412
439
|
return page;
|
|
413
440
|
}
|
|
414
441
|
async exportThread(threadId) {
|
|
@@ -427,8 +454,8 @@ class GatewayService {
|
|
|
427
454
|
409
|
|
428
455
|
);
|
|
429
456
|
const link = this.store.getThreadLink(threadId);
|
|
430
|
-
if (link) this.
|
|
431
|
-
else this.
|
|
457
|
+
if (link) this.workspaces.assertOwnership(link.projectId, thread.cwd, thread.id);
|
|
458
|
+
else this.workspaces.findOwner(thread.cwd, thread.id);
|
|
432
459
|
return renderThreadExport(thread, {
|
|
433
460
|
executionUncertain: Boolean(this.store.activeForThread(threadId)),
|
|
434
461
|
historyChanged: revision !== this.projection.revision(threadId) || thread.updatedAt !== latest.updatedAt || thread.status !== latest.status
|
|
@@ -449,21 +476,23 @@ class GatewayService {
|
|
|
449
476
|
this.normalize(await this.codex.readThreadMetadata(threadId));
|
|
450
477
|
link = this.store.getThreadLink(threadId);
|
|
451
478
|
}
|
|
452
|
-
const
|
|
479
|
+
const ownership = this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
|
|
480
|
+
const { cwd } = ownership;
|
|
453
481
|
const file = await read(path, {
|
|
454
482
|
cwd,
|
|
483
|
+
excludedRoots: this.workspaces.allocations().filter((row) => row.threadId !== threadId).map((row) => row.canonicalRoot),
|
|
455
484
|
roots: [
|
|
456
|
-
{ canonicalRoot:
|
|
457
|
-
...this.fileTemporaryRoots
|
|
485
|
+
{ canonicalRoot: ownership.canonicalRoot, aliases: [ownership.rootPath] },
|
|
486
|
+
...link.projectId === null ? [] : this.fileTemporaryRoots
|
|
458
487
|
]
|
|
459
488
|
});
|
|
460
|
-
this.
|
|
489
|
+
this.workspaces.assertOwnership(link.projectId, link.cwd, threadId);
|
|
461
490
|
return file;
|
|
462
491
|
}
|
|
463
492
|
async loadThread(threadId) {
|
|
464
493
|
const cached = this.projection.get(threadId);
|
|
465
494
|
if (cached) {
|
|
466
|
-
this.
|
|
495
|
+
this.workspaces.assertOwnership(cached.projectId, cached.cwd, cached.id);
|
|
467
496
|
const marker = this.store.activeForThread(threadId);
|
|
468
497
|
if (marker?.turnId && this.codex.ready) {
|
|
469
498
|
await this.refreshExecution(marker);
|
|
@@ -498,18 +527,26 @@ class GatewayService {
|
|
|
498
527
|
}
|
|
499
528
|
}
|
|
500
529
|
async createThread(projectId, input) {
|
|
501
|
-
return this.perform("thread.create", projectId, input, async () => {
|
|
502
|
-
const project = this.projects.get(projectId);
|
|
530
|
+
return this.perform("thread.create", projectId ?? UNASSIGNED_SCOPE, input, async () => {
|
|
503
531
|
this.ensureReady();
|
|
504
532
|
this.ensureCompatibilityConfirmed();
|
|
533
|
+
const cwd = projectId === null ? this.workspaces.allocate(input.clientOperationId).cwd : this.projects.get(projectId).canonicalRoot;
|
|
534
|
+
if (projectId !== null) this.workspaces.assertProjectDirectory(cwd);
|
|
505
535
|
this.store.associateProject(input.clientOperationId, projectId);
|
|
506
536
|
this.store.dispatch(input.clientOperationId);
|
|
507
|
-
this.pendingThreadCreations.set(input.clientOperationId,
|
|
537
|
+
this.pendingThreadCreations.set(input.clientOperationId, cwd);
|
|
508
538
|
const epoch = this.codex.epoch;
|
|
509
539
|
const settingsSequence = this.runtimeSettingsSequence;
|
|
510
|
-
const thread = await this.codex.createThread(
|
|
540
|
+
const thread = await this.codex.createThread(cwd, input.settings);
|
|
511
541
|
if (this.codex.epoch !== epoch || !this.codex.ready)
|
|
512
542
|
throw new Error("\u521B\u5EFA\u8BF7\u6C42\u5DF2\u53D1\u9001\uFF0C\u4F46 Codex \u5B9E\u4F8B\u5DF2\u53D8\u5316\uFF0C\u8BF7\u6838\u5BF9\u5386\u53F2");
|
|
543
|
+
if (projectId === null) {
|
|
544
|
+
try {
|
|
545
|
+
this.workspaces.bind(input.clientOperationId, thread.id, thread.cwd);
|
|
546
|
+
} catch (cause) {
|
|
547
|
+
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 });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
513
550
|
const normalized = this.normalize(thread, projectId, true);
|
|
514
551
|
this.projection.thread(
|
|
515
552
|
normalized,
|
|
@@ -526,7 +563,7 @@ class GatewayService {
|
|
|
526
563
|
this.ensureReady();
|
|
527
564
|
this.ensureArchiveAvailable(thread);
|
|
528
565
|
if (thread.archived) throw new AppError("THREAD_ARCHIVED", "\u8BF7\u5148\u53D6\u6D88\u5F52\u6863\u540E\u518D\u6062\u590D\u4F1A\u8BDD", 409);
|
|
529
|
-
this.
|
|
566
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
530
567
|
this.store.associateProject(input.clientOperationId, thread.projectId);
|
|
531
568
|
const link = this.store.getThreadLink(threadId);
|
|
532
569
|
if (link?.managedEpoch === this.codex.epoch) {
|
|
@@ -540,7 +577,7 @@ class GatewayService {
|
|
|
540
577
|
operationId: marker.operationId,
|
|
541
578
|
threadId
|
|
542
579
|
});
|
|
543
|
-
const ownership = this.
|
|
580
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
544
581
|
this.ensureCompatibilityConfirmed();
|
|
545
582
|
this.store.dispatch(input.clientOperationId);
|
|
546
583
|
let pending = this.resumes.get(threadId);
|
|
@@ -585,21 +622,29 @@ class GatewayService {
|
|
|
585
622
|
this.ensureReady();
|
|
586
623
|
const source = this.projection.get(threadId) ?? await this.codex.readThread(threadId);
|
|
587
624
|
const link = this.store.getThreadLink(threadId);
|
|
588
|
-
const ownership = link ? this.
|
|
625
|
+
const ownership = link ? this.workspaces.assertOwnership(link.projectId, source.cwd, threadId) : this.workspaces.findOwner(source.cwd, source.id);
|
|
589
626
|
this.ensureCompatibilityConfirmed();
|
|
590
627
|
const epoch = this.codex.epoch;
|
|
591
|
-
|
|
628
|
+
let forkCwd = ownership.cwd;
|
|
629
|
+
if (ownership.projectId === null) {
|
|
630
|
+
forkCwd = this.workspaces.allocate(input.clientOperationId).cwd;
|
|
631
|
+
await cp(ownership.cwd, forkCwd, { recursive: true, verbatimSymlinks: true });
|
|
632
|
+
this.workspaces.assertOwnership(null, source.cwd, source.id);
|
|
633
|
+
}
|
|
634
|
+
this.store.associateProject(input.clientOperationId, ownership.projectId);
|
|
592
635
|
this.store.dispatch(input.clientOperationId);
|
|
593
|
-
this.pendingThreadCreations.set(input.clientOperationId,
|
|
636
|
+
this.pendingThreadCreations.set(input.clientOperationId, forkCwd);
|
|
594
637
|
const settingsSequence = this.runtimeSettingsSequence;
|
|
595
|
-
const forked = await this.codex.forkThread(threadId,
|
|
638
|
+
const forked = await this.codex.forkThread(threadId, forkCwd, input.settings);
|
|
596
639
|
if (forked.id === threadId || this.codex.epoch !== epoch || !this.codex.ready)
|
|
597
640
|
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
641
|
let normalized;
|
|
599
642
|
try {
|
|
643
|
+
if (ownership.projectId === null)
|
|
644
|
+
this.workspaces.bind(input.clientOperationId, forked.id, forked.cwd);
|
|
600
645
|
normalized = this.normalize(
|
|
601
646
|
{ ...forked, forkedFromId: threadId },
|
|
602
|
-
ownership.
|
|
647
|
+
ownership.projectId,
|
|
603
648
|
true
|
|
604
649
|
);
|
|
605
650
|
} catch (error) {
|
|
@@ -618,7 +663,7 @@ class GatewayService {
|
|
|
618
663
|
return this.perform("turn.start", threadId, input, async () => {
|
|
619
664
|
const thread = await this.loadThread(threadId);
|
|
620
665
|
this.ensureManaged(thread);
|
|
621
|
-
const ownership = this.
|
|
666
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
622
667
|
if (isActive(thread.status))
|
|
623
668
|
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
669
|
this.ensureCompatibilityConfirmed();
|
|
@@ -651,7 +696,7 @@ class GatewayService {
|
|
|
651
696
|
return this.perform("thread.compact", threadId, input, async () => {
|
|
652
697
|
const thread = await this.loadThread(threadId);
|
|
653
698
|
this.ensureManaged(thread);
|
|
654
|
-
const ownership = this.
|
|
699
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
655
700
|
if (isActive(thread.status) || thread.turns.some((turn) => isActive(turn.status)))
|
|
656
701
|
throw new AppError("THREAD_BUSY", "\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u540E\u518D\u538B\u7F29\u4E0A\u4E0B\u6587", 409);
|
|
657
702
|
if (!thread.turns.length)
|
|
@@ -675,7 +720,7 @@ class GatewayService {
|
|
|
675
720
|
return this.perform("review.start", threadId, input, async () => {
|
|
676
721
|
const thread = await this.loadThread(threadId);
|
|
677
722
|
this.ensureManaged(thread);
|
|
678
|
-
const ownership = this.
|
|
723
|
+
const ownership = this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
679
724
|
if (isActive(thread.status) || thread.turns.some((turn2) => isActive(turn2.status)))
|
|
680
725
|
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
726
|
threadId
|
|
@@ -774,8 +819,8 @@ class GatewayService {
|
|
|
774
819
|
try {
|
|
775
820
|
const actual = await this.codex.readThread(marker.threadId);
|
|
776
821
|
const link = this.store.getThreadLink(marker.threadId);
|
|
777
|
-
if (link) this.
|
|
778
|
-
else this.
|
|
822
|
+
if (link) this.workspaces.assertOwnership(link.projectId, actual.cwd, actual.id);
|
|
823
|
+
else this.workspaces.findOwner(actual.cwd, actual.id);
|
|
779
824
|
if (this.markerIsActive(actual, marker.turnId))
|
|
780
825
|
throw new AppError("THREAD_BUSY", "\u4E0A\u6E38\u4ECD\u62A5\u544A\u8BE5\u4EFB\u52A1\u6D3B\u52A8\uFF0C\u4E0D\u80FD\u89E3\u9664\u5360\u7528", 409, {
|
|
781
826
|
threadId: marker.threadId
|
|
@@ -870,14 +915,14 @@ class GatewayService {
|
|
|
870
915
|
}
|
|
871
916
|
normalize(thread, projectId, managed = false) {
|
|
872
917
|
const link = this.store.getThreadLink(thread.id);
|
|
873
|
-
const ownership = link ? this.
|
|
874
|
-
if (projectId && ownership.
|
|
918
|
+
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);
|
|
919
|
+
if (projectId !== void 0 && ownership.projectId !== projectId)
|
|
875
920
|
throw new AppError("PROJECT_OWNERSHIP", "\u4F1A\u8BDD\u5C5E\u4E8E\u5176\u4ED6\u9879\u76EE\uFF0C\u4E0D\u80FD\u6539\u53D8\u9879\u76EE\u5F52\u5C5E", 403);
|
|
876
921
|
const managedEpoch = thread.archived ? null : managed ? this.codex.epoch : link?.managedEpoch ?? null;
|
|
877
922
|
const result = {
|
|
878
923
|
...thread,
|
|
879
924
|
turns: this.turnUsage.restore(thread.id, thread.turns),
|
|
880
|
-
projectId: ownership.
|
|
925
|
+
projectId: ownership.projectId,
|
|
881
926
|
cwd: ownership.cwd,
|
|
882
927
|
managed: managedEpoch === this.codex.epoch,
|
|
883
928
|
resumeError: managed ? void 0 : this.projection.get(thread.id)?.resumeError,
|
|
@@ -887,7 +932,7 @@ class GatewayService {
|
|
|
887
932
|
};
|
|
888
933
|
this.store.linkThread({
|
|
889
934
|
threadId: thread.id,
|
|
890
|
-
projectId: ownership.
|
|
935
|
+
projectId: ownership.projectId,
|
|
891
936
|
cwd: ownership.cwd,
|
|
892
937
|
source: thread.source,
|
|
893
938
|
managedEpoch
|
|
@@ -922,7 +967,7 @@ class GatewayService {
|
|
|
922
967
|
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
968
|
return;
|
|
924
969
|
const link = this.store.getThreadLink(marker.threadId);
|
|
925
|
-
const ownership = link ? this.
|
|
970
|
+
const ownership = link ? this.workspaces.assertOwnership(link.projectId, actual.cwd, actual.id) : this.workspaces.findOwner(actual.cwd, actual.id);
|
|
926
971
|
if (ownership.workspaceKey !== marker.workspaceKey) return;
|
|
927
972
|
const turn = actual.turns.find((entry) => entry.id === marker.turnId);
|
|
928
973
|
if (turn && isActive(turn.status) && latest.state === "active" && this.projection.get(marker.threadId)?.status === "unknown") {
|
|
@@ -1072,7 +1117,7 @@ class GatewayService {
|
|
|
1072
1117
|
const encoded = JSON.stringify(summary);
|
|
1073
1118
|
if (this.summaries.get(threadId) === encoded) return;
|
|
1074
1119
|
try {
|
|
1075
|
-
this.
|
|
1120
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
1076
1121
|
} catch {
|
|
1077
1122
|
return;
|
|
1078
1123
|
}
|
|
@@ -1120,7 +1165,7 @@ class GatewayService {
|
|
|
1120
1165
|
const thread = await this.codex.readThreadMetadata(threadId);
|
|
1121
1166
|
const current = this.projection.get(threadId);
|
|
1122
1167
|
if (!current || thread.id !== threadId) return;
|
|
1123
|
-
this.
|
|
1168
|
+
this.workspaces.assertOwnership(current.projectId, thread.cwd, thread.id);
|
|
1124
1169
|
const settings = thread.runtimeSettings ? { ...current.runtimeSettings, ...thread.runtimeSettings } : void 0;
|
|
1125
1170
|
if (this.titleReads.get(threadId) === sequence && this.projection.revision(threadId) === revision && settings && JSON.stringify(current.runtimeSettings) !== JSON.stringify(settings)) {
|
|
1126
1171
|
current.runtimeSettings = settings;
|
|
@@ -1225,7 +1270,7 @@ class GatewayService {
|
|
|
1225
1270
|
this.projection.expireTurn(thread.id, event.turn.id);
|
|
1226
1271
|
if (newCompletion) {
|
|
1227
1272
|
try {
|
|
1228
|
-
this.
|
|
1273
|
+
this.workspaces.assertOwnership(thread.projectId, thread.cwd, thread.id);
|
|
1229
1274
|
this.events.publishCompletion({
|
|
1230
1275
|
threadId: thread.id,
|
|
1231
1276
|
turnId: event.turn.id,
|