@cjhyy/code-shell-server 0.9.8 → 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.
Files changed (109) 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/static-files.js +1 -0
  108. package/dist/worker-bridge-core.js +17 -6
  109. package/package.json +19 -5
@@ -0,0 +1,40 @@
1
+ import type { HubSession } from "../hub/auth-store.js";
2
+ import { ProjectRegistry, type ProjectView } from "./registry.js";
3
+ import type { ProjectRuntimeConnection, ProjectRuntimeProvider } from "./types.js";
4
+ export interface ProjectManagerOptions {
5
+ registry: ProjectRegistry;
6
+ provider: ProjectRuntimeProvider;
7
+ publicOrigin(): string;
8
+ isSessionActive(session: HubSession): boolean;
9
+ revokeProject(id: string): Promise<void>;
10
+ maxRunning?: number;
11
+ /** Clock seam for Docker observations; authorization is never cached. */
12
+ now?: () => number;
13
+ }
14
+ /** Lifecycle and ownership live outside the project; all actual work runs inside it. */
15
+ export declare class ProjectManager {
16
+ private readonly options;
17
+ private readonly starts;
18
+ private readonly stops;
19
+ private readonly targets;
20
+ private readonly statusChecks;
21
+ private closed;
22
+ private closing;
23
+ constructor(options: ProjectManagerOptions);
24
+ private authorize;
25
+ reconcile(): Promise<void>;
26
+ list(session: HubSession): ProjectView[];
27
+ get(session: HubSession, id: string): ProjectView;
28
+ create(session: HubSession, name: unknown): ProjectView;
29
+ start(session: HubSession, id: string): Promise<ProjectView>;
30
+ stop(session: HubSession, id: string): Promise<ProjectView>;
31
+ private beginStop;
32
+ private stopProject;
33
+ resolveTarget(session: HubSession, id: string): Promise<ProjectRuntimeConnection>;
34
+ private observeRuntime;
35
+ /** Stop the affected runtime without relying on its already-revoked outer session. */
36
+ stopAfterRevocationFailure(id: string): Promise<void>;
37
+ /** Emergency fallback when an outer owner's affected projects are unavailable. */
38
+ stopAll(): Promise<void>;
39
+ close(): Promise<void>;
40
+ }
@@ -0,0 +1,249 @@
1
+ import { ProjectRegistryError, projectView, } from "./registry.js";
2
+ /** Lifecycle and ownership live outside the project; all actual work runs inside it. */
3
+ export class ProjectManager {
4
+ options;
5
+ starts = new Map();
6
+ stops = new Map();
7
+ targets = new Map();
8
+ statusChecks = new Map();
9
+ closed = false;
10
+ closing;
11
+ constructor(options) {
12
+ this.options = options;
13
+ if (!Number.isSafeInteger(options.maxRunning ?? 4) ||
14
+ (options.maxRunning ?? 4) < 1 ||
15
+ (options.maxRunning ?? 4) > 32)
16
+ throw new Error("Project running limit must be between 1 and 32.");
17
+ }
18
+ authorize(session) {
19
+ if (this.closed)
20
+ throw new ProjectRegistryError(503, "项目服务正在关闭。");
21
+ if (!this.options.isSessionActive(session))
22
+ throw new ProjectRegistryError(401, "登录已过期,请重新登录。");
23
+ }
24
+ async reconcile() {
25
+ this.statusChecks.clear();
26
+ // Never adopt a previous controller's approvals or device credentials after a crash.
27
+ for (const project of this.options.registry.all()) {
28
+ if (project.generation === 0)
29
+ continue;
30
+ try {
31
+ await this.options.provider.stop(project);
32
+ this.options.registry.update(project.id, { status: "stopped" });
33
+ }
34
+ catch {
35
+ this.options.registry.update(project.id, {
36
+ status: "error",
37
+ error: "无法确认旧运行环境已停止。请检查 Docker 后重试停止项目。",
38
+ });
39
+ }
40
+ }
41
+ }
42
+ list(session) {
43
+ this.authorize(session);
44
+ return this.options.registry.list(session.username);
45
+ }
46
+ get(session, id) {
47
+ this.authorize(session);
48
+ return projectView(this.options.registry.get(session.username, id));
49
+ }
50
+ create(session, name) {
51
+ this.authorize(session);
52
+ return projectView(this.options.registry.create(session.username, name));
53
+ }
54
+ async start(session, id) {
55
+ this.authorize(session);
56
+ this.options.registry.get(session.username, id);
57
+ const stopping = this.stops.get(id);
58
+ if (stopping)
59
+ await stopping;
60
+ this.authorize(session);
61
+ const existing = this.starts.get(id);
62
+ if (existing) {
63
+ const result = await existing;
64
+ this.authorize(session);
65
+ return result;
66
+ }
67
+ const project = this.options.registry.get(session.username, id);
68
+ if (project.status === "running") {
69
+ await this.resolveTarget(session, id);
70
+ return this.get(session, id);
71
+ }
72
+ const count = this.options.registry
73
+ .all()
74
+ .filter((item) => item.id !== id &&
75
+ ["running", "starting", "stopping", "error"].includes(item.status) &&
76
+ item.generation > 0).length;
77
+ if (count >= (this.options.maxRunning ?? 4))
78
+ throw new ProjectRegistryError(429, `最多同时运行 ${this.options.maxRunning ?? 4} 个项目,请先停止其他项目。`);
79
+ this.statusChecks.delete(id);
80
+ const record = this.options.registry.update(id, {
81
+ status: "starting",
82
+ generation: project.generation + 1,
83
+ });
84
+ const operation = (async () => {
85
+ try {
86
+ await this.options.revokeProject(id);
87
+ this.authorize(session);
88
+ if (this.options.registry.get(session.username, id).status !== "starting")
89
+ throw new ProjectRegistryError(409, "项目启动已取消。");
90
+ const target = await this.options.provider.ensure(record, {
91
+ publicOrigin: this.options.publicOrigin(),
92
+ });
93
+ this.authorize(session);
94
+ const current = this.options.registry.get(session.username, id);
95
+ if (current.status !== "starting" || current.generation !== record.generation)
96
+ throw new ProjectRegistryError(409, "项目启动已取消。");
97
+ if (target.generation !== record.generation)
98
+ throw new Error("Runtime generation mismatch");
99
+ this.targets.set(id, target);
100
+ return projectView(this.options.registry.update(id, { status: "running" }));
101
+ }
102
+ catch (error) {
103
+ this.statusChecks.delete(id);
104
+ this.targets.delete(id);
105
+ let stopped = true;
106
+ try {
107
+ await this.options.provider.stop(record);
108
+ }
109
+ catch {
110
+ stopped = false;
111
+ }
112
+ const current = this.options.registry.get(session.username, id);
113
+ if (current.generation === record.generation && current.status === "starting") {
114
+ this.options.registry.update(id, {
115
+ status: stopped ? "stopped" : "error",
116
+ error: "项目未能启动。请检查 Docker 和项目镜像后重试。",
117
+ });
118
+ }
119
+ if (error instanceof ProjectRegistryError)
120
+ throw error;
121
+ throw new ProjectRegistryError(503, "项目未能启动。请检查 Docker 和项目镜像后重试。");
122
+ }
123
+ finally {
124
+ this.starts.delete(id);
125
+ }
126
+ })();
127
+ this.starts.set(id, operation);
128
+ return operation;
129
+ }
130
+ async stop(session, id) {
131
+ this.authorize(session);
132
+ this.options.registry.get(session.username, id);
133
+ const result = await this.beginStop(id);
134
+ this.authorize(session);
135
+ return result;
136
+ }
137
+ beginStop(id) {
138
+ const existing = this.stops.get(id);
139
+ if (existing)
140
+ return existing;
141
+ const operation = this.stopProject(id);
142
+ this.stops.set(id, operation);
143
+ void operation
144
+ .finally(() => {
145
+ if (this.stops.get(id) === operation)
146
+ this.stops.delete(id);
147
+ })
148
+ .catch(() => { });
149
+ return operation;
150
+ }
151
+ async stopProject(id) {
152
+ this.statusChecks.delete(id);
153
+ const record = this.options.registry.update(id, { status: "stopping" });
154
+ this.targets.delete(id);
155
+ // The proxy closes streams before trying the inner logout request. A failed
156
+ // network logout must never prevent termination of the actual task runtime.
157
+ await this.options.revokeProject(id).catch(() => { });
158
+ try {
159
+ await this.options.provider.stop(record);
160
+ await this.starts.get(id)?.catch(() => { });
161
+ return projectView(this.options.registry.update(id, { status: "stopped" }));
162
+ }
163
+ catch {
164
+ return projectView(this.options.registry.update(id, {
165
+ status: "error",
166
+ error: "项目尚未确认停止,请检查 Docker 后重试。",
167
+ }));
168
+ }
169
+ }
170
+ async resolveTarget(session, id) {
171
+ this.authorize(session);
172
+ const record = this.options.registry.get(session.username, id);
173
+ const target = this.targets.get(id);
174
+ if (record.status !== "running" || !target || target.generation !== record.generation)
175
+ throw new ProjectRegistryError(409, "项目尚未运行,请先启动项目。");
176
+ // The provider checks labels and the loopback binding, not just a remembered port.
177
+ const state = await this.observeRuntime(record, target);
178
+ this.authorize(session);
179
+ const current = this.options.registry.get(session.username, id);
180
+ if (current.status !== "running" || current.generation !== target.generation)
181
+ throw new ProjectRegistryError(409, "项目已停止或重新启动,请重新进入。");
182
+ if (state.state !== "running" || state.url !== target.url) {
183
+ this.statusChecks.delete(id);
184
+ this.targets.delete(id);
185
+ this.options.registry.update(id, {
186
+ status: "error",
187
+ error: "运行环境已离线,请停止后重新启动。",
188
+ });
189
+ throw new ProjectRegistryError(503, "运行环境已离线,请停止后重新启动。");
190
+ }
191
+ return { ...target };
192
+ }
193
+ observeRuntime(record, target) {
194
+ const now = this.options.now ?? Date.now;
195
+ const cached = this.statusChecks.get(record.id);
196
+ if (cached &&
197
+ cached.generation === record.generation &&
198
+ cached.url === target.url &&
199
+ cached.expiresAt > now())
200
+ return cached.promise;
201
+ const entry = {
202
+ generation: record.generation,
203
+ url: target.url,
204
+ // Pending checks share their provider deadline; completed checks live 2s.
205
+ expiresAt: Infinity,
206
+ promise: Promise.resolve({ state: "missing" }),
207
+ };
208
+ entry.promise = this.options.provider.status(record).then((state) => {
209
+ if (this.statusChecks.get(record.id) === entry)
210
+ entry.expiresAt = now() + 2000;
211
+ return state;
212
+ }, (error) => {
213
+ if (this.statusChecks.get(record.id) === entry)
214
+ this.statusChecks.delete(record.id);
215
+ throw error;
216
+ });
217
+ this.statusChecks.set(record.id, entry);
218
+ return entry.promise;
219
+ }
220
+ /** Stop the affected runtime without relying on its already-revoked outer session. */
221
+ async stopAfterRevocationFailure(id) {
222
+ const result = await this.beginStop(id);
223
+ if (result.status !== "stopped")
224
+ throw new ProjectRegistryError(503, "项目尚未确认停止,请检查 Docker。");
225
+ }
226
+ /** Emergency fallback when an outer owner's affected projects are unavailable. */
227
+ async stopAll() {
228
+ const results = await Promise.allSettled(this.options.registry
229
+ .all()
230
+ .filter((project) => project.generation > 0 && project.status !== "stopped")
231
+ .map((project) => this.beginStop(project.id)));
232
+ if (results.some((result) => result.status === "rejected" || result.value.status !== "stopped"))
233
+ throw new ProjectRegistryError(503, "部分项目尚未确认停止,请检查 Docker。");
234
+ }
235
+ close() {
236
+ if (this.closing)
237
+ return this.closing;
238
+ this.closed = true;
239
+ this.statusChecks.clear();
240
+ return (this.closing = (async () => {
241
+ try {
242
+ await this.stopAll();
243
+ }
244
+ finally {
245
+ await this.options.provider.close();
246
+ }
247
+ })());
248
+ }
249
+ }
@@ -0,0 +1,22 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import { type Duplex } from "node:stream";
3
+ import { type HubAuth } from "../hub/auth-http.js";
4
+ import type { HubSession } from "../hub/auth-store.js";
5
+ import type { ProjectRuntimeConnection } from "./types.js";
6
+ export type RuntimeTarget = ProjectRuntimeConnection;
7
+ export interface ProjectRuntimeProxyOptions {
8
+ auth: HubAuth;
9
+ resolveTarget(projectId: string, session: HubSession): Promise<RuntimeTarget>;
10
+ publicOrigin: () => string;
11
+ /** Stop this runtime if its private logout cannot acknowledge revocation.
12
+ * Dispatched after retirement settles; may call back into revokeProject. */
13
+ onRevocationFailure?: (projectId: string) => void | Promise<void>;
14
+ }
15
+ /** Private runtime credentials and cookies never cross this control-plane boundary. */
16
+ export declare function createProjectRuntimeProxy(options: ProjectRuntimeProxyOptions): {
17
+ handle: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
18
+ handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<boolean>;
19
+ revokeOwner: (sessionId: string) => Promise<void>;
20
+ revokeProject: (projectId: string) => Promise<void>;
21
+ close(): Promise<void>;
22
+ };