@cjhyy/code-shell-server 0.8.13 → 0.9.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/dist/mobile-remote/mobile-chat-turn.d.ts +4 -0
- package/dist/mobile-remote/mobile-chat-turn.js +3 -1
- package/dist/mobile-remote/mobile-client-event-validator.js +13 -12
- package/dist/mobile-remote/mobile-run-dispatch.d.ts +3 -2
- package/dist/mobile-remote/mobile-run-dispatch.js +2 -2
- package/dist/mobile-remote/trusted-device-store.js +4 -1
- package/dist/serve/headless-server.js +1 -1
- package/dist/worker-bridge-core.d.ts +9 -2
- package/dist/worker-bridge-core.js +23 -7
- package/package.json +4 -4
|
@@ -2,17 +2,21 @@ import { markAttachmentsSent, type InputAttachmentMeta } from "../attachment-ser
|
|
|
2
2
|
import { type MobileRunBridge } from "./mobile-run-dispatch.js";
|
|
3
3
|
import type { MobileUploadService } from "./mobile-upload-service.js";
|
|
4
4
|
import type { MobileAttachmentSummary, MobileImageAttachment, PermissionMode } from "./types.js";
|
|
5
|
+
import type { WorkerFrameMeta } from "../worker-bridge-core.js";
|
|
5
6
|
type ChatTurnUploads = Pick<MobileUploadService, "claim" | "release" | "finalize">;
|
|
6
7
|
export interface DispatchMobileChatTurnInput {
|
|
7
8
|
deviceId: string;
|
|
8
9
|
sessionId: string;
|
|
9
10
|
fallbackCwd: string;
|
|
11
|
+
projectId?: string;
|
|
12
|
+
rootId?: string;
|
|
10
13
|
text: string;
|
|
11
14
|
attachments?: MobileImageAttachment[];
|
|
12
15
|
clientMessageId?: string;
|
|
13
16
|
permissionMode?: PermissionMode;
|
|
14
17
|
runId: string;
|
|
15
18
|
bridge: MobileRunBridge;
|
|
19
|
+
meta: WorkerFrameMeta;
|
|
16
20
|
uploads: ChatTurnUploads;
|
|
17
21
|
resolveWorkspace: (sessionId: string, fallbackCwd: string) => Promise<string>;
|
|
18
22
|
markSent?: typeof markAttachmentsSent;
|
|
@@ -46,11 +46,13 @@ export async function dispatchMobileChatTurn(input) {
|
|
|
46
46
|
task: text,
|
|
47
47
|
cwd,
|
|
48
48
|
sessionId: input.sessionId,
|
|
49
|
+
...(input.projectId ? { projectId: input.projectId } : {}),
|
|
50
|
+
...(input.rootId ? { rootId: input.rootId } : {}),
|
|
49
51
|
clientMessageId,
|
|
50
52
|
attachments: materialized.metas,
|
|
51
53
|
...(input.permissionMode ? { permissionMode: input.permissionMode } : {}),
|
|
52
54
|
},
|
|
53
|
-
});
|
|
55
|
+
}, input.meta);
|
|
54
56
|
if (!acceptance.ok) {
|
|
55
57
|
await settleClaims(input.uploads, input.deviceId, materialized.claims, "release");
|
|
56
58
|
return { ok: false, message: acceptance.message };
|
|
@@ -46,9 +46,13 @@ export function parseMobileClientEvent(value) {
|
|
|
46
46
|
break;
|
|
47
47
|
case "session.create":
|
|
48
48
|
valid =
|
|
49
|
-
(event.cwd === undefined ||
|
|
50
|
-
event.
|
|
51
|
-
|
|
49
|
+
(event.cwd === undefined || event.cwd === null || boundedPath(event.cwd)) &&
|
|
50
|
+
(event.projectId === undefined ||
|
|
51
|
+
event.projectId === null ||
|
|
52
|
+
boundedString(event.projectId, MAX_ID_LENGTH)) &&
|
|
53
|
+
optionalBoundedString(event.rootId, MAX_ID_LENGTH) &&
|
|
54
|
+
(event.rootId === undefined || typeof event.projectId === "string") &&
|
|
55
|
+
!(event.projectId === null && event.rootId !== undefined) &&
|
|
52
56
|
optionalBoundedString(event.name, MAX_NAME_LENGTH);
|
|
53
57
|
break;
|
|
54
58
|
case "run.stop":
|
|
@@ -71,8 +75,7 @@ export function parseMobileClientEvent(value) {
|
|
|
71
75
|
break;
|
|
72
76
|
case "session.sync":
|
|
73
77
|
valid =
|
|
74
|
-
boundedString(event.sessionId, MAX_ID_LENGTH) &&
|
|
75
|
-
optionalNonNegativeInteger(event.sinceSeq);
|
|
78
|
+
boundedString(event.sessionId, MAX_ID_LENGTH) && optionalNonNegativeInteger(event.sinceSeq);
|
|
76
79
|
break;
|
|
77
80
|
case "permission.setMode":
|
|
78
81
|
valid =
|
|
@@ -114,8 +117,7 @@ export function parseMobileClientEvent(value) {
|
|
|
114
117
|
break;
|
|
115
118
|
case "room.history":
|
|
116
119
|
valid =
|
|
117
|
-
boundedString(event.roomId, MAX_ID_LENGTH) &&
|
|
118
|
-
optionalNonNegativeInteger(event.sinceSeq);
|
|
120
|
+
boundedString(event.roomId, MAX_ID_LENGTH) && optionalNonNegativeInteger(event.sinceSeq);
|
|
119
121
|
break;
|
|
120
122
|
case "ccRoom.probe":
|
|
121
123
|
valid =
|
|
@@ -202,9 +204,7 @@ function isBoundedHistoryLimit(value) {
|
|
|
202
204
|
}
|
|
203
205
|
function optionalAttachments(value) {
|
|
204
206
|
return (value === undefined ||
|
|
205
|
-
(Array.isArray(value) &&
|
|
206
|
-
value.length <= MAX_MOBILE_ATTACHMENTS &&
|
|
207
|
-
value.every(isAttachment)));
|
|
207
|
+
(Array.isArray(value) && value.length <= MAX_MOBILE_ATTACHMENTS && value.every(isAttachment)));
|
|
208
208
|
}
|
|
209
209
|
function isAttachment(value) {
|
|
210
210
|
if (!isRecord(value) ||
|
|
@@ -223,8 +223,9 @@ function isApprovalDecision(value) {
|
|
|
223
223
|
if (!isRecord(value))
|
|
224
224
|
return false;
|
|
225
225
|
if (value.behavior === "allow") {
|
|
226
|
-
return ((value.answer === undefined ||
|
|
226
|
+
return ((value.answer === undefined ||
|
|
227
|
+
boundedOptionalEmptyString(value.answer, MAX_MESSAGE_LENGTH)) &&
|
|
227
228
|
(value.updatedInput === undefined || isRecord(value.updatedInput)));
|
|
228
229
|
}
|
|
229
|
-
return
|
|
230
|
+
return value.behavior === "deny" && boundedOptionalEmptyString(value.message, MAX_MESSAGE_LENGTH);
|
|
230
231
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { WorkerFrameMeta } from "../worker-bridge-core.js";
|
|
1
2
|
export interface MobileRunBridge {
|
|
2
3
|
subscribeOutbound(listener: (line: string) => void): () => void;
|
|
3
|
-
injectWorkerMessage(line: string): void;
|
|
4
|
+
injectWorkerMessage(line: string, meta: WorkerFrameMeta): void;
|
|
4
5
|
}
|
|
5
6
|
export interface MobileRunRequest {
|
|
6
7
|
id: string;
|
|
@@ -14,4 +15,4 @@ export type MobileRunAcceptance = {
|
|
|
14
15
|
code?: number;
|
|
15
16
|
};
|
|
16
17
|
/** Inject agent/run and wait for its request-correlated validation/queue acknowledgement. */
|
|
17
|
-
export declare function injectMobileRunAndAwaitAcceptance(bridge: MobileRunBridge, request: MobileRunRequest, timeoutMs?: number): Promise<MobileRunAcceptance>;
|
|
18
|
+
export declare function injectMobileRunAndAwaitAcceptance(bridge: MobileRunBridge, request: MobileRunRequest, meta: WorkerFrameMeta, timeoutMs?: number): Promise<MobileRunAcceptance>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Inject agent/run and wait for its request-correlated validation/queue acknowledgement. */
|
|
2
|
-
export function injectMobileRunAndAwaitAcceptance(bridge, request, timeoutMs = 5_000) {
|
|
2
|
+
export function injectMobileRunAndAwaitAcceptance(bridge, request, meta, timeoutMs = 5_000) {
|
|
3
3
|
return new Promise((resolve) => {
|
|
4
4
|
let settled = false;
|
|
5
5
|
let timer;
|
|
@@ -47,7 +47,7 @@ export function injectMobileRunAndAwaitAcceptance(bridge, request, timeoutMs = 5
|
|
|
47
47
|
id: request.id,
|
|
48
48
|
method: "agent/run",
|
|
49
49
|
params: request.params,
|
|
50
|
-
}));
|
|
50
|
+
}), meta);
|
|
51
51
|
}
|
|
52
52
|
catch (error) {
|
|
53
53
|
finish({ ok: false, message: error instanceof Error ? error.message : String(error) });
|
|
@@ -38,8 +38,11 @@ export class TrustedDeviceStore {
|
|
|
38
38
|
// Get-or-create by secretHash. A phone's secret is stable per browser
|
|
39
39
|
// (persisted in localStorage as cs.deviceSecret), so re-scanning the QR
|
|
40
40
|
// code must reuse its live row rather than accumulating duplicates.
|
|
41
|
-
const existing = devices.find((d) => d.secretHash
|
|
41
|
+
const existing = devices.find((d) => secretHashEquals(d.secretHash, input.secretHash));
|
|
42
42
|
if (existing) {
|
|
43
|
+
if (existing.revokedAt) {
|
|
44
|
+
throw new Error("Trusted device was revoked; remove it before pairing again");
|
|
45
|
+
}
|
|
43
46
|
existing.name = name;
|
|
44
47
|
existing.lastSeenAt = Date.now();
|
|
45
48
|
return { result: this.toPublic(existing), changed: true };
|
|
@@ -232,7 +232,7 @@ export async function startHeadlessServer(opts) {
|
|
|
232
232
|
// Spawn-on-first-frame (idempotent): the browser's first request wakes
|
|
233
233
|
// the worker, mirroring the renderer's spawn-on-agent/run semantics.
|
|
234
234
|
bridge.ensureWorker(opts.cwd);
|
|
235
|
-
bridge.injectWorkerMessage(workerLine);
|
|
235
|
+
bridge.injectWorkerMessage(workerLine, { origin: "serve", producer: "serve-ws" });
|
|
236
236
|
});
|
|
237
237
|
ws.on("close", () => {
|
|
238
238
|
tabs.delete(ws);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/** Truncate a wire line for logs. */
|
|
2
2
|
export declare function previewLine(s: string, max?: number): string;
|
|
3
3
|
export type WorkerBridgeLog = (event: string, data?: Record<string, unknown>) => void;
|
|
4
|
+
export interface WorkerFrameMeta {
|
|
5
|
+
origin: "renderer" | "mobile" | "host" | "serve";
|
|
6
|
+
/** Producer label for logs and audits. It is never an authorization input. */
|
|
7
|
+
producer: string;
|
|
8
|
+
}
|
|
4
9
|
export interface WorkerExitInfo {
|
|
5
10
|
code: number | null;
|
|
6
11
|
signal: NodeJS.Signals | null;
|
|
@@ -55,6 +60,8 @@ export interface WorkerRequestOptions {
|
|
|
55
60
|
ensureWorker?: boolean;
|
|
56
61
|
/** cwd handed to ensureWorker() when it spawns. Ignored unless ensureWorker. */
|
|
57
62
|
ensureWorkerCwd?: string;
|
|
63
|
+
/** Main-owned provenance for this frame. */
|
|
64
|
+
meta: WorkerFrameMeta;
|
|
58
65
|
}
|
|
59
66
|
export interface WorkerBridgeCoreOptions {
|
|
60
67
|
/** Absolute path of the worker entry script (agent-server-stdio). */
|
|
@@ -71,7 +78,7 @@ export interface WorkerBridgeCoreOptions {
|
|
|
71
78
|
* injected or correlated (e.g. agent/run trust metadata + on-demand spawn).
|
|
72
79
|
* Returns the line to write; `method` is only used for log context.
|
|
73
80
|
*/
|
|
74
|
-
prepareInbound?: (line: string) => {
|
|
81
|
+
prepareInbound?: (line: string, meta: WorkerFrameMeta) => {
|
|
75
82
|
line: string;
|
|
76
83
|
method?: string;
|
|
77
84
|
};
|
|
@@ -141,7 +148,7 @@ export declare class WorkerBridgeCore {
|
|
|
141
148
|
* written, or dropped with a log when no worker is alive — identical
|
|
142
149
|
* semantics for every front end (renderer, mobile remote, orchestrators).
|
|
143
150
|
*/
|
|
144
|
-
injectWorkerMessage(rawLine: string): void;
|
|
151
|
+
injectWorkerMessage(rawLine: string, meta: WorkerFrameMeta): void;
|
|
145
152
|
/**
|
|
146
153
|
* Send a correlated JSON-RPC request and await its response by id.
|
|
147
154
|
* `params` is omitted from the frame when undefined. Never rejects — the
|
|
@@ -200,9 +200,7 @@ export class WorkerBridgeCore {
|
|
|
200
200
|
const pending = this.pendingRequests.get(msg.id);
|
|
201
201
|
if (!pending)
|
|
202
202
|
return false;
|
|
203
|
-
pending.settle(msg.error
|
|
204
|
-
? { status: "error", error: msg.error }
|
|
205
|
-
: { status: "result", result: msg.result });
|
|
203
|
+
pending.settle(msg.error ? { status: "error", error: msg.error } : { status: "result", result: msg.result });
|
|
206
204
|
return pending.consume;
|
|
207
205
|
}
|
|
208
206
|
settlePendingOnExit() {
|
|
@@ -239,18 +237,25 @@ export class WorkerBridgeCore {
|
|
|
239
237
|
* written, or dropped with a log when no worker is alive — identical
|
|
240
238
|
* semantics for every front end (renderer, mobile remote, orchestrators).
|
|
241
239
|
*/
|
|
242
|
-
injectWorkerMessage(rawLine) {
|
|
240
|
+
injectWorkerMessage(rawLine, meta) {
|
|
243
241
|
const prep = this.opts.prepareInbound
|
|
244
|
-
? this.opts.prepareInbound(rawLine)
|
|
242
|
+
? this.opts.prepareInbound(rawLine, meta)
|
|
245
243
|
: { line: rawLine, method: undefined };
|
|
246
244
|
if (!this.canSend()) {
|
|
247
245
|
this.log("inject.dropped", {
|
|
248
246
|
reason: this.child ? "stdin destroyed" : "no child",
|
|
249
247
|
method: prep.method,
|
|
248
|
+
origin: meta.origin,
|
|
249
|
+
producer: meta.producer,
|
|
250
250
|
});
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
253
|
-
this.log("inject→worker", {
|
|
253
|
+
this.log("inject→worker", {
|
|
254
|
+
method: prep.method,
|
|
255
|
+
origin: meta.origin,
|
|
256
|
+
producer: meta.producer,
|
|
257
|
+
raw: previewLine(prep.line),
|
|
258
|
+
});
|
|
254
259
|
this.sendLine(prep.line);
|
|
255
260
|
}
|
|
256
261
|
/**
|
|
@@ -298,7 +303,7 @@ export class WorkerBridgeCore {
|
|
|
298
303
|
// bypassing this hook left the main process unable to route the first
|
|
299
304
|
// Panel tool call for a restored session.
|
|
300
305
|
const prepared = this.opts.prepareInbound
|
|
301
|
-
? this.opts.prepareInbound(rawFrame)
|
|
306
|
+
? this.opts.prepareInbound(rawFrame, options.meta)
|
|
302
307
|
: { line: rawFrame, method: undefined };
|
|
303
308
|
// Wake a lazily-unspawned worker before writing, so a spawn-triggering
|
|
304
309
|
// request (pet/IM-gateway agent/run) isn't dropped by sendLine and left
|
|
@@ -311,6 +316,17 @@ export class WorkerBridgeCore {
|
|
|
311
316
|
catch (e) {
|
|
312
317
|
sendError = e;
|
|
313
318
|
}
|
|
319
|
+
const rejected = sendError;
|
|
320
|
+
if (typeof rejected?.code === "number") {
|
|
321
|
+
settle({
|
|
322
|
+
status: "error",
|
|
323
|
+
error: {
|
|
324
|
+
code: rejected.code,
|
|
325
|
+
message: typeof rejected.message === "string" ? rejected.message : "worker frame rejected",
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
314
330
|
if (options.failFast && (!sent || sendError !== undefined)) {
|
|
315
331
|
settle({ status: "sendFailed", error: sendError });
|
|
316
332
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjhyy/code-shell-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "CodeShell server transport layer — mobile remote HTTP/WS host, pairing, passcode gate, tunnel, rooms, uploads, and session/attachment services. Electron-free; precursor of the auth gateway.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@cjhyy/code-shell-capability-coding": "0.
|
|
43
|
-
"@cjhyy/code-shell-core": "0.
|
|
44
|
-
"@cjhyy/code-shell-web": "0.
|
|
42
|
+
"@cjhyy/code-shell-capability-coding": "0.9.0",
|
|
43
|
+
"@cjhyy/code-shell-core": "0.9.0",
|
|
44
|
+
"@cjhyy/code-shell-web": "0.9.0",
|
|
45
45
|
"@types/ws": "^8.18.1",
|
|
46
46
|
"ws": "^8.21.0"
|
|
47
47
|
},
|