@cjhyy/code-shell-server 0.9.49 → 0.9.51
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/index.panels.d.ts +1 -1
- package/dist/index.panels.js +1 -1
- package/dist/mobile-remote/mobile-client-event-validator.js +24 -3
- package/dist/mobile-remote/pending-approvals.d.ts +13 -1
- package/dist/mobile-remote/pending-approvals.js +45 -9
- package/dist/mobile-remote/remote-host-manager.d.ts +4 -0
- package/dist/mobile-remote/remote-host-manager.js +8 -0
- package/dist/panels/http.d.ts +1 -1
- package/dist/panels/management-http.d.ts +1 -1
- package/dist/panels/management.d.ts +11 -2
- package/dist/panels/management.js +15 -7
- package/dist/panels/resources/library.js +17 -3
- package/dist/panels/runtime.js +1 -1
- package/package.json +6 -6
package/dist/index.panels.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createPanelHttp } from "./panels/http.js";
|
|
2
2
|
export * from "./panels/automations.js";
|
|
3
3
|
export * from "./panels/hub-automations.js";
|
|
4
|
-
export { createPanelManagement, PanelManagementError } from "./panels/management.js";
|
|
4
|
+
export { createPanelManagement, createPanelSnapshotReads, PanelManagementError, type PanelSnapshotReads, } from "./panels/management.js";
|
|
5
5
|
export { createPanelRuntime, panelWebCompatibility } from "./panels/runtime.js";
|
|
6
6
|
export { createHubPanelBinding } from "./panels/hub-binding.js";
|
|
7
7
|
export * from "./panels/project-scope.js";
|
package/dist/index.panels.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createPanelHttp } from "./panels/http.js";
|
|
2
2
|
export * from "./panels/automations.js";
|
|
3
3
|
export * from "./panels/hub-automations.js";
|
|
4
|
-
export { createPanelManagement, PanelManagementError } from "./panels/management.js";
|
|
4
|
+
export { createPanelManagement, createPanelSnapshotReads, PanelManagementError, } from "./panels/management.js";
|
|
5
5
|
export { createPanelRuntime, panelWebCompatibility } from "./panels/runtime.js";
|
|
6
6
|
export { createHubPanelBinding } from "./panels/hub-binding.js";
|
|
7
7
|
export * from "./panels/project-scope.js";
|
|
@@ -28,7 +28,8 @@ export function parseMobileClientEvent(value) {
|
|
|
28
28
|
break;
|
|
29
29
|
case "chat.send":
|
|
30
30
|
valid =
|
|
31
|
-
|
|
31
|
+
optionalMember(event.permissionMode, PERMISSION_MODES) &&
|
|
32
|
+
boundedOptionalEmptyString(event.text, MAX_MESSAGE_LENGTH) &&
|
|
32
33
|
(event.modelKey === undefined ||
|
|
33
34
|
(boundedString(event.modelKey, MAX_ID_LENGTH) &&
|
|
34
35
|
/^(codex|claude-code)\/[^\r\n\0]*$/.test(event.modelKey))) &&
|
|
@@ -74,20 +75,37 @@ export function parseMobileClientEvent(value) {
|
|
|
74
75
|
((boundedString(event.throughEventId, 512) && event.cursor === undefined) ||
|
|
75
76
|
(boundedString(event.cursor, 2048) && event.throughEventId === undefined));
|
|
76
77
|
break;
|
|
78
|
+
case "session.historyView":
|
|
79
|
+
case "session.historyDetail":
|
|
80
|
+
valid =
|
|
81
|
+
boundedString(event.sessionId, 128) &&
|
|
82
|
+
boundedString(event.recoveryId, 128) &&
|
|
83
|
+
boundedString(event.requestId, 128) &&
|
|
84
|
+
(event.type === "session.historyView"
|
|
85
|
+
? optionalBoundedString(event.before, 2048) &&
|
|
86
|
+
Object.keys(event).every((key) => ["type", "sessionId", "recoveryId", "requestId", "before"].includes(key))
|
|
87
|
+
: boundedString(event.ref, 2048) &&
|
|
88
|
+
optionalBoundedString(event.cursor, 2048) &&
|
|
89
|
+
Object.keys(event).every((key) => ["type", "sessionId", "recoveryId", "requestId", "ref", "cursor"].includes(key)));
|
|
90
|
+
break;
|
|
77
91
|
case "session.outputJournal":
|
|
78
92
|
valid =
|
|
79
93
|
boundedString(event.sessionId, 128) &&
|
|
80
94
|
boundedString(event.recoveryId, 128) &&
|
|
81
95
|
boundedString(event.requestId, 128) &&
|
|
82
96
|
optionalBoundedString(event.after, 2048) &&
|
|
83
|
-
optionalBoundedString(event.through, 2048)
|
|
97
|
+
optionalBoundedString(event.through, 2048) &&
|
|
98
|
+
(event.frozenSnapshot === undefined || event.frozenSnapshot === true) &&
|
|
99
|
+
(event.maxFrames === undefined ||
|
|
100
|
+
(isPositiveInteger(event.maxFrames) && event.maxFrames <= 2048));
|
|
84
101
|
break;
|
|
85
102
|
case "session.history":
|
|
86
103
|
valid = boundedString(event.sessionId, MAX_ID_LENGTH);
|
|
87
104
|
break;
|
|
88
105
|
case "session.create":
|
|
89
106
|
valid =
|
|
90
|
-
|
|
107
|
+
optionalMember(event.permissionMode, PERMISSION_MODES) &&
|
|
108
|
+
optionalBoundedString(event.clientRequestId, MAX_ID_LENGTH) &&
|
|
91
109
|
(event.cwd === undefined || event.cwd === null || boundedPath(event.cwd)) &&
|
|
92
110
|
(event.projectId === undefined ||
|
|
93
111
|
event.projectId === null ||
|
|
@@ -105,6 +123,9 @@ export function parseMobileClientEvent(value) {
|
|
|
105
123
|
boundedString(event.approvalId, MAX_ID_LENGTH) &&
|
|
106
124
|
(event.decision === "approve" || event.decision === "reject") &&
|
|
107
125
|
optionalBoundedString(event.sessionId, MAX_ID_LENGTH) &&
|
|
126
|
+
optionalBoundedString(event.connectionId, MAX_ID_LENGTH) &&
|
|
127
|
+
(event.generation === undefined ||
|
|
128
|
+
(Number.isSafeInteger(event.generation) && event.generation >= 0)) &&
|
|
108
129
|
optionalBoundedString(event.reason, MAX_MESSAGE_LENGTH) &&
|
|
109
130
|
optionalBoundedString(event.answer, MAX_MESSAGE_LENGTH) &&
|
|
110
131
|
optionalMember(event.scope, SCOPES) &&
|
|
@@ -5,15 +5,27 @@
|
|
|
5
5
|
* are stateful: keep the latest unresolved raw JSON-RPC request here and replay
|
|
6
6
|
* it to a phone when that phone binds to the matching session.
|
|
7
7
|
*/
|
|
8
|
+
export interface PendingMobileApprovalRoute {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
requestId: string;
|
|
11
|
+
connectionId?: string;
|
|
12
|
+
generation?: number;
|
|
13
|
+
expiresAt?: number;
|
|
14
|
+
}
|
|
8
15
|
export declare class PendingMobileApprovals {
|
|
16
|
+
private readonly now;
|
|
9
17
|
private readonly bySession;
|
|
10
18
|
private workerGeneration;
|
|
19
|
+
constructor(now?: () => number);
|
|
11
20
|
/** Requests belong to the worker that can answer them, never its successor. */
|
|
12
21
|
setWorkerState(generation: number, alive: boolean): void;
|
|
13
22
|
observeOutboundLine(line: string): void;
|
|
14
|
-
resolve(requestId: string): void;
|
|
23
|
+
resolve(requestId: string, route?: Partial<PendingMobileApprovalRoute>): void;
|
|
15
24
|
replayLines(sessionId: string): string[];
|
|
25
|
+
/** The stored worker envelope, rather than a phone's mutable current session, owns routing. */
|
|
26
|
+
find(sessionId: string, requestId: string): PendingMobileApprovalRoute | undefined;
|
|
16
27
|
/** The desktop has a queue across sessions; return only its current pending requests. */
|
|
17
28
|
replayAllLines(): string[];
|
|
18
29
|
forgetSession(sessionId: string): void;
|
|
30
|
+
private pruneExpired;
|
|
19
31
|
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tracks currently pending core approval requests for mobile clients.
|
|
3
|
-
*
|
|
4
|
-
* Session snapshots intentionally contain only stream events. Approval requests
|
|
5
|
-
* are stateful: keep the latest unresolved raw JSON-RPC request here and replay
|
|
6
|
-
* it to a phone when that phone binds to the matching session.
|
|
7
|
-
*/
|
|
8
1
|
export class PendingMobileApprovals {
|
|
2
|
+
now;
|
|
9
3
|
bySession = new Map();
|
|
10
4
|
workerGeneration;
|
|
5
|
+
constructor(now = Date.now) {
|
|
6
|
+
this.now = now;
|
|
7
|
+
}
|
|
11
8
|
/** Requests belong to the worker that can answer them, never its successor. */
|
|
12
9
|
setWorkerState(generation, alive) {
|
|
13
10
|
if (!alive || this.workerGeneration !== generation)
|
|
@@ -32,6 +29,8 @@ export class PendingMobileApprovals {
|
|
|
32
29
|
return;
|
|
33
30
|
if (typeof requestId !== "string" || !requestId)
|
|
34
31
|
return;
|
|
32
|
+
if (typeof params?.expiresAt === "number" && params.expiresAt <= this.now())
|
|
33
|
+
return;
|
|
35
34
|
let pending = this.bySession.get(sessionId);
|
|
36
35
|
if (!pending) {
|
|
37
36
|
pending = new Map();
|
|
@@ -44,24 +43,61 @@ export class PendingMobileApprovals {
|
|
|
44
43
|
const params = msg.params;
|
|
45
44
|
const requestId = params?.requestId;
|
|
46
45
|
if (typeof requestId === "string" && requestId)
|
|
47
|
-
this.resolve(requestId);
|
|
46
|
+
this.resolve(requestId, params ?? {});
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
|
-
resolve(requestId) {
|
|
49
|
+
resolve(requestId, route = {}) {
|
|
51
50
|
for (const [sessionId, pending] of this.bySession) {
|
|
51
|
+
if (route.sessionId !== undefined && route.sessionId !== sessionId)
|
|
52
|
+
continue;
|
|
53
|
+
const line = pending.get(requestId);
|
|
54
|
+
if (!line)
|
|
55
|
+
continue;
|
|
56
|
+
const params = JSON.parse(line).params;
|
|
57
|
+
if ((params.connectionId !== undefined || params.generation !== undefined) &&
|
|
58
|
+
(route.sessionId !== sessionId ||
|
|
59
|
+
route.connectionId !== params.connectionId ||
|
|
60
|
+
route.generation !== params.generation))
|
|
61
|
+
continue;
|
|
62
|
+
if ((route.connectionId !== undefined && route.connectionId !== params.connectionId) ||
|
|
63
|
+
(route.generation !== undefined && route.generation !== params.generation))
|
|
64
|
+
continue;
|
|
52
65
|
pending.delete(requestId);
|
|
53
66
|
if (pending.size === 0)
|
|
54
67
|
this.bySession.delete(sessionId);
|
|
55
68
|
}
|
|
56
69
|
}
|
|
57
70
|
replayLines(sessionId) {
|
|
71
|
+
this.pruneExpired();
|
|
58
72
|
return [...(this.bySession.get(sessionId)?.values() ?? [])];
|
|
59
73
|
}
|
|
74
|
+
/** The stored worker envelope, rather than a phone's mutable current session, owns routing. */
|
|
75
|
+
find(sessionId, requestId) {
|
|
76
|
+
this.pruneExpired();
|
|
77
|
+
const line = this.bySession.get(sessionId)?.get(requestId);
|
|
78
|
+
if (!line)
|
|
79
|
+
return undefined;
|
|
80
|
+
const { connectionId, generation, expiresAt } = JSON.parse(line).params;
|
|
81
|
+
return { sessionId, requestId, connectionId, generation, expiresAt };
|
|
82
|
+
}
|
|
60
83
|
/** The desktop has a queue across sessions; return only its current pending requests. */
|
|
61
84
|
replayAllLines() {
|
|
85
|
+
this.pruneExpired();
|
|
62
86
|
return [...this.bySession.values()].flatMap((pending) => [...pending.values()]);
|
|
63
87
|
}
|
|
64
88
|
forgetSession(sessionId) {
|
|
65
89
|
this.bySession.delete(sessionId);
|
|
66
90
|
}
|
|
91
|
+
pruneExpired() {
|
|
92
|
+
const now = this.now();
|
|
93
|
+
for (const [sessionId, pending] of this.bySession) {
|
|
94
|
+
for (const [requestId, line] of pending) {
|
|
95
|
+
const expiresAt = JSON.parse(line).params.expiresAt;
|
|
96
|
+
if (typeof expiresAt === "number" && expiresAt <= now)
|
|
97
|
+
pending.delete(requestId);
|
|
98
|
+
}
|
|
99
|
+
if (!pending.size)
|
|
100
|
+
this.bySession.delete(sessionId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
67
103
|
}
|
|
@@ -40,6 +40,10 @@ export interface RemoteHostManagerOptions {
|
|
|
40
40
|
outputJournal?: boolean;
|
|
41
41
|
/** Enable only when the attached selected-session host implements legacy base pages. */
|
|
42
42
|
legacyBasePages?: boolean;
|
|
43
|
+
/** Enable only when the selected-session reader supports opt-in dense pages. */
|
|
44
|
+
outputJournalPageFrames?: 2048;
|
|
45
|
+
/** Selected-session Host implements verified display views and bounded detail parts. */
|
|
46
|
+
historyView?: boolean;
|
|
43
47
|
onClientEvent: (event: unknown, ws: WebSocket) => void | Promise<void>;
|
|
44
48
|
/**
|
|
45
49
|
* Absolute path to the built mobile app (out/mobile). Defaults to the
|
|
@@ -478,7 +478,11 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
478
478
|
? {
|
|
479
479
|
capabilities: {
|
|
480
480
|
outputJournal: 1,
|
|
481
|
+
...(this.opts.historyView ? { historyView: 1 } : {}),
|
|
481
482
|
...(this.opts.legacyBasePages ? { legacyBasePages: 1 } : {}),
|
|
483
|
+
...(this.opts.outputJournalPageFrames === 2048
|
|
484
|
+
? { outputJournalPageFrames: 2048 }
|
|
485
|
+
: {}),
|
|
482
486
|
},
|
|
483
487
|
}
|
|
484
488
|
: {}),
|
|
@@ -501,7 +505,11 @@ export class RemoteHostManager extends EventEmitter {
|
|
|
501
505
|
? {
|
|
502
506
|
capabilities: {
|
|
503
507
|
outputJournal: 1,
|
|
508
|
+
...(this.opts.historyView ? { historyView: 1 } : {}),
|
|
504
509
|
...(this.opts.legacyBasePages ? { legacyBasePages: 1 } : {}),
|
|
510
|
+
...(this.opts.outputJournalPageFrames === 2048
|
|
511
|
+
? { outputJournalPageFrames: 2048 }
|
|
512
|
+
: {}),
|
|
505
513
|
},
|
|
506
514
|
}
|
|
507
515
|
: {}),
|
package/dist/panels/http.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare function createPanelHttp(options: PanelManagementHttpOptions & {
|
|
|
17
17
|
authorizePanelDirectory?: PanelDirectoryAuthorizer;
|
|
18
18
|
}): {
|
|
19
19
|
service: {
|
|
20
|
-
snapshot: (appId?: string) => Promise<import("./types.js").PanelSnapshot>;
|
|
20
|
+
snapshot: (appId?: string, reads?: import("./management.js").PanelSnapshotReads) => Promise<import("./types.js").PanelSnapshot>;
|
|
21
21
|
packageHistory(context: import("./types.js").PanelOperationContext, id: unknown, expected: unknown): Promise<import("./types.js").PanelPackageHistory>;
|
|
22
22
|
previewRestore(context: import("./types.js").PanelOperationContext, id: unknown, digest: unknown, expected: unknown): Promise<import("./types.js").PanelPackageRestoreReview>;
|
|
23
23
|
cancelRestore(context: import("./types.js").PanelOperationContext, token: unknown): Promise<{
|
|
@@ -8,7 +8,7 @@ export interface PanelManagementHttpOptions extends PanelManagementOptions {
|
|
|
8
8
|
/** Call only after the host's same-origin authentication boundary. */
|
|
9
9
|
export declare function createPanelManagementHttp(options: PanelManagementHttpOptions): {
|
|
10
10
|
service: {
|
|
11
|
-
snapshot: (appId?: string) => Promise<import("./types.js").PanelSnapshot>;
|
|
11
|
+
snapshot: (appId?: string, reads?: import("./management.js").PanelSnapshotReads) => Promise<import("./types.js").PanelSnapshot>;
|
|
12
12
|
packageHistory(context: PanelOperationContext, id: unknown, expected: unknown): Promise<import("./types.js").PanelPackageHistory>;
|
|
13
13
|
previewRestore(context: PanelOperationContext, id: unknown, digest: unknown, expected: unknown): Promise<import("./types.js").PanelPackageRestoreReview>;
|
|
14
14
|
cancelRestore(context: PanelOperationContext, token: unknown): Promise<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PanelExecutionScope } from "./execution-gate.js";
|
|
2
|
-
import { type PanelAppPreview, type PanelAppSourceInput } from "@cjhyy/code-shell-core";
|
|
2
|
+
import { type PanelAppInspectionReads, type PanelAppPreview, type PanelAppSourceInput } from "@cjhyy/code-shell-core";
|
|
3
3
|
import type { PanelCompatibility, PanelDiscovery, PanelOperationContext, PanelReview, PanelProjectReview, PanelSnapshot, PanelPackageHistory, PanelPackageRestoreReview } from "./types.js";
|
|
4
4
|
export declare class PanelManagementError extends Error {
|
|
5
5
|
readonly status: number;
|
|
@@ -28,8 +28,17 @@ export interface PanelManagementOptions {
|
|
|
28
28
|
repo: string;
|
|
29
29
|
}, ref: string) => Promise<string>;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Verified reads shared by the snapshots of one multi-project request, so the
|
|
33
|
+
* same package is not re-verified once per project. Create one per request.
|
|
34
|
+
*/
|
|
35
|
+
export interface PanelSnapshotReads {
|
|
36
|
+
inspection: PanelAppInspectionReads;
|
|
37
|
+
digests: Map<string, Promise<string>>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createPanelSnapshotReads(): PanelSnapshotReads;
|
|
31
40
|
export declare function createPanelManagement(options: PanelManagementOptions): {
|
|
32
|
-
snapshot: (appId?: string) => Promise<PanelSnapshot>;
|
|
41
|
+
snapshot: (appId?: string, reads?: PanelSnapshotReads) => Promise<PanelSnapshot>;
|
|
33
42
|
packageHistory(context: PanelOperationContext, id: unknown, expected: unknown): Promise<PanelPackageHistory>;
|
|
34
43
|
previewRestore(context: PanelOperationContext, id: unknown, digest: unknown, expected: unknown): Promise<PanelPackageRestoreReview>;
|
|
35
44
|
cancelRestore(context: PanelOperationContext, token: unknown): Promise<{
|
|
@@ -3,7 +3,7 @@ import { panelPackageMutationMatches } from "./package-mutation.js";
|
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { lstatSync, mkdirSync, statSync } from "node:fs";
|
|
5
5
|
import { basename, extname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
-
import { SettingsManager, PanelAppAlreadyInstalledError, PanelAppInstallError, PanelAppReviewChangedError, assertSafePanelAppId, discoverGitPanelApps, installReviewedLocalPanelApp, invalidateSkillCache, listInstalledPanelApps, inspectProjectPanelApps, listRetainedPanelAppPackages, resolvePanelAppPackage, parsePanelAppPackagePins, currentPanelAppPackagePins, installedPanelAppUpdatedAt, retainInstalledPanelApp, userHome, panelAppsRoot, previewLocalPanelApp, resolveGitHubPanelAppCommit, resolvePanelAppBindingPolicy, uninstallPanelApp, } from "@cjhyy/code-shell-core";
|
|
6
|
+
import { SettingsManager, PanelAppAlreadyInstalledError, PanelAppInstallError, PanelAppReviewChangedError, assertSafePanelAppId, discoverGitPanelApps, installReviewedLocalPanelApp, invalidateSkillCache, listInstalledPanelApps, inspectProjectPanelApps, listRetainedPanelAppPackages, resolvePanelAppPackage, parsePanelAppPackagePins, currentPanelAppPackagePins, installedPanelAppUpdatedAt, retainInstalledPanelApp, userHome, panelAppsRoot, previewLocalPanelApp, inspectInstalledPanelAppDigest, resolveGitHubPanelAppCommit, resolvePanelAppBindingPolicy, uninstallPanelApp, } from "@cjhyy/code-shell-core";
|
|
7
7
|
import { mutateJsonFile } from "@cjhyy/code-shell-core/internal";
|
|
8
8
|
import { parseGithubUrl } from "@cjhyy/code-shell-core/internal/skills";
|
|
9
9
|
const REVIEW_TTL = 8 * 60_000;
|
|
@@ -151,6 +151,9 @@ function assertRevision(revision) {
|
|
|
151
151
|
if (typeof revision !== "string" || !/^[a-f0-9]{64}$/.test(revision))
|
|
152
152
|
invalid();
|
|
153
153
|
}
|
|
154
|
+
export function createPanelSnapshotReads() {
|
|
155
|
+
return { inspection: { packages: new Map() }, digests: new Map() };
|
|
156
|
+
}
|
|
154
157
|
export function createPanelManagement(options) {
|
|
155
158
|
const now = options.now ?? Date.now;
|
|
156
159
|
const workspace = resolve(options.bindingCwd ?? options.cwd);
|
|
@@ -206,12 +209,17 @@ export function createPanelManagement(options) {
|
|
|
206
209
|
const listApps = () => options.projectPackages
|
|
207
210
|
? inspectProjectPanelApps(workspace).then((result) => result.apps)
|
|
208
211
|
: listInstalledPanelApps();
|
|
209
|
-
async function inspectApp(app) {
|
|
212
|
+
async function inspectApp(app, reads) {
|
|
210
213
|
const info = lstatSync(app.installPath);
|
|
211
214
|
if (info.isSymbolicLink() || !info.isDirectory())
|
|
212
215
|
throw new PanelManagementError(403, "unsafe_path", "面板安装目录不支持链接。");
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
// Same digest a dir preview reports, without re-hashing unchanged installed bytes.
|
|
217
|
+
let digest = reads?.digests.get(app.installPath);
|
|
218
|
+
if (!digest) {
|
|
219
|
+
digest = inspectInstalledPanelAppDigest(app.installPath);
|
|
220
|
+
reads?.digests.set(app.installPath, digest);
|
|
221
|
+
}
|
|
222
|
+
return { app, digest: await digest };
|
|
215
223
|
}
|
|
216
224
|
async function selectedApp(id) {
|
|
217
225
|
const app = (await listApps()).find((item) => item.id === id);
|
|
@@ -240,12 +248,12 @@ export function createPanelManagement(options) {
|
|
|
240
248
|
commit: SHA.test(parsed.source.ref ?? "") ? parsed.source.ref : "",
|
|
241
249
|
};
|
|
242
250
|
}
|
|
243
|
-
async function snapshot(appId) {
|
|
251
|
+
async function snapshot(appId, reads) {
|
|
244
252
|
if (appId !== undefined)
|
|
245
253
|
assertSafePanelAppId(appId);
|
|
246
254
|
options.assertBinding?.();
|
|
247
255
|
const inspected = options.projectPackages
|
|
248
|
-
? await inspectProjectPanelApps(workspace, appId)
|
|
256
|
+
? await inspectProjectPanelApps(workspace, appId, reads?.inspection)
|
|
249
257
|
: undefined;
|
|
250
258
|
const apps = inspected?.apps ?? (await listInstalledPanelApps(appId));
|
|
251
259
|
options.assertBinding?.();
|
|
@@ -268,7 +276,7 @@ export function createPanelManagement(options) {
|
|
|
268
276
|
.digest("hex"),
|
|
269
277
|
}));
|
|
270
278
|
for (const listed of apps) {
|
|
271
|
-
const { app, digest } = await inspectApp(listed);
|
|
279
|
+
const { app, digest } = await inspectApp(listed, reads);
|
|
272
280
|
const source = savedSource(app, saved);
|
|
273
281
|
const { installPath: _path, source: _source, installedAt: _installedAt, lastUpdated: _lastUpdated, ...publicApp } = app;
|
|
274
282
|
const support = compatibility(app);
|
|
@@ -125,8 +125,15 @@ export class ResourceLibrary {
|
|
|
125
125
|
async importFile(scope, sourcePath, options = {}) {
|
|
126
126
|
const verify = async () => {
|
|
127
127
|
options.signal?.throwIfAborted();
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
// Both checks must complete before any source read or publication. They
|
|
129
|
+
// are independent; a captured directory may itself recheck the same scope.
|
|
130
|
+
const checks = await Promise.allSettled([
|
|
131
|
+
this.authorize(scope),
|
|
132
|
+
Promise.resolve().then(() => options.assertAuthorized?.()),
|
|
133
|
+
]);
|
|
134
|
+
for (const check of checks)
|
|
135
|
+
if (check.status === "rejected")
|
|
136
|
+
throw check.reason;
|
|
130
137
|
options.signal?.throwIfAborted();
|
|
131
138
|
};
|
|
132
139
|
await verify();
|
|
@@ -400,7 +407,14 @@ export class ResourceLibrary {
|
|
|
400
407
|
if (!info.isFile() || info.size !== asset.bytes)
|
|
401
408
|
throw new Error("Stored media content changed");
|
|
402
409
|
await this.authorize(scope);
|
|
403
|
-
return {
|
|
410
|
+
return {
|
|
411
|
+
status,
|
|
412
|
+
headers,
|
|
413
|
+
// Bulk readers authorize every delivered block. Bound read-ahead to
|
|
414
|
+
// 1 MiB so large downloads and integrity rechecks do not repeat a full
|
|
415
|
+
// project/package inspection for each default 64 KiB filesystem read.
|
|
416
|
+
body: handle.createReadStream({ ...bounds, autoClose: true, highWaterMark: 1024 * 1024 }),
|
|
417
|
+
};
|
|
404
418
|
}
|
|
405
419
|
catch (error) {
|
|
406
420
|
await handle.close();
|
package/dist/panels/runtime.js
CHANGED
|
@@ -1606,7 +1606,7 @@ export function createPanelRuntime(options) {
|
|
|
1606
1606
|
error(400, "不支持这个服务端目录。");
|
|
1607
1607
|
if (method === "filesystem.pickDirectory" &&
|
|
1608
1608
|
!(await confirm(grant, "使用服务端下载目录?", directory)))
|
|
1609
|
-
return
|
|
1609
|
+
return { cancelled: true };
|
|
1610
1610
|
await options.authorizePanelDirectory?.(grant.app, options.bindingCwd ?? options.cwd, options.cwd);
|
|
1611
1611
|
if (name !== "project")
|
|
1612
1612
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjhyy/code-shell-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.51",
|
|
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",
|
|
@@ -62,14 +62,14 @@
|
|
|
62
62
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@cjhyy/code-shell-capability-coding": "0.9.
|
|
66
|
-
"@cjhyy/code-shell-core": "0.9.
|
|
67
|
-
"@cjhyy/code-shell-web": "0.9.
|
|
65
|
+
"@cjhyy/code-shell-capability-coding": "0.9.51",
|
|
66
|
+
"@cjhyy/code-shell-core": "0.9.51",
|
|
67
|
+
"@cjhyy/code-shell-web": "0.9.51",
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
69
69
|
"@types/ws": "^8.18.1",
|
|
70
70
|
"ws": "^8.21.0",
|
|
71
|
-
"@cjhyy/code-shell-link": "0.9.
|
|
72
|
-
"@cjhyy/code-shell-host": "0.9.
|
|
71
|
+
"@cjhyy/code-shell-link": "0.9.51",
|
|
72
|
+
"@cjhyy/code-shell-host": "0.9.51"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=20.10"
|