@cjhyy/code-shell-server 0.9.8 → 0.9.10

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.
Files changed (111) hide show
  1. package/README.md +85 -23
  2. package/dist/bin/code-shell-project-runtime.d.ts +2 -0
  3. package/dist/bin/code-shell-project-runtime.js +8 -0
  4. package/dist/desktop-web/http-api.d.ts +43 -0
  5. package/dist/desktop-web/http-api.js +376 -0
  6. package/dist/hub/approval-lease.d.ts +30 -0
  7. package/dist/hub/approval-lease.js +89 -0
  8. package/dist/hub/auth-http.d.ts +23 -0
  9. package/dist/hub/auth-http.js +259 -0
  10. package/dist/hub/auth-store.d.ts +52 -0
  11. package/dist/hub/auth-store.js +316 -0
  12. package/dist/hub/configuration.d.ts +64 -0
  13. package/dist/hub/configuration.js +555 -0
  14. package/dist/hub/files.d.ts +9 -0
  15. package/dist/hub/files.js +278 -0
  16. package/dist/hub/mcp-configuration.d.ts +51 -0
  17. package/dist/hub/mcp-configuration.js +558 -0
  18. package/dist/hub/mcp-probe.d.ts +24 -0
  19. package/dist/hub/mcp-probe.js +392 -0
  20. package/dist/hub/model-probe.d.ts +20 -0
  21. package/dist/hub/model-probe.js +193 -0
  22. package/dist/hub/outbound-transport.d.ts +36 -0
  23. package/dist/hub/outbound-transport.js +122 -0
  24. package/dist/hub/run-replay.d.ts +42 -0
  25. package/dist/hub/run-replay.js +89 -0
  26. package/dist/hub/session-management.d.ts +36 -0
  27. package/dist/hub/session-management.js +405 -0
  28. package/dist/hub/skills-management.d.ts +43 -0
  29. package/dist/hub/skills-management.js +487 -0
  30. package/dist/hub/uploads.d.ts +22 -0
  31. package/dist/hub/uploads.js +241 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.desktop-web.d.ts +2 -0
  34. package/dist/index.desktop-web.js +2 -0
  35. package/dist/index.js +3 -0
  36. package/dist/index.links.d.ts +6 -0
  37. package/dist/index.links.js +6 -0
  38. package/dist/index.panels.d.ts +9 -0
  39. package/dist/index.panels.js +8 -0
  40. package/dist/index.serve.d.ts +1 -0
  41. package/dist/index.serve.js +1 -0
  42. package/dist/index.storage.d.ts +2 -0
  43. package/dist/index.storage.js +2 -0
  44. package/dist/links/catalog.d.ts +10 -0
  45. package/dist/links/catalog.js +41 -0
  46. package/dist/links/device-oauth.d.ts +52 -0
  47. package/dist/links/device-oauth.js +255 -0
  48. package/dist/links/http.d.ts +34 -0
  49. package/dist/links/http.js +144 -0
  50. package/dist/links/service.d.ts +54 -0
  51. package/dist/links/service.js +578 -0
  52. package/dist/links/types.d.ts +6 -0
  53. package/dist/links/types.js +1 -0
  54. package/dist/mobile-remote/mobile-client-event-validator.js +5 -2
  55. package/dist/mobile-remote/pending-approvals.js +2 -0
  56. package/dist/mobile-remote/remote-host-manager.d.ts +8 -0
  57. package/dist/mobile-remote/remote-host-manager.js +90 -15
  58. package/dist/mobile-remote/room-manager.js +17 -5
  59. package/dist/panels/agent-task-host.d.ts +32 -0
  60. package/dist/panels/agent-task-host.js +457 -0
  61. package/dist/panels/agent-task-models.d.ts +27 -0
  62. package/dist/panels/agent-task-models.js +51 -0
  63. package/dist/panels/agent-task-service.d.ts +105 -0
  64. package/dist/panels/agent-task-service.js +283 -0
  65. package/dist/panels/http.d.ts +34 -0
  66. package/dist/panels/http.js +37 -0
  67. package/dist/panels/hub-binding.d.ts +12 -0
  68. package/dist/panels/hub-binding.js +174 -0
  69. package/dist/panels/management-http.d.ts +29 -0
  70. package/dist/panels/management-http.js +119 -0
  71. package/dist/panels/management.d.ts +39 -0
  72. package/dist/panels/management.js +494 -0
  73. package/dist/panels/process-files.d.ts +14 -0
  74. package/dist/panels/process-files.js +299 -0
  75. package/dist/panels/process-service.d.ts +96 -0
  76. package/dist/panels/process-service.js +501 -0
  77. package/dist/panels/runtime-services.d.ts +24 -0
  78. package/dist/panels/runtime-services.js +462 -0
  79. package/dist/panels/runtime.d.ts +51 -0
  80. package/dist/panels/runtime.js +1063 -0
  81. package/dist/panels/storage-store.d.ts +9 -0
  82. package/dist/panels/storage-store.js +154 -0
  83. package/dist/panels/types.d.ts +52 -0
  84. package/dist/panels/types.js +1 -0
  85. package/dist/project-runtime/control-server.d.ts +19 -0
  86. package/dist/project-runtime/control-server.js +300 -0
  87. package/dist/project-runtime/docker-provider.d.ts +33 -0
  88. package/dist/project-runtime/docker-provider.js +487 -0
  89. package/dist/project-runtime/lock.d.ts +6 -0
  90. package/dist/project-runtime/lock.js +43 -0
  91. package/dist/project-runtime/managed-entry.d.ts +10 -0
  92. package/dist/project-runtime/managed-entry.js +113 -0
  93. package/dist/project-runtime/manager.d.ts +40 -0
  94. package/dist/project-runtime/manager.js +249 -0
  95. package/dist/project-runtime/proxy.d.ts +22 -0
  96. package/dist/project-runtime/proxy.js +706 -0
  97. package/dist/project-runtime/registry.d.ts +54 -0
  98. package/dist/project-runtime/registry.js +286 -0
  99. package/dist/project-runtime/types.d.ts +43 -0
  100. package/dist/project-runtime/types.js +1 -0
  101. package/dist/serve/cli.d.ts +5 -0
  102. package/dist/serve/cli.js +82 -23
  103. package/dist/serve/headless-server.d.ts +11 -0
  104. package/dist/serve/headless-server.js +737 -83
  105. package/dist/session-titles-store.d.ts +17 -0
  106. package/dist/session-titles-store.js +174 -0
  107. package/dist/sessions-service.d.ts +2 -0
  108. package/dist/sessions-service.js +8 -0
  109. package/dist/static-files.js +1 -0
  110. package/dist/worker-bridge-core.js +17 -6
  111. 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
+ }
@@ -32,6 +32,8 @@ export declare function deleteSession(id: string): Promise<void>;
32
32
  */
33
33
  export declare function cleanupStaleQuickChatSessions(baseDir?: string): Promise<string[]>;
34
34
  export interface DiskSessionMeta {
35
+ runId?: string;
36
+ clientMessageId?: string;
35
37
  id: string;
36
38
  engineSessionId: string;
37
39
  cwd: string;
@@ -312,6 +312,14 @@ export async function listDiskSessions(opts, baseDir = sessionsRoot()) {
312
312
  continue;
313
313
  sessions.push({
314
314
  id,
315
+ ...(typeof state.runId === "string" && state.runId.length > 0 && state.runId.length <= 256
316
+ ? { runId: state.runId }
317
+ : {}),
318
+ ...(typeof state.clientMessageId === "string" &&
319
+ state.clientMessageId.length > 0 &&
320
+ state.clientMessageId.length <= 256
321
+ ? { clientMessageId: state.clientMessageId }
322
+ : {}),
315
323
  engineSessionId: id,
316
324
  cwd: typeof state.cwd === "string" ? state.cwd : "",
317
325
  // Prefer the LLM-generated title (now persisted to state); fall back to
@@ -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
- const rawFrame = JSON.stringify({
292
- jsonrpc: "2.0",
293
- id,
294
- method,
295
- ...(params === undefined ? {} : { params }),
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.8",
3
+ "version": "0.9.10",
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.8",
43
- "@cjhyy/code-shell-core": "0.9.8",
44
- "@cjhyy/code-shell-web": "0.9.8",
54
+ "@cjhyy/code-shell-capability-coding": "0.9.10",
55
+ "@cjhyy/code-shell-core": "0.9.10",
56
+ "@cjhyy/code-shell-web": "0.9.10",
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.10"
47
61
  },
48
62
  "engines": {
49
63
  "node": ">=20.10"