@cjhyy/code-shell-server 0.9.7 → 0.9.9
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 +85 -23
- package/dist/bin/code-shell-project-runtime.d.ts +2 -0
- package/dist/bin/code-shell-project-runtime.js +8 -0
- package/dist/desktop-web/http-api.d.ts +43 -0
- package/dist/desktop-web/http-api.js +376 -0
- package/dist/hub/approval-lease.d.ts +30 -0
- package/dist/hub/approval-lease.js +89 -0
- package/dist/hub/auth-http.d.ts +23 -0
- package/dist/hub/auth-http.js +259 -0
- package/dist/hub/auth-store.d.ts +52 -0
- package/dist/hub/auth-store.js +316 -0
- package/dist/hub/configuration.d.ts +64 -0
- package/dist/hub/configuration.js +555 -0
- package/dist/hub/files.d.ts +9 -0
- package/dist/hub/files.js +278 -0
- package/dist/hub/mcp-configuration.d.ts +51 -0
- package/dist/hub/mcp-configuration.js +558 -0
- package/dist/hub/mcp-probe.d.ts +24 -0
- package/dist/hub/mcp-probe.js +392 -0
- package/dist/hub/model-probe.d.ts +20 -0
- package/dist/hub/model-probe.js +193 -0
- package/dist/hub/outbound-transport.d.ts +36 -0
- package/dist/hub/outbound-transport.js +122 -0
- package/dist/hub/run-replay.d.ts +42 -0
- package/dist/hub/run-replay.js +89 -0
- package/dist/hub/session-management.d.ts +36 -0
- package/dist/hub/session-management.js +405 -0
- package/dist/hub/skills-management.d.ts +43 -0
- package/dist/hub/skills-management.js +487 -0
- package/dist/hub/uploads.d.ts +22 -0
- package/dist/hub/uploads.js +241 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.desktop-web.d.ts +2 -0
- package/dist/index.desktop-web.js +2 -0
- package/dist/index.js +3 -0
- package/dist/index.links.d.ts +6 -0
- package/dist/index.links.js +6 -0
- package/dist/index.panels.d.ts +9 -0
- package/dist/index.panels.js +8 -0
- package/dist/index.serve.d.ts +1 -0
- package/dist/index.serve.js +1 -0
- package/dist/index.storage.d.ts +2 -0
- package/dist/index.storage.js +2 -0
- package/dist/links/catalog.d.ts +10 -0
- package/dist/links/catalog.js +41 -0
- package/dist/links/device-oauth.d.ts +52 -0
- package/dist/links/device-oauth.js +255 -0
- package/dist/links/http.d.ts +34 -0
- package/dist/links/http.js +144 -0
- package/dist/links/service.d.ts +54 -0
- package/dist/links/service.js +578 -0
- package/dist/links/types.d.ts +6 -0
- package/dist/links/types.js +1 -0
- package/dist/mobile-remote/mobile-client-event-validator.js +5 -2
- package/dist/mobile-remote/pending-approvals.js +2 -0
- package/dist/mobile-remote/remote-host-manager.d.ts +8 -0
- package/dist/mobile-remote/remote-host-manager.js +90 -15
- package/dist/mobile-remote/room-manager.js +17 -5
- package/dist/panels/agent-task-host.d.ts +32 -0
- package/dist/panels/agent-task-host.js +457 -0
- package/dist/panels/agent-task-models.d.ts +27 -0
- package/dist/panels/agent-task-models.js +51 -0
- package/dist/panels/agent-task-service.d.ts +105 -0
- package/dist/panels/agent-task-service.js +283 -0
- package/dist/panels/http.d.ts +34 -0
- package/dist/panels/http.js +37 -0
- package/dist/panels/hub-binding.d.ts +12 -0
- package/dist/panels/hub-binding.js +174 -0
- package/dist/panels/management-http.d.ts +29 -0
- package/dist/panels/management-http.js +119 -0
- package/dist/panels/management.d.ts +39 -0
- package/dist/panels/management.js +494 -0
- package/dist/panels/process-files.d.ts +14 -0
- package/dist/panels/process-files.js +299 -0
- package/dist/panels/process-service.d.ts +96 -0
- package/dist/panels/process-service.js +501 -0
- package/dist/panels/runtime-services.d.ts +24 -0
- package/dist/panels/runtime-services.js +462 -0
- package/dist/panels/runtime.d.ts +51 -0
- package/dist/panels/runtime.js +1063 -0
- package/dist/panels/storage-store.d.ts +9 -0
- package/dist/panels/storage-store.js +154 -0
- package/dist/panels/types.d.ts +52 -0
- package/dist/panels/types.js +1 -0
- package/dist/project-runtime/control-server.d.ts +19 -0
- package/dist/project-runtime/control-server.js +300 -0
- package/dist/project-runtime/docker-provider.d.ts +33 -0
- package/dist/project-runtime/docker-provider.js +487 -0
- package/dist/project-runtime/lock.d.ts +6 -0
- package/dist/project-runtime/lock.js +43 -0
- package/dist/project-runtime/managed-entry.d.ts +10 -0
- package/dist/project-runtime/managed-entry.js +113 -0
- package/dist/project-runtime/manager.d.ts +40 -0
- package/dist/project-runtime/manager.js +249 -0
- package/dist/project-runtime/proxy.d.ts +22 -0
- package/dist/project-runtime/proxy.js +706 -0
- package/dist/project-runtime/registry.d.ts +54 -0
- package/dist/project-runtime/registry.js +286 -0
- package/dist/project-runtime/types.d.ts +43 -0
- package/dist/project-runtime/types.js +1 -0
- package/dist/serve/cli.d.ts +5 -0
- package/dist/serve/cli.js +82 -23
- package/dist/serve/headless-server.d.ts +11 -0
- package/dist/serve/headless-server.js +737 -83
- package/dist/session-titles-store.d.ts +17 -0
- package/dist/session-titles-store.js +174 -0
- package/dist/static-files.js +1 -0
- package/dist/worker-bridge-core.js +17 -6
- package/package.json +19 -5
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Durable UI-side session titles, keyed by canonical engine session id. */
|
|
2
|
+
interface TitleMap {
|
|
3
|
+
[sessionId: string]: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class SessionTitleConflictError extends Error {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
/** Independent host stores share the same atomic title persistence and cross-process lock. */
|
|
9
|
+
export declare function createSessionTitlesStore(file: string, options?: {
|
|
10
|
+
strictMutations?: boolean;
|
|
11
|
+
}): {
|
|
12
|
+
listTitles: () => Promise<TitleMap>;
|
|
13
|
+
setTitle: (id: string, title: string, expected?: {
|
|
14
|
+
expectedTitle?: string | null;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/** Durable UI-side session titles, keyed by canonical engine session id. */
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { acquireLockOnPath } from "@cjhyy/code-shell-core/internal";
|
|
7
|
+
const MAX_TITLE_ENTRIES = 20_000;
|
|
8
|
+
const MAX_SESSION_ID_LENGTH = 256;
|
|
9
|
+
const MAX_TITLE_LENGTH = 1_024;
|
|
10
|
+
const MAX_TITLE_FILE_BYTES = 4 * 1024 * 1024;
|
|
11
|
+
const FORBIDDEN_MAP_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
|
12
|
+
export class SessionTitleConflictError extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Session title changed on another device");
|
|
15
|
+
this.name = "SessionTitleConflictError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function validSessionId(value) {
|
|
19
|
+
return (typeof value === "string" &&
|
|
20
|
+
value.length > 0 &&
|
|
21
|
+
value.length <= MAX_SESSION_ID_LENGTH &&
|
|
22
|
+
!FORBIDDEN_MAP_KEYS.has(value) &&
|
|
23
|
+
/^[A-Za-z0-9._-]+$/.test(value));
|
|
24
|
+
}
|
|
25
|
+
function validTitle(value) {
|
|
26
|
+
return typeof value === "string" && value.length <= MAX_TITLE_LENGTH && !value.includes("\0");
|
|
27
|
+
}
|
|
28
|
+
function parseTitleMap(value, strict = false) {
|
|
29
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30
|
+
if (strict)
|
|
31
|
+
throw new Error("session title registry is malformed");
|
|
32
|
+
return Object.create(null);
|
|
33
|
+
}
|
|
34
|
+
if (strict && Object.keys(value).length > MAX_TITLE_ENTRIES)
|
|
35
|
+
throw new Error("session title registry contains too many entries");
|
|
36
|
+
const result = Object.create(null);
|
|
37
|
+
for (const [id, title] of Object.entries(value).slice(0, MAX_TITLE_ENTRIES)) {
|
|
38
|
+
if (!validSessionId(id) || !validTitle(title)) {
|
|
39
|
+
if (strict)
|
|
40
|
+
throw new Error("session title registry contains malformed entries");
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (strict || title)
|
|
44
|
+
result[id] = title;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
function load(target, strict = false) {
|
|
49
|
+
let descriptor;
|
|
50
|
+
try {
|
|
51
|
+
const entry = fs.lstatSync(target);
|
|
52
|
+
if (entry.isSymbolicLink() || !entry.isFile() || entry.size > MAX_TITLE_FILE_BYTES)
|
|
53
|
+
throw new Error("session title registry must be a bounded regular file");
|
|
54
|
+
descriptor = fs.openSync(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
|
|
55
|
+
const opened = fs.fstatSync(descriptor);
|
|
56
|
+
if (!opened.isFile() || opened.size > MAX_TITLE_FILE_BYTES)
|
|
57
|
+
throw new Error("session title registry must be a bounded regular file");
|
|
58
|
+
// A concurrent append must not turn a checked four-megabyte file into an
|
|
59
|
+
// unbounded allocation. Read at most the limit plus one sentinel byte.
|
|
60
|
+
const buffer = Buffer.alloc(Math.min(opened.size + 1, MAX_TITLE_FILE_BYTES + 1));
|
|
61
|
+
let bytes = 0;
|
|
62
|
+
while (bytes < buffer.length) {
|
|
63
|
+
const count = fs.readSync(descriptor, buffer, bytes, buffer.length - bytes, bytes);
|
|
64
|
+
if (!count)
|
|
65
|
+
break;
|
|
66
|
+
bytes += count;
|
|
67
|
+
}
|
|
68
|
+
if (bytes > opened.size || bytes > MAX_TITLE_FILE_BYTES)
|
|
69
|
+
throw new Error("session title registry changed during read");
|
|
70
|
+
return parseTitleMap(JSON.parse(buffer.subarray(0, bytes).toString("utf8")), strict);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (strict && error.code !== "ENOENT")
|
|
74
|
+
throw error;
|
|
75
|
+
return Object.create(null);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
if (descriptor !== undefined)
|
|
79
|
+
fs.closeSync(descriptor);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function save(target, map) {
|
|
83
|
+
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
84
|
+
try {
|
|
85
|
+
const parent = path.dirname(target);
|
|
86
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
87
|
+
const parentInfo = fs.lstatSync(parent);
|
|
88
|
+
if (parentInfo.isSymbolicLink() || !parentInfo.isDirectory())
|
|
89
|
+
throw new Error("session title directory must be a real directory");
|
|
90
|
+
try {
|
|
91
|
+
const targetInfo = fs.lstatSync(target);
|
|
92
|
+
if (targetInfo.isSymbolicLink() || !targetInfo.isFile())
|
|
93
|
+
throw new Error("session title target must be a regular file");
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error.code !== "ENOENT")
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
const serialized = `${JSON.stringify(map, null, 2)}\n`;
|
|
100
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_TITLE_FILE_BYTES)
|
|
101
|
+
throw new Error("session title registry is too large");
|
|
102
|
+
fs.writeFileSync(temporary, serialized, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
103
|
+
fs.renameSync(temporary, target);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
try {
|
|
107
|
+
fs.rmSync(temporary, { force: true });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* preserve the original write error */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Independent host stores share the same atomic title persistence and cross-process lock. */
|
|
115
|
+
export function createSessionTitlesStore(file, options = {}) {
|
|
116
|
+
let mutationQueue = Promise.resolve();
|
|
117
|
+
function serializeMutation(mutation) {
|
|
118
|
+
const target = file;
|
|
119
|
+
const result = mutationQueue.then(() => mutation(target));
|
|
120
|
+
mutationQueue = result.catch(() => undefined);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
async function listTitles() {
|
|
124
|
+
await mutationQueue.catch(() => undefined);
|
|
125
|
+
return load(file);
|
|
126
|
+
}
|
|
127
|
+
function setTitle(id, title, expected = {}) {
|
|
128
|
+
if (!validSessionId(id))
|
|
129
|
+
throw new Error("invalid session id");
|
|
130
|
+
if (!validTitle(title))
|
|
131
|
+
throw new Error("invalid session title");
|
|
132
|
+
if (expected.expectedTitle !== undefined &&
|
|
133
|
+
expected.expectedTitle !== null &&
|
|
134
|
+
!validTitle(expected.expectedTitle))
|
|
135
|
+
throw new Error("invalid expected session title");
|
|
136
|
+
return serializeMutation((target) => {
|
|
137
|
+
const parent = path.dirname(target);
|
|
138
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
139
|
+
const parentInfo = fs.lstatSync(parent);
|
|
140
|
+
if (parentInfo.isSymbolicLink() || !parentInfo.isDirectory()) {
|
|
141
|
+
throw new Error("session title directory must be a real directory");
|
|
142
|
+
}
|
|
143
|
+
// A volume root such as /data is writable while its parent / is not.
|
|
144
|
+
// Keep both the lock anchor and its sibling lock inside that volume.
|
|
145
|
+
const anchor = path.join(parent, `.${path.basename(target)}.mutex`);
|
|
146
|
+
fs.mkdirSync(anchor, { recursive: true, mode: 0o700 });
|
|
147
|
+
const anchorInfo = fs.lstatSync(anchor);
|
|
148
|
+
if (!anchorInfo.isDirectory() || anchorInfo.isSymbolicLink())
|
|
149
|
+
throw new Error("session title lock anchor must be a real directory");
|
|
150
|
+
// No await inside this synchronous cross-process lock: a second store
|
|
151
|
+
// instance must never block the event loop while this holder waits for I/O.
|
|
152
|
+
const release = acquireLockOnPath(anchor);
|
|
153
|
+
try {
|
|
154
|
+
const map = load(target, options.strictMutations);
|
|
155
|
+
if (expected.expectedTitle !== undefined &&
|
|
156
|
+
(map[id] ?? "") !== (expected.expectedTitle ?? ""))
|
|
157
|
+
throw new SessionTitleConflictError();
|
|
158
|
+
if (title) {
|
|
159
|
+
if (!(id in map) && Object.keys(map).length >= MAX_TITLE_ENTRIES) {
|
|
160
|
+
throw new Error("session title registry is full");
|
|
161
|
+
}
|
|
162
|
+
map[id] = title;
|
|
163
|
+
}
|
|
164
|
+
else
|
|
165
|
+
delete map[id];
|
|
166
|
+
save(target, map);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
release();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return { listTitles, setTitle };
|
|
174
|
+
}
|
package/dist/static-files.js
CHANGED
|
@@ -9,6 +9,7 @@ const CONTENT_TYPES = {
|
|
|
9
9
|
".mjs": "text/javascript; charset=utf-8",
|
|
10
10
|
".css": "text/css; charset=utf-8",
|
|
11
11
|
".json": "application/json; charset=utf-8",
|
|
12
|
+
".webmanifest": "application/manifest+json; charset=utf-8",
|
|
12
13
|
".svg": "image/svg+xml",
|
|
13
14
|
".png": "image/png",
|
|
14
15
|
".jpg": "image/jpeg",
|
|
@@ -195,6 +195,8 @@ export class WorkerBridgeCore {
|
|
|
195
195
|
catch {
|
|
196
196
|
return false;
|
|
197
197
|
}
|
|
198
|
+
if (!msg || typeof msg !== "object" || Array.isArray(msg))
|
|
199
|
+
return false;
|
|
198
200
|
if (msg.id === undefined || msg.id === null)
|
|
199
201
|
return false;
|
|
200
202
|
const pending = this.pendingRequests.get(msg.id);
|
|
@@ -288,12 +290,21 @@ export class WorkerBridgeCore {
|
|
|
288
290
|
settleOnExit: options.settleOnExit === true,
|
|
289
291
|
settle,
|
|
290
292
|
});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
293
|
+
let rawFrame;
|
|
294
|
+
try {
|
|
295
|
+
rawFrame = JSON.stringify({
|
|
296
|
+
jsonrpc: "2.0",
|
|
297
|
+
id,
|
|
298
|
+
method,
|
|
299
|
+
...(params === undefined ? {} : { params }),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
// Invalid params can never reach the wire. Honor the never-rejecting
|
|
304
|
+
// outcome contract and clear their correlation/timer immediately.
|
|
305
|
+
settle({ status: "sendFailed", error });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
297
308
|
let sent = false;
|
|
298
309
|
let sendError;
|
|
299
310
|
try {
|
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.9",
|
|
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",
|
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
"./serve": {
|
|
26
26
|
"types": "./dist/index.serve.d.ts",
|
|
27
27
|
"import": "./dist/index.serve.js"
|
|
28
|
+
},
|
|
29
|
+
"./desktop-web": {
|
|
30
|
+
"types": "./dist/index.desktop-web.d.ts",
|
|
31
|
+
"import": "./dist/index.desktop-web.js"
|
|
32
|
+
},
|
|
33
|
+
"./links": {
|
|
34
|
+
"types": "./dist/index.links.d.ts",
|
|
35
|
+
"import": "./dist/index.links.js"
|
|
36
|
+
},
|
|
37
|
+
"./panels": {
|
|
38
|
+
"types": "./dist/index.panels.d.ts",
|
|
39
|
+
"import": "./dist/index.panels.js"
|
|
28
40
|
}
|
|
29
41
|
},
|
|
30
42
|
"bin": {
|
|
@@ -39,11 +51,13 @@
|
|
|
39
51
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
40
52
|
},
|
|
41
53
|
"dependencies": {
|
|
42
|
-
"@cjhyy/code-shell-capability-coding": "0.9.
|
|
43
|
-
"@cjhyy/code-shell-core": "0.9.
|
|
44
|
-
"@cjhyy/code-shell-web": "0.9.
|
|
54
|
+
"@cjhyy/code-shell-capability-coding": "0.9.9",
|
|
55
|
+
"@cjhyy/code-shell-core": "0.9.9",
|
|
56
|
+
"@cjhyy/code-shell-web": "0.9.9",
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
45
58
|
"@types/ws": "^8.18.1",
|
|
46
|
-
"ws": "^8.21.0"
|
|
59
|
+
"ws": "^8.21.0",
|
|
60
|
+
"@cjhyy/code-shell-link": "0.9.9"
|
|
47
61
|
},
|
|
48
62
|
"engines": {
|
|
49
63
|
"node": ">=20.10"
|