@buzzni/saycode-cli 0.9.8 → 0.9.9
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 +61 -11
- package/index.mjs +7709 -4204
- package/official-extensions/buzzni.discord-1.3.0.saycode-extension.base64 +1 -0
- package/official-extensions/buzzni.slack-1.2.0.saycode-extension.base64 +1 -0
- package/official-extensions/buzzni.telegram-1.3.0.saycode-extension.base64 +1 -0
- package/official-extensions/catalog.v1.json +12 -12
- package/package.json +1 -1
- package/windows-host/saycode-windows-host.exe +0 -0
- package/official-extensions/buzzni.discord-1.1.0.saycode-extension.base64 +0 -1
- package/official-extensions/buzzni.slack-1.1.0.saycode-extension.base64 +0 -1
- package/official-extensions/buzzni.telegram-1.1.0.saycode-extension.base64 +0 -1
|
@@ -48,6 +48,15 @@ function contributionList(value, path2, allowedKeys, parse) {
|
|
|
48
48
|
return parse(entry, entryPath);
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
+
const ENTRYPOINT_PATTERN = /\.(?:cjs|mjs|js)$/;
|
|
52
|
+
function relativePath(value, path2, requireScript = false) {
|
|
53
|
+
const parsed = string(value, path2);
|
|
54
|
+
const segments = parsed.split("/");
|
|
55
|
+
if (parsed.startsWith("/") || parsed.includes("\\") || segments.some((segment) => segment === "" || segment === "." || segment === "..") || requireScript && !ENTRYPOINT_PATTERN.test(parsed)) {
|
|
56
|
+
fail$5(path2, "expected safe relative path");
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
51
60
|
function keys$1(value, allowed) {
|
|
52
61
|
const result = record$1(value, "widget");
|
|
53
62
|
assertOnlyKeys(result, new Set(allowed), "widget");
|
|
@@ -138,6 +147,23 @@ function assertExtensionFileViewers(extensionId2, viewers, panels, permissions)
|
|
|
138
147
|
if (!permissions.includes("filePreviews.read") || !viewer.id.startsWith(`${extensionId2}.`) || !viewer.panelId.startsWith(`${extensionId2}.`) || !panels.some((panel) => panel.id === viewer.panelId)) fail$5("manifest.contributes.fileViewers", "requires filePreviews.read and an owned panel");
|
|
139
148
|
}
|
|
140
149
|
}
|
|
150
|
+
const ELEMENT_PICK_INSPECT_PERMISSION = "browserPicks.inspect";
|
|
151
|
+
function parseExtensionElementPickInspectors(value, apiVersion) {
|
|
152
|
+
if (apiVersion < 4) fail$5("manifest.contributes.elementPickInspectors", "requires Extension API version 4");
|
|
153
|
+
return contributionList(value, "manifest.contributes.elementPickInspectors", ["id", "title", "script"], (entry, path2) => ({
|
|
154
|
+
id: id(entry.id, `${path2}.id`),
|
|
155
|
+
title: string(entry.title, `${path2}.title`),
|
|
156
|
+
script: relativePath(entry.script, `${path2}.script`, true)
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
function assertExtensionElementPickInspectors(extensionId2, inspectors, permissions) {
|
|
160
|
+
if (inspectors.length > 0 !== permissions.includes(ELEMENT_PICK_INSPECT_PERMISSION)) {
|
|
161
|
+
fail$5("manifest.contributes.elementPickInspectors", `requires ${ELEMENT_PICK_INSPECT_PERMISSION} permission and an inspector that uses it`);
|
|
162
|
+
}
|
|
163
|
+
for (const inspector of inspectors) {
|
|
164
|
+
if (!inspector.id.startsWith(`${extensionId2}.`)) fail$5("manifest.contributes.elementPickInspectors", "expected an id declared by the same extension");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
141
167
|
function parseMachineCommands(value, extensionId2, apiVersion) {
|
|
142
168
|
if (apiVersion < 3) fail$5("manifest.machineCommands", "requires Extension API version 3");
|
|
143
169
|
const root = record$1(value, "manifest.machineCommands");
|
|
@@ -594,6 +620,7 @@ const EXTENSION_PERMISSIONS = [
|
|
|
594
620
|
"browserViewer.installChrome",
|
|
595
621
|
"artifacts.publishPublic",
|
|
596
622
|
"filePreviews.read",
|
|
623
|
+
"browserPicks.inspect",
|
|
597
624
|
// One permission per channel wire method (`src/domain/channelProtocol.ts`), deliberately not a
|
|
598
625
|
// single adapter grant. Receiving and acking messages is transport work; reading a session,
|
|
599
626
|
// driving it, or approving a tool call once are three larger and separately-refusable
|
|
@@ -633,9 +660,8 @@ class UnsupportedExtensionApiVersionError extends Error {
|
|
|
633
660
|
}
|
|
634
661
|
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
635
662
|
const ENGINE_RANGE_PATTERN = /^(?:\^|~|>=|<=|>|<)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
636
|
-
const ENTRYPOINT_PATTERN = /\.(?:cjs|mjs|js)$/;
|
|
637
663
|
const LOCALE_PATTERN = /^[a-z]{2,3}(?:-[A-Z]{2})?$/;
|
|
638
|
-
const CONTRIBUTION_KEYS = /* @__PURE__ */ new Set(["chatWidgets", "commands", "settings", "panels", "projectTemplates", "machineActions", "projectTools", "artifactActions", "channels", "fileViewers"]);
|
|
664
|
+
const CONTRIBUTION_KEYS = /* @__PURE__ */ new Set(["chatWidgets", "commands", "settings", "panels", "projectTemplates", "machineActions", "projectTools", "artifactActions", "channels", "fileViewers", "elementPickInspectors"]);
|
|
639
665
|
const V2_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
640
666
|
"browserViewer.open",
|
|
641
667
|
"browserViewer.install",
|
|
@@ -707,14 +733,6 @@ function parseApiVersion(value, options) {
|
|
|
707
733
|
}
|
|
708
734
|
return value;
|
|
709
735
|
}
|
|
710
|
-
function relativePath(value, path2, requireScript = false) {
|
|
711
|
-
const parsed = string(value, path2);
|
|
712
|
-
const segments = parsed.split("/");
|
|
713
|
-
if (parsed.startsWith("/") || parsed.includes("\\") || segments.some((segment) => segment === "" || segment === "." || segment === "..") || requireScript && !ENTRYPOINT_PATTERN.test(parsed)) {
|
|
714
|
-
fail$5(path2, "expected safe relative path");
|
|
715
|
-
}
|
|
716
|
-
return parsed;
|
|
717
|
-
}
|
|
718
736
|
function parseChannelApiVersion(value, apiVersion) {
|
|
719
737
|
if (value === void 0) return void 0;
|
|
720
738
|
if (apiVersion < 3) fail$5("manifest.channelApiVersion", "requires Extension API version 3");
|
|
@@ -738,6 +756,7 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
738
756
|
}
|
|
739
757
|
const result = {};
|
|
740
758
|
if (parsed.fileViewers !== void 0) result.fileViewers = parseExtensionFileViewers(parsed.fileViewers, apiVersion);
|
|
759
|
+
if (parsed.elementPickInspectors !== void 0) result.elementPickInspectors = parseExtensionElementPickInspectors(parsed.elementPickInspectors, apiVersion);
|
|
741
760
|
if (parsed.commands !== void 0) {
|
|
742
761
|
result.commands = contributionList(
|
|
743
762
|
parsed.commands,
|
|
@@ -860,7 +879,8 @@ function parseContributions(value, apiVersion, channelApiVersion) {
|
|
|
860
879
|
...result.machineActions ?? [],
|
|
861
880
|
...result.projectTools ?? [],
|
|
862
881
|
...result.artifactActions ?? [],
|
|
863
|
-
...result.channels ?? []
|
|
882
|
+
...result.channels ?? [],
|
|
883
|
+
...result.elementPickInspectors ?? []
|
|
864
884
|
].map((entry) => entry.id);
|
|
865
885
|
if (new Set(ids).size !== ids.length) fail$5("manifest.contributes", "duplicate contribution id");
|
|
866
886
|
return result;
|
|
@@ -893,6 +913,7 @@ function parseExtensionManifest(value, options) {
|
|
|
893
913
|
const typed = parsed;
|
|
894
914
|
if (typed.startsWith("sessionContext.") && apiVersion !== 5) fail$5("manifest.permissions", "sessionContext requires API 5");
|
|
895
915
|
if (typed === "filePreviews.read" && apiVersion < 4) fail$5("manifest.permissions", "filePreviews.read requires Extension API version 4");
|
|
916
|
+
if (typed === "browserPicks.inspect" && apiVersion < 4) fail$5("manifest.permissions", "browserPicks.inspect requires Extension API version 4");
|
|
896
917
|
if (apiVersion < 2 && V2_PERMISSIONS.has(typed)) {
|
|
897
918
|
fail$5(`manifest.permissions[${index}]`, "requires Extension API version 2");
|
|
898
919
|
}
|
|
@@ -971,6 +992,7 @@ function parseExtensionManifest(value, options) {
|
|
|
971
992
|
}
|
|
972
993
|
}
|
|
973
994
|
assertExtensionFileViewers(parsedId, contributes.fileViewers ?? [], contributes.panels ?? [], permissions);
|
|
995
|
+
assertExtensionElementPickInspectors(parsedId, contributes.elementPickInspectors ?? [], permissions);
|
|
974
996
|
assertExtensionProjectToolTargets(parsedId, contributes.projectTools ?? [], contributes.commands ?? []);
|
|
975
997
|
return {
|
|
976
998
|
id: parsedId,
|
|
@@ -1120,6 +1142,13 @@ function parseRequest(message) {
|
|
|
1120
1142
|
connectionRevision: Number(params.connectionRevision)
|
|
1121
1143
|
} };
|
|
1122
1144
|
}
|
|
1145
|
+
case "channel.intake": {
|
|
1146
|
+
exactKeys(params, ["apiVersion", "channelId", "connectionId", "provider", "connectionRevision"], "request.params");
|
|
1147
|
+
const lifecycle = parseRequest({ kind: "request", id: id2, method: "channel.lifecycle", params: { ...params, state: "start" } });
|
|
1148
|
+
if (lifecycle.method !== "channel.lifecycle") fail("invalid channel intake");
|
|
1149
|
+
const { state: _state, ...identity } = lifecycle.params;
|
|
1150
|
+
return { kind: "request", id: id2, method, params: identity };
|
|
1151
|
+
}
|
|
1123
1152
|
case "channel.formatReply": {
|
|
1124
1153
|
const formatKeys = [
|
|
1125
1154
|
"apiVersion",
|
|
@@ -1268,6 +1297,7 @@ class ExtensionHostRuntime {
|
|
|
1268
1297
|
widgets = /* @__PURE__ */ new Map();
|
|
1269
1298
|
channelHandlers = /* @__PURE__ */ new Map();
|
|
1270
1299
|
channelFormatters = /* @__PURE__ */ new Map();
|
|
1300
|
+
channelIntakeHandlers = /* @__PURE__ */ new Map();
|
|
1271
1301
|
channelStates = /* @__PURE__ */ new Map();
|
|
1272
1302
|
channelCalls = /* @__PURE__ */ new Map();
|
|
1273
1303
|
channelEpoch = 0;
|
|
@@ -1402,6 +1432,7 @@ class ExtensionHostRuntime {
|
|
|
1402
1432
|
this.commandHandlers.clear();
|
|
1403
1433
|
this.channelHandlers.clear();
|
|
1404
1434
|
this.channelFormatters.clear();
|
|
1435
|
+
this.channelIntakeHandlers.clear();
|
|
1405
1436
|
this.channelStates.clear();
|
|
1406
1437
|
this.channelCalls.clear();
|
|
1407
1438
|
}
|
|
@@ -1444,6 +1475,7 @@ class ExtensionHostRuntime {
|
|
|
1444
1475
|
this.commandHandlers.clear();
|
|
1445
1476
|
this.channelHandlers.clear();
|
|
1446
1477
|
this.channelFormatters.clear();
|
|
1478
|
+
this.channelIntakeHandlers.clear();
|
|
1447
1479
|
this.channelStates.clear();
|
|
1448
1480
|
this.channelCalls.clear();
|
|
1449
1481
|
this.deactivating = false;
|
|
@@ -1463,6 +1495,7 @@ class ExtensionHostRuntime {
|
|
|
1463
1495
|
}
|
|
1464
1496
|
if (request.method === "channel.lifecycle") return this.deliverChannel(request.params);
|
|
1465
1497
|
if (request.method === "channel.formatReply") return this.formatChannelReply(request.params);
|
|
1498
|
+
if (request.method === "channel.intake") return this.deliverChannelIntake(request.params);
|
|
1466
1499
|
const handler = this.commandHandlers.get(request.params.commandId);
|
|
1467
1500
|
if (!handler) {
|
|
1468
1501
|
throw { code: "COMMAND_NOT_REGISTERED", message: `extension command is not registered: ${request.params.commandId}` };
|
|
@@ -1511,6 +1544,14 @@ class ExtensionHostRuntime {
|
|
|
1511
1544
|
if (this.channelCalls.get(key) === next) this.channelCalls.delete(key);
|
|
1512
1545
|
}
|
|
1513
1546
|
}
|
|
1547
|
+
/** A hint for a live connection only; anything else is answered and dropped, never an error. */
|
|
1548
|
+
async deliverChannelIntake(input) {
|
|
1549
|
+
const current = this.channelStates.get(JSON.stringify([input.channelId, input.connectionId]));
|
|
1550
|
+
const handler = this.channelIntakeHandlers.get(input.channelId);
|
|
1551
|
+
if (!handler || this.deactivating || current?.state !== "start" || current.provider !== input.provider || current.connectionRevision !== input.connectionRevision) return null;
|
|
1552
|
+
await handler(Object.freeze({ ...input }));
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1514
1555
|
async formatChannelReply(input) {
|
|
1515
1556
|
const key = JSON.stringify([input.channelId, input.connectionId]);
|
|
1516
1557
|
const current = this.channelStates.get(key);
|
|
@@ -1555,6 +1596,7 @@ class ExtensionHostRuntime {
|
|
|
1555
1596
|
const registered = /* @__PURE__ */ new Map();
|
|
1556
1597
|
const registeredChannels = /* @__PURE__ */ new Map();
|
|
1557
1598
|
const registeredFormatters = /* @__PURE__ */ new Map();
|
|
1599
|
+
const registeredIntake = /* @__PURE__ */ new Map();
|
|
1558
1600
|
await loaded.activate({
|
|
1559
1601
|
extensionId: this.options.manifest.id,
|
|
1560
1602
|
chatWidgets: { register: (id2, render) => {
|
|
@@ -1573,6 +1615,12 @@ class ExtensionHostRuntime {
|
|
|
1573
1615
|
throw { code: "INVALID_CHANNEL_REGISTRATION", message: "formatter must be declared and registered once" };
|
|
1574
1616
|
}
|
|
1575
1617
|
registeredFormatters.set(channelId, formatter);
|
|
1618
|
+
},
|
|
1619
|
+
onIntake: (channelId, handler) => {
|
|
1620
|
+
if (!this.activating || this.options.manifest.channelApiVersion !== 1 || !this.options.manifest.contributes.channels?.some((channel) => channel.id === channelId) || typeof handler !== "function" || registeredIntake.has(channelId)) {
|
|
1621
|
+
throw { code: "INVALID_CHANNEL_REGISTRATION", message: "intake handler must be declared and registered once" };
|
|
1622
|
+
}
|
|
1623
|
+
registeredIntake.set(channelId, handler);
|
|
1576
1624
|
}
|
|
1577
1625
|
},
|
|
1578
1626
|
commands: {
|
|
@@ -1592,6 +1640,8 @@ class ExtensionHostRuntime {
|
|
|
1592
1640
|
this.channelHandlers.clear();
|
|
1593
1641
|
this.channelFormatters.clear();
|
|
1594
1642
|
for (const [id2, formatter] of registeredFormatters) this.channelFormatters.set(id2, formatter);
|
|
1643
|
+
this.channelIntakeHandlers.clear();
|
|
1644
|
+
for (const [id2, handler] of registeredIntake) this.channelIntakeHandlers.set(id2, handler);
|
|
1595
1645
|
for (const [id2, handler] of registeredChannels) this.channelHandlers.set(id2, handler);
|
|
1596
1646
|
this.commandHandlers.clear();
|
|
1597
1647
|
for (const [commandId, handler] of registered) this.commandHandlers.set(commandId, handler);
|