@buzzni/saycode-cli 0.8.1 → 0.8.2
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/channel-extension-host.mjs +405 -190
- package/index.mjs +4559 -1955
- package/package.json +1 -1
|
@@ -7,89 +7,322 @@ const ASSET_PERMISSIONS = ["assets.read", "assets.write", "files.select", "draft
|
|
|
7
7
|
function usesAssetWorkflow(permissions) {
|
|
8
8
|
return permissions.some((permission) => ASSET_PERMISSIONS.includes(permission));
|
|
9
9
|
}
|
|
10
|
+
const LONG_RUNNING_PERMISSIONS = ["localTools.inspect", "localTools.install", "localTools.control", "localBrowser.setup", "machine.run"];
|
|
11
|
+
function extensionRequestTimeoutMs(permissions, side) {
|
|
12
|
+
const long = usesAssetWorkflow(permissions) || permissions.some((permission) => LONG_RUNNING_PERMISSIONS.includes(permission));
|
|
13
|
+
if (!long) return void 0;
|
|
14
|
+
return side === "runtime" ? 13e4 : 14e4;
|
|
15
|
+
}
|
|
16
|
+
const ID_PATTERN$2 = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
|
|
17
|
+
function fail$5(path2, detail) {
|
|
18
|
+
throw new Error(`${path2}: ${detail}`);
|
|
19
|
+
}
|
|
20
|
+
function record$1(value, path2) {
|
|
21
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail$5(path2, "expected object");
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function string(value, path2) {
|
|
25
|
+
if (typeof value !== "string" || value.trim() !== value || value.length === 0) {
|
|
26
|
+
fail$5(path2, "expected non-empty trimmed string");
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function id(value, path2) {
|
|
31
|
+
const parsed = string(value, path2);
|
|
32
|
+
if (!ID_PATTERN$2.test(parsed) || parsed.includes("..")) fail$5(path2, "invalid stable identifier");
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
function array(value, path2) {
|
|
36
|
+
if (!Array.isArray(value)) fail$5(path2, "expected array");
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function assertOnlyKeys(value, allowed, path2) {
|
|
40
|
+
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
41
|
+
if (unknown) fail$5(path2, `unknown field ${unknown}`);
|
|
42
|
+
}
|
|
43
|
+
function contributionList(value, path2, allowedKeys, parse) {
|
|
44
|
+
return array(value, path2).map((raw, index) => {
|
|
45
|
+
const entryPath = `${path2}[${index}]`;
|
|
46
|
+
const entry = record$1(raw, entryPath);
|
|
47
|
+
assertOnlyKeys(entry, new Set(allowedKeys), entryPath);
|
|
48
|
+
return parse(entry, entryPath);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function parseMachineCommands(value, extensionId2, apiVersion) {
|
|
52
|
+
if (apiVersion < 3) fail$5("manifest.machineCommands", "requires Extension API version 3");
|
|
53
|
+
const root = record$1(value, "manifest.machineCommands");
|
|
54
|
+
assertOnlyKeys(root, /* @__PURE__ */ new Set(["version", "profiles"]), "manifest.machineCommands");
|
|
55
|
+
if (root.version !== 1) fail$5("manifest.machineCommands.version", "unsupported version");
|
|
56
|
+
const profiles = contributionList(
|
|
57
|
+
root.profiles,
|
|
58
|
+
"manifest.machineCommands.profiles",
|
|
59
|
+
["id", "executable", "argv", "parameters", "cwd", "envAllowlist", "timeoutMs", "outputLimitBytes", "stdin", "writeScope", "descendantAllowlist"],
|
|
60
|
+
(entry, path2) => {
|
|
61
|
+
const profileId = id(entry.id, `${path2}.id`);
|
|
62
|
+
if (!profileId.startsWith(`${extensionId2}.`)) fail$5(`${path2}.id`, "expected an id declared by the same extension");
|
|
63
|
+
const executable = string(entry.executable, `${path2}.executable`);
|
|
64
|
+
if (!/^[a-z][a-z0-9._-]*$/.test(executable) || /^(sh|bash|zsh|fish|cmd|powershell|pwsh|node|nodejs|deno|bun|python|python2|python3|ruby|perl|php|java|awk|gawk|osascript|npm|npx|env|xargs|ssh|tmux|make|cargo)$/i.test(executable)) {
|
|
65
|
+
fail$5(`${path2}.executable`, "unsupported executable");
|
|
66
|
+
}
|
|
67
|
+
const argv = array(entry.argv, `${path2}.argv`).map((arg, index) => {
|
|
68
|
+
const value2 = string(arg, `${path2}.argv[${index}]`);
|
|
69
|
+
if (/^(?:--from|--wake|--interactive|--tty|--tui|-i)$/.test(value2) || value2.includes("\\") || value2.includes("/") || value2.includes("..") || value2.includes("{{workspaceRoot}}") && value2 !== "{{workspaceRoot}}") fail$5(`${path2}.argv[${index}]`, "unsafe argument template");
|
|
70
|
+
return value2;
|
|
71
|
+
});
|
|
72
|
+
if (!argv.length || argv.length > 32) fail$5(`${path2}.argv`, "expected 1-32 arguments");
|
|
73
|
+
const parameters = {};
|
|
74
|
+
for (const [name, raw] of Object.entries(record$1(entry.parameters, `${path2}.parameters`))) {
|
|
75
|
+
if (!/^[a-z][a-zA-Z0-9_]{0,63}$/.test(name)) fail$5(`${path2}.parameters`, "invalid parameter name");
|
|
76
|
+
const parameter = record$1(raw, `${path2}.parameters.${name}`);
|
|
77
|
+
if (parameter.type === "string") {
|
|
78
|
+
assertOnlyKeys(parameter, /* @__PURE__ */ new Set(["type", "maxLength", "values"]), `${path2}.parameters.${name}`);
|
|
79
|
+
if (!Number.isInteger(parameter.maxLength) || Number(parameter.maxLength) < 1 || Number(parameter.maxLength) > 4096) fail$5(`${path2}.parameters.${name}.maxLength`, "invalid maxLength");
|
|
80
|
+
if (parameter.values !== void 0 && (!Array.isArray(parameter.values) || parameter.values.length === 0 || parameter.values.some((item) => typeof item !== "string" || !item))) fail$5(`${path2}.parameters.${name}.values`, "invalid values");
|
|
81
|
+
parameters[name] = { type: "string", maxLength: Number(parameter.maxLength), ...parameter.values === void 0 ? {} : { values: parameter.values } };
|
|
82
|
+
} else if (parameter.type === "integer") {
|
|
83
|
+
assertOnlyKeys(parameter, /* @__PURE__ */ new Set(["type", "min", "max"]), `${path2}.parameters.${name}`);
|
|
84
|
+
if (!Number.isSafeInteger(parameter.min) || !Number.isSafeInteger(parameter.max) || Number(parameter.min) > Number(parameter.max)) fail$5(`${path2}.parameters.${name}`, "invalid integer bounds");
|
|
85
|
+
parameters[name] = { type: "integer", min: Number(parameter.min), max: Number(parameter.max) };
|
|
86
|
+
} else fail$5(`${path2}.parameters.${name}.type`, "unsupported parameter type");
|
|
87
|
+
}
|
|
88
|
+
const cwd = string(entry.cwd, `${path2}.cwd`);
|
|
89
|
+
if (cwd !== "workspaceRoot" && cwd !== "extensionData" && cwd !== "temp") fail$5(`${path2}.cwd`, "unsupported cwd root");
|
|
90
|
+
const envAllowlist = array(entry.envAllowlist, `${path2}.envAllowlist`).map((name, index) => {
|
|
91
|
+
const value2 = string(name, `${path2}.envAllowlist[${index}]`);
|
|
92
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(value2) || /^(PATH|LD_PRELOAD|LD_LIBRARY_PATH|LD_AUDIT|DYLD_|NODE_OPTIONS|BASH_ENV|GIT_SSH_COMMAND|GIT_CONFIG_|PYTHONSTARTUP)/.test(value2)) fail$5(`${path2}.envAllowlist[${index}]`, "dangerous environment key");
|
|
93
|
+
return value2;
|
|
94
|
+
});
|
|
95
|
+
if (!Number.isInteger(entry.timeoutMs) || Number(entry.timeoutMs) < 1 || Number(entry.timeoutMs) > 3e5) fail$5(`${path2}.timeoutMs`, "invalid timeout");
|
|
96
|
+
if (!Number.isInteger(entry.outputLimitBytes) || Number(entry.outputLimitBytes) < 1 || Number(entry.outputLimitBytes) > 1048576) fail$5(`${path2}.outputLimitBytes`, "invalid output limit");
|
|
97
|
+
if (entry.stdin !== "none") fail$5(`${path2}.stdin`, "stdin must be none");
|
|
98
|
+
const writeScope = entry.writeScope === void 0 ? "none" : entry.writeScope;
|
|
99
|
+
if (writeScope !== "none" && writeScope !== "moai" && writeScope !== "project") fail$5(`${path2}.writeScope`, "unsupported write scope");
|
|
100
|
+
const descendantAllowlist = entry.descendantAllowlist === void 0 ? [] : array(entry.descendantAllowlist, `${path2}.descendantAllowlist`).map((name, index) => {
|
|
101
|
+
const value2 = string(name, `${path2}.descendantAllowlist[${index}]`);
|
|
102
|
+
if (!/^[a-z][a-z0-9._-]{0,63}$/.test(value2) || /^(?:sh|bash|zsh|fish|cmd|powershell|node|python|npm|npx|env|xargs|ssh|tmux|vi|vim|nvim|nano|less|more)$/i.test(value2)) fail$5(`${path2}.descendantAllowlist[${index}]`, "unsupported descendant");
|
|
103
|
+
return value2;
|
|
104
|
+
});
|
|
105
|
+
if (descendantAllowlist.length > 8) fail$5(`${path2}.descendantAllowlist`, "expected at most 8 descendants");
|
|
106
|
+
if (writeScope !== "none" && descendantAllowlist.length === 0) fail$5(`${path2}.descendantAllowlist`, "write profiles must declare descendants");
|
|
107
|
+
return {
|
|
108
|
+
id: profileId,
|
|
109
|
+
executable,
|
|
110
|
+
argv,
|
|
111
|
+
parameters,
|
|
112
|
+
cwd,
|
|
113
|
+
envAllowlist,
|
|
114
|
+
timeoutMs: Number(entry.timeoutMs),
|
|
115
|
+
outputLimitBytes: Number(entry.outputLimitBytes),
|
|
116
|
+
stdin: "none",
|
|
117
|
+
...entry.writeScope === void 0 ? {} : { writeScope },
|
|
118
|
+
...entry.descendantAllowlist === void 0 ? {} : { descendantAllowlist }
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
if (!profiles.length || new Set(profiles.map((profile) => profile.id)).size !== profiles.length) fail$5("manifest.machineCommands.profiles", "duplicate profile id");
|
|
123
|
+
return { version: 1, profiles };
|
|
124
|
+
}
|
|
125
|
+
const ID = /^[a-z][a-z0-9.-]{0,127}$/;
|
|
126
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
127
|
+
const NAME = /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/;
|
|
128
|
+
function fail$4() {
|
|
129
|
+
throw new Error("INVALID_TOOL_DESCRIPTOR");
|
|
130
|
+
}
|
|
131
|
+
function row(value) {
|
|
132
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail$4();
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
function keys(value, allowed) {
|
|
136
|
+
if (Object.keys(value).some((key) => !allowed.includes(key))) fail$4();
|
|
137
|
+
}
|
|
138
|
+
function text$2(value, limit = 4096) {
|
|
139
|
+
if (typeof value !== "string" || !value || value.length > limit || /[\x00-\x1f]/.test(value)) fail$4();
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
function path(value) {
|
|
143
|
+
const parsed = text$2(value);
|
|
144
|
+
if (parsed.startsWith("/") || parsed.includes("\\") || parsed.includes(":") || parsed.split("/").some((part) => !part || part === ".." || part === ".")) fail$4();
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
function parseManagedLocalTool(value, extensionId2) {
|
|
148
|
+
const root = row(value);
|
|
149
|
+
keys(root, ["version", "id", "toolVersion", "platforms", "operations"]);
|
|
150
|
+
if (root.version !== 1 || !ID.test(text$2(root.id)) || !String(root.id).startsWith(extensionId2 + ".") || !/^\d+\.\d+\.\d+$/.test(text$2(root.toolVersion))) fail$4();
|
|
151
|
+
const platforms = {};
|
|
152
|
+
for (const [platform, raw] of Object.entries(row(root.platforms))) {
|
|
153
|
+
if (!/^(darwin|win32)-(arm64|x64)$/.test(platform)) fail$4();
|
|
154
|
+
const source = row(raw);
|
|
155
|
+
keys(source, ["url", "sha256", "archiveRoot", "executable", "executableSha256", "appBundle", "signingTeamId", "existingExecutables"]);
|
|
156
|
+
const url = new URL(text$2(source.url));
|
|
157
|
+
if (url.protocol !== "https:" || url.username || url.password || !/\.(tar\.gz|zip)$/.test(url.pathname) || !HASH.test(text$2(source.sha256)) || !HASH.test(text$2(source.executableSha256))) fail$4();
|
|
158
|
+
if (!Array.isArray(source.existingExecutables) || source.existingExecutables.length > 4 || source.existingExecutables.some((entry) => typeof entry !== "string" || /[\x00-\x1f]/.test(entry) || !/^(\/|[A-Za-z]:\\)/.test(entry) || /(^|[\\/])\.\.([\\/]|$)/.test(entry))) fail$4();
|
|
159
|
+
if (platform.startsWith("darwin") && (!source.appBundle || !/^[A-Z0-9]{10}$/.test(text$2(source.signingTeamId)))) fail$4();
|
|
160
|
+
platforms[platform] = {
|
|
161
|
+
url: url.toString(),
|
|
162
|
+
sha256: String(source.sha256),
|
|
163
|
+
archiveRoot: path(source.archiveRoot),
|
|
164
|
+
executable: path(source.executable),
|
|
165
|
+
executableSha256: String(source.executableSha256),
|
|
166
|
+
existingExecutables: source.existingExecutables,
|
|
167
|
+
...source.appBundle ? { appBundle: path(source.appBundle), signingTeamId: text$2(source.signingTeamId) } : {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const operations = {};
|
|
171
|
+
for (const [name, raw] of Object.entries(row(root.operations))) {
|
|
172
|
+
if (!NAME.test(name)) fail$4();
|
|
173
|
+
const source = row(raw);
|
|
174
|
+
keys(source, ["permission", "argv", "payload", "inputs"]);
|
|
175
|
+
if (!["inspect", "control"].includes(String(source.permission)) || !Array.isArray(source.argv) || source.argv.length > 16) fail$4();
|
|
176
|
+
const argv = source.argv.map((arg) => {
|
|
177
|
+
const value2 = text$2(arg);
|
|
178
|
+
if (value2.includes("{{") && value2 !== "{{json}}") fail$4();
|
|
179
|
+
return value2;
|
|
180
|
+
});
|
|
181
|
+
const inputs = {};
|
|
182
|
+
for (const [name2, raw2] of Object.entries(source.inputs === void 0 ? {} : row(source.inputs))) {
|
|
183
|
+
if (!NAME.test(name2) || /^(session|token|secret|password|command|argv|env)$/i.test(name2)) fail$4();
|
|
184
|
+
const input = row(raw2);
|
|
185
|
+
if (input.type === "integer") {
|
|
186
|
+
keys(input, ["type", "min", "max"]);
|
|
187
|
+
if (!Number.isSafeInteger(input.min) || !Number.isSafeInteger(input.max) || Number(input.min) > Number(input.max)) fail$4();
|
|
188
|
+
inputs[name2] = { type: "integer", min: Number(input.min), max: Number(input.max) };
|
|
189
|
+
} else if (input.type === "string") {
|
|
190
|
+
keys(input, ["type", "maxLength", "values"]);
|
|
191
|
+
if (!Number.isInteger(input.maxLength) || Number(input.maxLength) < 1 || Number(input.maxLength) > 4096 || input.values !== void 0 && (!Array.isArray(input.values) || input.values.some((item) => typeof item !== "string"))) fail$4();
|
|
192
|
+
inputs[name2] = { type: "string", maxLength: Number(input.maxLength), ...input.values ? { values: input.values } : {} };
|
|
193
|
+
} else fail$4();
|
|
194
|
+
}
|
|
195
|
+
const operation = {
|
|
196
|
+
permission: source.permission,
|
|
197
|
+
argv,
|
|
198
|
+
inputs,
|
|
199
|
+
...source.payload === void 0 ? {} : { payload: source.payload }
|
|
200
|
+
};
|
|
201
|
+
if (argv.includes("{{json}}") !== (source.payload !== void 0)) fail$4();
|
|
202
|
+
managedToolArguments(operation, Object.fromEntries(Object.entries(inputs).map(([key, input]) => [key, input.type === "integer" ? input.min : input.values?.[0] ?? "test"])), "validated");
|
|
203
|
+
operations[name] = operation;
|
|
204
|
+
}
|
|
205
|
+
if (!Object.keys(platforms).length || !Object.keys(operations).length || Object.keys(operations).length > 32) fail$4();
|
|
206
|
+
return { version: 1, id: String(root.id), toolVersion: String(root.toolVersion), platforms, operations };
|
|
207
|
+
}
|
|
208
|
+
function managedToolArguments(operation, parameters, session) {
|
|
209
|
+
const inputs = operation.inputs ?? {};
|
|
210
|
+
const invalid = () => {
|
|
211
|
+
throw new Error("INVALID_TOOL_INPUT");
|
|
212
|
+
};
|
|
213
|
+
if (Object.keys(parameters).some((key) => !Object.hasOwn(inputs, key))) invalid();
|
|
214
|
+
for (const [key, schema] of Object.entries(inputs)) {
|
|
215
|
+
const value = parameters[key];
|
|
216
|
+
if (schema.type === "integer") {
|
|
217
|
+
if (!Number.isSafeInteger(value) || Number(value) < schema.min || Number(value) > schema.max) invalid();
|
|
218
|
+
} else if (typeof value !== "string" || !value || value.length > schema.maxLength || /[\x00-\x1f]/.test(value) || schema.values && !schema.values.includes(value)) invalid();
|
|
219
|
+
}
|
|
220
|
+
const render = (value) => {
|
|
221
|
+
if (Array.isArray(value)) return value.map(render);
|
|
222
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, render(child)]));
|
|
223
|
+
if (typeof value !== "string") return value;
|
|
224
|
+
const exact = value.match(/^\{\{([A-Za-z][A-Za-z0-9_]*)\}\}$/);
|
|
225
|
+
if (exact) {
|
|
226
|
+
if (exact[1] === "session") return session;
|
|
227
|
+
if (!Object.hasOwn(inputs, exact[1])) invalid();
|
|
228
|
+
return parameters[exact[1]];
|
|
229
|
+
}
|
|
230
|
+
return value.replace(/\{\{([A-Za-z][A-Za-z0-9_]*)\}\}/g, (_, key) => {
|
|
231
|
+
if (key === "session") return session;
|
|
232
|
+
if (!Object.hasOwn(inputs, key)) invalid();
|
|
233
|
+
return String(parameters[key]);
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
const payload = operation.payload === void 0 ? void 0 : JSON.stringify(render(operation.payload));
|
|
237
|
+
return operation.argv.map((arg) => arg === "{{json}}" ? payload : arg);
|
|
238
|
+
}
|
|
239
|
+
const EXTENSION_PANEL_SURFACE_SIZES = ["compact", "standard", "wide"];
|
|
240
|
+
function isExtensionPanelSurfaceSize(value) {
|
|
241
|
+
return EXTENSION_PANEL_SURFACE_SIZES.some((size) => size === value);
|
|
242
|
+
}
|
|
10
243
|
const EXTENSION_ARTIFACT_ACTION_OPERATIONS = ["publishPublic"];
|
|
11
244
|
const EXTENSION_ARTIFACT_SOURCE_TYPES = ["project-file", "personal-chat-file"];
|
|
12
|
-
const ID_PATTERN$
|
|
245
|
+
const ID_PATTERN$1 = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
|
|
13
246
|
const LOCALE_PATTERN$1 = /^[a-z]{2,3}(?:-[A-Z]{2})?$/;
|
|
14
247
|
const EXTENSION_PATTERN = /^[a-z0-9]+$/;
|
|
15
248
|
const MAX_ACTIONS = 32;
|
|
16
249
|
const MAX_TITLE_CHARACTERS = 120;
|
|
17
250
|
const MAX_LOCALES = 16;
|
|
18
251
|
const MAX_EXTENSIONS = 16;
|
|
19
|
-
function fail$
|
|
20
|
-
throw new Error(`${
|
|
252
|
+
function fail$3(path2, detail) {
|
|
253
|
+
throw new Error(`${path2}: ${detail}`);
|
|
21
254
|
}
|
|
22
|
-
function record
|
|
23
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) fail$
|
|
255
|
+
function record(value, path2) {
|
|
256
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail$3(path2, "expected object");
|
|
24
257
|
return value;
|
|
25
258
|
}
|
|
26
|
-
function exactKeys$1(value, required, optional,
|
|
259
|
+
function exactKeys$1(value, required, optional, path2) {
|
|
27
260
|
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
28
261
|
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
29
|
-
if (unknown) fail$
|
|
262
|
+
if (unknown) fail$3(path2, `unknown field ${unknown}`);
|
|
30
263
|
const missing = required.find((key) => !(key in value));
|
|
31
|
-
if (missing) fail$
|
|
264
|
+
if (missing) fail$3(path2, `missing field ${missing}`);
|
|
32
265
|
}
|
|
33
|
-
function text$1(value,
|
|
266
|
+
function text$1(value, path2) {
|
|
34
267
|
if (typeof value !== "string" || !value || value.trim() !== value) {
|
|
35
|
-
fail$
|
|
268
|
+
fail$3(path2, "expected non-empty trimmed string");
|
|
36
269
|
}
|
|
37
270
|
return value;
|
|
38
271
|
}
|
|
39
|
-
function title(value,
|
|
40
|
-
const parsed = text$1(value,
|
|
41
|
-
if ([...parsed].length > MAX_TITLE_CHARACTERS) fail$
|
|
272
|
+
function title(value, path2) {
|
|
273
|
+
const parsed = text$1(value, path2);
|
|
274
|
+
if ([...parsed].length > MAX_TITLE_CHARACTERS) fail$3(path2, "title exceeds 120 characters");
|
|
42
275
|
return parsed;
|
|
43
276
|
}
|
|
44
|
-
function uniqueList(values,
|
|
45
|
-
if (new Set(values).size !== values.length) fail$
|
|
277
|
+
function uniqueList(values, path2) {
|
|
278
|
+
if (new Set(values).size !== values.length) fail$3(path2, "duplicate value");
|
|
46
279
|
return values;
|
|
47
280
|
}
|
|
48
|
-
function parseLocalizations(value,
|
|
49
|
-
const localizations = record
|
|
50
|
-
if (Object.keys(localizations).length > MAX_LOCALES) fail$
|
|
281
|
+
function parseLocalizations(value, path2) {
|
|
282
|
+
const localizations = record(value, path2);
|
|
283
|
+
if (Object.keys(localizations).length > MAX_LOCALES) fail$3(path2, "too many locales");
|
|
51
284
|
return Object.fromEntries(Object.entries(localizations).map(([locale, raw]) => {
|
|
52
|
-
if (!LOCALE_PATTERN$1.test(locale)) fail$
|
|
53
|
-
const localizationPath = `${
|
|
54
|
-
const localization = record
|
|
285
|
+
if (!LOCALE_PATTERN$1.test(locale)) fail$3(path2, `invalid locale ${locale}`);
|
|
286
|
+
const localizationPath = `${path2}.${locale}`;
|
|
287
|
+
const localization = record(raw, localizationPath);
|
|
55
288
|
exactKeys$1(localization, ["title"], [], localizationPath);
|
|
56
289
|
return [locale, { title: title(localization.title, `${localizationPath}.title`) }];
|
|
57
290
|
}));
|
|
58
291
|
}
|
|
59
|
-
function parseSourceTypes(value,
|
|
60
|
-
if (!Array.isArray(value) || value.length === 0) fail$
|
|
292
|
+
function parseSourceTypes(value, path2) {
|
|
293
|
+
if (!Array.isArray(value) || value.length === 0) fail$3(path2, "expected non-empty array");
|
|
61
294
|
const parsed = value.map((sourceType, index) => {
|
|
62
295
|
if (!EXTENSION_ARTIFACT_SOURCE_TYPES.includes(sourceType)) {
|
|
63
|
-
fail$
|
|
296
|
+
fail$3(`${path2}[${index}]`, "unsupported source type");
|
|
64
297
|
}
|
|
65
298
|
return sourceType;
|
|
66
299
|
});
|
|
67
|
-
return uniqueList(parsed,
|
|
300
|
+
return uniqueList(parsed, path2);
|
|
68
301
|
}
|
|
69
|
-
function parseExtensions(value,
|
|
302
|
+
function parseExtensions(value, path2) {
|
|
70
303
|
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_EXTENSIONS) {
|
|
71
|
-
fail$
|
|
304
|
+
fail$3(path2, "expected 1-16 extensions");
|
|
72
305
|
}
|
|
73
306
|
const parsed = value.map((extension, index) => {
|
|
74
307
|
if (typeof extension !== "string" || !EXTENSION_PATTERN.test(extension)) {
|
|
75
|
-
fail$
|
|
308
|
+
fail$3(`${path2}[${index}]`, "expected lowercase extension without a dot");
|
|
76
309
|
}
|
|
77
310
|
return extension;
|
|
78
311
|
});
|
|
79
|
-
return uniqueList(parsed,
|
|
312
|
+
return uniqueList(parsed, path2);
|
|
80
313
|
}
|
|
81
|
-
function parseExtensionArtifactActions(value,
|
|
82
|
-
if (!Array.isArray(value) || value.length > MAX_ACTIONS) fail$
|
|
314
|
+
function parseExtensionArtifactActions(value, path2 = "manifest.contributes.artifactActions") {
|
|
315
|
+
if (!Array.isArray(value) || value.length > MAX_ACTIONS) fail$3(path2, "expected at most 32 actions");
|
|
83
316
|
return value.map((raw, index) => {
|
|
84
|
-
const actionPath = `${
|
|
85
|
-
const action = record
|
|
317
|
+
const actionPath = `${path2}[${index}]`;
|
|
318
|
+
const action = record(raw, actionPath);
|
|
86
319
|
exactKeys$1(action, ["id", "title", "operation", "when"], ["localizations"], actionPath);
|
|
87
320
|
const parsedId = text$1(action.id, `${actionPath}.id`);
|
|
88
|
-
if (!ID_PATTERN$
|
|
321
|
+
if (!ID_PATTERN$1.test(parsedId) || parsedId.includes("..")) fail$3(`${actionPath}.id`, "invalid stable identifier");
|
|
89
322
|
if (!EXTENSION_ARTIFACT_ACTION_OPERATIONS.includes(action.operation)) {
|
|
90
|
-
fail$
|
|
323
|
+
fail$3(`${actionPath}.operation`, "unsupported operation");
|
|
91
324
|
}
|
|
92
|
-
const when = record
|
|
325
|
+
const when = record(action.when, `${actionPath}.when`);
|
|
93
326
|
exactKeys$1(when, ["sourceTypes", "extensions"], [], `${actionPath}.when`);
|
|
94
327
|
return {
|
|
95
328
|
id: parsedId,
|
|
@@ -119,13 +352,14 @@ const CHANNEL_COMMAND_SCHEMA = Object.freeze({
|
|
|
119
352
|
// only reach `/clear` by typing it as a message, while every other command sits in the picker.
|
|
120
353
|
// `reset` is deliberately not here: Core carries it as an unlisted alias of the same intent, and
|
|
121
354
|
// registering both would put two identical entries in that picker.
|
|
122
|
-
clear: Object.freeze([])
|
|
355
|
+
clear: Object.freeze([]),
|
|
356
|
+
settings: Object.freeze(["agent", "model", "effort"])
|
|
123
357
|
});
|
|
124
358
|
const MAX_DESCRIPTION = 100;
|
|
125
359
|
const ENTRY_KEYS$1 = /* @__PURE__ */ new Set(["id", "description", "options"]);
|
|
126
360
|
const OPTION_KEYS = /* @__PURE__ */ new Set(["name", "description", "required"]);
|
|
127
|
-
function fail$
|
|
128
|
-
throw new Error(`${
|
|
361
|
+
function fail$2(path2, detail) {
|
|
362
|
+
throw new Error(`${path2}: ${detail}`);
|
|
129
363
|
}
|
|
130
364
|
function hasControlCharacter(value) {
|
|
131
365
|
for (const character of value) {
|
|
@@ -134,31 +368,31 @@ function hasControlCharacter(value) {
|
|
|
134
368
|
}
|
|
135
369
|
return false;
|
|
136
370
|
}
|
|
137
|
-
function description(value,
|
|
371
|
+
function description(value, path2) {
|
|
138
372
|
if (typeof value !== "string" || value.trim().length === 0 || value.length > MAX_DESCRIPTION) {
|
|
139
|
-
fail$
|
|
373
|
+
fail$2(path2, "invalid description");
|
|
140
374
|
}
|
|
141
|
-
if (hasControlCharacter(value)) fail$
|
|
375
|
+
if (hasControlCharacter(value)) fail$2(path2, "invalid description");
|
|
142
376
|
return value;
|
|
143
377
|
}
|
|
144
|
-
function parseChannelCommandDeclarations(value,
|
|
145
|
-
if (!Array.isArray(value)) fail$
|
|
378
|
+
function parseChannelCommandDeclarations(value, path2) {
|
|
379
|
+
if (!Array.isArray(value)) fail$2(path2, "expected array");
|
|
146
380
|
const names = Object.keys(CHANNEL_COMMAND_SCHEMA);
|
|
147
381
|
if (value.length === 0 || value.length > names.length) {
|
|
148
|
-
fail$
|
|
382
|
+
fail$2(path2, `expected between 1 and ${names.length} commands`);
|
|
149
383
|
}
|
|
150
384
|
const seen = /* @__PURE__ */ new Set();
|
|
151
385
|
return value.map((item, index) => {
|
|
152
|
-
const entryPath = `${
|
|
153
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) fail$
|
|
386
|
+
const entryPath = `${path2}[${index}]`;
|
|
387
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) fail$2(entryPath, "expected object");
|
|
154
388
|
const source = item;
|
|
155
389
|
const extra = Object.keys(source).find((key) => !ENTRY_KEYS$1.has(key));
|
|
156
|
-
if (extra !== void 0) fail$
|
|
390
|
+
if (extra !== void 0) fail$2(entryPath, `unknown field ${extra}`);
|
|
157
391
|
const id2 = source.id;
|
|
158
392
|
if (typeof id2 !== "string" || !Object.prototype.hasOwnProperty.call(CHANNEL_COMMAND_SCHEMA, id2)) {
|
|
159
|
-
fail$
|
|
393
|
+
fail$2(`${entryPath}.id`, "unknown command");
|
|
160
394
|
}
|
|
161
|
-
if (seen.has(id2)) fail$
|
|
395
|
+
if (seen.has(id2)) fail$2(path2, `duplicate command ${id2}`);
|
|
162
396
|
seen.add(id2);
|
|
163
397
|
return {
|
|
164
398
|
id: id2,
|
|
@@ -167,24 +401,24 @@ function parseChannelCommandDeclarations(value, path) {
|
|
|
167
401
|
};
|
|
168
402
|
});
|
|
169
403
|
}
|
|
170
|
-
function parseOptions(value,
|
|
404
|
+
function parseOptions(value, path2, expected) {
|
|
171
405
|
if (value === void 0) {
|
|
172
|
-
if (expected.length > 0) fail$
|
|
406
|
+
if (expected.length > 0) fail$2(path2, `expected ${expected.length} options`);
|
|
173
407
|
return Object.freeze([]);
|
|
174
408
|
}
|
|
175
|
-
if (!Array.isArray(value)) fail$
|
|
176
|
-
if (value.length !== expected.length) fail$
|
|
409
|
+
if (!Array.isArray(value)) fail$2(path2, "expected array");
|
|
410
|
+
if (value.length !== expected.length) fail$2(path2, `expected ${expected.length} options`);
|
|
177
411
|
return Object.freeze(value.map((item, index) => {
|
|
178
|
-
const optionPath = `${
|
|
179
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) fail$
|
|
412
|
+
const optionPath = `${path2}[${index}]`;
|
|
413
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) fail$2(optionPath, "expected object");
|
|
180
414
|
const source = item;
|
|
181
415
|
const extra = Object.keys(source).find((key) => !OPTION_KEYS.has(key));
|
|
182
|
-
if (extra !== void 0) fail$
|
|
416
|
+
if (extra !== void 0) fail$2(optionPath, `unknown field ${extra}`);
|
|
183
417
|
const spec = expected[index];
|
|
184
418
|
const required = spec.endsWith("!");
|
|
185
419
|
const name = required ? spec.slice(0, -1) : spec;
|
|
186
|
-
if (source.name !== name) fail$
|
|
187
|
-
if (source.required !== required) fail$
|
|
420
|
+
if (source.name !== name) fail$2(`${optionPath}.name`, `expected ${name}`);
|
|
421
|
+
if (source.required !== required) fail$2(`${optionPath}.required`, `expected ${required}`);
|
|
188
422
|
return Object.freeze({
|
|
189
423
|
name,
|
|
190
424
|
required,
|
|
@@ -193,34 +427,34 @@ function parseOptions(value, path, expected) {
|
|
|
193
427
|
}));
|
|
194
428
|
}
|
|
195
429
|
const MAX_CHANNEL_CONTRIBUTIONS = CHANNEL_PROVIDERS.length;
|
|
196
|
-
const ID_PATTERN
|
|
430
|
+
const ID_PATTERN = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
|
|
197
431
|
const ENTRY_KEYS = /* @__PURE__ */ new Set(["id", "provider", "commands"]);
|
|
198
|
-
function fail$
|
|
199
|
-
throw new Error(`${
|
|
432
|
+
function fail$1(path2, detail) {
|
|
433
|
+
throw new Error(`${path2}: ${detail}`);
|
|
200
434
|
}
|
|
201
435
|
function parseExtensionChannelContributions(value) {
|
|
202
|
-
if (!Array.isArray(value)) fail$
|
|
436
|
+
if (!Array.isArray(value)) fail$1("manifest.contributes.channels", "expected array");
|
|
203
437
|
if (value.length === 0 || value.length > MAX_CHANNEL_CONTRIBUTIONS) {
|
|
204
|
-
fail$
|
|
438
|
+
fail$1("manifest.contributes.channels", `expected between 1 and ${MAX_CHANNEL_CONTRIBUTIONS} adapters`);
|
|
205
439
|
}
|
|
206
440
|
const providers = /* @__PURE__ */ new Set();
|
|
207
441
|
return value.map((item, index) => {
|
|
208
|
-
const
|
|
209
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) fail$
|
|
442
|
+
const path2 = `manifest.contributes.channels[${index}]`;
|
|
443
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) fail$1(path2, "expected object");
|
|
210
444
|
const source = item;
|
|
211
445
|
const extra = Object.keys(source).find((key) => !ENTRY_KEYS.has(key));
|
|
212
|
-
if (extra !== void 0) fail$
|
|
446
|
+
if (extra !== void 0) fail$1(path2, `unknown field ${extra}`);
|
|
213
447
|
const id2 = source.id;
|
|
214
|
-
if (typeof id2 !== "string" || !ID_PATTERN
|
|
215
|
-
fail$
|
|
448
|
+
if (typeof id2 !== "string" || !ID_PATTERN.test(id2) || id2.includes("..")) {
|
|
449
|
+
fail$1(`${path2}.id`, "invalid identifier");
|
|
216
450
|
}
|
|
217
451
|
const provider = source.provider;
|
|
218
452
|
if (typeof provider !== "string" || !CHANNEL_PROVIDERS.includes(provider)) {
|
|
219
|
-
fail$
|
|
453
|
+
fail$1(`${path2}.provider`, "unsupported provider");
|
|
220
454
|
}
|
|
221
|
-
if (providers.has(provider)) fail$
|
|
455
|
+
if (providers.has(provider)) fail$1("manifest.contributes.channels", "duplicate provider");
|
|
222
456
|
providers.add(provider);
|
|
223
|
-
const commands = source.commands === void 0 ? void 0 : parseChannelCommandDeclarations(source.commands, `${
|
|
457
|
+
const commands = source.commands === void 0 ? void 0 : parseChannelCommandDeclarations(source.commands, `${path2}.commands`);
|
|
224
458
|
return {
|
|
225
459
|
id: id2,
|
|
226
460
|
provider,
|
|
@@ -237,6 +471,11 @@ const EXTENSION_PERMISSIONS = [
|
|
|
237
471
|
"remoteFiles.read",
|
|
238
472
|
"remoteFiles.write",
|
|
239
473
|
"machine.execute",
|
|
474
|
+
"machine.run",
|
|
475
|
+
"localTools.inspect",
|
|
476
|
+
"localTools.install",
|
|
477
|
+
"localTools.control",
|
|
478
|
+
"localBrowser.setup",
|
|
240
479
|
"network.fetch",
|
|
241
480
|
"notifications.show",
|
|
242
481
|
"storage.read",
|
|
@@ -281,7 +520,6 @@ class UnsupportedExtensionApiVersionError extends Error {
|
|
|
281
520
|
}
|
|
282
521
|
name = "UnsupportedExtensionApiVersionError";
|
|
283
522
|
}
|
|
284
|
-
const ID_PATTERN = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
|
|
285
523
|
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
286
524
|
const ENGINE_RANGE_PATTERN = /^(?:\^|~|>=|<=|>|<)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
287
525
|
const ENTRYPOINT_PATTERN = /\.(?:cjs|mjs|js)$/;
|
|
@@ -292,7 +530,7 @@ const V2_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
|
292
530
|
"browserViewer.install",
|
|
293
531
|
"browserViewer.installChrome"
|
|
294
532
|
]);
|
|
295
|
-
const V3_PERMISSIONS = /* @__PURE__ */ new Set(["artifacts.publishPublic", "assets.read", "assets.write", "files.select", "drafts.prepare"]);
|
|
533
|
+
const V3_PERMISSIONS = /* @__PURE__ */ new Set(["machine.run", "artifacts.publishPublic", "assets.read", "assets.write", "files.select", "drafts.prepare", "localTools.inspect", "localTools.install", "localTools.control", "localBrowser.setup"]);
|
|
296
534
|
const CHANNEL_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
297
535
|
"channels.receive",
|
|
298
536
|
"channels.ack",
|
|
@@ -307,6 +545,8 @@ const TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
307
545
|
"version",
|
|
308
546
|
"apiVersion",
|
|
309
547
|
"channelApiVersion",
|
|
548
|
+
"managedLocalTool",
|
|
549
|
+
"machineCommands",
|
|
310
550
|
"engines",
|
|
311
551
|
"entrypoint",
|
|
312
552
|
"permissions",
|
|
@@ -342,14 +582,11 @@ function engineSupports(range, version) {
|
|
|
342
582
|
if (operator === "<") return compared < 0;
|
|
343
583
|
return compared === 0;
|
|
344
584
|
}
|
|
345
|
-
function fail$1(path, detail) {
|
|
346
|
-
throw new Error(`${path}: ${detail}`);
|
|
347
|
-
}
|
|
348
585
|
function parseApiVersion(value, options) {
|
|
349
586
|
const current = options.supportedApiVersion;
|
|
350
587
|
const floor = options.minimumSupportedApiVersion ?? current;
|
|
351
588
|
if (floor > current || current - floor + 1 > MAX_EXTENSION_API_WINDOW_WIDTH) {
|
|
352
|
-
fail$
|
|
589
|
+
fail$5(
|
|
353
590
|
"manifest.apiVersion",
|
|
354
591
|
`invalid Desktop API support window [${floor}, ${current}]`
|
|
355
592
|
);
|
|
@@ -359,65 +596,34 @@ function parseApiVersion(value, options) {
|
|
|
359
596
|
}
|
|
360
597
|
return value;
|
|
361
598
|
}
|
|
362
|
-
function
|
|
363
|
-
|
|
364
|
-
return value;
|
|
365
|
-
}
|
|
366
|
-
function string(value, path) {
|
|
367
|
-
if (typeof value !== "string" || value.trim() !== value || value.length === 0) {
|
|
368
|
-
fail$1(path, "expected non-empty trimmed string");
|
|
369
|
-
}
|
|
370
|
-
return value;
|
|
371
|
-
}
|
|
372
|
-
function id(value, path) {
|
|
373
|
-
const parsed = string(value, path);
|
|
374
|
-
if (!ID_PATTERN.test(parsed) || parsed.includes("..")) fail$1(path, "invalid stable identifier");
|
|
375
|
-
return parsed;
|
|
376
|
-
}
|
|
377
|
-
function relativePath(value, path, requireScript = false) {
|
|
378
|
-
const parsed = string(value, path);
|
|
599
|
+
function relativePath(value, path2, requireScript = false) {
|
|
600
|
+
const parsed = string(value, path2);
|
|
379
601
|
const segments = parsed.split("/");
|
|
380
602
|
if (parsed.startsWith("/") || parsed.includes("\\") || segments.some((segment) => segment === "" || segment === "." || segment === "..") || requireScript && !ENTRYPOINT_PATTERN.test(parsed)) {
|
|
381
|
-
fail$
|
|
603
|
+
fail$5(path2, "expected safe relative path");
|
|
382
604
|
}
|
|
383
605
|
return parsed;
|
|
384
606
|
}
|
|
385
|
-
function array(value, path) {
|
|
386
|
-
if (!Array.isArray(value)) fail$1(path, "expected array");
|
|
387
|
-
return value;
|
|
388
|
-
}
|
|
389
|
-
function assertOnlyKeys(value, allowed, path) {
|
|
390
|
-
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
391
|
-
if (unknown) fail$1(path, `unknown field ${unknown}`);
|
|
392
|
-
}
|
|
393
|
-
function contributionList(value, path, allowedKeys, parse) {
|
|
394
|
-
return array(value, path).map((raw, index) => {
|
|
395
|
-
const entryPath = `${path}[${index}]`;
|
|
396
|
-
const entry = record(raw, entryPath);
|
|
397
|
-
assertOnlyKeys(entry, new Set(allowedKeys), entryPath);
|
|
398
|
-
return parse(entry, entryPath);
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
607
|
function parseChannelApiVersion(value, apiVersion) {
|
|
402
608
|
if (value === void 0) return void 0;
|
|
403
|
-
if (apiVersion < 3) fail$
|
|
609
|
+
if (apiVersion < 3) fail$5("manifest.channelApiVersion", "requires Extension API version 3");
|
|
404
610
|
if (typeof value !== "number" || !Number.isInteger(value) || value < SAYCODE_CHANNEL_MINIMUM_API_VERSION || value > SAYCODE_CHANNEL_API_VERSION) throw new UnsupportedChannelApiVersionError(value);
|
|
405
611
|
return value;
|
|
406
612
|
}
|
|
407
613
|
function parseContributions(value, apiVersion, channelApiVersion) {
|
|
408
|
-
const parsed = record(value, "manifest.contributes");
|
|
614
|
+
const parsed = record$1(value, "manifest.contributes");
|
|
409
615
|
assertOnlyKeys(parsed, CONTRIBUTION_KEYS, "manifest.contributes");
|
|
410
616
|
if (parsed.channels !== void 0) {
|
|
411
|
-
if (apiVersion < 3) fail$
|
|
617
|
+
if (apiVersion < 3) fail$5("manifest.contributes.channels", "requires Extension API version 3");
|
|
412
618
|
if (channelApiVersion === void 0) {
|
|
413
|
-
fail$
|
|
619
|
+
fail$5("manifest.contributes.channels", "requires manifest.channelApiVersion");
|
|
414
620
|
}
|
|
415
621
|
}
|
|
416
622
|
if (apiVersion < 2 && parsed.machineActions !== void 0) {
|
|
417
|
-
fail$
|
|
623
|
+
fail$5("manifest.contributes.machineActions", "requires Extension API version 2");
|
|
418
624
|
}
|
|
419
625
|
if (apiVersion < 3 && parsed.artifactActions !== void 0) {
|
|
420
|
-
fail$
|
|
626
|
+
fail$5("manifest.contributes.artifactActions", "requires Extension API version 3");
|
|
421
627
|
}
|
|
422
628
|
const result = {};
|
|
423
629
|
if (parsed.commands !== void 0) {
|
|
@@ -425,10 +631,10 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
425
631
|
parsed.commands,
|
|
426
632
|
"manifest.contributes.commands",
|
|
427
633
|
["id", "title", "panelId"],
|
|
428
|
-
(entry,
|
|
429
|
-
id: id(entry.id, `${
|
|
430
|
-
title: string(entry.title, `${
|
|
431
|
-
...entry.panelId === void 0 ? {} : { panelId: id(entry.panelId, `${
|
|
634
|
+
(entry, path2) => ({
|
|
635
|
+
id: id(entry.id, `${path2}.id`),
|
|
636
|
+
title: string(entry.title, `${path2}.title`),
|
|
637
|
+
...entry.panelId === void 0 ? {} : { panelId: id(entry.panelId, `${path2}.panelId`) }
|
|
432
638
|
})
|
|
433
639
|
);
|
|
434
640
|
}
|
|
@@ -437,12 +643,12 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
437
643
|
parsed.settings,
|
|
438
644
|
"manifest.contributes.settings",
|
|
439
645
|
["id", "title", "type"],
|
|
440
|
-
(entry,
|
|
441
|
-
const type = string(entry.type, `${
|
|
646
|
+
(entry, path2) => {
|
|
647
|
+
const type = string(entry.type, `${path2}.type`);
|
|
442
648
|
if (type !== "boolean" && type !== "number" && type !== "string") {
|
|
443
|
-
fail$
|
|
649
|
+
fail$5(`${path2}.type`, "unsupported setting type");
|
|
444
650
|
}
|
|
445
|
-
return { id: id(entry.id, `${
|
|
651
|
+
return { id: id(entry.id, `${path2}.id`), title: string(entry.title, `${path2}.title`), type };
|
|
446
652
|
}
|
|
447
653
|
);
|
|
448
654
|
}
|
|
@@ -450,11 +656,12 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
450
656
|
result.panels = contributionList(
|
|
451
657
|
parsed.panels,
|
|
452
658
|
"manifest.contributes.panels",
|
|
453
|
-
["id", "title", "entrypoint"],
|
|
454
|
-
(entry,
|
|
455
|
-
id: id(entry.id, `${
|
|
456
|
-
title: string(entry.title, `${
|
|
457
|
-
entrypoint: relativePath(entry.entrypoint, `${
|
|
659
|
+
["id", "title", "entrypoint", "surfaceSize"],
|
|
660
|
+
(entry, path2) => ({
|
|
661
|
+
id: id(entry.id, `${path2}.id`),
|
|
662
|
+
title: string(entry.title, `${path2}.title`),
|
|
663
|
+
entrypoint: relativePath(entry.entrypoint, `${path2}.entrypoint`),
|
|
664
|
+
...entry.surfaceSize === void 0 ? {} : { surfaceSize: isExtensionPanelSurfaceSize(entry.surfaceSize) ? entry.surfaceSize : fail$5(`${path2}.surfaceSize`, "expected compact, standard or wide") }
|
|
458
665
|
})
|
|
459
666
|
);
|
|
460
667
|
}
|
|
@@ -472,15 +679,15 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
472
679
|
"assetsRoot",
|
|
473
680
|
"localizations"
|
|
474
681
|
],
|
|
475
|
-
(entry,
|
|
682
|
+
(entry, path2) => {
|
|
476
683
|
let localizations;
|
|
477
684
|
if (entry.localizations !== void 0) {
|
|
478
|
-
const rawLocalizations = record(entry.localizations, `${
|
|
685
|
+
const rawLocalizations = record$1(entry.localizations, `${path2}.localizations`);
|
|
479
686
|
localizations = {};
|
|
480
687
|
for (const [locale, rawLocalization] of Object.entries(rawLocalizations)) {
|
|
481
|
-
if (!LOCALE_PATTERN.test(locale)) fail$
|
|
482
|
-
const localizationPath = `${
|
|
483
|
-
const localization = record(rawLocalization, localizationPath);
|
|
688
|
+
if (!LOCALE_PATTERN.test(locale)) fail$5(`${path2}.localizations`, `invalid locale ${locale}`);
|
|
689
|
+
const localizationPath = `${path2}.localizations.${locale}`;
|
|
690
|
+
const localization = record$1(rawLocalization, localizationPath);
|
|
484
691
|
assertOnlyKeys(
|
|
485
692
|
localization,
|
|
486
693
|
/* @__PURE__ */ new Set(["title", "description", "firstPrompt"]),
|
|
@@ -494,13 +701,13 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
494
701
|
}
|
|
495
702
|
}
|
|
496
703
|
return {
|
|
497
|
-
id: id(entry.id, `${
|
|
498
|
-
title: string(entry.title, `${
|
|
499
|
-
assetsRoot: relativePath(entry.assetsRoot, `${
|
|
500
|
-
...entry.description === void 0 ? {} : { description: string(entry.description, `${
|
|
501
|
-
...entry.stack === void 0 ? {} : { stack: string(entry.stack, `${
|
|
502
|
-
...entry.firstPrompt === void 0 ? {} : { firstPrompt: string(entry.firstPrompt, `${
|
|
503
|
-
...entry.devServerCommand === void 0 ? {} : { devServerCommand: string(entry.devServerCommand, `${
|
|
704
|
+
id: id(entry.id, `${path2}.id`),
|
|
705
|
+
title: string(entry.title, `${path2}.title`),
|
|
706
|
+
assetsRoot: relativePath(entry.assetsRoot, `${path2}.assetsRoot`),
|
|
707
|
+
...entry.description === void 0 ? {} : { description: string(entry.description, `${path2}.description`) },
|
|
708
|
+
...entry.stack === void 0 ? {} : { stack: string(entry.stack, `${path2}.stack`) },
|
|
709
|
+
...entry.firstPrompt === void 0 ? {} : { firstPrompt: string(entry.firstPrompt, `${path2}.firstPrompt`) },
|
|
710
|
+
...entry.devServerCommand === void 0 ? {} : { devServerCommand: string(entry.devServerCommand, `${path2}.devServerCommand`) },
|
|
504
711
|
...localizations === void 0 ? {} : { localizations }
|
|
505
712
|
};
|
|
506
713
|
}
|
|
@@ -511,14 +718,14 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
511
718
|
parsed.machineActions,
|
|
512
719
|
"manifest.contributes.machineActions",
|
|
513
720
|
["id", "title", "command", "when"],
|
|
514
|
-
(entry,
|
|
515
|
-
const when = record(entry.when, `${
|
|
516
|
-
assertOnlyKeys(when, /* @__PURE__ */ new Set(["online"]), `${
|
|
517
|
-
if (when.online !== true) fail$
|
|
721
|
+
(entry, path2) => {
|
|
722
|
+
const when = record$1(entry.when, `${path2}.when`);
|
|
723
|
+
assertOnlyKeys(when, /* @__PURE__ */ new Set(["online"]), `${path2}.when`);
|
|
724
|
+
if (when.online !== true) fail$5(`${path2}.when.online`, "expected true");
|
|
518
725
|
return {
|
|
519
|
-
id: id(entry.id, `${
|
|
520
|
-
title: string(entry.title, `${
|
|
521
|
-
command: id(entry.command, `${
|
|
726
|
+
id: id(entry.id, `${path2}.id`),
|
|
727
|
+
title: string(entry.title, `${path2}.title`),
|
|
728
|
+
command: id(entry.command, `${path2}.command`),
|
|
522
729
|
when: { online: true }
|
|
523
730
|
};
|
|
524
731
|
}
|
|
@@ -539,25 +746,25 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
539
746
|
...result.artifactActions ?? [],
|
|
540
747
|
...result.channels ?? []
|
|
541
748
|
].map((entry) => entry.id);
|
|
542
|
-
if (new Set(ids).size !== ids.length) fail$
|
|
749
|
+
if (new Set(ids).size !== ids.length) fail$5("manifest.contributes", "duplicate contribution id");
|
|
543
750
|
return result;
|
|
544
751
|
}
|
|
545
752
|
function parseExtensionManifest(value, options) {
|
|
546
|
-
const manifest = record(value, "manifest");
|
|
753
|
+
const manifest = record$1(value, "manifest");
|
|
547
754
|
assertOnlyKeys(manifest, TOP_LEVEL_KEYS, "manifest");
|
|
548
755
|
const parsedId = id(manifest.id, "manifest.id");
|
|
549
756
|
const version = string(manifest.version, "manifest.version");
|
|
550
|
-
if (!SEMVER_PATTERN.test(version)) fail$
|
|
757
|
+
if (!SEMVER_PATTERN.test(version)) fail$5("manifest.version", "expected semantic version");
|
|
551
758
|
const apiVersion = parseApiVersion(manifest.apiVersion, options);
|
|
552
759
|
const channelApiVersion = parseChannelApiVersion(manifest.channelApiVersion, apiVersion);
|
|
553
|
-
const engines = record(manifest.engines, "manifest.engines");
|
|
760
|
+
const engines = record$1(manifest.engines, "manifest.engines");
|
|
554
761
|
assertOnlyKeys(engines, /* @__PURE__ */ new Set(["saycode"]), "manifest.engines");
|
|
555
762
|
const saycodeEngine = string(engines.saycode, "manifest.engines.saycode");
|
|
556
763
|
if (!ENGINE_RANGE_PATTERN.test(saycodeEngine)) {
|
|
557
|
-
fail$
|
|
764
|
+
fail$5("manifest.engines.saycode", "expected simple semantic version range");
|
|
558
765
|
}
|
|
559
766
|
if (options.saycodeVersion && !engineSupports(saycodeEngine, options.saycodeVersion)) {
|
|
560
|
-
fail$
|
|
767
|
+
fail$5(
|
|
561
768
|
"manifest.engines.saycode",
|
|
562
769
|
`extension requires Saycode ${saycodeEngine}, but Desktop provides ${options.saycodeVersion}`
|
|
563
770
|
);
|
|
@@ -565,51 +772,57 @@ function parseExtensionManifest(value, options) {
|
|
|
565
772
|
const permissions = array(manifest.permissions, "manifest.permissions").map((permission, index) => {
|
|
566
773
|
const parsed = string(permission, `manifest.permissions[${index}]`);
|
|
567
774
|
if (!EXTENSION_PERMISSIONS.includes(parsed)) {
|
|
568
|
-
fail$
|
|
775
|
+
fail$5("manifest.permissions", `unknown permission ${parsed}`);
|
|
569
776
|
}
|
|
570
777
|
const typed = parsed;
|
|
571
778
|
if (apiVersion < 2 && V2_PERMISSIONS.has(typed)) {
|
|
572
|
-
fail$
|
|
779
|
+
fail$5(`manifest.permissions[${index}]`, "requires Extension API version 2");
|
|
573
780
|
}
|
|
574
781
|
if (apiVersion < 3 && V3_PERMISSIONS.has(typed)) {
|
|
575
|
-
fail$
|
|
782
|
+
fail$5(`manifest.permissions[${index}]`, "requires Extension API version 3");
|
|
576
783
|
}
|
|
577
784
|
if (CHANNEL_PERMISSIONS.has(typed)) {
|
|
578
|
-
if (apiVersion < 3) fail$
|
|
785
|
+
if (apiVersion < 3) fail$5(`manifest.permissions[${index}]`, "requires Extension API version 3");
|
|
579
786
|
if (channelApiVersion === void 0) {
|
|
580
|
-
fail$
|
|
787
|
+
fail$5(`manifest.permissions[${index}]`, "requires manifest.channelApiVersion");
|
|
581
788
|
}
|
|
582
789
|
}
|
|
583
790
|
return typed;
|
|
584
791
|
});
|
|
585
792
|
if (new Set(permissions).size !== permissions.length) {
|
|
586
|
-
fail$
|
|
793
|
+
fail$5("manifest.permissions", "duplicate permission");
|
|
794
|
+
}
|
|
795
|
+
const managedLocalTool = manifest.managedLocalTool === void 0 ? void 0 : parseManagedLocalTool(manifest.managedLocalTool, parsedId);
|
|
796
|
+
const machineCommands = manifest.machineCommands === void 0 ? void 0 : parseMachineCommands(manifest.machineCommands, parsedId, apiVersion);
|
|
797
|
+
if (machineCommands !== void 0 !== permissions.includes("machine.run")) {
|
|
798
|
+
fail$5("manifest.machineCommands", "machineCommands and machine.run permission must be declared together");
|
|
587
799
|
}
|
|
800
|
+
if (permissions.some((permission) => permission.startsWith("localTools.")) && (!managedLocalTool || apiVersion < 3)) fail$5("manifest.managedLocalTool", "managed tool metadata and API 3 required");
|
|
588
801
|
const activationEvents = array(manifest.activationEvents, "manifest.activationEvents").map(
|
|
589
802
|
(event, index) => string(event, `manifest.activationEvents[${index}]`)
|
|
590
803
|
);
|
|
591
804
|
const contributes = parseContributions(manifest.contributes, apiVersion, channelApiVersion);
|
|
592
805
|
if (contributes.channels?.length && !permissions.includes("channels.receive")) {
|
|
593
|
-
fail$
|
|
806
|
+
fail$5("manifest.contributes.channels", "requires channels.receive permission");
|
|
594
807
|
}
|
|
595
808
|
const strandedChannelPermission = permissions.find((permission) => CHANNEL_PERMISSIONS.has(permission));
|
|
596
809
|
if (strandedChannelPermission !== void 0 && contributes.channels === void 0) {
|
|
597
|
-
fail$
|
|
810
|
+
fail$5("manifest.permissions", `${strandedChannelPermission} requires a channels contribution`);
|
|
598
811
|
}
|
|
599
812
|
for (const [index, channel] of (contributes.channels ?? []).entries()) {
|
|
600
813
|
if (!channel.id.startsWith(`${parsedId}.`)) {
|
|
601
|
-
fail$
|
|
814
|
+
fail$5(
|
|
602
815
|
`manifest.contributes.channels[${index}].id`,
|
|
603
816
|
"expected an id declared by the same extension"
|
|
604
817
|
);
|
|
605
818
|
}
|
|
606
819
|
}
|
|
607
820
|
if (contributes.artifactActions?.length && !permissions.includes("artifacts.publishPublic")) {
|
|
608
|
-
fail$
|
|
821
|
+
fail$5("manifest.contributes.artifactActions", "requires artifacts.publishPublic permission");
|
|
609
822
|
}
|
|
610
823
|
for (const [index, action] of (contributes.artifactActions ?? []).entries()) {
|
|
611
824
|
if (!action.id.startsWith(`${parsedId}.`)) {
|
|
612
|
-
fail$
|
|
825
|
+
fail$5(
|
|
613
826
|
`manifest.contributes.artifactActions[${index}].id`,
|
|
614
827
|
"expected an id declared by the same extension"
|
|
615
828
|
);
|
|
@@ -618,7 +831,7 @@ function parseExtensionManifest(value, options) {
|
|
|
618
831
|
const panelIds = new Set((contributes.panels ?? []).map((panel) => panel.id));
|
|
619
832
|
for (const [index, command] of (contributes.commands ?? []).entries()) {
|
|
620
833
|
if (command.panelId !== void 0 && (!command.panelId.startsWith(`${parsedId}.`) || !panelIds.has(command.panelId))) {
|
|
621
|
-
fail$
|
|
834
|
+
fail$5(
|
|
622
835
|
`manifest.contributes.commands[${index}].panelId`,
|
|
623
836
|
"expected a panel declared by the same extension"
|
|
624
837
|
);
|
|
@@ -627,7 +840,7 @@ function parseExtensionManifest(value, options) {
|
|
|
627
840
|
const commandIds = new Set((contributes.commands ?? []).map((command) => command.id));
|
|
628
841
|
for (const [index, action] of (contributes.machineActions ?? []).entries()) {
|
|
629
842
|
if (!action.id.startsWith(`${parsedId}.`) || !action.command.startsWith(`${parsedId}.`) || !commandIds.has(action.command)) {
|
|
630
|
-
fail$
|
|
843
|
+
fail$5(
|
|
631
844
|
`manifest.contributes.machineActions[${index}].id/command`,
|
|
632
845
|
"expected ids declared by the same extension"
|
|
633
846
|
);
|
|
@@ -639,6 +852,8 @@ function parseExtensionManifest(value, options) {
|
|
|
639
852
|
apiVersion,
|
|
640
853
|
...channelApiVersion === void 0 ? {} : { channelApiVersion },
|
|
641
854
|
engines: { saycode: saycodeEngine },
|
|
855
|
+
...managedLocalTool ? { managedLocalTool } : {},
|
|
856
|
+
...machineCommands ? { machineCommands } : {},
|
|
642
857
|
entrypoint: relativePath(manifest.entrypoint, "manifest.entrypoint", true),
|
|
643
858
|
permissions,
|
|
644
859
|
activationEvents,
|
|
@@ -648,22 +863,22 @@ function parseExtensionManifest(value, options) {
|
|
|
648
863
|
function fail(detail) {
|
|
649
864
|
throw new Error(`extension wire message must be JSON-serializable: ${detail}`);
|
|
650
865
|
}
|
|
651
|
-
function plainRecord(value,
|
|
652
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${
|
|
866
|
+
function plainRecord(value, path2) {
|
|
867
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${path2} must be an object`);
|
|
653
868
|
const prototype = Object.getPrototypeOf(value);
|
|
654
|
-
if (prototype !== Object.prototype && prototype !== null) fail(`${
|
|
869
|
+
if (prototype !== Object.prototype && prototype !== null) fail(`${path2} must be a plain object`);
|
|
655
870
|
return value;
|
|
656
871
|
}
|
|
657
|
-
function text(value,
|
|
658
|
-
if (typeof value !== "string" || value.length === 0) fail(`${
|
|
872
|
+
function text(value, path2) {
|
|
873
|
+
if (typeof value !== "string" || value.length === 0) fail(`${path2} must be a non-empty string`);
|
|
659
874
|
return value;
|
|
660
875
|
}
|
|
661
|
-
function exactKeys(value,
|
|
662
|
-
const allowed = new Set(
|
|
876
|
+
function exactKeys(value, keys2, path2) {
|
|
877
|
+
const allowed = new Set(keys2);
|
|
663
878
|
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
664
|
-
if (unknown) fail(`${
|
|
665
|
-
const missing =
|
|
666
|
-
if (missing) fail(`${
|
|
879
|
+
if (unknown) fail(`${path2} contains unknown field ${unknown}`);
|
|
880
|
+
const missing = keys2.find((key) => !(key in value));
|
|
881
|
+
if (missing) fail(`${path2} is missing ${missing}`);
|
|
667
882
|
}
|
|
668
883
|
const MAX_CHANNEL_NATIVE_PAYLOAD_BYTES = 4096;
|
|
669
884
|
function parseChannelReplyFormatResult(value, request) {
|
|
@@ -710,22 +925,22 @@ function parseChannelApprovalRequest(value) {
|
|
|
710
925
|
}
|
|
711
926
|
return { approvalHandle, approveLabel, denyLabel };
|
|
712
927
|
}
|
|
713
|
-
function finiteNumber(value,
|
|
714
|
-
if (typeof value !== "number" || !Number.isFinite(value)) fail(`${
|
|
928
|
+
function finiteNumber(value, path2) {
|
|
929
|
+
if (typeof value !== "number" || !Number.isFinite(value)) fail(`${path2} must be a finite number`);
|
|
715
930
|
return value;
|
|
716
931
|
}
|
|
717
|
-
function jsonValue(value,
|
|
932
|
+
function jsonValue(value, path2, ancestors = /* @__PURE__ */ new Set()) {
|
|
718
933
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
719
|
-
if (typeof value === "number") return finiteNumber(value,
|
|
720
|
-
if (typeof value !== "object") fail(`${
|
|
721
|
-
if (ancestors.has(value)) fail(`${
|
|
934
|
+
if (typeof value === "number") return finiteNumber(value, path2);
|
|
935
|
+
if (typeof value !== "object") fail(`${path2} contains ${typeof value}`);
|
|
936
|
+
if (ancestors.has(value)) fail(`${path2} contains a cycle`);
|
|
722
937
|
ancestors.add(value);
|
|
723
938
|
try {
|
|
724
|
-
if (Array.isArray(value)) return value.map((item, index) => jsonValue(item, `${
|
|
725
|
-
const source = plainRecord(value,
|
|
939
|
+
if (Array.isArray(value)) return value.map((item, index) => jsonValue(item, `${path2}[${index}]`, ancestors));
|
|
940
|
+
const source = plainRecord(value, path2);
|
|
726
941
|
const result = {};
|
|
727
942
|
for (const [key, item] of Object.entries(source)) {
|
|
728
|
-
result[key] = jsonValue(item, `${
|
|
943
|
+
result[key] = jsonValue(item, `${path2}.${key}`, ancestors);
|
|
729
944
|
}
|
|
730
945
|
return result;
|
|
731
946
|
} finally {
|
|
@@ -996,7 +1211,7 @@ class ExtensionHostRuntime {
|
|
|
996
1211
|
return;
|
|
997
1212
|
}
|
|
998
1213
|
if (message.ok) pending.resolve(message.result);
|
|
999
|
-
else pending.reject(new Error(message.error.message));
|
|
1214
|
+
else pending.reject(Object.assign(new Error(message.error.message), { code: message.error.code }));
|
|
1000
1215
|
return;
|
|
1001
1216
|
}
|
|
1002
1217
|
const envelope = requestEnvelope(value);
|
|
@@ -1292,7 +1507,7 @@ async function startExtensionHost(deps) {
|
|
|
1292
1507
|
});
|
|
1293
1508
|
const runtime = new ExtensionHostRuntime({
|
|
1294
1509
|
manifest,
|
|
1295
|
-
requestTimeoutMs:
|
|
1510
|
+
requestTimeoutMs: extensionRequestTimeoutMs(manifest.permissions, "runtime"),
|
|
1296
1511
|
loadModule: (entrypoint) => deps.importModule(protocolUrl(deps.extensionId, entrypoint)),
|
|
1297
1512
|
send: (message) => deps.bridge.send(message)
|
|
1298
1513
|
});
|