@cjhyy/code-shell-core 0.8.9 → 0.8.10
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/automation/scheduler.js +49 -0
- package/dist/automation/store.d.ts +1 -1
- package/dist/automation/store.js +184 -10
- package/dist/cli/agent-server-stdio.js +7 -0
- package/dist/credentials/store.d.ts +14 -0
- package/dist/credentials/store.js +245 -42
- package/dist/engine/engine.js +45 -6
- package/dist/engine/file-history-hook.js +24 -5
- package/dist/engine/run-types.d.ts +9 -0
- package/dist/engine/turn-loop.js +9 -8
- package/dist/goal/lifecycle.d.ts +2 -0
- package/dist/goal/lifecycle.js +56 -33
- package/dist/index.d.ts +2 -3
- package/dist/index.internal.d.ts +1 -0
- package/dist/index.internal.js +1 -0
- package/dist/index.js +2 -2
- package/dist/links/cli.d.ts +2 -0
- package/dist/links/cli.js +11 -4
- package/dist/model-catalog/index.js +19 -4
- package/dist/model-catalog/save-entry.js +122 -61
- package/dist/model-catalog/types.js +27 -23
- package/dist/panel-apps/installer.js +27 -14
- package/dist/panel-apps/registry.js +60 -12
- package/dist/plugins/installedPlugins.d.ts +4 -0
- package/dist/plugins/installedPlugins.js +70 -30
- package/dist/plugins/installer/types.d.ts +12 -12
- package/dist/plugins/installer/update.js +37 -38
- package/dist/plugins/knownMarketplaces.d.ts +7 -3
- package/dist/plugins/knownMarketplaces.js +127 -23
- package/dist/plugins/pluginCatalog.js +18 -4
- package/dist/plugins/pluginHookApproval.js +56 -60
- package/dist/plugins/pluginMcpApproval.js +50 -52
- package/dist/profile/catalog-store.js +39 -4
- package/dist/profile/catalog.js +55 -15
- package/dist/profile/store.js +51 -21
- package/dist/protocol/chat-session-manager.d.ts +9 -0
- package/dist/protocol/chat-session-manager.js +13 -0
- package/dist/protocol/chat-session.d.ts +5 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/server.d.ts +2 -0
- package/dist/protocol/server.js +75 -29
- package/dist/protocol/types.d.ts +8 -0
- package/dist/run/FileRunStore.d.ts +2 -0
- package/dist/run/FileRunStore.js +153 -18
- package/dist/run/Heartbeat.js +63 -4
- package/dist/services/auto-dream.js +39 -17
- package/dist/services/session-memory.js +107 -8
- package/dist/session/file-history.d.ts +63 -2
- package/dist/session/file-history.js +593 -86
- package/dist/session/session-manager.d.ts +1 -0
- package/dist/session/session-manager.js +52 -21
- package/dist/session/transcript.js +33 -3
- package/dist/session/undo-target.d.ts +15 -6
- package/dist/session/undo-target.js +26 -9
- package/dist/settings/manager.d.ts +22 -3
- package/dist/settings/manager.js +185 -50
- package/dist/settings/schema.d.ts +3 -3
- package/dist/sources/adapters/local-files.js +49 -4
- package/dist/sources/catalog.js +64 -18
- package/dist/sources/types.d.ts +3 -3
- package/dist/sources/types.js +7 -4
- package/dist/themes/installer.js +192 -28
- package/dist/tool-system/builtin/add-marketplace.js +21 -1
- package/dist/tool-system/builtin/cron.d.ts +2 -1
- package/dist/tool-system/builtin/cron.js +20 -6
- package/dist/tool-system/builtin/index.js +44 -0
- package/dist/tool-system/builtin/install-capability.d.ts +52 -0
- package/dist/tool-system/builtin/install-capability.js +1057 -0
- package/dist/tool-system/builtin/skill.js +3 -1
- package/dist/tool-system/executor.js +1 -0
- package/dist/tool-system/registry.js +5 -0
- package/dist/utils/file-mutex.d.ts +2 -0
- package/dist/utils/file-mutex.js +29 -4
- package/package.json +2 -1
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* ("openai"/"google"/"fal"). `shape` is documentation/future only.
|
|
13
13
|
*/
|
|
14
14
|
import { z } from "zod";
|
|
15
|
+
const id = z.string().min(1).max(128);
|
|
16
|
+
const shortText = z.string().max(256);
|
|
17
|
+
const longText = z.string().max(32_768);
|
|
18
|
+
const boundedNumber = z.number().finite().min(0).max(1_000_000_000);
|
|
15
19
|
/**
|
|
16
20
|
* How the param maps onto the outgoing request body. `field` is the request
|
|
17
21
|
* field this param lands on — same param name can land differently per
|
|
@@ -20,7 +24,7 @@ import { z } from "zod";
|
|
|
20
24
|
* in `if (kind === ...)` branches in the engine. Minimal v1: field only.
|
|
21
25
|
*/
|
|
22
26
|
export const wireSpecSchema = z.object({
|
|
23
|
-
field:
|
|
27
|
+
field: id,
|
|
24
28
|
});
|
|
25
29
|
/**
|
|
26
30
|
* One **generic** declarative param — no special-casing per param. reasoning
|
|
@@ -32,18 +36,18 @@ export const wireSpecSchema = z.object({
|
|
|
32
36
|
*/
|
|
33
37
|
export const paramSpecSchema = z.object({
|
|
34
38
|
/** Logical name, e.g. "reasoning" / "size" / "quality" / "temperature". */
|
|
35
|
-
name:
|
|
39
|
+
name: id,
|
|
36
40
|
/** UI control label (falls back to `name`). */
|
|
37
|
-
label:
|
|
41
|
+
label: shortText.optional(),
|
|
38
42
|
control: z.enum(["enum", "number", "toggle", "text"]),
|
|
39
43
|
/** control=enum allowed values, e.g. ["low","medium","high","xhigh"]. */
|
|
40
|
-
options: z.array(
|
|
44
|
+
options: z.array(shortText).max(64).optional(),
|
|
41
45
|
/** control=number bounds. */
|
|
42
|
-
min:
|
|
43
|
-
max:
|
|
44
|
-
default: z.union([
|
|
46
|
+
min: boundedNumber.optional(),
|
|
47
|
+
max: boundedNumber.optional(),
|
|
48
|
+
default: z.union([shortText, boundedNumber, z.boolean()]).optional(),
|
|
45
49
|
/** Natural-language usage note → injected into the tool description. */
|
|
46
|
-
doc: z.string().optional(),
|
|
50
|
+
doc: z.string().max(4_096).optional(),
|
|
47
51
|
/** How this param lands on the request body. */
|
|
48
52
|
wire: wireSpecSchema.optional(),
|
|
49
53
|
});
|
|
@@ -53,17 +57,17 @@ export const paramSpecSchema = z.object({
|
|
|
53
57
|
* can expose different params, since gateways normalize differently).
|
|
54
58
|
*/
|
|
55
59
|
export const modelPresetSchema = z.object({
|
|
56
|
-
value:
|
|
57
|
-
label:
|
|
58
|
-
maxContextTokens:
|
|
59
|
-
maxOutputTokens:
|
|
60
|
+
value: id,
|
|
61
|
+
label: shortText.optional(),
|
|
62
|
+
maxContextTokens: boundedNumber.optional(),
|
|
63
|
+
maxOutputTokens: boundedNumber.optional(),
|
|
60
64
|
supportsVision: z.boolean().optional(),
|
|
61
65
|
/** Params this model supports; absent → no adjustable knobs. */
|
|
62
|
-
params: z.array(paramSpecSchema).optional(),
|
|
66
|
+
params: z.array(paramSpecSchema).max(64).optional(),
|
|
63
67
|
});
|
|
64
68
|
export const catalogEntrySchema = z.object({
|
|
65
69
|
/** Template id, e.g. "openai" / "openai-images" / "fal-video". */
|
|
66
|
-
id
|
|
70
|
+
id,
|
|
67
71
|
/** Which 连接 page group this lands in. (audio = speech-to-text / voice input.) */
|
|
68
72
|
tag: z.enum(["text", "image", "video", "audio"]),
|
|
69
73
|
/**
|
|
@@ -71,15 +75,15 @@ export const catalogEntrySchema = z.object({
|
|
|
71
75
|
* runtime adapter for image/video. OpenRouter is `openrouter` even though its
|
|
72
76
|
* text protocol is `openai-compat`.
|
|
73
77
|
*/
|
|
74
|
-
adapterKind:
|
|
78
|
+
adapterKind: id,
|
|
75
79
|
/** LLM client protocol (text entries). */
|
|
76
80
|
protocol: z.enum(["openai-compat", "anthropic-style"]).optional(),
|
|
77
81
|
/** HTTP shape — documentation/future only; runtime dispatches on adapterKind. */
|
|
78
82
|
shape: z.enum(["generic-sync", "fal-queue"]).optional(),
|
|
79
|
-
displayName: z.string(),
|
|
80
|
-
description: z.string(),
|
|
81
|
-
defaultBaseUrl: z.string(),
|
|
82
|
-
defaultModel:
|
|
83
|
+
displayName: z.string().min(1).max(256),
|
|
84
|
+
description: z.string().max(4_096),
|
|
85
|
+
defaultBaseUrl: z.string().max(4_096),
|
|
86
|
+
defaultModel: shortText.optional(),
|
|
83
87
|
/** Whether this provider needs an API key (ollama/local = false). */
|
|
84
88
|
needsKey: z.boolean().optional(),
|
|
85
89
|
/**
|
|
@@ -89,8 +93,8 @@ export const catalogEntrySchema = z.object({
|
|
|
89
93
|
* `value`, so future built-in models remain visible.
|
|
90
94
|
*/
|
|
91
95
|
modelPresetsMode: z.enum(["replace", "merge"]).optional(),
|
|
92
|
-
modelPresets: z.array(modelPresetSchema).optional(),
|
|
93
|
-
signupUrl: z.string().optional(),
|
|
96
|
+
modelPresets: z.array(modelPresetSchema).max(512).optional(),
|
|
97
|
+
signupUrl: z.string().max(4_096).optional(),
|
|
94
98
|
/** Whether the 连接 card offers a "测试" button (image=true, video=false). */
|
|
95
99
|
test: z.boolean().optional(),
|
|
96
100
|
/**
|
|
@@ -98,7 +102,7 @@ export const catalogEntrySchema = z.object({
|
|
|
98
102
|
* the agent via the dynamic GenerateImage/GenerateVideo tool description so it
|
|
99
103
|
* knows what a configured model accepts (different models differ).
|
|
100
104
|
*/
|
|
101
|
-
paramsDoc:
|
|
105
|
+
paramsDoc: longText.optional(),
|
|
102
106
|
});
|
|
103
107
|
/** A user-catalog file is just an array of entries (zod-validated on load). */
|
|
104
|
-
export const userCatalogFileSchema = z.array(catalogEntrySchema);
|
|
108
|
+
export const userCatalogFileSchema = z.array(catalogEntrySchema).max(256);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { cp, lstat, mkdir, mkdtemp,
|
|
2
|
+
import { constants, existsSync } from "node:fs";
|
|
3
|
+
import { cp, lstat, mkdir, mkdtemp, open, readdir, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { basename, extname, join, posix, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { extractZip, extractZipSubdirectory } from "../plugins/installer/unzip.js";
|
|
@@ -279,22 +279,34 @@ async function walkBoundedTree(root, directory, depth, budget, files, directorie
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
async function readManifest(sourceRoot) {
|
|
282
|
-
const file = join(sourceRoot, PANEL_APP_MANIFEST_FILE);
|
|
283
|
-
let info;
|
|
284
282
|
try {
|
|
285
|
-
|
|
283
|
+
const raw = await readBoundedPackageFile(sourceRoot, PANEL_APP_MANIFEST_FILE, MAX_MANIFEST_BYTES);
|
|
284
|
+
return PanelAppManifest.parse(JSON.parse(raw.toString("utf8")));
|
|
286
285
|
}
|
|
287
|
-
catch {
|
|
288
|
-
throw new PanelAppInstallError(`
|
|
286
|
+
catch (error) {
|
|
287
|
+
throw new PanelAppInstallError(`invalid Panel App manifest: ${error instanceof Error ? error.message : String(error)}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function readBoundedPackageFile(root, relativePath, maxBytes) {
|
|
291
|
+
const candidate = join(root, ...relativePath.split("/"));
|
|
292
|
+
const metadata = await lstat(candidate);
|
|
293
|
+
if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.size > maxBytes) {
|
|
294
|
+
throw new PanelAppInstallError(`Panel App file is not a bounded regular file: ${relativePath}`);
|
|
289
295
|
}
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
const physical = await realpath(candidate);
|
|
297
|
+
if (physical !== root && !physical.startsWith(`${root}${sep}`)) {
|
|
298
|
+
throw new PanelAppInstallError(`Panel App file escapes its package: ${relativePath}`);
|
|
292
299
|
}
|
|
300
|
+
const handle = await open(candidate, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
293
301
|
try {
|
|
294
|
-
|
|
302
|
+
const opened = await handle.stat();
|
|
303
|
+
if (!opened.isFile() || opened.size > maxBytes) {
|
|
304
|
+
throw new PanelAppInstallError(`Panel App file is not a bounded regular file: ${relativePath}`);
|
|
305
|
+
}
|
|
306
|
+
return await handle.readFile();
|
|
295
307
|
}
|
|
296
|
-
|
|
297
|
-
|
|
308
|
+
finally {
|
|
309
|
+
await handle.close();
|
|
298
310
|
}
|
|
299
311
|
}
|
|
300
312
|
async function inspectPanelAppSource(sourceRoot) {
|
|
@@ -323,10 +335,11 @@ async function inspectPanelAppSource(sourceRoot) {
|
|
|
323
335
|
if (!files.includes(skillEntry)) {
|
|
324
336
|
throw new PanelAppInstallError(`declared Panel App skill is missing: ${skillEntry}`);
|
|
325
337
|
}
|
|
326
|
-
const skillInfo = await
|
|
338
|
+
const skillInfo = await lstat(join(root, ...skillEntry.split("/")));
|
|
327
339
|
if (!skillInfo.isFile() || skillInfo.size > MAX_AGENT_SKILL_BYTES) {
|
|
328
340
|
throw new PanelAppInstallError(`declared Panel App skill must be a file no larger than 256 KiB: ${skillEntry}`);
|
|
329
341
|
}
|
|
342
|
+
await readBoundedPackageFile(root, skillEntry, MAX_AGENT_SKILL_BYTES);
|
|
330
343
|
}
|
|
331
344
|
for (const file of files) {
|
|
332
345
|
if (file === PANEL_APP_MANIFEST_FILE || file === PANEL_APP_META_FILE)
|
|
@@ -352,7 +365,7 @@ async function inspectPanelAppSource(sourceRoot) {
|
|
|
352
365
|
hash
|
|
353
366
|
.update(file)
|
|
354
367
|
.update("\0")
|
|
355
|
-
.update(await
|
|
368
|
+
.update(await readBoundedPackageFile(root, file, file === PANEL_APP_MANIFEST_FILE ? MAX_MANIFEST_BYTES : MAX_FILE_BYTES));
|
|
356
369
|
}
|
|
357
370
|
return { manifest, files, digest: hash.digest("hex") };
|
|
358
371
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import { mkdir,
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { chmod, lstat, mkdir, open, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { panelAppsRegistryPath } from "./paths.js";
|
|
@@ -28,47 +28,95 @@ const Registry = z
|
|
|
28
28
|
apps: z.array(RegistryEntry).max(1_024),
|
|
29
29
|
})
|
|
30
30
|
.strict();
|
|
31
|
-
|
|
31
|
+
const MAX_PANEL_APP_REGISTRY_BYTES = 4 * 1024 * 1024;
|
|
32
|
+
async function registryEntry(path) {
|
|
33
|
+
try {
|
|
34
|
+
return await lstat(path);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === "ENOENT")
|
|
38
|
+
return null;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function checkedRegistryDirectory() {
|
|
43
|
+
const directory = dirname(panelAppsRegistryPath());
|
|
44
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
45
|
+
const metadata = await lstat(directory);
|
|
46
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
47
|
+
throw new Error("Panel App registry directory must be a real directory");
|
|
48
|
+
}
|
|
49
|
+
return directory;
|
|
50
|
+
}
|
|
51
|
+
async function readRegistryUnlocked(strict = false) {
|
|
32
52
|
const path = panelAppsRegistryPath();
|
|
33
|
-
|
|
34
|
-
return [];
|
|
53
|
+
let handle;
|
|
35
54
|
try {
|
|
36
|
-
|
|
55
|
+
const metadata = await registryEntry(path);
|
|
56
|
+
if (!metadata)
|
|
57
|
+
return [];
|
|
58
|
+
if (metadata.isSymbolicLink() ||
|
|
59
|
+
!metadata.isFile() ||
|
|
60
|
+
metadata.size > MAX_PANEL_APP_REGISTRY_BYTES) {
|
|
61
|
+
throw new Error("Panel App registry must be a bounded regular file");
|
|
62
|
+
}
|
|
63
|
+
handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
64
|
+
const opened = await handle.stat();
|
|
65
|
+
if (!opened.isFile() || opened.size > MAX_PANEL_APP_REGISTRY_BYTES) {
|
|
66
|
+
throw new Error("Panel App registry must be a bounded regular file");
|
|
67
|
+
}
|
|
68
|
+
return Registry.parse(JSON.parse(await handle.readFile("utf8"))).apps;
|
|
37
69
|
}
|
|
38
|
-
catch {
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (strict) {
|
|
72
|
+
const detail = error instanceof Error ? `: ${error.message}` : "";
|
|
73
|
+
throw new Error(`Panel App registry is corrupt${detail}`, { cause: error });
|
|
74
|
+
}
|
|
39
75
|
return [];
|
|
40
76
|
}
|
|
77
|
+
finally {
|
|
78
|
+
await handle?.close().catch(() => undefined);
|
|
79
|
+
}
|
|
41
80
|
}
|
|
42
81
|
async function writeRegistryUnlocked(apps) {
|
|
43
82
|
const path = panelAppsRegistryPath();
|
|
44
|
-
await
|
|
83
|
+
await checkedRegistryDirectory();
|
|
84
|
+
const target = await registryEntry(path);
|
|
85
|
+
if (target && (target.isSymbolicLink() || !target.isFile())) {
|
|
86
|
+
throw new Error("Panel App registry target must be a regular file");
|
|
87
|
+
}
|
|
45
88
|
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
46
89
|
try {
|
|
47
90
|
const registry = Registry.parse({
|
|
48
91
|
version: 1,
|
|
49
92
|
apps: [...apps].sort((left, right) => left.id.localeCompare(right.id)),
|
|
50
93
|
});
|
|
51
|
-
|
|
94
|
+
const serialized = `${JSON.stringify(registry, null, 2)}\n`;
|
|
95
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_PANEL_APP_REGISTRY_BYTES) {
|
|
96
|
+
throw new Error("Panel App registry is too large");
|
|
97
|
+
}
|
|
98
|
+
await writeFile(tmp, serialized, {
|
|
52
99
|
encoding: "utf-8",
|
|
53
100
|
flag: "wx",
|
|
54
101
|
mode: 0o600,
|
|
55
102
|
});
|
|
56
103
|
await rename(tmp, path);
|
|
104
|
+
if (process.platform !== "win32")
|
|
105
|
+
await chmod(path, 0o600);
|
|
57
106
|
}
|
|
58
107
|
finally {
|
|
59
108
|
await rm(tmp, { force: true });
|
|
60
109
|
}
|
|
61
110
|
}
|
|
62
111
|
async function mutateRegistry(mutation) {
|
|
63
|
-
const directory =
|
|
64
|
-
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
112
|
+
const directory = await checkedRegistryDirectory();
|
|
65
113
|
const release = await lock(directory, {
|
|
66
114
|
realpath: true,
|
|
67
115
|
stale: 10_000,
|
|
68
116
|
retries: { retries: 8, minTimeout: 10, maxTimeout: 120, factor: 1.5 },
|
|
69
117
|
});
|
|
70
118
|
try {
|
|
71
|
-
const next = mutation(await readRegistryUnlocked());
|
|
119
|
+
const next = mutation(await readRegistryUnlocked(true));
|
|
72
120
|
await writeRegistryUnlocked(next.apps);
|
|
73
121
|
return next.result;
|
|
74
122
|
}
|
|
@@ -7,6 +7,10 @@ import type { InstalledPluginsV2, PluginInstallEntry } from "./types.js";
|
|
|
7
7
|
export declare function installedPluginsPath(): string;
|
|
8
8
|
export declare function readInstalledPlugins(): InstalledPluginsV2;
|
|
9
9
|
export declare function writeInstalledPlugins(data: InstalledPluginsV2): void;
|
|
10
|
+
export declare function mutateInstalledPlugins<R>(mutation: (current: InstalledPluginsV2) => {
|
|
11
|
+
value?: InstalledPluginsV2;
|
|
12
|
+
result?: R;
|
|
13
|
+
}): R | undefined;
|
|
10
14
|
/**
|
|
11
15
|
* Append an install entry for `<plugin>@<marketplace>`. Multiple entries
|
|
12
16
|
* for the same key are allowed (different scopes); MVP only writes scope:"user".
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Reads the Claude Code-compatible V2 shape plus optional CodeShell integrity
|
|
4
4
|
* fields on each install entry.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
6
|
+
import { chmodSync, closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
9
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { mutateJsonFile } from "../utils/file-mutex.js";
|
|
10
10
|
const MAX_PLUGIN_KEYS = 2_048;
|
|
11
11
|
const MAX_INSTALLS_PER_KEY = 16;
|
|
12
12
|
const MAX_KEY_LENGTH = 256;
|
|
@@ -16,6 +16,7 @@ const DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
|
16
16
|
const MAX_HOOK_REVIEW_ITEMS = 256;
|
|
17
17
|
const MAX_HOOK_REVIEW_COMMAND_LENGTH = 4_096;
|
|
18
18
|
const MAX_HOOK_REVIEW_MATCHER_LENGTH = 4_096;
|
|
19
|
+
const MAX_INSTALLED_PLUGINS_FILE_BYTES = 16 * 1024 * 1024;
|
|
19
20
|
function userHome() {
|
|
20
21
|
return process.env.HOME ?? homedir();
|
|
21
22
|
}
|
|
@@ -142,52 +143,91 @@ function registryOf(value) {
|
|
|
142
143
|
}
|
|
143
144
|
export function readInstalledPlugins() {
|
|
144
145
|
const path = installedPluginsPath();
|
|
145
|
-
|
|
146
|
-
return { version: 2, plugins: {} };
|
|
146
|
+
let descriptor;
|
|
147
147
|
try {
|
|
148
|
-
const
|
|
148
|
+
const metadata = lstatSync(path);
|
|
149
|
+
if (metadata.isSymbolicLink() ||
|
|
150
|
+
!metadata.isFile() ||
|
|
151
|
+
metadata.size > MAX_INSTALLED_PLUGINS_FILE_BYTES) {
|
|
152
|
+
return { version: 2, plugins: {} };
|
|
153
|
+
}
|
|
154
|
+
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
155
|
+
const opened = fstatSync(descriptor);
|
|
156
|
+
if (!opened.isFile() || opened.size > MAX_INSTALLED_PLUGINS_FILE_BYTES) {
|
|
157
|
+
return { version: 2, plugins: {} };
|
|
158
|
+
}
|
|
159
|
+
const parsed = registryOf(JSON.parse(readFileSync(descriptor, "utf-8")));
|
|
149
160
|
if (parsed)
|
|
150
161
|
return parsed;
|
|
151
162
|
}
|
|
152
163
|
catch {
|
|
153
164
|
// Corrupt — treat as empty so the user can re-install.
|
|
154
165
|
}
|
|
166
|
+
finally {
|
|
167
|
+
if (descriptor !== undefined)
|
|
168
|
+
closeSync(descriptor);
|
|
169
|
+
}
|
|
155
170
|
return { version: 2, plugins: {} };
|
|
156
171
|
}
|
|
157
172
|
export function writeInstalledPlugins(data) {
|
|
158
173
|
const path = installedPluginsPath();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
const normalized = registryOf(data);
|
|
175
|
+
if (!normalized)
|
|
176
|
+
throw new Error("invalid installed plugins registry");
|
|
177
|
+
mutateJsonFile(path, {
|
|
178
|
+
parse: parseInstalledPlugins,
|
|
179
|
+
serialize: (value) => `${JSON.stringify(value, null, 2)}\n`,
|
|
180
|
+
mutation: () => ({ value: normalized }),
|
|
181
|
+
mode: 0o600,
|
|
182
|
+
maxBytes: MAX_INSTALLED_PLUGINS_FILE_BYTES,
|
|
183
|
+
});
|
|
184
|
+
if (process.platform !== "win32")
|
|
185
|
+
chmodSync(path, 0o600);
|
|
186
|
+
}
|
|
187
|
+
function parseInstalledPlugins(raw) {
|
|
188
|
+
if (raw === undefined)
|
|
189
|
+
return { version: 2, plugins: {} };
|
|
190
|
+
const parsed = registryOf(JSON.parse(raw));
|
|
191
|
+
if (!parsed)
|
|
192
|
+
throw new Error("installed plugins registry is corrupt");
|
|
193
|
+
return parsed;
|
|
194
|
+
}
|
|
195
|
+
export function mutateInstalledPlugins(mutation) {
|
|
196
|
+
return mutateJsonFile(installedPluginsPath(), {
|
|
197
|
+
parse: parseInstalledPlugins,
|
|
198
|
+
serialize: (value) => `${JSON.stringify(value, null, 2)}\n`,
|
|
199
|
+
mutation,
|
|
200
|
+
mode: 0o600,
|
|
201
|
+
maxBytes: MAX_INSTALLED_PLUGINS_FILE_BYTES,
|
|
202
|
+
});
|
|
172
203
|
}
|
|
173
204
|
/**
|
|
174
205
|
* Append an install entry for `<plugin>@<marketplace>`. Multiple entries
|
|
175
206
|
* for the same key are allowed (different scopes); MVP only writes scope:"user".
|
|
176
207
|
*/
|
|
177
208
|
export function appendInstallEntry(key, entry) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
209
|
+
if (key.length === 0 || key.length > MAX_KEY_LENGTH || key.includes("\0")) {
|
|
210
|
+
throw new Error("invalid plugin install key");
|
|
211
|
+
}
|
|
212
|
+
const normalized = installEntryOf(entry);
|
|
213
|
+
if (!normalized)
|
|
214
|
+
throw new Error("invalid plugin install entry");
|
|
215
|
+
mutateInstalledPlugins((data) => {
|
|
216
|
+
const list = [...(data.plugins[key] ?? [])];
|
|
217
|
+
if (list.length >= MAX_INSTALLS_PER_KEY)
|
|
218
|
+
throw new Error("too many plugin installs for key");
|
|
219
|
+
list.push(normalized);
|
|
220
|
+
data.plugins[key] = list;
|
|
221
|
+
return { value: data };
|
|
222
|
+
});
|
|
183
223
|
}
|
|
184
224
|
export function removeInstallEntries(key) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
225
|
+
return (mutateInstalledPlugins((data) => {
|
|
226
|
+
if (!Object.prototype.hasOwnProperty.call(data.plugins, key))
|
|
227
|
+
return { result: false };
|
|
228
|
+
delete data.plugins[key];
|
|
229
|
+
return { value: data, result: true };
|
|
230
|
+
}) ?? false);
|
|
191
231
|
}
|
|
192
232
|
export function pluginInstallKey(plugin, marketplace) {
|
|
193
233
|
return `${plugin}@${marketplace}`;
|
|
@@ -29,8 +29,8 @@ export declare const PluginAutomationTemplate: z.ZodObject<{
|
|
|
29
29
|
en?: string | undefined;
|
|
30
30
|
"zh-CN"?: string | undefined;
|
|
31
31
|
};
|
|
32
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
33
32
|
schedule: string;
|
|
33
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
34
34
|
description?: string | undefined;
|
|
35
35
|
timezone?: string | undefined;
|
|
36
36
|
}, {
|
|
@@ -79,8 +79,8 @@ export declare const PluginAutomationsManifest: z.ZodEffects<z.ZodObject<{
|
|
|
79
79
|
en?: string | undefined;
|
|
80
80
|
"zh-CN"?: string | undefined;
|
|
81
81
|
};
|
|
82
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
83
82
|
schedule: string;
|
|
83
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
84
84
|
description?: string | undefined;
|
|
85
85
|
timezone?: string | undefined;
|
|
86
86
|
}, {
|
|
@@ -108,8 +108,8 @@ export declare const PluginAutomationsManifest: z.ZodEffects<z.ZodObject<{
|
|
|
108
108
|
en?: string | undefined;
|
|
109
109
|
"zh-CN"?: string | undefined;
|
|
110
110
|
};
|
|
111
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
112
111
|
schedule: string;
|
|
112
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
113
113
|
description?: string | undefined;
|
|
114
114
|
timezone?: string | undefined;
|
|
115
115
|
}[];
|
|
@@ -140,8 +140,8 @@ export declare const PluginAutomationsManifest: z.ZodEffects<z.ZodObject<{
|
|
|
140
140
|
en?: string | undefined;
|
|
141
141
|
"zh-CN"?: string | undefined;
|
|
142
142
|
};
|
|
143
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
144
143
|
schedule: string;
|
|
144
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
145
145
|
description?: string | undefined;
|
|
146
146
|
timezone?: string | undefined;
|
|
147
147
|
}[];
|
|
@@ -249,8 +249,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
249
249
|
en?: string | undefined;
|
|
250
250
|
"zh-CN"?: string | undefined;
|
|
251
251
|
};
|
|
252
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
253
252
|
schedule: string;
|
|
253
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
254
254
|
description?: string | undefined;
|
|
255
255
|
timezone?: string | undefined;
|
|
256
256
|
}, {
|
|
@@ -278,8 +278,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
278
278
|
en?: string | undefined;
|
|
279
279
|
"zh-CN"?: string | undefined;
|
|
280
280
|
};
|
|
281
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
282
281
|
schedule: string;
|
|
282
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
283
283
|
description?: string | undefined;
|
|
284
284
|
timezone?: string | undefined;
|
|
285
285
|
}[];
|
|
@@ -310,8 +310,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
310
310
|
en?: string | undefined;
|
|
311
311
|
"zh-CN"?: string | undefined;
|
|
312
312
|
};
|
|
313
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
314
313
|
schedule: string;
|
|
314
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
315
315
|
description?: string | undefined;
|
|
316
316
|
timezone?: string | undefined;
|
|
317
317
|
}[];
|
|
@@ -345,8 +345,8 @@ export declare const CodeShellPluginOverlay: z.ZodObject<{
|
|
|
345
345
|
en?: string | undefined;
|
|
346
346
|
"zh-CN"?: string | undefined;
|
|
347
347
|
};
|
|
348
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
349
348
|
schedule: string;
|
|
349
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
350
350
|
description?: string | undefined;
|
|
351
351
|
timezone?: string | undefined;
|
|
352
352
|
}[];
|
|
@@ -634,8 +634,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
634
634
|
en?: string | undefined;
|
|
635
635
|
"zh-CN"?: string | undefined;
|
|
636
636
|
};
|
|
637
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
638
637
|
schedule: string;
|
|
638
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
639
639
|
description?: string | undefined;
|
|
640
640
|
timezone?: string | undefined;
|
|
641
641
|
}, {
|
|
@@ -663,8 +663,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
663
663
|
en?: string | undefined;
|
|
664
664
|
"zh-CN"?: string | undefined;
|
|
665
665
|
};
|
|
666
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
667
666
|
schedule: string;
|
|
667
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
668
668
|
description?: string | undefined;
|
|
669
669
|
timezone?: string | undefined;
|
|
670
670
|
}[];
|
|
@@ -695,8 +695,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
695
695
|
en?: string | undefined;
|
|
696
696
|
"zh-CN"?: string | undefined;
|
|
697
697
|
};
|
|
698
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
699
698
|
schedule: string;
|
|
699
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
700
700
|
description?: string | undefined;
|
|
701
701
|
timezone?: string | undefined;
|
|
702
702
|
}[];
|
|
@@ -750,8 +750,8 @@ export declare const CanonicalPluginManifest: z.ZodObject<{
|
|
|
750
750
|
en?: string | undefined;
|
|
751
751
|
"zh-CN"?: string | undefined;
|
|
752
752
|
};
|
|
753
|
-
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
754
753
|
schedule: string;
|
|
754
|
+
permissionLevel: "full" | "read-only" | "workspace-write";
|
|
755
755
|
description?: string | undefined;
|
|
756
756
|
timezone?: string | undefined;
|
|
757
757
|
}[];
|