@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.
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,122 @@
1
+ const DEFAULT_LIMITS = {
2
+ maxFrameBytes: 64 * 1024 * 1024,
3
+ maxSocketBacklogBytes: 8 * 1024 * 1024,
4
+ maxTotalBacklogBytes: 128 * 1024 * 1024,
5
+ };
6
+ /**
7
+ * Bound the WS write queue separately from the replay buffer. A disconnected
8
+ * browser can recover through the existing authenticated snapshot protocol;
9
+ * keeping an unread socket alive would retain every subsequent stream frame.
10
+ */
11
+ export class HubOutboundTransport {
12
+ options;
13
+ limits;
14
+ dropped = new WeakSet();
15
+ largeFrames = new WeakMap();
16
+ constructor(options) {
17
+ this.options = options;
18
+ this.limits = { ...DEFAULT_LIMITS, ...options.limits };
19
+ for (const value of Object.values(this.limits)) {
20
+ if (!Number.isSafeInteger(value) || value <= 0)
21
+ throw new Error("Outbound transport limits must be positive safe integers");
22
+ }
23
+ }
24
+ send(tab, line) {
25
+ if (tab.readyState !== tab.OPEN || this.dropped.has(tab))
26
+ return false;
27
+ const frameBytes = Buffer.byteLength(line);
28
+ if (frameBytes > this.limits.maxFrameBytes) {
29
+ this.drop(tab, "frame-too-large", frameBytes);
30
+ return false;
31
+ }
32
+ const largeFrame = this.largeFrames.get(tab);
33
+ const followingBytes = frameBytes + 14;
34
+ if (largeFrame
35
+ ? largeFrame.followingBytes + followingBytes > this.limits.maxSocketBacklogBytes
36
+ : this.backlog(tab) > this.limits.maxSocketBacklogBytes) {
37
+ this.drop(tab, "socket-backlog", frameBytes);
38
+ return false;
39
+ }
40
+ // Prefer retiring the largest lagging peer to disconnecting a healthy
41
+ // recipient. Still count closing sockets until ws releases their buffers:
42
+ // terminate() can complete asynchronously, and ignoring those bytes would
43
+ // permit transient allocations above the shared bound.
44
+ const wireBytes = followingBytes;
45
+ if (this.totalBacklog() + wireBytes > this.limits.maxTotalBacklogBytes) {
46
+ const lagging = [...this.options.tabs()]
47
+ .filter((peer) => peer !== tab &&
48
+ peer.readyState === peer.OPEN &&
49
+ !this.dropped.has(peer) &&
50
+ this.backlog(peer) > 0)
51
+ .sort((left, right) => this.backlog(right) - this.backlog(left));
52
+ for (const peer of lagging) {
53
+ this.drop(peer, "total-backlog", frameBytes);
54
+ if (this.totalBacklog() + wireBytes <= this.limits.maxTotalBacklogBytes)
55
+ break;
56
+ }
57
+ if (this.totalBacklog() + wireBytes > this.limits.maxTotalBacklogBytes) {
58
+ this.drop(tab, "total-backlog", frameBytes);
59
+ return false;
60
+ }
61
+ }
62
+ // A valid large snapshot must be allowed to reach the browser before the
63
+ // next small stream notification. Otherwise a perfectly healthy but slow
64
+ // connection would be terminated mid-history and reconnect forever. Bound
65
+ // its following stream tail independently until the large write drains.
66
+ const pendingLarge = !largeFrame && frameBytes > this.limits.maxSocketBacklogBytes
67
+ ? { followingBytes: 0 }
68
+ : undefined;
69
+ if (pendingLarge)
70
+ this.largeFrames.set(tab, pendingLarge);
71
+ if (largeFrame)
72
+ largeFrame.followingBytes += followingBytes;
73
+ try {
74
+ tab.send(line, (error) => {
75
+ if (pendingLarge && this.largeFrames.get(tab) === pendingLarge)
76
+ this.largeFrames.delete(tab);
77
+ if (error)
78
+ this.drop(tab, "send-error", frameBytes);
79
+ });
80
+ return true;
81
+ }
82
+ catch {
83
+ this.drop(tab, "send-error", frameBytes);
84
+ return false;
85
+ }
86
+ }
87
+ backlog(tab) {
88
+ const amount = tab.bufferedAmount;
89
+ return Number.isFinite(amount) && amount >= 0 ? amount : Number.POSITIVE_INFINITY;
90
+ }
91
+ totalBacklog() {
92
+ let total = 0;
93
+ for (const tab of this.options.tabs())
94
+ total += this.backlog(tab);
95
+ return total;
96
+ }
97
+ drop(tab, reason, frameBytes) {
98
+ if (this.dropped.has(tab))
99
+ return;
100
+ this.dropped.add(tab);
101
+ this.largeFrames.delete(tab);
102
+ const metadata = {
103
+ reason,
104
+ frameBytes,
105
+ socketBacklogBytes: this.backlog(tab),
106
+ totalBacklogBytes: this.totalBacklog(),
107
+ };
108
+ // A graceful close frame would wait behind the very backlog being bounded.
109
+ try {
110
+ tab.terminate();
111
+ }
112
+ catch {
113
+ // Races with socket teardown must not affect other viewers or worker routing.
114
+ }
115
+ try {
116
+ this.options.onDrop?.(metadata);
117
+ }
118
+ catch {
119
+ // Diagnostics must not break the stream router either.
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,42 @@
1
+ export interface HubStreamCursor {
2
+ epoch: string;
3
+ sequence: number;
4
+ }
5
+ /**
6
+ * A refresh can happen before the worker persists its current assistant text.
7
+ * Retain that run's stream separately from the durable pre-run transcript.
8
+ * The host takes snapshots synchronously, so its cursor also covers all live
9
+ * notifications sent before the snapshot response, without replaying twice.
10
+ */
11
+ export declare class HubRunReplay {
12
+ private readonly limits;
13
+ private readonly epoch;
14
+ private sequence;
15
+ private totalBytes;
16
+ private readonly sessions;
17
+ constructor(limits?: {
18
+ maxBytes: number;
19
+ maxEvents: number;
20
+ maxTotalBytes?: number;
21
+ });
22
+ begin(sessionId: string, requestId: string, baselineEvents: number): void;
23
+ /** Only a new, still-tracked conversation can safely replay before disk publication. */
24
+ hasEmptyBaseline(sessionId: string): boolean;
25
+ append(sessionId: string, event: unknown): HubStreamCursor;
26
+ finish(sessionId: string, requestId: string): void;
27
+ snapshot<T>(sessionId: string, durableEvents: readonly T[]): {
28
+ liveStream?: {
29
+ events: {
30
+ sequence: number;
31
+ event: unknown;
32
+ }[];
33
+ truncated: boolean;
34
+ } | undefined;
35
+ transcript: T[];
36
+ streamCursor: {
37
+ epoch: `${string}-${string}-${string}-${string}-${string}`;
38
+ sequence: number;
39
+ };
40
+ };
41
+ clear(): void;
42
+ }
@@ -0,0 +1,89 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * A refresh can happen before the worker persists its current assistant text.
4
+ * Retain that run's stream separately from the durable pre-run transcript.
5
+ * The host takes snapshots synchronously, so its cursor also covers all live
6
+ * notifications sent before the snapshot response, without replaying twice.
7
+ */
8
+ export class HubRunReplay {
9
+ limits;
10
+ epoch = randomUUID();
11
+ sequence = 0;
12
+ totalBytes = 0;
13
+ sessions = new Map();
14
+ constructor(limits = {
15
+ maxBytes: 8 * 1024 * 1024,
16
+ maxEvents: 16_000,
17
+ maxTotalBytes: 64 * 1024 * 1024,
18
+ }) {
19
+ this.limits = limits;
20
+ }
21
+ begin(sessionId, requestId, baselineEvents) {
22
+ let log = this.sessions.get(sessionId);
23
+ if (!log) {
24
+ log = {
25
+ baselineEvents: Number.isSafeInteger(baselineEvents) ? Math.max(0, baselineEvents) : 0,
26
+ events: [],
27
+ bytes: 0,
28
+ truncated: false,
29
+ requests: new Set(),
30
+ };
31
+ this.sessions.set(sessionId, log);
32
+ }
33
+ log.requests.add(requestId);
34
+ }
35
+ /** Only a new, still-tracked conversation can safely replay before disk publication. */
36
+ hasEmptyBaseline(sessionId) {
37
+ return this.sessions.get(sessionId)?.baselineEvents === 0;
38
+ }
39
+ append(sessionId, event) {
40
+ const sequence = ++this.sequence;
41
+ const log = this.sessions.get(sessionId);
42
+ if (log && !log.truncated) {
43
+ const serialized = JSON.stringify(event) ?? "null";
44
+ const bytes = Buffer.byteLength(serialized);
45
+ if (log.bytes + bytes > this.limits.maxBytes ||
46
+ log.events.length >= this.limits.maxEvents ||
47
+ this.totalBytes + bytes > (this.limits.maxTotalBytes ?? 64 * 1024 * 1024)) {
48
+ // Never silently present an arbitrary tail as a complete conversation.
49
+ // The caller falls back to the full durable log and exposes this flag.
50
+ log.truncated = true;
51
+ log.events = [];
52
+ this.totalBytes -= log.bytes;
53
+ log.bytes = 0;
54
+ }
55
+ else {
56
+ // Own the wire value. Later changes to the worker message object must
57
+ // not change a previously captured stream or invalidate its byte limit.
58
+ log.events.push({ sequence, event: JSON.parse(serialized) });
59
+ log.bytes += bytes;
60
+ this.totalBytes += bytes;
61
+ }
62
+ }
63
+ return { epoch: this.epoch, sequence };
64
+ }
65
+ finish(sessionId, requestId) {
66
+ const log = this.sessions.get(sessionId);
67
+ if (!log)
68
+ return;
69
+ log.requests.delete(requestId);
70
+ if (!log.requests.size) {
71
+ this.totalBytes -= log.bytes;
72
+ this.sessions.delete(sessionId);
73
+ }
74
+ }
75
+ snapshot(sessionId, durableEvents) {
76
+ const log = this.sessions.get(sessionId);
77
+ return {
78
+ transcript: log && !log.truncated ? durableEvents.slice(0, log.baselineEvents) : durableEvents.slice(),
79
+ streamCursor: { epoch: this.epoch, sequence: this.sequence },
80
+ ...(log
81
+ ? { liveStream: { events: structuredClone(log.events), truncated: log.truncated } }
82
+ : {}),
83
+ };
84
+ }
85
+ clear() {
86
+ this.sessions.clear();
87
+ this.totalBytes = 0;
88
+ }
89
+ }
@@ -0,0 +1,36 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { type SessionState } from "@cjhyy/code-shell-core";
3
+ /** Synchronous, side-effect-free transcript snapshot used by HTTP exports and WS replay. */
4
+ export declare function readHubTranscript(sessionRootDir: string, sessionId: string): unknown[];
5
+ /** Host authorization reads only bounded metadata, never a transcript or mutable engine bundle. */
6
+ export declare function readHubSessionState(sessionRootDir: string, sessionId: string): SessionState;
7
+ export declare function createHubSessions(options: {
8
+ cwd: string;
9
+ sessionRootDir: string;
10
+ dataDir: string;
11
+ isAuthorized: (req: IncomingMessage) => Promise<boolean>;
12
+ isRunning: (sessionId: string) => boolean;
13
+ onChanged: (sessionId: string) => void;
14
+ }): {
15
+ handle: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
16
+ list: (params?: URLSearchParams) => Promise<{
17
+ sessions: {
18
+ preview?: string | undefined;
19
+ sessionId: string;
20
+ cwd: string;
21
+ title: string;
22
+ customTitle: string;
23
+ startedAt: number;
24
+ lastActiveAt: number;
25
+ model: string;
26
+ status: import("@cjhyy/code-shell-core").SessionStatus;
27
+ turnCount: number;
28
+ archivedAt: number | null;
29
+ running: boolean;
30
+ }[];
31
+ nextCursor: string | null;
32
+ }>;
33
+ };
34
+ /** Keep engine attachment wrappers out of title/search/preview surfaces. */
35
+ export declare function hubSessionPreview(value: unknown): string | undefined;
36
+ export declare function transcriptMarkdown(title: string, transcript: readonly unknown[]): string;