@cjhyy/code-shell-server 0.8.0

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 (70) hide show
  1. package/README.md +77 -0
  2. package/dist/attachment-service.d.ts +80 -0
  3. package/dist/attachment-service.js +587 -0
  4. package/dist/bin/code-shell-serve.d.ts +2 -0
  5. package/dist/bin/code-shell-serve.js +7 -0
  6. package/dist/client-message-id.d.ts +1 -0
  7. package/dist/client-message-id.js +8 -0
  8. package/dist/image-byte-probe.d.ts +17 -0
  9. package/dist/image-byte-probe.js +330 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.js +10 -0
  12. package/dist/index.mobile-remote.d.ts +21 -0
  13. package/dist/index.mobile-remote.js +21 -0
  14. package/dist/index.serve.d.ts +3 -0
  15. package/dist/index.serve.js +3 -0
  16. package/dist/index.storage.d.ts +10 -0
  17. package/dist/index.storage.js +10 -0
  18. package/dist/index.worker.d.ts +2 -0
  19. package/dist/index.worker.js +2 -0
  20. package/dist/mobile-remote/access-passcode.d.ts +80 -0
  21. package/dist/mobile-remote/access-passcode.js +354 -0
  22. package/dist/mobile-remote/cloudflared-binary.d.ts +76 -0
  23. package/dist/mobile-remote/cloudflared-binary.js +242 -0
  24. package/dist/mobile-remote/codex-parse.d.ts +34 -0
  25. package/dist/mobile-remote/codex-parse.js +131 -0
  26. package/dist/mobile-remote/codex-room-agent.d.ts +94 -0
  27. package/dist/mobile-remote/codex-room-agent.js +213 -0
  28. package/dist/mobile-remote/mobile-attachments.d.ts +21 -0
  29. package/dist/mobile-remote/mobile-attachments.js +119 -0
  30. package/dist/mobile-remote/mobile-chat-turn.d.ts +32 -0
  31. package/dist/mobile-remote/mobile-chat-turn.js +67 -0
  32. package/dist/mobile-remote/mobile-history.d.ts +22 -0
  33. package/dist/mobile-remote/mobile-history.js +27 -0
  34. package/dist/mobile-remote/mobile-run-dispatch.d.ts +17 -0
  35. package/dist/mobile-remote/mobile-run-dispatch.js +56 -0
  36. package/dist/mobile-remote/mobile-static.d.ts +43 -0
  37. package/dist/mobile-remote/mobile-static.js +154 -0
  38. package/dist/mobile-remote/mobile-upload-service.d.ts +57 -0
  39. package/dist/mobile-remote/mobile-upload-service.js +370 -0
  40. package/dist/mobile-remote/pairing.d.ts +8 -0
  41. package/dist/mobile-remote/pairing.js +23 -0
  42. package/dist/mobile-remote/path-bins.d.ts +7 -0
  43. package/dist/mobile-remote/path-bins.js +16 -0
  44. package/dist/mobile-remote/pending-approvals.d.ts +14 -0
  45. package/dist/mobile-remote/pending-approvals.js +54 -0
  46. package/dist/mobile-remote/remote-host-manager.d.ts +117 -0
  47. package/dist/mobile-remote/remote-host-manager.js +345 -0
  48. package/dist/mobile-remote/resident-agent.d.ts +98 -0
  49. package/dist/mobile-remote/resident-agent.js +216 -0
  50. package/dist/mobile-remote/room-manager.d.ts +286 -0
  51. package/dist/mobile-remote/room-manager.js +765 -0
  52. package/dist/mobile-remote/trusted-device-store.d.ts +19 -0
  53. package/dist/mobile-remote/trusted-device-store.js +118 -0
  54. package/dist/mobile-remote/tunnel-manager.d.ts +105 -0
  55. package/dist/mobile-remote/tunnel-manager.js +375 -0
  56. package/dist/mobile-remote/types.d.ts +1 -0
  57. package/dist/mobile-remote/types.js +1 -0
  58. package/dist/mobile-remote/viewer-identity.d.ts +5 -0
  59. package/dist/mobile-remote/viewer-identity.js +5 -0
  60. package/dist/serve/cli.d.ts +15 -0
  61. package/dist/serve/cli.js +115 -0
  62. package/dist/serve/headless-server.d.ts +40 -0
  63. package/dist/serve/headless-server.js +276 -0
  64. package/dist/sessions-service.d.ts +73 -0
  65. package/dist/sessions-service.js +303 -0
  66. package/dist/static-files.d.ts +1 -0
  67. package/dist/static-files.js +25 -0
  68. package/dist/worker-bridge-core.d.ts +154 -0
  69. package/dist/worker-bridge-core.js +315 -0
  70. package/package.json +63 -0
@@ -0,0 +1,286 @@
1
+ import type { ResidentAgentEvent } from "./resident-agent.js";
2
+ import type { InputAttachmentMeta } from "../attachment-service.js";
3
+ export declare function isValidRoomId(id: unknown): id is string;
4
+ export type RoomPermissionMode = "default" | "acceptEdits" | "bypassPermissions";
5
+ /**
6
+ * Build the `updatedInput` that answers an AskUserQuestion `can_use_tool`
7
+ * request. Empirically (claude CLI, verified against the live control protocol
8
+ * + the Agent SDK docs), the answer must go in an `answers` RECORD keyed by each
9
+ * question's `question` text, with STRING values (arrays/objects fail schema
10
+ * validation). For multiSelect the caller joins chosen labels with ", ". The
11
+ * original `questions` array is passed through (claude validates against it).
12
+ * `answersByQuestion` maps question text → answer string.
13
+ */
14
+ export declare function buildAskUserUpdatedInput(input: unknown, answersByQuestion: Record<string, string>): Record<string, unknown>;
15
+ /**
16
+ * Parse an AskUserQuestion input into the first question's prompt/header/options
17
+ * so the UI can render a choice card. The room UI answers one question at a time
18
+ * (the first); returns undefined for a non-AskUser / malformed input (no
19
+ * questions), which the caller treats as "fall back to auto-allow".
20
+ */
21
+ export declare function askUserPrompt(input: unknown): {
22
+ question: string;
23
+ header?: string;
24
+ options: string[];
25
+ multiSelect: boolean;
26
+ } | undefined;
27
+ /** Which external CLI backs the room. "claude-code" drives `claude` (long-lived
28
+ * stdin process with per-tool approval); "codex" drives `codex exec` (one
29
+ * process per turn, resumed by thread id, no per-tool approval — the sandbox
30
+ * tier chosen at open is the only guardrail). */
31
+ export type RoomKind = "claude-code" | "codex";
32
+ /** Exact external-session identity proved by the host's transcript locator.
33
+ * RoomManager deliberately does not know CLI-specific transcript paths; the
34
+ * host resolves those paths and returns the identity read from disk. */
35
+ export interface LinkedSessionTarget {
36
+ externalSessionId: string;
37
+ cwd: string;
38
+ kind: RoomKind;
39
+ }
40
+ export interface RoomMeta {
41
+ id: string;
42
+ name: string;
43
+ cwd: string;
44
+ kind: RoomKind;
45
+ permissionMode: RoomPermissionMode;
46
+ createdAt: number;
47
+ lastActiveAt: number;
48
+ /** Session/thread id to resume: claude's session_id OR codex's thread_id.
49
+ * (Named claudeSessionId for back-compat with persisted room.json files.) */
50
+ claudeSessionId?: string;
51
+ /** Persisted capability boundary for a room bound from an external CLI
52
+ * transcript. While set, ordinary open/send calls cannot start an agent;
53
+ * only takeOverLinkedSession may clear it after revalidating the tuple. */
54
+ linkedSessionMode?: "observe-only";
55
+ }
56
+ export type RoomOpenStatus = "running" | "missing" | "observing";
57
+ export interface RoomMessage {
58
+ seq: number;
59
+ ts: number;
60
+ from: "user" | "agent" | "system";
61
+ type: string;
62
+ text?: string;
63
+ tool?: string;
64
+ summary?: string;
65
+ reason?: string;
66
+ isError?: boolean;
67
+ /** claude's tool_use block id — present on `tool` (the start) and
68
+ * `tool_result` (the matching result) so the UI can pair them by id rather
69
+ * than guessing "the last open tool". Absent on legacy messages. */
70
+ toolId?: string;
71
+ /** Full structured tool_use input (e.g. a sub-agent's `prompt`) on `tool`
72
+ * messages. `summary` is a lossy one-field preview; `args` is what the tool
73
+ * card expands to so the real parameters are visible. Absent on legacy
74
+ * messages that predate args persistence. */
75
+ args?: Record<string, unknown>;
76
+ attachments?: Array<{
77
+ name: string;
78
+ mime?: string;
79
+ size: number;
80
+ path: string;
81
+ }>;
82
+ }
83
+ export declare function roomTurnText(text: string, attachments: InputAttachmentMeta[]): string;
84
+ export declare function stripRoomAttachmentBlock(text: string): string;
85
+ /**
86
+ * Minimal interface a resident agent must satisfy, so RoomManager can be unit
87
+ * tested with a fake (no real claude process).
88
+ */
89
+ export interface RoomAgent {
90
+ start(): void;
91
+ send(text: string): boolean;
92
+ isRunning(): boolean;
93
+ stop(): void;
94
+ respondControl?(requestId: string, decision: {
95
+ behavior: "allow";
96
+ updatedInput?: unknown;
97
+ } | {
98
+ behavior: "deny";
99
+ message: string;
100
+ }): void;
101
+ }
102
+ export interface RoomAgentFactory {
103
+ (room: RoomMeta, onEvent: (event: ResidentAgentEvent) => void): RoomAgent;
104
+ }
105
+ export interface RoomManagerOptions {
106
+ rootDir: string;
107
+ createAgent: RoomAgentFactory;
108
+ /** Locate an already-running external CLI transcript without starting a CLI.
109
+ * Returning null means the transcript is absent. The returned identity is
110
+ * checked again here so a stale or mismatched locator fails closed. */
111
+ resolveLinkedSession?: (target: Readonly<LinkedSessionTarget>) => LinkedSessionTarget | null | undefined;
112
+ /** Called whenever a room gains a new persisted message (push to phone). */
113
+ onMessage: (roomId: string, msg: RoomMessage) => void;
114
+ /** Called when a room's resident agent requests tool-use approval. For
115
+ * AskUserQuestion, `askUser` carries the parsed prompt + options so the UI
116
+ * renders a choice card; the user's pick is routed back via respondApproval's
117
+ * `answer` field and baked into updatedInput.answers here in main. */
118
+ onApprovalRequest?: (roomId: string, req: {
119
+ requestId: string;
120
+ toolName: string;
121
+ displayName?: string;
122
+ input: unknown;
123
+ description?: string;
124
+ askUser?: {
125
+ question: string;
126
+ header?: string;
127
+ options: string[];
128
+ multiSelect: boolean;
129
+ };
130
+ }) => void;
131
+ /** Called after a room's resident process exits or the room is explicitly
132
+ * closed. Live transcript followers use it as their natural terminal hook. */
133
+ onRoomEnded?: (roomId: string) => void;
134
+ now?: () => number;
135
+ }
136
+ /**
137
+ * Owns room lifecycle and the on-disk message log (the authoritative source).
138
+ * A room's resident agent is started on open() and its normalized events are
139
+ * appended to messages.jsonl and mirrored via onMessage. seq is monotonic per
140
+ * room so the phone can sync incrementally.
141
+ */
142
+ export declare class RoomManager {
143
+ private readonly opts;
144
+ private agents;
145
+ /** Rooms whose visible output is currently sourced from the external CLI's
146
+ * transcript tail. While present, the resident agent's stdout mirror is
147
+ * suppressed so the same CLI output is not pushed twice. */
148
+ private transcriptFollowedRooms;
149
+ /** Pending AskUserQuestion control requests, keyed by `${roomId}:${requestId}`.
150
+ * Holds the raw tool input so respondApproval can bake the user's answer into
151
+ * the `answers` record the CLI expects. Cleared on response. */
152
+ private pendingAskUser;
153
+ /** User turns are echoed immediately for responsive room UX. When a
154
+ * transcript follower later sees the same CLI-written user line, this queue
155
+ * suppresses that duplicate. */
156
+ private pendingTranscriptUserEchoes;
157
+ /**
158
+ * Rooms whose agent events must be buffered instead of handled immediately.
159
+ *
160
+ * Only set for the duration of `agent.send()` inside `send()`: acceptance has
161
+ * to be known before the user turn is persisted, but the user turn must still
162
+ * come FIRST in the history. An agent that emits synchronously from send()
163
+ * would otherwise have its reply appended ahead of the prompt it answers.
164
+ */
165
+ private deferredEmitRooms;
166
+ private deferredEmits;
167
+ /**
168
+ * Last assigned message seq per room, so appends do not re-read the whole
169
+ * JSONL. Seeded lazily from disk on the first append after startup; dropped
170
+ * when the room's messages are deleted so the next append re-derives it.
171
+ */
172
+ private lastSeqByRoom;
173
+ /** Throttled lastActiveAt bookkeeping — see touchLastActive. */
174
+ private pendingLastActive;
175
+ private lastActiveWrittenAt;
176
+ private now;
177
+ /** Drop all pending AskUser entries for a room. Called when its agent goes
178
+ * away (exit/close) so a request that can no longer be answered doesn't leak
179
+ * in the map (small memory leak) and its approval card doesn't hang until the
180
+ * 5-min timeout. Keys are `${roomId}:${requestId}`. */
181
+ private clearPendingAskUser;
182
+ constructor(opts: RoomManagerOptions);
183
+ private roomDir;
184
+ private metaPath;
185
+ private msgPath;
186
+ createRoom(input: {
187
+ name?: string;
188
+ cwd: string;
189
+ kind?: RoomKind;
190
+ permissionMode?: RoomPermissionMode;
191
+ claudeSessionId?: string;
192
+ linkedSessionMode?: "observe-only";
193
+ }): RoomMeta;
194
+ listRooms(): RoomMeta[];
195
+ getRoom(id: string): RoomMeta | undefined;
196
+ private nextSeq;
197
+ private append;
198
+ /**
199
+ * Refresh the room's `lastActiveAt`, throttled.
200
+ *
201
+ * This re-read and rewrote the whole room.json on EVERY appended message. The
202
+ * field only feeds idle-based pruning (measured in hours), so second-level
203
+ * precision is worthless while the write cost is paid per message — together
204
+ * with the old full-history seq scan it made a busy room progressively slower.
205
+ *
206
+ * Writes at most once per THROTTLE window per room; `flushLastActive` forces
207
+ * one out at close/prune time so a room that goes quiet still records its real
208
+ * last activity.
209
+ */
210
+ private touchLastActive;
211
+ /** Persist a pending lastActiveAt immediately, if there is one. */
212
+ private flushLastActive;
213
+ /** Persist the room's resume id (claude session_id / codex thread_id) so the
214
+ * next open() can continue the same conversation. No-op if unchanged. */
215
+ setRoomSessionId(id: string, sessionId: string): void;
216
+ getMessages(id: string, sinceSeq?: number): RoomMessage[];
217
+ latestSeq(id: string): number;
218
+ beginTranscriptFollow(id: string): void;
219
+ endTranscriptFollow(id: string): void;
220
+ /** Persist normalized transcript-tail messages through the same seq/history/
221
+ * broadcast path as resident-agent output. */
222
+ ingestTranscriptMessages(id: string, messages: Omit<RoomMessage, "seq" | "ts">[]): void;
223
+ /**
224
+ * Open (or create) the room bound to a claude session id, deduping by that id
225
+ * so a given claude session maps to exactly one room. Returns the room id.
226
+ */
227
+ openForSession(claudeSessionId: string, cwd: string, mode: RoomPermissionMode, kind?: RoomKind): {
228
+ roomId: string;
229
+ status: RoomOpenStatus;
230
+ };
231
+ /** Bind an external CLI transcript to a room for observation only.
232
+ *
233
+ * This method never creates or starts a resident agent. The host must first
234
+ * prove that an on-disk transcript exists for the exact kind + session id +
235
+ * cwd tuple. A missing locator, missing transcript, or mismatched identity
236
+ * fails closed before a room is created.
237
+ */
238
+ openLinkedSession(externalSessionId: string, cwd: string, kind: RoomKind): {
239
+ roomId: string;
240
+ status: "observing" | "running";
241
+ mode: RoomPermissionMode;
242
+ cwd: string;
243
+ };
244
+ /** Explicitly take control of a previously observed external CLI room.
245
+ * This is the linked-session operation that may create/start an agent. The
246
+ * external identity is revalidated at takeover time so stale links cannot
247
+ * silently start a different session. */
248
+ takeOverLinkedSession(roomId: string, externalSessionId: string, cwd: string, kind: RoomKind): {
249
+ roomId: string;
250
+ status: "running";
251
+ mode: RoomPermissionMode;
252
+ cwd: string;
253
+ };
254
+ private requireLinkedSessionTarget;
255
+ /**
256
+ * Forward a phone-side approval decision to the room's resident agent.
257
+ * Returns false if the room has no live agent (or it can't take control
258
+ * responses).
259
+ */
260
+ respondApproval(roomId: string, requestId: string, decision: {
261
+ behavior: "allow";
262
+ updatedInput?: unknown;
263
+ answer?: string;
264
+ } | {
265
+ behavior: "deny";
266
+ message: string;
267
+ }): boolean;
268
+ /** Open a room: start its resident agent if not already running. Observe-only
269
+ * linked rooms fail closed until takeOverLinkedSession clears the boundary. */
270
+ open(id: string): {
271
+ status: RoomOpenStatus;
272
+ };
273
+ private onAgentEvent;
274
+ /** Post a user message: persist it, ensure agent running, feed it. */
275
+ send(id: string, text: string, attachments?: InputAttachmentMeta[]): boolean;
276
+ close(id: string): void;
277
+ closeAll(): void;
278
+ isOpen(id: string): boolean;
279
+ /**
280
+ * Delete rooms whose last activity is older than maxAgeMs (idle-based GC,
281
+ * replacing the removed one-shot /cc path's lack of cleanup). A room with a
282
+ * currently running resident agent is NEVER reaped, regardless of age — only
283
+ * truly dormant rooms (whole directory) are removed. Returns the ids deleted.
284
+ */
285
+ pruneStaleRooms(maxAgeMs: number): string[];
286
+ }