@cjhyy/code-shell-server 0.9.10 → 0.9.12

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 (33) hide show
  1. package/dist/index.panels.d.ts +8 -0
  2. package/dist/index.panels.js +8 -0
  3. package/dist/panels/bridge-contract.d.ts +72 -0
  4. package/dist/panels/bridge-contract.js +92 -0
  5. package/dist/panels/connections.d.ts +31 -0
  6. package/dist/panels/connections.js +118 -0
  7. package/dist/panels/process-entry.d.ts +2 -0
  8. package/dist/panels/process-entry.js +52 -0
  9. package/dist/panels/process-service.d.ts +61 -0
  10. package/dist/panels/process-service.js +367 -33
  11. package/dist/panels/process-state.d.ts +58 -0
  12. package/dist/panels/process-state.js +87 -0
  13. package/dist/panels/resources/directories.d.ts +12 -0
  14. package/dist/panels/resources/directories.js +89 -0
  15. package/dist/panels/resources/library.d.ts +63 -0
  16. package/dist/panels/resources/library.js +423 -0
  17. package/dist/panels/resources/service.d.ts +45 -0
  18. package/dist/panels/resources/service.js +317 -0
  19. package/dist/panels/resources/storage.d.ts +10 -0
  20. package/dist/panels/resources/storage.js +102 -0
  21. package/dist/panels/resources/types.d.ts +16 -0
  22. package/dist/panels/resources/types.js +1 -0
  23. package/dist/panels/resources/uploads.d.ts +64 -0
  24. package/dist/panels/resources/uploads.js +527 -0
  25. package/dist/panels/runtime.d.ts +1 -1
  26. package/dist/panels/runtime.js +137 -15
  27. package/dist/panels/tool-executor.d.ts +58 -0
  28. package/dist/panels/tool-executor.js +421 -0
  29. package/dist/panels/tool-jobs-storage.d.ts +16 -0
  30. package/dist/panels/tool-jobs-storage.js +169 -0
  31. package/dist/panels/tool-jobs.d.ts +103 -0
  32. package/dist/panels/tool-jobs.js +530 -0
  33. package/package.json +5 -5
@@ -7,3 +7,11 @@ export * from "./panels/agent-task-service.js";
7
7
  export * from "./panels/agent-task-models.js";
8
8
  export * from "./panels/agent-task-host.js";
9
9
  export type * from "./panels/types.js";
10
+ export * from "./panels/resources/types.js";
11
+ export * from "./panels/resources/library.js";
12
+ export * from "./panels/resources/service.js";
13
+ export { MAX_MEDIA_JSON_BYTES, normalizeMediaScope, mediaScopeKey, prepareMediaRoot, mediaDirectory, cloneMediaJson, readMediaJson, writeMediaJson, } from "./panels/resources/storage.js";
14
+ export * from "./panels/bridge-contract.js";
15
+ export * from "./panels/connections.js";
16
+ export * from "./panels/tool-jobs.js";
17
+ export * from "./panels/tool-executor.js";
@@ -6,3 +6,11 @@ export * from "./panels/process-service.js";
6
6
  export * from "./panels/agent-task-service.js";
7
7
  export * from "./panels/agent-task-models.js";
8
8
  export * from "./panels/agent-task-host.js";
9
+ export * from "./panels/resources/types.js";
10
+ export * from "./panels/resources/library.js";
11
+ export * from "./panels/resources/service.js";
12
+ export { MAX_MEDIA_JSON_BYTES, normalizeMediaScope, mediaScopeKey, prepareMediaRoot, mediaDirectory, cloneMediaJson, readMediaJson, writeMediaJson, } from "./panels/resources/storage.js";
13
+ export * from "./panels/bridge-contract.js";
14
+ export * from "./panels/connections.js";
15
+ export * from "./panels/tool-jobs.js";
16
+ export * from "./panels/tool-executor.js";
@@ -0,0 +1,72 @@
1
+ export declare const panelRuntimeApiVersion = 14;
2
+ export declare const panelProcessMethods: string[];
3
+ export declare const panelResourceMethods: string[];
4
+ export declare const panelToolJobMethods: string[];
5
+ export declare const panelBridgeLimits: Readonly<{
6
+ maxParamsBytes: number;
7
+ maxResultBytes: number;
8
+ rateWindowMs: 10000;
9
+ maxCallsPerWindow: 30;
10
+ maxTransferCallsPerWindow: 512;
11
+ callTimeoutMs: 15000;
12
+ consentTimeoutMs: number;
13
+ }>;
14
+ export type PanelBridgeErrorCode = "PERMISSION_DENIED" | "REVOKED" | "NOT_SUPPORTED" | "RATE_LIMITED" | "PARAMS_TOO_LARGE" | "RESULT_TOO_LARGE" | "TIMEOUT" | "INVALID_ARGUMENT" | "OPERATION_FAILED";
15
+ export declare class PanelBridgeError extends Error {
16
+ readonly code: PanelBridgeErrorCode;
17
+ readonly retryAfterMs?: number | undefined;
18
+ constructor(code: PanelBridgeErrorCode, message: string, retryAfterMs?: number | undefined);
19
+ }
20
+ /** IPC otherwise strips custom Error properties. Only the preload unwraps this envelope. */
21
+ export declare function panelBridgeFailure(error: unknown): {
22
+ __codeshellPanelError: {
23
+ retryAfterMs?: number | undefined;
24
+ code: PanelBridgeErrorCode;
25
+ message: string;
26
+ };
27
+ };
28
+ export declare function panelRuntimeCapabilities(input: {
29
+ process: boolean;
30
+ resources?: unknown;
31
+ tasks?: unknown;
32
+ limits?: Partial<Record<keyof typeof panelBridgeLimits, number>>;
33
+ }): {
34
+ tasks?: {} | undefined;
35
+ resources?: {
36
+ materialize: boolean;
37
+ capture: boolean;
38
+ } | undefined;
39
+ process?: Readonly<{
40
+ maxArguments: 256;
41
+ maxArgumentLength: 8192;
42
+ maxArgumentBytes: number;
43
+ maxConcurrentProcesses: 3;
44
+ maxFileArguments: 8;
45
+ maxEventChars: 16384;
46
+ maxOutputBytes: number;
47
+ maxLifetimeMs: number;
48
+ receiptTtlMs: number;
49
+ maxRetainedProcesses: 128;
50
+ maxRetainedProcessesPerGuest: 32;
51
+ maxEventsPerProcess: 256;
52
+ maxRetainedOutputBytes: number;
53
+ maxStdinChunkBytes: number;
54
+ maxStdinBytes: number;
55
+ maxStdinPendingBytes: number;
56
+ maxStdinPendingWrites: 4;
57
+ stdinWriteTimeoutMs: 10000;
58
+ maxEntryBytes: number;
59
+ maxEntryHandlesPerGuest: 64;
60
+ }> | undefined;
61
+ bridge: {
62
+ structuredErrors: boolean;
63
+ maxParamsBytes: number;
64
+ maxResultBytes: number;
65
+ rateWindowMs: number;
66
+ maxCallsPerWindow: number;
67
+ maxTransferCallsPerWindow: number;
68
+ callTimeoutMs: number;
69
+ consentTimeoutMs: number;
70
+ };
71
+ errors: string[];
72
+ };
@@ -0,0 +1,92 @@
1
+ import { processLimits } from "./process-state.js";
2
+ export const panelRuntimeApiVersion = 14;
3
+ export const panelProcessMethods = [
4
+ "process.find",
5
+ "process.info",
6
+ "process.resolveEntry",
7
+ "process.spawn",
8
+ "process.get",
9
+ "process.cancel",
10
+ "process.write",
11
+ "process.end",
12
+ "filesystem.getKnownDirectory",
13
+ "filesystem.pickDirectory",
14
+ "filesystem.openDirectory",
15
+ ];
16
+ export const panelResourceMethods = [
17
+ "resources.list",
18
+ "resources.get",
19
+ "resources.read",
20
+ "resources.upload.begin",
21
+ "resources.upload.write",
22
+ "resources.upload.get",
23
+ "resources.upload.finish",
24
+ "resources.upload.cancel",
25
+ "resources.materialize",
26
+ "resources.capture",
27
+ ];
28
+ export const panelToolJobMethods = [
29
+ "tasks.start",
30
+ "tasks.list",
31
+ "tasks.get",
32
+ "tasks.cancel",
33
+ "tasks.retry",
34
+ ];
35
+ export const panelBridgeLimits = Object.freeze({
36
+ maxParamsBytes: 64 * 1024,
37
+ maxResultBytes: 256 * 1024,
38
+ rateWindowMs: 10000,
39
+ maxCallsPerWindow: 30,
40
+ maxTransferCallsPerWindow: 512,
41
+ callTimeoutMs: 15000,
42
+ consentTimeoutMs: 30 * 60 * 1000,
43
+ });
44
+ export class PanelBridgeError extends Error {
45
+ code;
46
+ retryAfterMs;
47
+ constructor(code, message, retryAfterMs) {
48
+ super(message);
49
+ this.code = code;
50
+ this.retryAfterMs = retryAfterMs;
51
+ this.name = "PanelBridgeError";
52
+ }
53
+ }
54
+ /** IPC otherwise strips custom Error properties. Only the preload unwraps this envelope. */
55
+ export function panelBridgeFailure(error) {
56
+ return {
57
+ __codeshellPanelError: {
58
+ code: error instanceof PanelBridgeError ? error.code : "OPERATION_FAILED",
59
+ message: error instanceof Error ? error.message : String(error),
60
+ ...(error instanceof PanelBridgeError && error.retryAfterMs !== undefined
61
+ ? { retryAfterMs: error.retryAfterMs }
62
+ : {}),
63
+ },
64
+ };
65
+ }
66
+ export function panelRuntimeCapabilities(input) {
67
+ return {
68
+ bridge: { ...panelBridgeLimits, ...input.limits, structuredErrors: true },
69
+ errors: [
70
+ "PERMISSION_DENIED",
71
+ "REVOKED",
72
+ "NOT_SUPPORTED",
73
+ "RATE_LIMITED",
74
+ "PARAMS_TOO_LARGE",
75
+ "RESULT_TOO_LARGE",
76
+ "TIMEOUT",
77
+ "INVALID_ARGUMENT",
78
+ "OPERATION_FAILED",
79
+ ],
80
+ ...(input.process ? { process: processLimits } : {}),
81
+ ...(input.resources
82
+ ? {
83
+ resources: {
84
+ ...input.resources,
85
+ materialize: input.process,
86
+ capture: input.process,
87
+ },
88
+ }
89
+ : {}),
90
+ ...(input.tasks ? { tasks: input.tasks } : {}),
91
+ };
92
+ }
@@ -0,0 +1,31 @@
1
+ import { SettingsManager } from "@cjhyy/code-shell-core";
2
+ import { type CatalogEntry } from "@cjhyy/code-shell-core/internal";
3
+ /** Resolve existing credentials only; provider workflows and request formats stay in tools. */
4
+ export declare function panelConnections(cwd: string, selectedIds?: string[]): {
5
+ connections: Record<string, any>[];
6
+ defaults: {
7
+ text?: string | undefined;
8
+ image?: string | undefined;
9
+ video?: string | undefined;
10
+ audio?: string | undefined;
11
+ speech?: string | undefined;
12
+ auxText?: string | undefined;
13
+ };
14
+ };
15
+ export declare function resolvePanelConnections(settings: Pick<ReturnType<SettingsManager["get"]>, "modelConnections" | "credentials" | "defaults">, catalog: CatalogEntry[], selectedIds?: string[]): {
16
+ connections: Record<string, any>[];
17
+ defaults: {
18
+ text?: string | undefined;
19
+ image?: string | undefined;
20
+ video?: string | undefined;
21
+ audio?: string | undefined;
22
+ speech?: string | undefined;
23
+ auxText?: string | undefined;
24
+ };
25
+ };
26
+ export declare function panelConnectionIds(input: unknown): string[];
27
+ /** Sealed launch argument, never sent to a Guest or persisted in job input. */
28
+ export declare function materializePanelConnections(root: string, cwd: string, selectedIds: string[]): Promise<{
29
+ path: string;
30
+ cleanup: () => void;
31
+ }>;
@@ -0,0 +1,118 @@
1
+ import { createHash } from "node:crypto";
2
+ import { SettingsManager } from "@cjhyy/code-shell-core";
3
+ import { getMergedCatalog, resolveInstance, } from "@cjhyy/code-shell-core/internal";
4
+ import { mkdir, mkdtemp, writeFile, rm } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ /** Resolve existing credentials only; provider workflows and request formats stay in tools. */
7
+ export function panelConnections(cwd, selectedIds) {
8
+ const settings = new SettingsManager(cwd, "full").get();
9
+ return resolvePanelConnections(settings, getMergedCatalog(), selectedIds);
10
+ }
11
+ export function resolvePanelConnections(settings, catalog, selectedIds) {
12
+ const connections = (settings.modelConnections ?? []).flatMap((connection) => {
13
+ if (selectedIds && !selectedIds.includes(connection.id))
14
+ return [];
15
+ const value = resolveInstance(connection, settings.credentials ?? [], catalog);
16
+ if (!value)
17
+ return [];
18
+ const identity = {
19
+ id: connection.id,
20
+ catalogId: connection.catalogId,
21
+ tag: connection.tag,
22
+ adapterKind: value.adapterKind,
23
+ model: value.model,
24
+ hasCredentials: !!value.apiKey || !value.needsKey,
25
+ };
26
+ if (selectedIds)
27
+ return [
28
+ {
29
+ ...identity,
30
+ baseUrl: value.baseUrl,
31
+ apiKey: value.apiKey ?? "",
32
+ entry: value.entry,
33
+ preset: value.preset,
34
+ paramValues: value.paramValues,
35
+ },
36
+ ];
37
+ const secretName = /(?:authorization|api.?key|token|secret|password|credential|header|cookie)/i;
38
+ const parameters = (value.preset?.params ?? [])
39
+ .filter((param) => !secretName.test(param.name))
40
+ .map((param) => ({
41
+ name: param.name,
42
+ control: param.control,
43
+ ...(param.control === "enum"
44
+ ? {
45
+ options: param.options ?? [],
46
+ ...(typeof param.default === "string" && param.options?.includes(param.default)
47
+ ? { default: param.default }
48
+ : {}),
49
+ }
50
+ : {}),
51
+ ...(param.control === "number"
52
+ ? {
53
+ min: param.min,
54
+ max: param.max,
55
+ ...(typeof param.default === "number" ? { default: param.default } : {}),
56
+ }
57
+ : {}),
58
+ ...(param.control === "toggle" && typeof param.default === "boolean"
59
+ ? { default: param.default }
60
+ : {}),
61
+ }));
62
+ const paramValues = Object.fromEntries(parameters.flatMap((param) => {
63
+ const current = value.paramValues[param.name];
64
+ return (param.control === "enum" &&
65
+ typeof current === "string" &&
66
+ param.options?.includes(current)) ||
67
+ (param.control === "number" && typeof current === "number" && Number.isFinite(current)) ||
68
+ (param.control === "toggle" && typeof current === "boolean")
69
+ ? [[param.name, current]]
70
+ : [];
71
+ }));
72
+ return [
73
+ {
74
+ ...identity,
75
+ fingerprint: createHash("sha256")
76
+ .update(JSON.stringify([connection.id, connection.catalogId, value.model, value.baseUrl]))
77
+ .digest("hex")
78
+ .slice(0, 32),
79
+ providerName: value.entry.displayName,
80
+ entry: { displayName: value.entry.displayName, tag: value.entry.tag },
81
+ preset: value.preset
82
+ ? { value: value.preset.value, label: value.preset.label, params: parameters }
83
+ : undefined,
84
+ paramValues,
85
+ },
86
+ ];
87
+ });
88
+ if (selectedIds?.some((id) => !connections.some((connection) => connection.id === id)))
89
+ throw new Error("Selected connection is unavailable");
90
+ return { connections, defaults: settings.defaults ?? {} };
91
+ }
92
+ export function panelConnectionIds(input) {
93
+ if (!Array.isArray(input) ||
94
+ !input.length ||
95
+ input.length > 8 ||
96
+ input.some((id) => typeof id !== "string" || !id || id.length > 200) ||
97
+ new Set(input).size !== input.length)
98
+ throw new Error("Select between one and eight configured connections");
99
+ return input;
100
+ }
101
+ /** Sealed launch argument, never sent to a Guest or persisted in job input. */
102
+ export async function materializePanelConnections(root, cwd, selectedIds) {
103
+ const value = panelConnections(cwd, panelConnectionIds(selectedIds));
104
+ await mkdir(root, { recursive: true, mode: 0o700 });
105
+ const directory = await mkdtemp(join(root, "connections-"));
106
+ const path = join(directory, "connections.json");
107
+ const cleanup = () => {
108
+ void rm(directory, { recursive: true, force: true }).catch(() => { });
109
+ };
110
+ try {
111
+ await writeFile(path, JSON.stringify(value), { mode: 0o600, flag: "wx" });
112
+ }
113
+ catch (error) {
114
+ cleanup();
115
+ throw error;
116
+ }
117
+ return { path, cleanup };
118
+ }
@@ -0,0 +1,2 @@
1
+ /** The callback supplies an installed, reviewed entry, never a Guest-provided path. */
2
+ export declare function verifyProcessEntry(path: string, sha256: string): Promise<string>;
@@ -0,0 +1,52 @@
1
+ import { createHash } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { lstat, open, realpath } from "node:fs/promises";
4
+ import { isAbsolute } from "node:path";
5
+ import { processLimits } from "./process-state.js";
6
+ function identity(info) {
7
+ return [info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs, info.mode].join(":");
8
+ }
9
+ /** The callback supplies an installed, reviewed entry, never a Guest-provided path. */
10
+ export async function verifyProcessEntry(path, sha256) {
11
+ if (!isAbsolute(path) || !/^[a-f0-9]{64}$/.test(sha256))
12
+ throw new Error("invalid reviewed package entry");
13
+ if ((await realpath(path)) !== path)
14
+ throw new Error("package entry path contains a symbolic link");
15
+ const before = await lstat(path);
16
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1)
17
+ throw new Error("package entry must be an unlinked regular file");
18
+ if (before.size > processLimits.maxEntryBytes)
19
+ throw new Error("package entry exceeds size limit");
20
+ const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
21
+ try {
22
+ const opened = await file.stat();
23
+ if (identity(opened) !== identity(before))
24
+ throw new Error("package entry changed while opening");
25
+ const hash = createHash("sha256");
26
+ const buffer = Buffer.alloc(64 * 1024);
27
+ let total = 0;
28
+ while (true) {
29
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, null);
30
+ if (!bytesRead)
31
+ break;
32
+ total += bytesRead;
33
+ if (total > processLimits.maxEntryBytes)
34
+ throw new Error("package entry exceeds size limit");
35
+ hash.update(buffer.subarray(0, bytesRead));
36
+ }
37
+ const after = await file.stat();
38
+ const current = await lstat(path);
39
+ if (identity(opened) !== identity(after) ||
40
+ identity(opened) !== identity(current) ||
41
+ current.isSymbolicLink() ||
42
+ current.nlink !== 1 ||
43
+ (await realpath(path)) !== path ||
44
+ total !== opened.size ||
45
+ hash.digest("hex") !== sha256)
46
+ throw new Error("reviewed package entry changed or failed its hash check");
47
+ return identity(opened);
48
+ }
49
+ finally {
50
+ await file.close();
51
+ }
52
+ }
@@ -1,3 +1,4 @@
1
+ export { processLimits } from "./process-state.js";
1
2
  export interface PanelProcessApprovalScope {
2
3
  appId: string;
3
4
  revision: string;
@@ -32,6 +33,13 @@ export interface PanelAppProcessServiceOptions {
32
33
  }): Promise<boolean>;
33
34
  isExecutionApproved?(scope: PanelProcessApprovalScope): Promise<boolean>;
34
35
  rememberExecutionApproval?(scope: PanelProcessApprovalScope): Promise<void>;
36
+ /** Resolve a declared entry in this app revision; return a canonical, reviewed file. */
37
+ resolvePackageEntry?(owner: PanelProcessOwner, name: string): Promise<{
38
+ path: string;
39
+ sha256: string;
40
+ }>;
41
+ /** Clock injection for bounded receipt retention; does not alter process deadlines. */
42
+ now?: () => number;
35
43
  }
36
44
  export declare function panelProcessInfo(platform?: NodeJS.Platform, arch?: string, report?: unknown): {
37
45
  platform: NodeJS.Platform;
@@ -53,12 +61,16 @@ export declare class PanelAppProcessService {
53
61
  private readonly executables;
54
62
  private readonly directories;
55
63
  private readonly fileArguments;
64
+ private readonly entries;
56
65
  private readonly processes;
66
+ private readonly retiring;
67
+ private readonly receipts;
57
68
  private readonly approvedExecutables;
58
69
  private readonly guestEpochs;
59
70
  private readonly reservations;
60
71
  private closed;
61
72
  constructor(options: PanelAppProcessServiceOptions);
73
+ private now;
62
74
  private epoch;
63
75
  private assertLive;
64
76
  private authorize;
@@ -81,7 +93,20 @@ export declare class PanelAppProcessService {
81
93
  }): Promise<{
82
94
  handle: string;
83
95
  }>;
96
+ /** Seal an already authorized directory as a long-option argument for one executable. */
97
+ grantDirectoryArgument(owner: PanelProcessOwner, input: {
98
+ executableHandle: unknown;
99
+ argumentName: unknown;
100
+ directoryHandle: unknown;
101
+ }): Promise<{
102
+ handle: string;
103
+ }>;
84
104
  directoryPath(owner: PanelProcessOwner, handle: unknown): string;
105
+ resolveEntry(owner: PanelProcessOwner, params: unknown): Promise<{
106
+ handle: string;
107
+ name: string;
108
+ sha256: string;
109
+ }>;
85
110
  start(owner: PanelProcessOwner, params: unknown): Promise<{
86
111
  processId: string;
87
112
  executable: string;
@@ -89,6 +114,42 @@ export declare class PanelAppProcessService {
89
114
  cancel(owner: PanelProcessOwner, params: unknown): {
90
115
  cancelled: boolean;
91
116
  };
117
+ /** Host-only wait captured while the owner is live; never exposes output after revocation. */
118
+ waitForExit(owner: PanelProcessOwner, params: {
119
+ processId: string;
120
+ }): Promise<{
121
+ code: number | null;
122
+ signal: string | null;
123
+ }>;
124
+ get(owner: PanelProcessOwner, params: unknown): Promise<{
125
+ found: false;
126
+ processId: string;
127
+ } | {
128
+ cancelRequested: boolean;
129
+ sequence: number;
130
+ nextSequence: number;
131
+ events: import("./process-state.js").PanelProcessEvent[];
132
+ truncated: boolean;
133
+ hasMore: boolean;
134
+ signal?: string | null | undefined;
135
+ code?: number | null | undefined;
136
+ expiresAt?: number | undefined;
137
+ exitedAt?: number | undefined;
138
+ found: true;
139
+ processId: string;
140
+ status: "running" | "stopping" | "exited";
141
+ startedAt: number;
142
+ }>;
143
+ private writableProcess;
144
+ write(owner: PanelProcessOwner, params: unknown): Promise<{
145
+ bytesWritten: number;
146
+ totalBytes: number;
147
+ }>;
148
+ end(owner: PanelProcessOwner, params: unknown): Promise<{
149
+ ended: true;
150
+ totalBytes: number;
151
+ }>;
152
+ private sendInput;
92
153
  revokeGuest(guestId: number): void;
93
154
  /** Permanently shut down all guests, including operations awaiting approval. */
94
155
  close(): void;