@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.
- 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/sessions-service.d.ts +2 -0
- package/dist/sessions-service.js +8 -0
- package/dist/static-files.js +1 -0
- package/dist/worker-bridge-core.js +17 -6
- package/package.json +19 -5
|
@@ -39,6 +39,7 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
39
39
|
wss;
|
|
40
40
|
started;
|
|
41
41
|
starting;
|
|
42
|
+
stopping;
|
|
42
43
|
pairing = new PairingTokenManager();
|
|
43
44
|
/** ws → authenticated device id. A socket absent here is unauthenticated. */
|
|
44
45
|
authed = new WeakMap();
|
|
@@ -91,6 +92,10 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
async start(options) {
|
|
95
|
+
// A prior close owns the shared HTTP facade/upload services until every
|
|
96
|
+
// cleanup step settles. A restart must not reopen them underneath it.
|
|
97
|
+
if (this.stopping)
|
|
98
|
+
await this.stopping;
|
|
94
99
|
if (this.started)
|
|
95
100
|
return this.started;
|
|
96
101
|
if (this.starting)
|
|
@@ -109,6 +114,7 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
109
114
|
const tunnel = options.mode === "tunnel";
|
|
110
115
|
this.passcode = tunnel ? options.passcode : undefined;
|
|
111
116
|
const gate = this.passcode;
|
|
117
|
+
this.opts.webApi?.start();
|
|
112
118
|
const server = createServer((req, res) => {
|
|
113
119
|
// Tunnel mode: a passcode gate sits in front of EVERY route. The gate
|
|
114
120
|
// either allows (returns true) or writes its own 401/403 challenge.
|
|
@@ -119,6 +125,29 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
119
125
|
res.end(JSON.stringify({ ok: true }));
|
|
120
126
|
return;
|
|
121
127
|
}
|
|
128
|
+
if (req.url?.startsWith("/api/v1/") && this.opts.webApi) {
|
|
129
|
+
const baseUrl = this.publicBaseUrl ?? this.started?.url;
|
|
130
|
+
if (!baseUrl) {
|
|
131
|
+
res.writeHead(503);
|
|
132
|
+
res.end("remote host is starting");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
void this.opts.webApi
|
|
136
|
+
.handle(req, res, { baseUrl })
|
|
137
|
+
.then((handled) => {
|
|
138
|
+
if (!handled && !res.writableEnded) {
|
|
139
|
+
res.writeHead(404);
|
|
140
|
+
res.end("not found");
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
.catch(() => {
|
|
144
|
+
if (!res.headersSent)
|
|
145
|
+
res.writeHead(500);
|
|
146
|
+
if (!res.writableEnded)
|
|
147
|
+
res.end("remote request failed");
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
122
151
|
const uploadPath = req.url
|
|
123
152
|
? new URL(req.url, "http://localhost").pathname.match(/^\/api\/mobile\/uploads\/([A-Za-z0-9_-]+)$/)
|
|
124
153
|
: null;
|
|
@@ -179,6 +208,13 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
179
208
|
this.wss.on("connection", (ws) => {
|
|
180
209
|
const viewerId = `viewer-${this.nextViewerId++}`;
|
|
181
210
|
this.viewers.set(ws, viewerId);
|
|
211
|
+
ws.on("error", (error) => {
|
|
212
|
+
// ws emits parser/size-limit failures on the individual socket. An
|
|
213
|
+
// unhandled error here would terminate the entire Node/Electron host.
|
|
214
|
+
this.releaseSocket(ws);
|
|
215
|
+
ws.terminate();
|
|
216
|
+
this.emit("host-error", error);
|
|
217
|
+
});
|
|
182
218
|
ws.on("message", (raw) => {
|
|
183
219
|
let parsed;
|
|
184
220
|
try {
|
|
@@ -205,15 +241,11 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
205
241
|
return;
|
|
206
242
|
}
|
|
207
243
|
if (event.type === "auth.device" && reply?.type === "auth.ok") {
|
|
208
|
-
|
|
209
|
-
this.markOnline(reply.device.id);
|
|
210
|
-
this.authed.set(ws, reply.device.id);
|
|
244
|
+
this.bindSocketDevice(ws, reply.device.id);
|
|
211
245
|
}
|
|
212
246
|
// Pairing also yields a live, identified socket → count it online.
|
|
213
247
|
if (event.type === "pair.complete" && reply?.type === "pair.ok") {
|
|
214
|
-
|
|
215
|
-
this.markOnline(reply.device.id);
|
|
216
|
-
this.authed.set(ws, reply.device.id);
|
|
248
|
+
this.bindSocketDevice(ws, reply.device.id);
|
|
217
249
|
}
|
|
218
250
|
if (reply) {
|
|
219
251
|
ws.send(JSON.stringify(reply));
|
|
@@ -277,6 +309,7 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
277
309
|
this.server = undefined;
|
|
278
310
|
this.wss = undefined;
|
|
279
311
|
this.passcode = undefined;
|
|
312
|
+
await this.opts.webApi?.close();
|
|
280
313
|
throw error;
|
|
281
314
|
}
|
|
282
315
|
const addr = server.address();
|
|
@@ -395,7 +428,42 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
395
428
|
}
|
|
396
429
|
this.markOffline(deviceId);
|
|
397
430
|
}
|
|
398
|
-
|
|
431
|
+
bindSocketDevice(ws, deviceId) {
|
|
432
|
+
const previous = this.authed.get(ws);
|
|
433
|
+
if (previous === deviceId)
|
|
434
|
+
return;
|
|
435
|
+
if (previous) {
|
|
436
|
+
// One socket can authenticate/pair again. Retire its old viewer before
|
|
437
|
+
// switching owners so subscriptions and grants cannot cross identities.
|
|
438
|
+
this.releaseSocket(ws);
|
|
439
|
+
this.viewers.set(ws, `viewer-${this.nextViewerId++}`);
|
|
440
|
+
}
|
|
441
|
+
this.authed.set(ws, deviceId);
|
|
442
|
+
this.markOnline(deviceId);
|
|
443
|
+
}
|
|
444
|
+
/** Call after revoking/removing a trusted device to release both transports immediately. */
|
|
445
|
+
revokeDevice(deviceId) {
|
|
446
|
+
this.opts.webApi?.revokeDevice(deviceId);
|
|
447
|
+
for (const client of this.wss?.clients ?? []) {
|
|
448
|
+
if (this.authed.get(client) !== deviceId)
|
|
449
|
+
continue;
|
|
450
|
+
this.releaseSocket(client);
|
|
451
|
+
client.terminate();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
stop() {
|
|
455
|
+
if (this.stopping)
|
|
456
|
+
return this.stopping;
|
|
457
|
+
const attempt = Promise.resolve()
|
|
458
|
+
.then(() => this.stopOnce())
|
|
459
|
+
.finally(() => {
|
|
460
|
+
if (this.stopping === attempt)
|
|
461
|
+
this.stopping = undefined;
|
|
462
|
+
});
|
|
463
|
+
this.stopping = attempt;
|
|
464
|
+
return attempt;
|
|
465
|
+
}
|
|
466
|
+
async stopOnce() {
|
|
399
467
|
if (this.starting)
|
|
400
468
|
await this.starting.catch(() => undefined);
|
|
401
469
|
const server = this.server;
|
|
@@ -419,14 +487,21 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
419
487
|
for (const deviceId of this.onlineCounts.keys())
|
|
420
488
|
this.emit("device-offline", deviceId);
|
|
421
489
|
this.onlineCounts.clear();
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
server
|
|
429
|
-
|
|
490
|
+
// A failed service cleanup must not strand an unreachable HTTP listener or
|
|
491
|
+
// prevent another service from releasing its uploads/resources.
|
|
492
|
+
const cleanup = await Promise.allSettled([
|
|
493
|
+
Promise.resolve().then(() => this.opts.webApi?.close()),
|
|
494
|
+
Promise.resolve().then(() => this.opts.uploads?.cancelActiveTransfers()),
|
|
495
|
+
]);
|
|
496
|
+
if (server) {
|
|
497
|
+
// server.close() waits for live connections. Drop any lingering HTTP
|
|
498
|
+
// keep-alive sockets after services have cancelled their active work.
|
|
499
|
+
server.closeAllConnections?.();
|
|
500
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
501
|
+
}
|
|
502
|
+
const failure = cleanup.find((result) => result.status === "rejected");
|
|
503
|
+
if (failure?.status === "rejected")
|
|
504
|
+
throw failure.reason;
|
|
430
505
|
}
|
|
431
506
|
status() {
|
|
432
507
|
return this.started;
|
|
@@ -84,9 +84,7 @@ function parseRoomMeta(value, expectedId) {
|
|
|
84
84
|
permissionMode: raw.permissionMode,
|
|
85
85
|
createdAt: raw.createdAt,
|
|
86
86
|
lastActiveAt: raw.lastActiveAt,
|
|
87
|
-
...(typeof raw.claudeSessionId === "string"
|
|
88
|
-
? { claudeSessionId: raw.claudeSessionId }
|
|
89
|
-
: {}),
|
|
87
|
+
...(typeof raw.claudeSessionId === "string" ? { claudeSessionId: raw.claudeSessionId } : {}),
|
|
90
88
|
...(raw.linkedSessionMode === "observe-only"
|
|
91
89
|
? { linkedSessionMode: raw.linkedSessionMode }
|
|
92
90
|
: {}),
|
|
@@ -114,7 +112,8 @@ function parseRoomMessage(value) {
|
|
|
114
112
|
}
|
|
115
113
|
if (raw.isError !== undefined && typeof raw.isError !== "boolean")
|
|
116
114
|
return undefined;
|
|
117
|
-
if (raw.args !== undefined &&
|
|
115
|
+
if (raw.args !== undefined &&
|
|
116
|
+
(!raw.args || typeof raw.args !== "object" || Array.isArray(raw.args))) {
|
|
118
117
|
return undefined;
|
|
119
118
|
}
|
|
120
119
|
if (raw.attachments !== undefined && !Array.isArray(raw.attachments))
|
|
@@ -780,6 +779,12 @@ export class RoomManager {
|
|
|
780
779
|
const hasOriginalInput = this.pendingApprovalInputs.has(askKey);
|
|
781
780
|
const originalInput = this.pendingApprovalInputs.get(askKey);
|
|
782
781
|
this.pendingApprovalInputs.delete(askKey);
|
|
782
|
+
// A control response consumes exactly one server-observed request. The
|
|
783
|
+
// bridge can settle multiple waiters when a duplicate control ID arrives;
|
|
784
|
+
// only the first settlement may reach the resident process. This also
|
|
785
|
+
// rejects unsolicited and late replies after a room has been closed.
|
|
786
|
+
if (!hasPendingAsk && !hasOriginalInput)
|
|
787
|
+
return false;
|
|
783
788
|
const agent = this.agents.get(roomId);
|
|
784
789
|
if (!agent?.respondControl)
|
|
785
790
|
return false;
|
|
@@ -821,7 +826,14 @@ export class RoomManager {
|
|
|
821
826
|
if (meta.linkedSessionMode === "observe-only")
|
|
822
827
|
return { status: "observing" };
|
|
823
828
|
if (!this.agents.has(id)) {
|
|
824
|
-
|
|
829
|
+
let agent = undefined;
|
|
830
|
+
agent = this.opts.createAgent(meta, (event) => {
|
|
831
|
+
// stop() sends a signal; an old process can still drain output or emit
|
|
832
|
+
// exit after the room has reopened. Only the current process owns its
|
|
833
|
+
// transcript, pending approvals, and terminal lifecycle transition.
|
|
834
|
+
if (agent && this.agents.get(id) === agent)
|
|
835
|
+
this.onAgentEvent(id, event);
|
|
836
|
+
});
|
|
825
837
|
this.agents.set(id, agent);
|
|
826
838
|
try {
|
|
827
839
|
agent.start();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PanelAgentTaskModelCatalog } from "./agent-task-models.js";
|
|
2
|
+
export interface PanelAgentTaskScope {
|
|
3
|
+
instanceId: string;
|
|
4
|
+
ownerId: string;
|
|
5
|
+
appId: string;
|
|
6
|
+
appTitle: string;
|
|
7
|
+
projectPath: string;
|
|
8
|
+
cwd: string;
|
|
9
|
+
permissions: readonly string[];
|
|
10
|
+
availableSkills: readonly string[];
|
|
11
|
+
isAuthorized: () => Promise<boolean>;
|
|
12
|
+
emit: (event: string, payload: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface PanelAgentTaskHostOptions {
|
|
15
|
+
workerEntryPath?: string;
|
|
16
|
+
execPath?: string;
|
|
17
|
+
buildEnv?: () => NodeJS.ProcessEnv;
|
|
18
|
+
models?: (cwd: string) => PanelAgentTaskModelCatalog | Promise<PanelAgentTaskModelCatalog>;
|
|
19
|
+
/** The host implements panel discovery and callbacks, scoped to this one installed app. */
|
|
20
|
+
onPanelAction?: (scope: PanelAgentTaskScope, input: Record<string, unknown>) => Promise<unknown>;
|
|
21
|
+
maxConcurrentTasks?: number;
|
|
22
|
+
authorizationPollMs?: number;
|
|
23
|
+
runTimeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Real independent Core workers shared by Desktop Web and standalone Hub. */
|
|
26
|
+
export declare function createPanelAgentTaskHost(options?: PanelAgentTaskHostOptions): {
|
|
27
|
+
call(scope: PanelAgentTaskScope, method: string, params?: unknown): Promise<unknown>;
|
|
28
|
+
revokeInstance: (instanceId: string) => void;
|
|
29
|
+
close(): void;
|
|
30
|
+
activeTaskCount: () => number;
|
|
31
|
+
};
|
|
32
|
+
export type PanelAgentTaskHost = ReturnType<typeof createPanelAgentTaskHost>;
|