@cjhyy/code-shell-server 0.9.21 → 0.9.23
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 +45 -1
- package/dist/bin/code-shell-settings-recovery.d.ts +2 -0
- package/dist/bin/code-shell-settings-recovery.js +4 -0
- package/dist/desktop-web/http-api.js +14 -0
- package/dist/environment-identity.d.ts +4 -0
- package/dist/environment-identity.js +67 -0
- package/dist/hub/auth-store.d.ts +2 -0
- package/dist/hub/auth-store.js +7 -0
- package/dist/index.auth.d.ts +3 -0
- package/dist/index.auth.js +2 -0
- package/dist/index.links.d.ts +1 -0
- package/dist/index.links.js +1 -0
- package/dist/index.panels.d.ts +7 -0
- package/dist/index.panels.js +7 -0
- package/dist/index.remote-relay.d.ts +5 -0
- package/dist/index.remote-relay.js +6 -0
- package/dist/index.serve.d.ts +2 -0
- package/dist/index.serve.js +2 -0
- package/dist/links/http.d.ts +3 -0
- package/dist/links/http.js +11 -2
- package/dist/links/remote-configuration.d.ts +4 -0
- package/dist/links/remote-configuration.js +32 -0
- package/dist/links/service.d.ts +6 -1
- package/dist/links/service.js +241 -19
- package/dist/mobile-remote/mobile-upload-service.d.ts +4 -0
- package/dist/mobile-remote/mobile-upload-service.js +69 -11
- package/dist/mobile-remote/remote-host-manager.d.ts +10 -5
- package/dist/mobile-remote/remote-host-manager.js +53 -5
- package/dist/panels/agent-task-service.js +9 -2
- package/dist/panels/automations.d.ts +49 -0
- package/dist/panels/automations.js +134 -0
- package/dist/panels/bridge-contract.d.ts +5 -3
- package/dist/panels/bridge-contract.js +11 -1
- package/dist/panels/directory-bookmarks.d.ts +9 -1
- package/dist/panels/directory-bookmarks.js +64 -5
- package/dist/panels/execution-gate.d.ts +26 -0
- package/dist/panels/execution-gate.js +70 -0
- package/dist/panels/http.d.ts +26 -2
- package/dist/panels/http.js +7 -1
- package/dist/panels/hub-automations.d.ts +31 -0
- package/dist/panels/hub-automations.js +352 -0
- package/dist/panels/management-http.d.ts +20 -2
- package/dist/panels/management-http.js +15 -2
- package/dist/panels/management.d.ts +26 -4
- package/dist/panels/management.js +382 -39
- package/dist/panels/package-mutation.d.ts +8 -0
- package/dist/panels/package-mutation.js +23 -0
- package/dist/panels/process-service.d.ts +3 -0
- package/dist/panels/process-service.js +77 -4
- package/dist/panels/resources/http.d.ts +11 -0
- package/dist/panels/resources/http.js +74 -0
- package/dist/panels/resources/references.d.ts +5 -0
- package/dist/panels/resources/references.js +26 -0
- package/dist/panels/runtime-services.js +16 -3
- package/dist/panels/runtime.d.ts +16 -4
- package/dist/panels/runtime.js +522 -64
- package/dist/panels/shared-tool-jobs.d.ts +40 -0
- package/dist/panels/shared-tool-jobs.js +37 -0
- package/dist/panels/storage-store.d.ts +21 -0
- package/dist/panels/storage-store.js +43 -0
- package/dist/panels/task-cookie-host.d.ts +26 -0
- package/dist/panels/task-cookie-host.js +145 -0
- package/dist/panels/task-cookies.d.ts +51 -0
- package/dist/panels/task-cookies.js +183 -0
- package/dist/panels/tool-executor.d.ts +21 -5
- package/dist/panels/tool-executor.js +171 -30
- package/dist/panels/tool-jobs-storage.d.ts +2 -0
- package/dist/panels/tool-jobs-storage.js +34 -0
- package/dist/panels/tool-jobs.d.ts +49 -3
- package/dist/panels/tool-jobs.js +270 -15
- package/dist/panels/types.d.ts +43 -0
- package/dist/project-runtime/backup.d.ts +26 -0
- package/dist/project-runtime/backup.js +500 -0
- package/dist/project-runtime/control-server.d.ts +4 -0
- package/dist/project-runtime/control-server.js +19 -1
- package/dist/project-runtime/docker-provider.d.ts +4 -0
- package/dist/project-runtime/docker-provider.js +16 -1
- package/dist/project-runtime/managed-entry.js +4 -0
- package/dist/project-runtime/registry.d.ts +2 -0
- package/dist/project-runtime/registry.js +15 -0
- package/dist/project-runtime/seccomp.d.ts +8 -0
- package/dist/project-runtime/seccomp.js +72 -0
- package/dist/project-runtime/types.d.ts +2 -0
- package/dist/remote-relay/byte-stream.d.ts +9 -0
- package/dist/remote-relay/byte-stream.js +71 -0
- package/dist/remote-relay/connector.d.ts +27 -0
- package/dist/remote-relay/connector.js +197 -0
- package/dist/remote-relay/local-stream.d.ts +10 -0
- package/dist/remote-relay/local-stream.js +120 -0
- package/dist/remote-relay/protocol.d.ts +39 -0
- package/dist/remote-relay/protocol.js +64 -0
- package/dist/serve/automation-worker.d.ts +16 -0
- package/dist/serve/automation-worker.js +126 -0
- package/dist/serve/cli.d.ts +3 -0
- package/dist/serve/cli.js +17 -0
- package/dist/serve/headless-server.d.ts +3 -0
- package/dist/serve/headless-server.js +140 -10
- package/dist/serve/settings-recovery-cli.d.ts +29 -0
- package/dist/serve/settings-recovery-cli.js +128 -0
- package/dist/session-http-responses.d.ts +7 -0
- package/dist/session-http-responses.js +40 -0
- package/dist/worker-bridge-core.d.ts +9 -1
- package/dist/worker-bridge-core.js +45 -0
- package/package.json +15 -6
|
@@ -271,6 +271,7 @@ export class PanelAppProcessService {
|
|
|
271
271
|
await this.authorize(owner, epoch);
|
|
272
272
|
const info = await stat(resolved);
|
|
273
273
|
await this.authorize(owner, epoch);
|
|
274
|
+
await input.validate?.();
|
|
274
275
|
this.assertLive(owner, epoch);
|
|
275
276
|
if (!info.isFile())
|
|
276
277
|
throw new Error("sealed process input is not a file");
|
|
@@ -283,6 +284,7 @@ export class PanelAppProcessService {
|
|
|
283
284
|
argumentName: input.argumentName,
|
|
284
285
|
path: resolved,
|
|
285
286
|
cleanup: input.cleanup ?? (() => undefined),
|
|
287
|
+
validate: input.validate,
|
|
286
288
|
});
|
|
287
289
|
return { handle };
|
|
288
290
|
}
|
|
@@ -391,6 +393,31 @@ export class PanelAppProcessService {
|
|
|
391
393
|
}
|
|
392
394
|
const rawArgs = validateArguments(input.args);
|
|
393
395
|
const fileArgumentHandles = validateFileArgumentHandles(input.fileArgumentHandles);
|
|
396
|
+
const validateInputs = async () => {
|
|
397
|
+
for (const handle of fileArgumentHandles) {
|
|
398
|
+
const grant = this.fileArguments.get(handle);
|
|
399
|
+
if (!grant || !sameProcessOwner(grant, owner))
|
|
400
|
+
throw new Error("sealed process input was revoked");
|
|
401
|
+
try {
|
|
402
|
+
await grant.validate?.();
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
if (this.fileArguments.get(handle) === grant) {
|
|
406
|
+
this.fileArguments.delete(handle);
|
|
407
|
+
try {
|
|
408
|
+
grant.cleanup();
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
/* Revocation still takes effect. */
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
throw new Error("sealed process input authorization changed; authorize it again");
|
|
415
|
+
}
|
|
416
|
+
this.assertLive(owner, epoch);
|
|
417
|
+
if (this.fileArguments.get(handle) !== grant)
|
|
418
|
+
throw new Error("sealed process input was revoked");
|
|
419
|
+
}
|
|
420
|
+
};
|
|
394
421
|
const sealedArgs = [];
|
|
395
422
|
for (const handle of fileArgumentHandles) {
|
|
396
423
|
const grant = this.fileArguments.get(handle);
|
|
@@ -411,6 +438,8 @@ export class PanelAppProcessService {
|
|
|
411
438
|
throw new Error(`Panel App may run at most ${MAX_CONCURRENT_PROCESSES} processes at once`);
|
|
412
439
|
}
|
|
413
440
|
// Admission is reserved before the first await, including a user's approval dialog.
|
|
441
|
+
const releaseExecution = this.options.acquireExecution?.(owner);
|
|
442
|
+
let processOwnsExecution = false;
|
|
414
443
|
this.reservations.set(owner.guestId, reserved + 1);
|
|
415
444
|
try {
|
|
416
445
|
await this.authorize(owner, epoch);
|
|
@@ -463,6 +492,8 @@ export class PanelAppProcessService {
|
|
|
463
492
|
if ((await verifyProcessEntry(entry.path, entry.sha256)) !== entry.identity)
|
|
464
493
|
throw new Error("reviewed package entry was replaced; resolve it again");
|
|
465
494
|
}
|
|
495
|
+
await validateInputs();
|
|
496
|
+
await this.authorize(owner, epoch);
|
|
466
497
|
const env = safeProcessEnv(this.options.env ?? process.env, this.options.extraPathDirectories?.() ?? []);
|
|
467
498
|
this.assertLive(owner, epoch);
|
|
468
499
|
if (this.executables.get(input.executableHandle) !== executable ||
|
|
@@ -570,29 +601,71 @@ export class PanelAppProcessService {
|
|
|
570
601
|
}),
|
|
571
602
|
};
|
|
572
603
|
this.processes.set(processId, running);
|
|
604
|
+
let checkingInputs = false;
|
|
605
|
+
const inputChecks = fileArgumentHandles.some((handle) => this.fileArguments.get(handle)?.validate)
|
|
606
|
+
? setInterval(() => {
|
|
607
|
+
if (checkingInputs || this.processes.get(processId) !== running || running.termination)
|
|
608
|
+
return;
|
|
609
|
+
checkingInputs = true;
|
|
610
|
+
void validateInputs()
|
|
611
|
+
.catch(() => {
|
|
612
|
+
if (this.processes.get(processId) !== running || running.termination)
|
|
613
|
+
return;
|
|
614
|
+
emit("process.output", {
|
|
615
|
+
processId,
|
|
616
|
+
stream: "stderr",
|
|
617
|
+
text: "Process input authorization changed; CodeShell stopped it.\n",
|
|
618
|
+
});
|
|
619
|
+
this.terminate(running);
|
|
620
|
+
})
|
|
621
|
+
.finally(() => {
|
|
622
|
+
checkingInputs = false;
|
|
623
|
+
});
|
|
624
|
+
}, 1000)
|
|
625
|
+
: undefined;
|
|
626
|
+
inputChecks?.unref();
|
|
573
627
|
child.once("close", (code, signal) => {
|
|
574
628
|
void (async () => {
|
|
575
629
|
clearTimeout(lifetime);
|
|
630
|
+
if (inputChecks)
|
|
631
|
+
clearInterval(inputChecks);
|
|
576
632
|
// Cancellation also waits for the process group, not just its leader.
|
|
577
633
|
await running.termination;
|
|
578
|
-
|
|
634
|
+
let finalCode = code;
|
|
635
|
+
if (inputChecks && this.processes.get(processId) === running) {
|
|
636
|
+
try {
|
|
637
|
+
await validateInputs();
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
finalCode = code === 0 ? 1 : code;
|
|
641
|
+
emit("process.output", {
|
|
642
|
+
processId,
|
|
643
|
+
stream: "stderr",
|
|
644
|
+
text: "Process input authorization changed; results were rejected.\n",
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
finishExit({ code: finalCode, signal });
|
|
579
649
|
this.retiring.delete(processId);
|
|
580
650
|
if (this.processes.get(processId)?.child !== child)
|
|
581
651
|
return;
|
|
582
652
|
this.processes.delete(processId);
|
|
583
653
|
record.status = "exited";
|
|
584
654
|
record.exitedAt = this.now();
|
|
585
|
-
record.code =
|
|
655
|
+
record.code = finalCode;
|
|
586
656
|
record.signal = signal;
|
|
587
|
-
emit("process.exit", { processId, code, signal });
|
|
657
|
+
emit("process.exit", { processId, code: finalCode, signal });
|
|
588
658
|
// Keep the receipt only after the operating system reports the real close.
|
|
589
659
|
if (!this.closed && (this.guestEpochs.get(owner.guestId) ?? 0) === epoch)
|
|
590
660
|
this.receipts.add(record);
|
|
591
|
-
})();
|
|
661
|
+
})().finally(() => releaseExecution?.());
|
|
592
662
|
});
|
|
663
|
+
processOwnsExecution = true;
|
|
593
664
|
return { processId, executable: executable.name };
|
|
594
665
|
}
|
|
595
666
|
finally {
|
|
667
|
+
if (!processOwnsExecution)
|
|
668
|
+
releaseExecution?.();
|
|
596
669
|
const remaining = (this.reservations.get(owner.guestId) ?? 1) - 1;
|
|
597
670
|
if (remaining)
|
|
598
671
|
this.reservations.set(owner.guestId, remaining);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { PanelResourceService } from "./service.js";
|
|
3
|
+
import type { ResourceScope } from "./types.js";
|
|
4
|
+
/** Browser byte transport; scope and reader are resolved by the authenticated Host. */
|
|
5
|
+
export declare function servePanelResource(request: IncomingMessage, response: ServerResponse, options: {
|
|
6
|
+
service: PanelResourceService;
|
|
7
|
+
scope: ResourceScope;
|
|
8
|
+
id: string;
|
|
9
|
+
download: boolean;
|
|
10
|
+
isAuthorized(): Promise<boolean>;
|
|
11
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { pipeline } from "node:stream/promises";
|
|
3
|
+
/** Browser byte transport; scope and reader are resolved by the authenticated Host. */
|
|
4
|
+
export async function servePanelResource(request, response, options) {
|
|
5
|
+
let source = null;
|
|
6
|
+
const authorize = async () => {
|
|
7
|
+
if (request.aborted || response.destroyed || !(await options.isAuthorized()))
|
|
8
|
+
throw new Error("Resource authorization expired");
|
|
9
|
+
};
|
|
10
|
+
try {
|
|
11
|
+
await authorize();
|
|
12
|
+
if (!["GET", "HEAD"].includes(request.method ?? "")) {
|
|
13
|
+
response.writeHead(405, { Allow: "GET, HEAD" }).end();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const asset = await options.service.get(options.scope, options.id);
|
|
17
|
+
const result = await options.service.openRead(options.scope, options.id, {
|
|
18
|
+
range: request.headers.range,
|
|
19
|
+
method: asset.bytes === 0 ? "HEAD" : request.method,
|
|
20
|
+
});
|
|
21
|
+
source = result.body;
|
|
22
|
+
await authorize();
|
|
23
|
+
const headers = { ...result.headers, "Referrer-Policy": "no-referrer" };
|
|
24
|
+
if (options.download)
|
|
25
|
+
headers["Content-Disposition"] =
|
|
26
|
+
`attachment; filename*=UTF-8''${encodeURIComponent(asset.name).replace(/['()*!]/g, (value) => `%${value.charCodeAt(0).toString(16).toUpperCase()}`)}`;
|
|
27
|
+
response.writeHead(result.status, headers);
|
|
28
|
+
if (!source) {
|
|
29
|
+
response.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let checking = false;
|
|
33
|
+
const timer = setInterval(() => {
|
|
34
|
+
if (checking)
|
|
35
|
+
return;
|
|
36
|
+
checking = true;
|
|
37
|
+
void authorize()
|
|
38
|
+
.catch(() => {
|
|
39
|
+
source?.destroy();
|
|
40
|
+
response.destroy();
|
|
41
|
+
})
|
|
42
|
+
.finally(() => {
|
|
43
|
+
checking = false;
|
|
44
|
+
});
|
|
45
|
+
}, 250);
|
|
46
|
+
timer.unref();
|
|
47
|
+
try {
|
|
48
|
+
await pipeline(Readable.from((async function* () {
|
|
49
|
+
for await (const chunk of source) {
|
|
50
|
+
await authorize();
|
|
51
|
+
yield chunk;
|
|
52
|
+
}
|
|
53
|
+
})(), { objectMode: false }), response);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearInterval(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
if (response.headersSent || response.destroyed)
|
|
61
|
+
response.destroy();
|
|
62
|
+
else
|
|
63
|
+
response
|
|
64
|
+
.writeHead(404, {
|
|
65
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
66
|
+
"Cache-Control": "no-store",
|
|
67
|
+
"X-Content-Type-Options": "nosniff",
|
|
68
|
+
})
|
|
69
|
+
.end(request.method === "HEAD" ? undefined : "文件不可用或访问授权已失效,请重新打开面板。");
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
source?.destroy();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -30,6 +30,11 @@ export declare class ExternalResourceReferences {
|
|
|
30
30
|
createFromDirectory(scope: ResourceScope, root: string, path: string, options?: ExternalReferenceOptions): Promise<ExternalResourceReference>;
|
|
31
31
|
createFromSelectedPath(scope: ResourceScope, path: string, options?: ExternalReferenceOptions): Promise<ExternalResourceReference>;
|
|
32
32
|
get(scope: ResourceScope, id: string): Promise<ExternalResourceReference>;
|
|
33
|
+
/**
|
|
34
|
+
* Host-only: the verified absolute location of a reference, for a read-only
|
|
35
|
+
* handoff to a reviewed tool. Never return it to a Guest.
|
|
36
|
+
*/
|
|
37
|
+
location(scope: ResourceScope, id: string, options?: ExternalReferenceOptions): Promise<string>;
|
|
33
38
|
relinkFromDirectory(scope: ResourceScope, id: string, root: string, path: string, options?: ExternalReferenceOptions): Promise<ExternalResourceReference>;
|
|
34
39
|
relinkFromSelectedPath(scope: ResourceScope, id: string, path: string, options?: ExternalReferenceOptions): Promise<ExternalResourceReference>;
|
|
35
40
|
forget(scope: ResourceScope, id: string): Promise<{
|
|
@@ -270,6 +270,32 @@ export class ExternalResourceReferences {
|
|
|
270
270
|
}
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Host-only: the verified absolute location of a reference, for a read-only
|
|
275
|
+
* handoff to a reviewed tool. Never return it to a Guest.
|
|
276
|
+
*/
|
|
277
|
+
location(scope, id, options = {}) {
|
|
278
|
+
return this.locked(scope, async () => {
|
|
279
|
+
const record = await this.record(scope, id);
|
|
280
|
+
const source = await this.checked(scope, record, options);
|
|
281
|
+
try {
|
|
282
|
+
await source.verify();
|
|
283
|
+
return join(record.root, record.path);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
// A drive may disappear after checked() returns. Preserve revocation
|
|
287
|
+
// and abort semantics before hiding native errors containing its path.
|
|
288
|
+
await this.authorize(scope, options);
|
|
289
|
+
if (error instanceof ReferenceUnavailable)
|
|
290
|
+
throw error;
|
|
291
|
+
const code = error.code;
|
|
292
|
+
throw new ReferenceUnavailable(["ENOENT", "ENOTDIR", "EACCES", "EPERM"].includes(code ?? "") ? "missing" : "changed");
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
await source.close();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
273
299
|
relinkFromDirectory(scope, id, root, path, options = {}) {
|
|
274
300
|
return this.locked(scope, async () => {
|
|
275
301
|
const previous = await this.record(scope, id);
|
|
@@ -3,7 +3,7 @@ import { constants } from "node:fs";
|
|
|
3
3
|
import { chmod, link, lstat, mkdir, open, opendir, realpath, rename, rm, writeFile, } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
import { acquireLockOnPath } from "@cjhyy/code-shell-core/internal";
|
|
6
|
-
import { DEFAULT_PANEL_APP_STORAGE_QUOTA_BYTES, panelAppStorageKey, panelAppStoragePath, preparePanelAppStorage, readPanelAppStorage, writePanelAppStorage, } from "./storage-store.js";
|
|
6
|
+
import { DEFAULT_PANEL_APP_STORAGE_QUOTA_BYTES, panelAppStorageSnapshot, panelAppStorageChange, applyPanelAppStorageChange, panelAppStorageKey, panelAppStoragePath, preparePanelAppStorage, readPanelAppStorage, writePanelAppStorage, } from "./storage-store.js";
|
|
7
7
|
const MAX_READ_BYTES = 480 * 1024;
|
|
8
8
|
const MAX_WRITE_BYTES = 384 * 1024;
|
|
9
9
|
const MAX_LIST_ENTRIES = 200;
|
|
@@ -21,6 +21,8 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
21
21
|
]);
|
|
22
22
|
const METHOD_PERMISSIONS = {
|
|
23
23
|
"storage.get": "storage",
|
|
24
|
+
"storage.getSnapshot": "storage",
|
|
25
|
+
"storage.compareAndSet": "storage",
|
|
24
26
|
"storage.set": "storage",
|
|
25
27
|
"storage.delete": "storage",
|
|
26
28
|
"workspace.info": "workspace.info",
|
|
@@ -134,10 +136,15 @@ export class PanelRuntimeServices {
|
|
|
134
136
|
const key = panelAppStorageKey(params);
|
|
135
137
|
const file = panelAppStoragePath(this.dataDir, scope.appId, scope.projectPath);
|
|
136
138
|
const quota = DEFAULT_PANEL_APP_STORAGE_QUOTA_BYTES;
|
|
137
|
-
if (method === "storage.get") {
|
|
139
|
+
if (method === "storage.get" || method === "storage.getSnapshot") {
|
|
138
140
|
const state = await readPanelAppStorage(file, quota);
|
|
139
|
-
return
|
|
141
|
+
return method === "storage.getSnapshot"
|
|
142
|
+
? panelAppStorageSnapshot(state, key)
|
|
143
|
+
: Object.hasOwn(state, key)
|
|
144
|
+
? state[key]
|
|
145
|
+
: null;
|
|
140
146
|
}
|
|
147
|
+
const change = method === "storage.compareAndSet" ? panelAppStorageChange(params) : null;
|
|
141
148
|
const encoded = method === "storage.set" ? JSON.stringify(params?.value) : null;
|
|
142
149
|
if (method === "storage.set" && encoded === undefined)
|
|
143
150
|
throw new Error("Panel App storage only accepts JSON values");
|
|
@@ -147,6 +154,12 @@ export class PanelRuntimeServices {
|
|
|
147
154
|
const release = await storageLock(file);
|
|
148
155
|
try {
|
|
149
156
|
const state = await readPanelAppStorage(file, quota);
|
|
157
|
+
if (change) {
|
|
158
|
+
const result = applyPanelAppStorageChange(state, change);
|
|
159
|
+
if (result.updated)
|
|
160
|
+
await writePanelAppStorage(file, state, quota, () => authorized(scope));
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
150
163
|
const existed = Object.hasOwn(state, key);
|
|
151
164
|
if (method === "storage.set")
|
|
152
165
|
state[key] = JSON.parse(encoded);
|
package/dist/panels/runtime.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { type InstalledPanelApp } from "@cjhyy/code-shell-core";
|
|
3
|
-
import {
|
|
3
|
+
import type { SharedPanelToolHost } from "./shared-tool-jobs.js";
|
|
4
|
+
import { type PanelDirectoryAuthorizer } from "./directory-bookmarks.js";
|
|
4
5
|
import type { PanelSnapshot } from "./types.js";
|
|
5
|
-
|
|
6
|
+
import { type PanelAutomationHost } from "./automations.js";
|
|
7
|
+
export declare const panelWebCompatibility: (app: Pick<InstalledPanelApp, "permissions">, options?: {
|
|
8
|
+
automations?: boolean;
|
|
9
|
+
}) => {
|
|
10
|
+
supported: boolean;
|
|
11
|
+
reasons: string[];
|
|
12
|
+
};
|
|
6
13
|
export interface PanelTaskScope {
|
|
7
14
|
instanceId: string;
|
|
8
15
|
ownerId: string;
|
|
@@ -24,16 +31,21 @@ export interface PanelTaskHost {
|
|
|
24
31
|
export interface PanelRuntimeOptions {
|
|
25
32
|
cwd: string;
|
|
26
33
|
bindingCwd?: string;
|
|
34
|
+
projectPackages?: boolean;
|
|
27
35
|
dataDir: string;
|
|
28
36
|
host: "hub" | "desktop";
|
|
29
37
|
publicPathPrefix?: string;
|
|
30
|
-
snapshot: () => Promise<PanelSnapshot>;
|
|
38
|
+
snapshot: (appId?: string) => Promise<PanelSnapshot>;
|
|
31
39
|
ownerId: (request: IncomingMessage) => Promise<string | undefined>;
|
|
32
40
|
isAuthorized: (request: IncomingMessage) => Promise<boolean>;
|
|
33
41
|
/** Host/test seam. HTTP callers never provide install paths or this catalog. */
|
|
34
|
-
listInstalled?: () => Promise<InstalledPanelApp[]>;
|
|
42
|
+
listInstalled?: (appId?: string) => Promise<InstalledPanelApp[]>;
|
|
35
43
|
now?: () => number;
|
|
36
44
|
agentTasks?: PanelTaskHost;
|
|
45
|
+
automations?: PanelAutomationHost;
|
|
46
|
+
/** Reuse the Desktop coordinator; this transport never owns its lifetime. */
|
|
47
|
+
sharedToolJobs?: SharedPanelToolHost;
|
|
48
|
+
authorizePanelDirectory?: PanelDirectoryAuthorizer;
|
|
37
49
|
createAgentTasks?: (hooks: {
|
|
38
50
|
onPanelAction(scope: PanelTaskScope, input: Record<string, unknown>): Promise<unknown>;
|
|
39
51
|
}) => PanelTaskHost;
|