@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
@@ -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
- if (!this.authed.has(ws))
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
- if (!this.authed.has(ws))
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
- async stop() {
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
- await this.opts.uploads?.cancelActiveTransfers();
423
- if (!server)
424
- return;
425
- // server.close() only stops accepting new connections and waits for live
426
- // ones to end — a lingering upgraded/keep-alive socket keeps its callback
427
- // from ever firing. Destroy any remaining connections so close() resolves.
428
- server.closeAllConnections?.();
429
- await new Promise((resolve) => server.close(() => resolve()));
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 && (!raw.args || typeof raw.args !== "object" || Array.isArray(raw.args))) {
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
- const agent = this.opts.createAgent(meta, (event) => this.onAgentEvent(id, event));
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>;