@cjhyy/code-shell-web 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.
- package/README.md +42 -0
- package/dist/hooks/remoteAppSync.d.ts +21 -0
- package/dist/hooks/remoteAppSync.js +102 -0
- package/dist/hooks/useRemoteApp.d.ts +89 -0
- package/dist/hooks/useRemoteApp.js +1015 -0
- package/dist/hooks/useRemoteSocket.d.ts +25 -0
- package/dist/hooks/useRemoteSocket.js +267 -0
- package/dist/i18n/mobile.d.ts +323 -0
- package/dist/i18n/mobile.js +323 -0
- package/dist/i18n/translate.d.ts +351 -0
- package/dist/i18n/translate.js +35 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +28 -0
- package/dist/lib/deviceCredential.d.ts +14 -0
- package/dist/lib/deviceCredential.js +19 -0
- package/dist/lib/format.d.ts +26 -0
- package/dist/lib/format.js +96 -0
- package/dist/lib/messageMappers.d.ts +37 -0
- package/dist/lib/messageMappers.js +145 -0
- package/dist/lib/mobileAttachments.d.ts +23 -0
- package/dist/lib/mobileAttachments.js +121 -0
- package/dist/lib/pairing.d.ts +6 -0
- package/dist/lib/pairing.js +8 -0
- package/dist/lib/riskClassify.d.ts +5 -0
- package/dist/lib/riskClassify.js +56 -0
- package/dist/lib/storage.d.ts +10 -0
- package/dist/lib/storage.js +53 -0
- package/dist/lib/streamReducer.d.ts +90 -0
- package/dist/lib/streamReducer.js +564 -0
- package/dist/lib/uiLanguage.d.ts +15 -0
- package/dist/lib/uiLanguage.js +34 -0
- package/dist-app/assets/index-CbC5kEGZ.js +11 -0
- package/dist-app/assets/index-xwzva9ZI.css +1 -0
- package/dist-app/index.html +13 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @cjhyy/code-shell-web
|
|
2
|
+
|
|
3
|
+
Browser-only state and UI helpers for CodeShell's remote client. The package
|
|
4
|
+
contains no Electron code and imports CodeShell core contracts as types only.
|
|
5
|
+
|
|
6
|
+
## Public surface
|
|
7
|
+
|
|
8
|
+
The root entry exports:
|
|
9
|
+
|
|
10
|
+
- the reconnecting WebSocket hook and remote-app state machine;
|
|
11
|
+
- protocol stream reducers and message mappers;
|
|
12
|
+
- pairing, device-credential, attachment, risk and storage helpers;
|
|
13
|
+
- the mobile i18n namespace and translation helpers.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { useRemoteApp, useRemoteSocket, classifyRisk, translate } from "@cjhyy/code-shell-web";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The reusable browser logic is emitted to `dist/`. The package also ships the
|
|
20
|
+
standalone Vite application in `dist-app/`; `@cjhyy/code-shell-server/serve`
|
|
21
|
+
uses those assets for the ready-made headless web host.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bun run --cwd packages/web build
|
|
27
|
+
bun run --cwd packages/web build:app
|
|
28
|
+
bun run --cwd packages/web dev:app
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`build` cleans both output directories before compiling the library and
|
|
32
|
+
building the application, so published assets cannot contain stale files.
|
|
33
|
+
|
|
34
|
+
## Boundary
|
|
35
|
+
|
|
36
|
+
This package may use browser APIs and type-only core protocol imports. It must
|
|
37
|
+
not import Electron, Desktop preload globals, Node-only runtime modules or core
|
|
38
|
+
runtime implementations.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface SessionReplayEntry {
|
|
2
|
+
seq: number;
|
|
3
|
+
event: unknown;
|
|
4
|
+
}
|
|
5
|
+
export interface SessionReplaySelection {
|
|
6
|
+
events: unknown[];
|
|
7
|
+
cursor: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function selectSessionReplayEntries(entries: SessionReplayEntry[], appliedSeq: number): SessionReplaySelection;
|
|
10
|
+
export declare function rawApprovalResolvedRequestId(raw: unknown): string | null;
|
|
11
|
+
export declare function removeResolvedApproval<T extends {
|
|
12
|
+
requestId: string;
|
|
13
|
+
}>(approvals: T[], requestId: string): T[];
|
|
14
|
+
export declare function roomMessageSeq(msg: unknown): number | undefined;
|
|
15
|
+
export declare function markRoomSeqApplied(appliedSeqs: Map<string, Set<number>>, roomId: string, seq: number): void;
|
|
16
|
+
export declare function filterNewRoomMessages(roomId: string, messages: unknown[], appliedSeqs: Map<string, Set<number>>): unknown[];
|
|
17
|
+
export declare function maxRoomSeq(current: number, messages: unknown[], latestSeq?: number): number;
|
|
18
|
+
export declare function noteSessionSeq(latestSeqs: Map<string, number>, sessionId: string, seq: number): boolean;
|
|
19
|
+
export declare function markSessionUnread(unreadSessionIds: Set<string>, sessionId: string, activeSessionId?: string): Set<string>;
|
|
20
|
+
export declare function clearUnreadSession(unreadSessionIds: Set<string>, sessionId?: string): Set<string>;
|
|
21
|
+
export declare function pruneUnreadSessions(unreadSessionIds: Set<string>, sessionIds: Iterable<string>): Set<string>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export function selectSessionReplayEntries(entries, appliedSeq) {
|
|
2
|
+
let cursor = appliedSeq;
|
|
3
|
+
const events = [];
|
|
4
|
+
for (const entry of entries) {
|
|
5
|
+
if (entry.seq > appliedSeq) {
|
|
6
|
+
events.push(entry.event);
|
|
7
|
+
if (entry.seq > cursor)
|
|
8
|
+
cursor = entry.seq;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return { events, cursor };
|
|
12
|
+
}
|
|
13
|
+
export function rawApprovalResolvedRequestId(raw) {
|
|
14
|
+
const obj = raw;
|
|
15
|
+
if (!obj || obj.method !== "agent/approvalResolved")
|
|
16
|
+
return null;
|
|
17
|
+
const params = obj.params;
|
|
18
|
+
const requestId = params?.requestId;
|
|
19
|
+
return typeof requestId === "string" && requestId ? requestId : null;
|
|
20
|
+
}
|
|
21
|
+
export function removeResolvedApproval(approvals, requestId) {
|
|
22
|
+
return approvals.filter((approval) => approval.requestId !== requestId);
|
|
23
|
+
}
|
|
24
|
+
export function roomMessageSeq(msg) {
|
|
25
|
+
const seq = msg?.seq;
|
|
26
|
+
return typeof seq === "number" && Number.isFinite(seq) ? seq : undefined;
|
|
27
|
+
}
|
|
28
|
+
export function markRoomSeqApplied(appliedSeqs, roomId, seq) {
|
|
29
|
+
let set = appliedSeqs.get(roomId);
|
|
30
|
+
if (!set) {
|
|
31
|
+
set = new Set();
|
|
32
|
+
appliedSeqs.set(roomId, set);
|
|
33
|
+
}
|
|
34
|
+
set.add(seq);
|
|
35
|
+
if (set.size > 1000) {
|
|
36
|
+
const sorted = [...set].sort((a, b) => a - b);
|
|
37
|
+
for (const old of sorted.slice(0, set.size - 1000))
|
|
38
|
+
set.delete(old);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function filterNewRoomMessages(roomId, messages, appliedSeqs) {
|
|
42
|
+
const seen = appliedSeqs.get(roomId);
|
|
43
|
+
const next = [];
|
|
44
|
+
for (const msg of messages) {
|
|
45
|
+
const seq = roomMessageSeq(msg);
|
|
46
|
+
if (seq !== undefined && seen?.has(seq))
|
|
47
|
+
continue;
|
|
48
|
+
next.push(msg);
|
|
49
|
+
}
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
export function maxRoomSeq(current, messages, latestSeq) {
|
|
53
|
+
let next = typeof latestSeq === "number" && Number.isFinite(latestSeq) ? latestSeq : current;
|
|
54
|
+
for (const msg of messages) {
|
|
55
|
+
const seq = roomMessageSeq(msg);
|
|
56
|
+
if (seq !== undefined && seq > next)
|
|
57
|
+
next = seq;
|
|
58
|
+
}
|
|
59
|
+
return next;
|
|
60
|
+
}
|
|
61
|
+
export function noteSessionSeq(latestSeqs, sessionId, seq) {
|
|
62
|
+
if (!Number.isFinite(seq))
|
|
63
|
+
return false;
|
|
64
|
+
const current = latestSeqs.get(sessionId) ?? 0;
|
|
65
|
+
if (seq <= current)
|
|
66
|
+
return false;
|
|
67
|
+
latestSeqs.set(sessionId, seq);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
export function markSessionUnread(unreadSessionIds, sessionId, activeSessionId) {
|
|
71
|
+
if (!sessionId || sessionId === activeSessionId || unreadSessionIds.has(sessionId)) {
|
|
72
|
+
return unreadSessionIds;
|
|
73
|
+
}
|
|
74
|
+
const next = new Set(unreadSessionIds);
|
|
75
|
+
next.add(sessionId);
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
78
|
+
export function clearUnreadSession(unreadSessionIds, sessionId) {
|
|
79
|
+
if (!sessionId || !unreadSessionIds.has(sessionId))
|
|
80
|
+
return unreadSessionIds;
|
|
81
|
+
const next = new Set(unreadSessionIds);
|
|
82
|
+
next.delete(sessionId);
|
|
83
|
+
return next;
|
|
84
|
+
}
|
|
85
|
+
export function pruneUnreadSessions(unreadSessionIds, sessionIds) {
|
|
86
|
+
const known = new Set(sessionIds);
|
|
87
|
+
let changed = false;
|
|
88
|
+
for (const id of unreadSessionIds) {
|
|
89
|
+
if (!known.has(id)) {
|
|
90
|
+
changed = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!changed)
|
|
95
|
+
return unreadSessionIds;
|
|
96
|
+
const next = new Set();
|
|
97
|
+
for (const id of unreadSessionIds) {
|
|
98
|
+
if (known.has(id))
|
|
99
|
+
next.add(id);
|
|
100
|
+
}
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { MobileSessionMeta, MobileProjectMeta, RoomPublic, MobileRemotePermissionMode as PermissionMode, ApprovalScope, ApprovalPathScope, CcDiscoveredSession } from "@cjhyy/code-shell-core";
|
|
2
|
+
import { type ChatState } from "../lib/streamReducer.js";
|
|
3
|
+
import { type Risk } from "../lib/riskClassify.js";
|
|
4
|
+
import { type ConnStatus } from "./useRemoteSocket.js";
|
|
5
|
+
import { type MobileComposerAttachment } from "../lib/mobileAttachments.js";
|
|
6
|
+
/** Which external coding-CLI the CC pane drives. Mirrors desktop CCRoomView. */
|
|
7
|
+
export type CcCliKind = "claude-code" | "codex";
|
|
8
|
+
export interface PendingApproval {
|
|
9
|
+
requestId: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
/** Set for cc-room (external claude CLI) approvals — routes the response via
|
|
12
|
+
* respondCcApproval(roomId, …) instead of the session approval path. */
|
|
13
|
+
roomId?: string;
|
|
14
|
+
toolName: string;
|
|
15
|
+
description: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
risk: Risk;
|
|
18
|
+
/** AskUser options, when this is an AskUser approval. */
|
|
19
|
+
options?: string[];
|
|
20
|
+
/** When true, the user must pick an option (no free text). */
|
|
21
|
+
optionsOnly?: boolean;
|
|
22
|
+
/** True for path-scoped tools (Read/Edit/Write/…) → show file/dir scope. */
|
|
23
|
+
pathScoped: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface RemoteApp {
|
|
26
|
+
status: ConnStatus;
|
|
27
|
+
deviceName: string;
|
|
28
|
+
chat: ChatState;
|
|
29
|
+
sessions: MobileSessionMeta[];
|
|
30
|
+
unreadSessionIds: ReadonlySet<string>;
|
|
31
|
+
activeSessionId?: string;
|
|
32
|
+
activeCwd?: string | null;
|
|
33
|
+
projects: MobileProjectMeta[];
|
|
34
|
+
/** The bound room — for the user this is always an external CC (Claude Code)
|
|
35
|
+
* session. The room is internal transport; there is no user-facing room list. */
|
|
36
|
+
activeRoom?: RoomPublic;
|
|
37
|
+
approvals: PendingApproval[];
|
|
38
|
+
permissionMode: PermissionMode;
|
|
39
|
+
loading: {
|
|
40
|
+
sessions: boolean;
|
|
41
|
+
sessionHistory: boolean;
|
|
42
|
+
/** Internal: the room.list fetch that resolves `activeRoom`'s metadata for a
|
|
43
|
+
* bound CC session. No user-facing room list consumes this. */
|
|
44
|
+
rooms: boolean;
|
|
45
|
+
projects: boolean;
|
|
46
|
+
roomHistory: boolean;
|
|
47
|
+
ccSessions: boolean;
|
|
48
|
+
};
|
|
49
|
+
notice?: string;
|
|
50
|
+
logout: () => void;
|
|
51
|
+
sendChat: (input: {
|
|
52
|
+
text: string;
|
|
53
|
+
attachments: MobileComposerAttachment[];
|
|
54
|
+
}) => Promise<boolean>;
|
|
55
|
+
stopRun: () => void;
|
|
56
|
+
selectSession: (id: string) => void;
|
|
57
|
+
newSession: (cwd?: string | null, name?: string) => void;
|
|
58
|
+
refreshSessions: () => void;
|
|
59
|
+
respondApproval: (requestId: string, decision: "approve" | "reject", opts?: {
|
|
60
|
+
reason?: string;
|
|
61
|
+
answer?: string;
|
|
62
|
+
scope?: ApprovalScope;
|
|
63
|
+
pathScope?: ApprovalPathScope;
|
|
64
|
+
}) => void;
|
|
65
|
+
setPermissionMode: (mode: PermissionMode) => void;
|
|
66
|
+
extendGoal: (sessionId: string) => void;
|
|
67
|
+
clearGoal: (sessionId: string) => void;
|
|
68
|
+
/** Leave the bound CC session (drops the room binding, clears the feed). */
|
|
69
|
+
leaveRoom: () => void;
|
|
70
|
+
activeProjectCwd: string | null;
|
|
71
|
+
selectProject: (cwd: string) => void;
|
|
72
|
+
ccSessions: CcDiscoveredSession[];
|
|
73
|
+
ccProbe: {
|
|
74
|
+
available: boolean;
|
|
75
|
+
reason?: string;
|
|
76
|
+
} | null;
|
|
77
|
+
/** Selected CC CLI (Claude Code / Codex). Switching re-probes + re-lists. */
|
|
78
|
+
ccCliKind: CcCliKind;
|
|
79
|
+
setCcCliKind: (kind: CcCliKind) => void;
|
|
80
|
+
openCcSession: (sessionId: string, cwd: string, mode: PermissionMode) => void;
|
|
81
|
+
respondCcApproval: (roomId: string, requestId: string, decision: {
|
|
82
|
+
behavior: "allow";
|
|
83
|
+
updatedInput?: unknown;
|
|
84
|
+
} | {
|
|
85
|
+
behavior: "deny";
|
|
86
|
+
message: string;
|
|
87
|
+
}) => void;
|
|
88
|
+
}
|
|
89
|
+
export declare function useRemoteApp(): RemoteApp;
|