@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.
@@ -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$2 = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
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$4(path, detail) {
20
- throw new Error(`${path}: ${detail}`);
252
+ function fail$3(path2, detail) {
253
+ throw new Error(`${path2}: ${detail}`);
21
254
  }
22
- function record$1(value, path) {
23
- if (!value || typeof value !== "object" || Array.isArray(value)) fail$4(path, "expected object");
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, path) {
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$4(path, `unknown field ${unknown}`);
262
+ if (unknown) fail$3(path2, `unknown field ${unknown}`);
30
263
  const missing = required.find((key) => !(key in value));
31
- if (missing) fail$4(path, `missing field ${missing}`);
264
+ if (missing) fail$3(path2, `missing field ${missing}`);
32
265
  }
33
- function text$1(value, path) {
266
+ function text$1(value, path2) {
34
267
  if (typeof value !== "string" || !value || value.trim() !== value) {
35
- fail$4(path, "expected non-empty trimmed string");
268
+ fail$3(path2, "expected non-empty trimmed string");
36
269
  }
37
270
  return value;
38
271
  }
39
- function title(value, path) {
40
- const parsed = text$1(value, path);
41
- if ([...parsed].length > MAX_TITLE_CHARACTERS) fail$4(path, "title exceeds 120 characters");
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, path) {
45
- if (new Set(values).size !== values.length) fail$4(path, "duplicate value");
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, path) {
49
- const localizations = record$1(value, path);
50
- if (Object.keys(localizations).length > MAX_LOCALES) fail$4(path, "too many locales");
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$4(path, `invalid locale ${locale}`);
53
- const localizationPath = `${path}.${locale}`;
54
- const localization = record$1(raw, localizationPath);
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, path) {
60
- if (!Array.isArray(value) || value.length === 0) fail$4(path, "expected non-empty array");
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$4(`${path}[${index}]`, "unsupported source type");
296
+ fail$3(`${path2}[${index}]`, "unsupported source type");
64
297
  }
65
298
  return sourceType;
66
299
  });
67
- return uniqueList(parsed, path);
300
+ return uniqueList(parsed, path2);
68
301
  }
69
- function parseExtensions(value, path) {
302
+ function parseExtensions(value, path2) {
70
303
  if (!Array.isArray(value) || value.length === 0 || value.length > MAX_EXTENSIONS) {
71
- fail$4(path, "expected 1-16 extensions");
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$4(`${path}[${index}]`, "expected lowercase extension without a dot");
308
+ fail$3(`${path2}[${index}]`, "expected lowercase extension without a dot");
76
309
  }
77
310
  return extension;
78
311
  });
79
- return uniqueList(parsed, path);
312
+ return uniqueList(parsed, path2);
80
313
  }
81
- function parseExtensionArtifactActions(value, path = "manifest.contributes.artifactActions") {
82
- if (!Array.isArray(value) || value.length > MAX_ACTIONS) fail$4(path, "expected at most 32 actions");
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 = `${path}[${index}]`;
85
- const action = record$1(raw, actionPath);
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$2.test(parsedId) || parsedId.includes("..")) fail$4(`${actionPath}.id`, "invalid stable identifier");
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$4(`${actionPath}.operation`, "unsupported operation");
323
+ fail$3(`${actionPath}.operation`, "unsupported operation");
91
324
  }
92
- const when = record$1(action.when, `${actionPath}.when`);
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$3(path, detail) {
128
- throw new Error(`${path}: ${detail}`);
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, path) {
371
+ function description(value, path2) {
138
372
  if (typeof value !== "string" || value.trim().length === 0 || value.length > MAX_DESCRIPTION) {
139
- fail$3(path, "invalid description");
373
+ fail$2(path2, "invalid description");
140
374
  }
141
- if (hasControlCharacter(value)) fail$3(path, "invalid description");
375
+ if (hasControlCharacter(value)) fail$2(path2, "invalid description");
142
376
  return value;
143
377
  }
144
- function parseChannelCommandDeclarations(value, path) {
145
- if (!Array.isArray(value)) fail$3(path, "expected array");
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$3(path, `expected between 1 and ${names.length} commands`);
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 = `${path}[${index}]`;
153
- if (!item || typeof item !== "object" || Array.isArray(item)) fail$3(entryPath, "expected object");
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$3(entryPath, `unknown field ${extra}`);
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$3(`${entryPath}.id`, "unknown command");
393
+ fail$2(`${entryPath}.id`, "unknown command");
160
394
  }
161
- if (seen.has(id2)) fail$3(path, `duplicate command ${id2}`);
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, path, expected) {
404
+ function parseOptions(value, path2, expected) {
171
405
  if (value === void 0) {
172
- if (expected.length > 0) fail$3(path, `expected ${expected.length} options`);
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$3(path, "expected array");
176
- if (value.length !== expected.length) fail$3(path, `expected ${expected.length} options`);
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 = `${path}[${index}]`;
179
- if (!item || typeof item !== "object" || Array.isArray(item)) fail$3(optionPath, "expected object");
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$3(optionPath, `unknown field ${extra}`);
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$3(`${optionPath}.name`, `expected ${name}`);
187
- if (source.required !== required) fail$3(`${optionPath}.required`, `expected ${required}`);
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$1 = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
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$2(path, detail) {
199
- throw new Error(`${path}: ${detail}`);
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$2("manifest.contributes.channels", "expected array");
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$2("manifest.contributes.channels", `expected between 1 and ${MAX_CHANNEL_CONTRIBUTIONS} adapters`);
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 path = `manifest.contributes.channels[${index}]`;
209
- if (!item || typeof item !== "object" || Array.isArray(item)) fail$2(path, "expected object");
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$2(path, `unknown field ${extra}`);
446
+ if (extra !== void 0) fail$1(path2, `unknown field ${extra}`);
213
447
  const id2 = source.id;
214
- if (typeof id2 !== "string" || !ID_PATTERN$1.test(id2) || id2.includes("..")) {
215
- fail$2(`${path}.id`, "invalid identifier");
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$2(`${path}.provider`, "unsupported provider");
453
+ fail$1(`${path2}.provider`, "unsupported provider");
220
454
  }
221
- if (providers.has(provider)) fail$2("manifest.contributes.channels", "duplicate provider");
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, `${path}.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$1(
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 record(value, path) {
363
- if (!value || typeof value !== "object" || Array.isArray(value)) fail$1(path, "expected object");
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$1(path, "expected safe relative path");
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$1("manifest.channelApiVersion", "requires Extension API version 3");
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$1("manifest.contributes.channels", "requires Extension API version 3");
617
+ if (apiVersion < 3) fail$5("manifest.contributes.channels", "requires Extension API version 3");
412
618
  if (channelApiVersion === void 0) {
413
- fail$1("manifest.contributes.channels", "requires manifest.channelApiVersion");
619
+ fail$5("manifest.contributes.channels", "requires manifest.channelApiVersion");
414
620
  }
415
621
  }
416
622
  if (apiVersion < 2 && parsed.machineActions !== void 0) {
417
- fail$1("manifest.contributes.machineActions", "requires Extension API version 2");
623
+ fail$5("manifest.contributes.machineActions", "requires Extension API version 2");
418
624
  }
419
625
  if (apiVersion < 3 && parsed.artifactActions !== void 0) {
420
- fail$1("manifest.contributes.artifactActions", "requires Extension API version 3");
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, path) => ({
429
- id: id(entry.id, `${path}.id`),
430
- title: string(entry.title, `${path}.title`),
431
- ...entry.panelId === void 0 ? {} : { panelId: id(entry.panelId, `${path}.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, path) => {
441
- const type = string(entry.type, `${path}.type`);
646
+ (entry, path2) => {
647
+ const type = string(entry.type, `${path2}.type`);
442
648
  if (type !== "boolean" && type !== "number" && type !== "string") {
443
- fail$1(`${path}.type`, "unsupported setting type");
649
+ fail$5(`${path2}.type`, "unsupported setting type");
444
650
  }
445
- return { id: id(entry.id, `${path}.id`), title: string(entry.title, `${path}.title`), type };
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, path) => ({
455
- id: id(entry.id, `${path}.id`),
456
- title: string(entry.title, `${path}.title`),
457
- entrypoint: relativePath(entry.entrypoint, `${path}.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, path) => {
682
+ (entry, path2) => {
476
683
  let localizations;
477
684
  if (entry.localizations !== void 0) {
478
- const rawLocalizations = record(entry.localizations, `${path}.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$1(`${path}.localizations`, `invalid locale ${locale}`);
482
- const localizationPath = `${path}.localizations.${locale}`;
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, `${path}.id`),
498
- title: string(entry.title, `${path}.title`),
499
- assetsRoot: relativePath(entry.assetsRoot, `${path}.assetsRoot`),
500
- ...entry.description === void 0 ? {} : { description: string(entry.description, `${path}.description`) },
501
- ...entry.stack === void 0 ? {} : { stack: string(entry.stack, `${path}.stack`) },
502
- ...entry.firstPrompt === void 0 ? {} : { firstPrompt: string(entry.firstPrompt, `${path}.firstPrompt`) },
503
- ...entry.devServerCommand === void 0 ? {} : { devServerCommand: string(entry.devServerCommand, `${path}.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, path) => {
515
- const when = record(entry.when, `${path}.when`);
516
- assertOnlyKeys(when, /* @__PURE__ */ new Set(["online"]), `${path}.when`);
517
- if (when.online !== true) fail$1(`${path}.when.online`, "expected true");
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, `${path}.id`),
520
- title: string(entry.title, `${path}.title`),
521
- command: id(entry.command, `${path}.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$1("manifest.contributes", "duplicate contribution id");
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$1("manifest.version", "expected semantic version");
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$1("manifest.engines.saycode", "expected simple semantic version range");
764
+ fail$5("manifest.engines.saycode", "expected simple semantic version range");
558
765
  }
559
766
  if (options.saycodeVersion && !engineSupports(saycodeEngine, options.saycodeVersion)) {
560
- fail$1(
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$1("manifest.permissions", `unknown permission ${parsed}`);
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$1(`manifest.permissions[${index}]`, "requires Extension API version 2");
779
+ fail$5(`manifest.permissions[${index}]`, "requires Extension API version 2");
573
780
  }
574
781
  if (apiVersion < 3 && V3_PERMISSIONS.has(typed)) {
575
- fail$1(`manifest.permissions[${index}]`, "requires Extension API version 3");
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$1(`manifest.permissions[${index}]`, "requires Extension API version 3");
785
+ if (apiVersion < 3) fail$5(`manifest.permissions[${index}]`, "requires Extension API version 3");
579
786
  if (channelApiVersion === void 0) {
580
- fail$1(`manifest.permissions[${index}]`, "requires manifest.channelApiVersion");
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$1("manifest.permissions", "duplicate permission");
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$1("manifest.contributes.channels", "requires channels.receive permission");
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$1("manifest.permissions", `${strandedChannelPermission} requires a channels contribution`);
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$1(
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$1("manifest.contributes.artifactActions", "requires artifacts.publishPublic permission");
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$1(
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$1(
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$1(
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, path) {
652
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${path} must be an object`);
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(`${path} must be a plain object`);
869
+ if (prototype !== Object.prototype && prototype !== null) fail(`${path2} must be a plain object`);
655
870
  return value;
656
871
  }
657
- function text(value, path) {
658
- if (typeof value !== "string" || value.length === 0) fail(`${path} must be a non-empty string`);
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, keys, path) {
662
- const allowed = new Set(keys);
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(`${path} contains unknown field ${unknown}`);
665
- const missing = keys.find((key) => !(key in value));
666
- if (missing) fail(`${path} is missing ${missing}`);
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, path) {
714
- if (typeof value !== "number" || !Number.isFinite(value)) fail(`${path} must be a finite number`);
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, path, ancestors = /* @__PURE__ */ new Set()) {
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, path);
720
- if (typeof value !== "object") fail(`${path} contains ${typeof value}`);
721
- if (ancestors.has(value)) fail(`${path} contains a cycle`);
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, `${path}[${index}]`, ancestors));
725
- const source = plainRecord(value, path);
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, `${path}.${key}`, ancestors);
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: usesAssetWorkflow(manifest.permissions) ? 13e4 : void 0,
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
  });