@cjhyy/code-shell-server 0.9.35 → 0.9.37
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/dist/bin/code-shell-cloud-account.d.ts +2 -0
- package/dist/bin/code-shell-cloud-account.js +53 -0
- package/dist/index.serve.d.ts +3 -0
- package/dist/index.serve.js +2 -0
- package/dist/mobile-remote/mobile-client-event-validator.js +10 -1
- package/dist/mobile-remote/room-history.d.ts +20 -0
- package/dist/mobile-remote/room-history.js +327 -0
- package/dist/mobile-remote/room-manager.d.ts +11 -0
- package/dist/mobile-remote/room-manager.js +107 -11
- package/dist/panels/management.js +12 -1
- package/dist/panels/runtime-services.js +32 -7
- package/dist/panels/types.d.ts +5 -3
- package/dist/project-runtime/account-adoption.d.ts +34 -0
- package/dist/project-runtime/account-adoption.js +280 -0
- package/dist/project-runtime/account-binding.d.ts +12 -0
- package/dist/project-runtime/account-binding.js +33 -0
- package/dist/project-runtime/backup.d.ts +7 -0
- package/dist/project-runtime/backup.js +56 -8
- package/dist/project-runtime/cloud-account-auth.d.ts +23 -0
- package/dist/project-runtime/cloud-account-auth.js +287 -0
- package/dist/project-runtime/control-authority.d.ts +46 -0
- package/dist/project-runtime/control-authority.js +15 -0
- package/dist/project-runtime/control-server.d.ts +3 -0
- package/dist/project-runtime/control-server.js +60 -24
- package/dist/project-runtime/lock.js +181 -12
- package/dist/project-runtime/manager.d.ts +12 -8
- package/dist/project-runtime/manager.js +56 -15
- package/dist/project-runtime/proxy.d.ts +3 -3
- package/dist/project-runtime/proxy.js +4 -1
- package/dist/project-runtime/registry.d.ts +14 -1
- package/dist/project-runtime/registry.js +91 -2
- package/package.json +7 -6
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { constants, chmodSync, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { acquireProjectControllerLock } from "./lock.js";
|
|
5
|
+
import { CLOUD_ADOPTION_PENDING, isCloudAccountBinding, } from "./account-binding.js";
|
|
5
6
|
const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
6
7
|
const TOKEN = /^[A-Za-z0-9_-]{43}$/;
|
|
7
8
|
const STATUSES = new Set(["stopped", "starting", "running", "stopping", "error"]);
|
|
@@ -58,6 +59,7 @@ export class ProjectRegistry {
|
|
|
58
59
|
controllerLock;
|
|
59
60
|
diskDigest;
|
|
60
61
|
markerEstablished = false;
|
|
62
|
+
storageFailed = false;
|
|
61
63
|
constructor(dataDir, now = Date.now, io = {}) {
|
|
62
64
|
this.now = now;
|
|
63
65
|
this.io = io;
|
|
@@ -72,6 +74,16 @@ export class ProjectRegistry {
|
|
|
72
74
|
this.marker = join(this.directory, "initialized");
|
|
73
75
|
this.controllerLock = acquireProjectControllerLock(this.directory);
|
|
74
76
|
try {
|
|
77
|
+
if (!this.io.offlineAccountAdoption) {
|
|
78
|
+
try {
|
|
79
|
+
lstatSync(join(this.directory, CLOUD_ADOPTION_PENDING));
|
|
80
|
+
throw new Error("Cloud account adoption is incomplete; retry its explicit offline transaction.");
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error.code !== "ENOENT")
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
75
87
|
if (existsSync(this.marker)) {
|
|
76
88
|
if (privateFileBytes(this.marker, 16).toString() !== "1\n")
|
|
77
89
|
throw new Error("Project initialization marker is corrupt.");
|
|
@@ -109,6 +121,8 @@ export class ProjectRegistry {
|
|
|
109
121
|
throw new Error("Stop all projects before restoring an installation.");
|
|
110
122
|
const next = structuredClone(this.data);
|
|
111
123
|
next.installationId = randomUUID();
|
|
124
|
+
if (next.accountBinding)
|
|
125
|
+
next.accountBinding.managementEpoch = randomUUID();
|
|
112
126
|
for (const project of next.projects) {
|
|
113
127
|
if (project.generation > 0)
|
|
114
128
|
project.generation += 1;
|
|
@@ -122,15 +136,43 @@ export class ProjectRegistry {
|
|
|
122
136
|
this.assertStorage();
|
|
123
137
|
return this.data.installationId;
|
|
124
138
|
}
|
|
139
|
+
get accountBinding() {
|
|
140
|
+
this.assertStorage();
|
|
141
|
+
return this.data.accountBinding && { ...this.data.accountBinding };
|
|
142
|
+
}
|
|
143
|
+
/** Offline migration only; the caller must durably preserve source evidence first. */
|
|
144
|
+
bindAccount(binding) {
|
|
145
|
+
this.assertStorage();
|
|
146
|
+
if (this.data.version !== 1 ||
|
|
147
|
+
this.data.accountBinding !== undefined ||
|
|
148
|
+
!isCloudAccountBinding(binding) ||
|
|
149
|
+
this.data.owners.length !== 1 ||
|
|
150
|
+
this.data.owners[0].id !== binding.ownerId ||
|
|
151
|
+
this.data.projects.some((project) => project.status !== "stopped"))
|
|
152
|
+
throw new Error("Cloud adoption requires one explicit legacy owner and stopped projects.");
|
|
153
|
+
const next = structuredClone(this.data);
|
|
154
|
+
// The real historical registry rejects version 2. Unknown metadata alone
|
|
155
|
+
// cannot prevent an old controller from continuing its username authority.
|
|
156
|
+
next.version = 2;
|
|
157
|
+
next.accountBinding = { ...binding };
|
|
158
|
+
this.write(next);
|
|
159
|
+
this.data = next;
|
|
160
|
+
}
|
|
125
161
|
validate(data = this.data) {
|
|
126
162
|
if (!data ||
|
|
127
|
-
data.version !== 1 ||
|
|
163
|
+
(data.version !== 1 && data.version !== 2) ||
|
|
128
164
|
!UUID.test(data.installationId) ||
|
|
129
165
|
!Array.isArray(data.owners) ||
|
|
130
166
|
!Array.isArray(data.projects) ||
|
|
131
167
|
data.owners.length > 128 ||
|
|
132
168
|
data.projects.length > 128)
|
|
133
169
|
throw new Error("Project registry is corrupt; restore its backup");
|
|
170
|
+
if ((data.version === 1 && data.accountBinding !== undefined) ||
|
|
171
|
+
(data.version === 2 &&
|
|
172
|
+
(!isCloudAccountBinding(data.accountBinding) ||
|
|
173
|
+
data.owners.length !== 1 ||
|
|
174
|
+
data.owners[0]?.id !== data.accountBinding.ownerId)))
|
|
175
|
+
throw new Error("Cloud account ownership is missing or corrupt; restore its backup.");
|
|
134
176
|
const owners = new Set();
|
|
135
177
|
const names = new Set();
|
|
136
178
|
for (const owner of data.owners) {
|
|
@@ -174,6 +216,8 @@ export class ProjectRegistry {
|
|
|
174
216
|
}
|
|
175
217
|
}
|
|
176
218
|
assertStorage() {
|
|
219
|
+
if (this.storageFailed)
|
|
220
|
+
throw new Error("Project registry durability failed; restart and diagnose before continuing.");
|
|
177
221
|
this.controllerLock.assertHeld();
|
|
178
222
|
if (this.markerEstablished && privateFileBytes(this.marker, 16).toString() !== "1\n")
|
|
179
223
|
throw new Error("Project initialization marker changed.");
|
|
@@ -192,15 +236,29 @@ export class ProjectRegistry {
|
|
|
192
236
|
throw new Error("Project registry is too large.");
|
|
193
237
|
const file = join(this.directory, `.registry-${randomUUID()}.tmp`);
|
|
194
238
|
let fd;
|
|
239
|
+
let committed = false;
|
|
195
240
|
try {
|
|
196
241
|
fd = openSync(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
197
242
|
writeFileSync(fd, bytes);
|
|
198
|
-
fsyncSync(fd);
|
|
243
|
+
(this.io.fsync ?? fsyncSync)(fd);
|
|
199
244
|
closeSync(fd);
|
|
200
245
|
fd = undefined;
|
|
201
246
|
this.assertStorage();
|
|
202
247
|
(this.io.rename ?? renameSync)(file, this.file);
|
|
248
|
+
committed = true;
|
|
203
249
|
this.diskDigest = digest(bytes);
|
|
250
|
+
const directory = openSync(this.directory, constants.O_RDONLY);
|
|
251
|
+
try {
|
|
252
|
+
(this.io.fsync ?? fsyncSync)(directory);
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
closeSync(directory);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (committed)
|
|
260
|
+
this.storageFailed = true;
|
|
261
|
+
throw error;
|
|
204
262
|
}
|
|
205
263
|
finally {
|
|
206
264
|
if (fd !== undefined)
|
|
@@ -214,11 +272,13 @@ export class ProjectRegistry {
|
|
|
214
272
|
}
|
|
215
273
|
list(username) {
|
|
216
274
|
this.assertStorage();
|
|
275
|
+
this.requireLegacy();
|
|
217
276
|
const owner = this.data.owners.find((item) => item.username === username);
|
|
218
277
|
return this.data.projects.filter((item) => item.ownerId === owner?.id).map(projectView);
|
|
219
278
|
}
|
|
220
279
|
get(username, id) {
|
|
221
280
|
this.assertStorage();
|
|
281
|
+
this.requireLegacy();
|
|
222
282
|
const owner = this.data.owners.find((item) => item.username === username);
|
|
223
283
|
const project = this.data.projects.find((item) => item.id === id && item.ownerId === owner?.id);
|
|
224
284
|
if (!project)
|
|
@@ -227,6 +287,35 @@ export class ProjectRegistry {
|
|
|
227
287
|
}
|
|
228
288
|
create(username, name) {
|
|
229
289
|
this.assertStorage();
|
|
290
|
+
this.requireLegacy();
|
|
291
|
+
return this.createForOwner(username, name);
|
|
292
|
+
}
|
|
293
|
+
listAccount(ownerId) {
|
|
294
|
+
this.requireAccountOwner(ownerId);
|
|
295
|
+
return this.data.projects.filter((item) => item.ownerId === ownerId).map(projectView);
|
|
296
|
+
}
|
|
297
|
+
getAccount(ownerId, id) {
|
|
298
|
+
this.requireAccountOwner(ownerId);
|
|
299
|
+
const project = this.data.projects.find((item) => item.id === id && item.ownerId === ownerId);
|
|
300
|
+
if (!project)
|
|
301
|
+
throw new ProjectRegistryError(404, "找不到这个项目。");
|
|
302
|
+
return { ...project };
|
|
303
|
+
}
|
|
304
|
+
createAccount(ownerId, name) {
|
|
305
|
+
this.requireAccountOwner(ownerId);
|
|
306
|
+
// This username is internal legacy metadata, never the Account display name.
|
|
307
|
+
return this.createForOwner(this.data.owners[0].username, name);
|
|
308
|
+
}
|
|
309
|
+
requireLegacy() {
|
|
310
|
+
if (this.data.version !== 1)
|
|
311
|
+
throw new ProjectRegistryError(403, "此 Cloud 已采用明确的账号归属,请通过云账号管理。");
|
|
312
|
+
}
|
|
313
|
+
requireAccountOwner(ownerId) {
|
|
314
|
+
this.assertStorage();
|
|
315
|
+
if (this.data.version !== 2 || this.data.accountBinding?.ownerId !== ownerId)
|
|
316
|
+
throw new ProjectRegistryError(403, "此账号没有 Cloud 管理权限。");
|
|
317
|
+
}
|
|
318
|
+
createForOwner(username, name) {
|
|
230
319
|
if (typeof name !== "string" ||
|
|
231
320
|
!name.trim() ||
|
|
232
321
|
name.trim().length > 80 ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjhyy/code-shell-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.37",
|
|
4
4
|
"description": "CodeShell server transport layer — mobile remote HTTP/WS host, pairing, passcode gate, tunnel, rooms, uploads, and session/attachment services. Electron-free; precursor of the auth gateway.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
},
|
|
50
50
|
"bin": {
|
|
51
51
|
"code-shell-serve": "./dist/bin/code-shell-serve.js",
|
|
52
|
-
"code-shell-settings-recovery": "./dist/bin/code-shell-settings-recovery.js"
|
|
52
|
+
"code-shell-settings-recovery": "./dist/bin/code-shell-settings-recovery.js",
|
|
53
|
+
"code-shell-cloud-account": "./dist/bin/code-shell-cloud-account.js"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
56
|
"dist"
|
|
@@ -60,13 +61,13 @@
|
|
|
60
61
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
63
|
-
"@cjhyy/code-shell-capability-coding": "0.9.
|
|
64
|
-
"@cjhyy/code-shell-core": "0.9.
|
|
65
|
-
"@cjhyy/code-shell-web": "0.9.
|
|
64
|
+
"@cjhyy/code-shell-capability-coding": "0.9.37",
|
|
65
|
+
"@cjhyy/code-shell-core": "0.9.37",
|
|
66
|
+
"@cjhyy/code-shell-web": "0.9.37",
|
|
66
67
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
67
68
|
"@types/ws": "^8.18.1",
|
|
68
69
|
"ws": "^8.21.0",
|
|
69
|
-
"@cjhyy/code-shell-link": "0.9.
|
|
70
|
+
"@cjhyy/code-shell-link": "0.9.37"
|
|
70
71
|
},
|
|
71
72
|
"engines": {
|
|
72
73
|
"node": ">=20.10"
|