@get-bb/plugin-sdk 0.4.56 → 0.4.87
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/README.md +0 -12
- package/bundled-types/bb-plugin-sdk-app.d.ts +137 -20
- package/bundled-types/bb-plugin-sdk-environment-provider.d.ts +35 -3
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +406 -52
- package/bundled-types/bb-plugin-sdk-internal-plugin-app-collector.d.ts +8 -3
- package/bundled-types/bb-plugin-sdk-machine-provider.d.ts +84 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +57 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +131 -2
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +7 -2
- package/bundled-types/bb-plugin-sdk-testing.d.ts +212 -12
- package/bundled-types/bb-plugin-sdk.d.ts +749 -49
- package/dist/app.js +4 -0
- package/dist/host.js +1 -1
- package/dist/internal/host-policy.js +855 -68
- package/dist/internal/plugin-app-collector.js +116 -91
- package/dist/machine-provider.js +0 -0
- package/dist/provider-bridge-acp.js +1964 -2045
- package/dist/provider-bridge-testing.js +1378 -1404
- package/dist/provider-bridge-worker-entry.mjs +0 -1
- package/dist/provider-bridge.js +1455 -1498
- package/dist/testing/app.js +134 -91
- package/dist/testing/index.js +1092 -730
- package/package.json +7 -2
package/dist/testing/index.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
// src/testing/fake-plugin-host.ts
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import Database from "better-sqlite3";
|
|
7
|
-
import { CronExpressionParser } from "cron-parser";
|
|
8
|
-
import { Hono } from "hono";
|
|
9
|
-
|
|
10
|
-
// ../domain/src/plugin-interaction-limits.ts
|
|
11
|
-
var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
|
|
12
|
-
var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
13
|
-
|
|
14
1
|
// src/internal/host-policy.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
15
3
|
import { z as z4 } from "zod";
|
|
16
4
|
|
|
17
5
|
// ../domain/src/plugin-icon.ts
|
|
@@ -57,6 +45,10 @@ var RESERVED_BB_CLI_COMMANDS = [
|
|
|
57
45
|
"voice"
|
|
58
46
|
];
|
|
59
47
|
|
|
48
|
+
// ../domain/src/plugin-interaction-limits.ts
|
|
49
|
+
var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
|
|
50
|
+
var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
51
|
+
|
|
60
52
|
// ../domain/src/provider-fork.ts
|
|
61
53
|
import { z } from "zod";
|
|
62
54
|
var PROVIDER_FORK_VALUES = ["none", "tip", "checkpoint"];
|
|
@@ -286,6 +278,51 @@ var providerNativeRootSetSchema = z3.object({
|
|
|
286
278
|
resolved: providerResolvedNativeRootsSchema
|
|
287
279
|
}).strict();
|
|
288
280
|
|
|
281
|
+
// ../domain/src/plugin-metadata.ts
|
|
282
|
+
var PLUGIN_METADATA_MAX_BYTES = 256 * 1024;
|
|
283
|
+
function exceedsPluginMetadataLimit(metadataJson) {
|
|
284
|
+
return new TextEncoder().encode(metadataJson).byteLength > PLUGIN_METADATA_MAX_BYTES;
|
|
285
|
+
}
|
|
286
|
+
var pluginMetadataSchema = jsonObjectSchema.superRefine(
|
|
287
|
+
(value, ctx) => {
|
|
288
|
+
if (exceedsPluginMetadataLimit(JSON.stringify(value))) {
|
|
289
|
+
ctx.addIssue({
|
|
290
|
+
code: "custom",
|
|
291
|
+
message: "pluginMetadata exceeds 256 KiB"
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
function assertPlainJsonData(value, seen) {
|
|
297
|
+
if (value === null || typeof value !== "object") return;
|
|
298
|
+
if (seen.has(value)) throw new Error("pluginMetadata contains a cycle");
|
|
299
|
+
const array = Array.isArray(value);
|
|
300
|
+
if (Object.getPrototypeOf(value) !== (array ? Array.prototype : Object.prototype)) {
|
|
301
|
+
throw new Error("pluginMetadata must contain plain JSON data");
|
|
302
|
+
}
|
|
303
|
+
seen.add(value);
|
|
304
|
+
for (const child of array ? value : Object.values(value)) {
|
|
305
|
+
assertPlainJsonData(child, seen);
|
|
306
|
+
}
|
|
307
|
+
seen.delete(value);
|
|
308
|
+
}
|
|
309
|
+
function validatePluginMetadata(value) {
|
|
310
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
311
|
+
throw new Error("pluginMetadata must be a plain JSON object");
|
|
312
|
+
}
|
|
313
|
+
assertPlainJsonData(value, /* @__PURE__ */ new Set());
|
|
314
|
+
return pluginMetadataSchema.parse(value);
|
|
315
|
+
}
|
|
316
|
+
function deepFreezeJsonValue(value) {
|
|
317
|
+
if (value === null || typeof value !== "object") return;
|
|
318
|
+
for (const child of Object.values(value)) deepFreezeJsonValue(child);
|
|
319
|
+
Object.freeze(value);
|
|
320
|
+
}
|
|
321
|
+
function deepFreezePluginMetadata(metadata) {
|
|
322
|
+
deepFreezeJsonValue(metadata);
|
|
323
|
+
return metadata;
|
|
324
|
+
}
|
|
325
|
+
|
|
289
326
|
// src/backend-contract.ts
|
|
290
327
|
var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
291
328
|
|
|
@@ -319,8 +356,7 @@ var pluginProviderEnvEntrySchema = z4.object({
|
|
|
319
356
|
z4.string(),
|
|
320
357
|
z4.object({ serverPath: z4.string().startsWith("/") }).strict()
|
|
321
358
|
]),
|
|
322
|
-
reason: z4.string()
|
|
323
|
-
secret: z4.boolean()
|
|
359
|
+
reason: z4.string()
|
|
324
360
|
}).strict();
|
|
325
361
|
var pluginProviderEnvEntriesSchema = z4.array(pluginProviderEnvEntrySchema).max(PLUGIN_PROVIDER_ENV_MAX_ENTRIES).superRefine((entries, context) => {
|
|
326
362
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -528,6 +564,22 @@ function validateSettingsUpdate(descriptors, values) {
|
|
|
528
564
|
}
|
|
529
565
|
return errors;
|
|
530
566
|
}
|
|
567
|
+
function coerceStoredPluginSettingValue(descriptor, value) {
|
|
568
|
+
let coerced = value;
|
|
569
|
+
if (descriptor.type === "number" && typeof coerced === "string") {
|
|
570
|
+
const legacyNumber = Number(coerced.trim());
|
|
571
|
+
coerced = coerced.trim().length > 0 && Number.isFinite(legacyNumber) ? legacyNumber : void 0;
|
|
572
|
+
}
|
|
573
|
+
if (descriptor.type === "number" && typeof coerced === "number" && !Number.isFinite(coerced)) {
|
|
574
|
+
coerced = void 0;
|
|
575
|
+
}
|
|
576
|
+
const expected = descriptor.type === "boolean" ? "boolean" : descriptor.type === "number" ? "number" : "string";
|
|
577
|
+
if (typeof coerced !== expected) coerced = void 0;
|
|
578
|
+
if (descriptor.type === "select" && typeof coerced === "string" && !descriptor.options.includes(coerced)) {
|
|
579
|
+
coerced = void 0;
|
|
580
|
+
}
|
|
581
|
+
return coerced ?? descriptor.default;
|
|
582
|
+
}
|
|
531
583
|
var PLUGIN_MENTION_TRIGGER_VALUES = [
|
|
532
584
|
"@",
|
|
533
585
|
"#",
|
|
@@ -1769,79 +1821,64 @@ function pluginHookAlreadyRegisteredMessage(hook) {
|
|
|
1769
1821
|
}
|
|
1770
1822
|
var ENVIRONMENT_PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
|
|
1771
1823
|
var ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS = 80;
|
|
1824
|
+
var ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
|
|
1772
1825
|
var ENVIRONMENT_PROVIDER_REQUIREMENT_NAMES = [
|
|
1773
1826
|
"projectCheckout",
|
|
1774
1827
|
"gitCheckout",
|
|
1775
1828
|
"gitRemote",
|
|
1776
1829
|
"projectless"
|
|
1777
1830
|
];
|
|
1778
|
-
|
|
1831
|
+
var environmentCompositionSchema = z4.object({
|
|
1832
|
+
id: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
|
|
1833
|
+
displayName: z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS),
|
|
1834
|
+
description: z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS).optional().transform((value) => value ?? null),
|
|
1835
|
+
icon: z4.string().trim().min(1).transform(
|
|
1836
|
+
(icon) => isPluginOwnedIconPath(icon) ? validateProviderRelativePath(icon, "Composition icon") : icon
|
|
1837
|
+
).optional().transform((value) => value ?? null),
|
|
1838
|
+
machineProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
|
|
1839
|
+
environmentProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN)
|
|
1840
|
+
}).strict();
|
|
1841
|
+
function requireProviderIdAndDisplayName(noun, declaration) {
|
|
1779
1842
|
if (typeof declaration !== "object" || declaration === null) {
|
|
1780
|
-
throw new Error(
|
|
1843
|
+
throw new Error(`${noun} declaration must be an object`);
|
|
1781
1844
|
}
|
|
1782
1845
|
const id = declaration.id;
|
|
1783
1846
|
if (typeof id !== "string" || !ENVIRONMENT_PROVIDER_ID_PATTERN.test(id)) {
|
|
1784
1847
|
throw new Error(
|
|
1785
|
-
`invalid
|
|
1848
|
+
`invalid ${noun} id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, or "-", starting with a letter or digit`
|
|
1786
1849
|
);
|
|
1787
1850
|
}
|
|
1788
1851
|
const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
|
|
1789
1852
|
if (displayName.length === 0 || displayName.length > ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
|
|
1790
1853
|
throw new Error(
|
|
1791
|
-
|
|
1792
|
-
);
|
|
1793
|
-
}
|
|
1794
|
-
const icon = declaration.icon === void 0 ? null : z4.string().min(1).parse(declaration.icon).trim();
|
|
1795
|
-
if (icon !== null) {
|
|
1796
|
-
if (isPluginOwnedIconPath(icon))
|
|
1797
|
-
validateProviderRelativePath(icon, `"${id}" icon`);
|
|
1798
|
-
else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon))
|
|
1799
|
-
throw new Error(
|
|
1800
|
-
`environment provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1801
|
-
);
|
|
1802
|
-
}
|
|
1803
|
-
if (icon !== null && icon.length === 0) {
|
|
1804
|
-
throw new Error(`environment provider "${id}" declares an empty icon`);
|
|
1805
|
-
}
|
|
1806
|
-
const requires = normalizeEnvironmentProviderRequirements(id, declaration);
|
|
1807
|
-
const inputs = normalizeEnvironmentProviderInputs(id, declaration);
|
|
1808
|
-
if (typeof declaration.create !== "function" || typeof declaration.remove !== "function") {
|
|
1809
|
-
throw new Error(
|
|
1810
|
-
`environment provider "${id}" must declare create and remove functions`
|
|
1854
|
+
`${noun} "${id}" needs a displayName of 1-${ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS} characters`
|
|
1811
1855
|
);
|
|
1812
1856
|
}
|
|
1813
|
-
|
|
1857
|
+
return { id, displayName };
|
|
1858
|
+
}
|
|
1859
|
+
function assertProviderIconShape(noun, id, icon) {
|
|
1860
|
+
if (isPluginOwnedIconPath(icon)) {
|
|
1861
|
+
validateProviderRelativePath(icon, `"${id}" icon`);
|
|
1862
|
+
} else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon)) {
|
|
1814
1863
|
throw new Error(
|
|
1815
|
-
|
|
1864
|
+
`${noun} "${id}" icon must be a glyph, declared icon, or plugin-relative path`
|
|
1816
1865
|
);
|
|
1817
1866
|
}
|
|
1818
|
-
|
|
1867
|
+
}
|
|
1868
|
+
function assertOptionalFunction(noun, id, value, phrase) {
|
|
1869
|
+
if (value !== void 0 && typeof value !== "function") {
|
|
1819
1870
|
throw new Error(
|
|
1820
|
-
|
|
1871
|
+
`${noun} "${id}" declares ${phrase} that is not a function`
|
|
1821
1872
|
);
|
|
1822
1873
|
}
|
|
1823
|
-
return {
|
|
1824
|
-
id,
|
|
1825
|
-
displayName,
|
|
1826
|
-
icon,
|
|
1827
|
-
requires,
|
|
1828
|
-
inputs: inputs === null ? null : inputs.schema,
|
|
1829
|
-
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
1830
|
-
availability: declaration.availability ?? null,
|
|
1831
|
-
validate: declaration.validate ?? null,
|
|
1832
|
-
create: declaration.create,
|
|
1833
|
-
remove: declaration.remove,
|
|
1834
|
-
policy: environmentProviderPolicySchema.parse(declaration.policy ?? {})
|
|
1835
|
-
};
|
|
1836
1874
|
}
|
|
1837
|
-
function
|
|
1838
|
-
const inputs = declaration.inputs;
|
|
1875
|
+
function normalizeProviderInputs(noun, id, inputs) {
|
|
1839
1876
|
if (inputs === void 0) {
|
|
1840
1877
|
return null;
|
|
1841
1878
|
}
|
|
1842
1879
|
if (!isStandardSchema(inputs)) {
|
|
1843
1880
|
throw new Error(
|
|
1844
|
-
|
|
1881
|
+
`${noun} "${id}" declares an inputs that is not a Standard Schema v1 validator`
|
|
1845
1882
|
);
|
|
1846
1883
|
}
|
|
1847
1884
|
let converted;
|
|
@@ -1849,17 +1886,65 @@ function normalizeEnvironmentProviderInputs(id, declaration) {
|
|
|
1849
1886
|
converted = JSON.parse(JSON.stringify(standardSchemaToJsonSchema(inputs)));
|
|
1850
1887
|
} catch (error) {
|
|
1851
1888
|
throw new Error(
|
|
1852
|
-
|
|
1889
|
+
`${noun} "${id}" declares an inputs validator that cannot be published as JSON Schema (${error instanceof Error ? error.message : String(error)}) \u2014 declare it with zod 4 or a validator exposing toJSONSchema()`
|
|
1853
1890
|
);
|
|
1854
1891
|
}
|
|
1855
1892
|
const jsonSchema = jsonValueSchema.safeParse(converted);
|
|
1856
1893
|
if (!jsonSchema.success) {
|
|
1857
1894
|
throw new Error(
|
|
1858
|
-
|
|
1895
|
+
`${noun} "${id}" declares an inputs schema whose JSON Schema is not JSON-serializable`
|
|
1859
1896
|
);
|
|
1860
1897
|
}
|
|
1861
1898
|
return { schema: inputs, jsonSchema: jsonSchema.data };
|
|
1862
1899
|
}
|
|
1900
|
+
function validatePluginEnvironmentProviderDeclaration(declaration) {
|
|
1901
|
+
const { id, displayName } = requireProviderIdAndDisplayName(
|
|
1902
|
+
"environment provider",
|
|
1903
|
+
declaration
|
|
1904
|
+
);
|
|
1905
|
+
const description = z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS).optional().transform((value) => value ?? null).parse(declaration.description);
|
|
1906
|
+
const icon = z4.string().trim().min(1).optional().transform((value) => value ?? null).parse(declaration.icon);
|
|
1907
|
+
if (icon !== null) {
|
|
1908
|
+
assertProviderIconShape("environment provider", id, icon);
|
|
1909
|
+
}
|
|
1910
|
+
const requires = normalizeEnvironmentProviderRequirements(id, declaration);
|
|
1911
|
+
const inputs = normalizeProviderInputs(
|
|
1912
|
+
"environment provider",
|
|
1913
|
+
id,
|
|
1914
|
+
declaration.inputs
|
|
1915
|
+
);
|
|
1916
|
+
if (typeof declaration.create !== "function" || typeof declaration.remove !== "function") {
|
|
1917
|
+
throw new Error(
|
|
1918
|
+
`environment provider "${id}" must declare create and remove functions`
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
assertOptionalFunction(
|
|
1922
|
+
"environment provider",
|
|
1923
|
+
id,
|
|
1924
|
+
declaration.validate,
|
|
1925
|
+
"a validate"
|
|
1926
|
+
);
|
|
1927
|
+
assertOptionalFunction(
|
|
1928
|
+
"environment provider",
|
|
1929
|
+
id,
|
|
1930
|
+
declaration.availability,
|
|
1931
|
+
"availability"
|
|
1932
|
+
);
|
|
1933
|
+
return {
|
|
1934
|
+
id,
|
|
1935
|
+
displayName,
|
|
1936
|
+
description,
|
|
1937
|
+
icon,
|
|
1938
|
+
requires,
|
|
1939
|
+
inputs: inputs === null ? null : inputs.schema,
|
|
1940
|
+
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
1941
|
+
availability: declaration.availability ?? null,
|
|
1942
|
+
validate: declaration.validate ?? null,
|
|
1943
|
+
create: declaration.create,
|
|
1944
|
+
remove: declaration.remove,
|
|
1945
|
+
policy: environmentProviderPolicySchema.parse(declaration.policy ?? {})
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1863
1948
|
function normalizeEnvironmentProviderRequirements(id, declaration) {
|
|
1864
1949
|
const requires = declaration.requires ?? {};
|
|
1865
1950
|
if (typeof requires !== "object" || requires === null || Array.isArray(requires)) {
|
|
@@ -1891,226 +1976,519 @@ var environmentProviderPolicySchema = z4.object({
|
|
|
1891
1976
|
retireGraceMs: z4.number().int().nonnegative().nullable().default(3e5),
|
|
1892
1977
|
pathKeys: z4.enum(["per-thread", "per-attempt"]).default("per-thread")
|
|
1893
1978
|
}).strict();
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
const
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
function addOverrides(prefix, value) {
|
|
1914
|
-
if (typeof value === "function") {
|
|
1915
|
-
stubs.set(prefix, value);
|
|
1916
|
-
return;
|
|
1917
|
-
}
|
|
1918
|
-
if (typeof value !== "object" || value === null) return;
|
|
1919
|
-
for (const [key, child] of Object.entries(value)) {
|
|
1920
|
-
addOverrides(prefix.length === 0 ? key : `${prefix}.${key}`, child);
|
|
1921
|
-
}
|
|
1922
|
-
}
|
|
1923
|
-
addOverrides("", options.overrides ?? {});
|
|
1924
|
-
function invoke(path, rawArgs) {
|
|
1925
|
-
const args = path === "threads.spawn" ? withSpawnAttribution(options.pluginId, rawArgs) : rawArgs;
|
|
1926
|
-
calls.push({ path, args });
|
|
1927
|
-
const stub = stubs.get(path);
|
|
1928
|
-
if (!stub) {
|
|
1929
|
-
throw new Error(
|
|
1930
|
-
`bb.sdk.${path} is not stubbed \u2014 pass an implementation via createFakePluginHost({ sdk: { ... } }) or harness.sdk.stub("${path}", fn)`
|
|
1931
|
-
);
|
|
1932
|
-
}
|
|
1933
|
-
return stub(...args);
|
|
1979
|
+
var MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
|
|
1980
|
+
function validatePluginMachineProviderDeclaration(declaration) {
|
|
1981
|
+
const { id, displayName } = requireProviderIdAndDisplayName(
|
|
1982
|
+
"machine provider",
|
|
1983
|
+
declaration
|
|
1984
|
+
);
|
|
1985
|
+
const description = z4.string().trim().min(1).max(MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS).parse(declaration.description);
|
|
1986
|
+
const icon = z4.string().trim().min(1).parse(declaration.icon);
|
|
1987
|
+
assertProviderIconShape("machine provider", id, icon);
|
|
1988
|
+
const ephemeral = z4.boolean().default(false).parse(declaration.ephemeral);
|
|
1989
|
+
const inputs = normalizeProviderInputs(
|
|
1990
|
+
"machine provider",
|
|
1991
|
+
id,
|
|
1992
|
+
declaration.inputs
|
|
1993
|
+
);
|
|
1994
|
+
if (typeof declaration.create !== "function" || typeof declaration.reconcileCleanup !== "function" || typeof declaration.remove !== "function") {
|
|
1995
|
+
throw new Error(
|
|
1996
|
+
`machine provider "${id}" must declare create, reconcileCleanup and remove functions`
|
|
1997
|
+
);
|
|
1934
1998
|
}
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
get(_target, prop) {
|
|
1942
|
-
if (typeof prop !== "string" || prop === "then") return void 0;
|
|
1943
|
-
return node(path === "" ? prop : `${path}.${prop}`);
|
|
1944
|
-
},
|
|
1945
|
-
apply(_target, _thisArg, args) {
|
|
1946
|
-
return invoke(path, args);
|
|
1947
|
-
}
|
|
1948
|
-
});
|
|
1949
|
-
nodes.set(path, created);
|
|
1950
|
-
return created;
|
|
1999
|
+
const hasSuspend = typeof declaration.suspend === "function";
|
|
2000
|
+
const hasResume = typeof declaration.resume === "function";
|
|
2001
|
+
if (hasSuspend !== hasResume) {
|
|
2002
|
+
throw new Error(
|
|
2003
|
+
`machine provider "${id}" must declare suspend and resume together`
|
|
2004
|
+
);
|
|
1951
2005
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2006
|
+
assertOptionalFunction(
|
|
2007
|
+
"machine provider",
|
|
2008
|
+
id,
|
|
2009
|
+
declaration.validate,
|
|
2010
|
+
"a validate"
|
|
2011
|
+
);
|
|
2012
|
+
assertOptionalFunction(
|
|
2013
|
+
"machine provider",
|
|
2014
|
+
id,
|
|
2015
|
+
declaration.availability,
|
|
2016
|
+
"availability"
|
|
2017
|
+
);
|
|
2018
|
+
return {
|
|
2019
|
+
id,
|
|
2020
|
+
displayName,
|
|
2021
|
+
description,
|
|
2022
|
+
icon,
|
|
2023
|
+
ephemeral,
|
|
2024
|
+
inputs: inputs === null ? null : inputs.schema,
|
|
2025
|
+
inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
|
|
2026
|
+
availability: declaration.availability ?? null,
|
|
2027
|
+
validate: declaration.validate ?? null,
|
|
2028
|
+
reconcileCleanup: declaration.reconcileCleanup,
|
|
2029
|
+
create: declaration.create,
|
|
2030
|
+
suspend: declaration.suspend ?? null,
|
|
2031
|
+
resume: declaration.resume ?? null,
|
|
2032
|
+
remove: declaration.remove
|
|
1960
2033
|
};
|
|
1961
|
-
return { sdk: node(""), harness };
|
|
1962
2034
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
2035
|
+
function validateServerAccessProviderDeclaration(declaration) {
|
|
2036
|
+
if (typeof declaration !== "object" || declaration === null)
|
|
2037
|
+
throw new Error("Invalid server access provider declaration");
|
|
2038
|
+
if (typeof declaration.id !== "string" || !/^[a-z][a-z0-9-]*$/u.test(declaration.id) || declaration.id === "direct")
|
|
2039
|
+
throw new Error("Invalid or reserved server access provider id");
|
|
2040
|
+
if (typeof declaration.displayName !== "string" || !declaration.displayName.trim() || typeof declaration.description !== "string" || !declaration.description.trim() || typeof declaration.availability !== "function" || typeof declaration.acquire !== "function" || typeof declaration.release !== "function")
|
|
2041
|
+
throw new Error("Invalid server access provider declaration");
|
|
2042
|
+
return declaration;
|
|
2043
|
+
}
|
|
1965
2044
|
var LEGACY_UNKNOWN_MIGRATION_HASH = "legacy-unknown";
|
|
1966
2045
|
function migrationStatementHash(statement) {
|
|
1967
2046
|
return createHash("sha256").update(statement).digest("hex");
|
|
1968
2047
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2048
|
+
function runPluginStorageMigrations(database, statements) {
|
|
2049
|
+
database.exec(
|
|
2050
|
+
"CREATE TABLE IF NOT EXISTS _bb_migrations (id INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL, statement_hash TEXT)"
|
|
2051
|
+
);
|
|
2052
|
+
const migrationColumns = database.prepare("PRAGMA table_info(_bb_migrations)").all();
|
|
2053
|
+
if (!migrationColumns.some((column) => column.name === "statement_hash")) {
|
|
2054
|
+
database.exec("ALTER TABLE _bb_migrations ADD COLUMN statement_hash TEXT");
|
|
2055
|
+
}
|
|
2056
|
+
const rows = database.prepare(
|
|
2057
|
+
"SELECT id, statement_hash FROM _bb_migrations ORDER BY id"
|
|
2058
|
+
).all();
|
|
2059
|
+
const applied = /* @__PURE__ */ new Map();
|
|
2060
|
+
for (const row of rows) applied.set(row.id, row.statement_hash);
|
|
2061
|
+
const statementHashes = statements.map(migrationStatementHash);
|
|
2062
|
+
statementHashes.forEach((statementHash, index) => {
|
|
2063
|
+
const recordedHash = applied.get(index);
|
|
2064
|
+
if (recordedHash !== void 0 && recordedHash !== null && recordedHash !== statementHash) {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`migration ${index} does not match the recorded statement; append a new migration instead of changing or reusing an index`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
const adopt = database.prepare(
|
|
2071
|
+
"UPDATE _bb_migrations SET statement_hash = ? WHERE id = ? AND statement_hash IS NULL"
|
|
2072
|
+
);
|
|
2073
|
+
const record = database.prepare(
|
|
2074
|
+
"INSERT INTO _bb_migrations (id, applied_at, statement_hash) VALUES (?, ?, ?)"
|
|
2075
|
+
);
|
|
2076
|
+
database.transaction(() => {
|
|
2077
|
+
for (const row of rows) {
|
|
2078
|
+
if (row.statement_hash !== null) continue;
|
|
2079
|
+
adopt.run(
|
|
2080
|
+
statementHashes[row.id] ?? LEGACY_UNKNOWN_MIGRATION_HASH,
|
|
2081
|
+
row.id
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
statements.forEach((statement, index) => {
|
|
2085
|
+
if (applied.has(index)) return;
|
|
2086
|
+
database.exec(statement);
|
|
2087
|
+
record.run(index, Date.now(), statementHashes[index]);
|
|
2088
|
+
});
|
|
2089
|
+
})();
|
|
2090
|
+
}
|
|
2091
|
+
function normalizeHttpRouteRegistration(method, path, handler, opts, existingRoutes) {
|
|
2092
|
+
const normalizedMethod = String(method).toUpperCase();
|
|
2093
|
+
if (!PLUGIN_HTTP_METHODS.has(normalizedMethod)) {
|
|
2094
|
+
throw new Error(
|
|
2095
|
+
`invalid http method "${String(method)}" \u2014 use one of: ${[...PLUGIN_HTTP_METHODS].join(", ")}`
|
|
1973
2096
|
);
|
|
1974
|
-
this.name = "PluginContextStaleError";
|
|
1975
2097
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2098
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
2099
|
+
throw new Error(
|
|
2100
|
+
`http route path must be a string starting with "/", got ${JSON.stringify(path)}`
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
if (typeof handler !== "function") {
|
|
2104
|
+
throw new Error(
|
|
2105
|
+
`http route handler for ${normalizedMethod} ${path} must be a function`
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
const auth = opts?.auth ?? "local";
|
|
2109
|
+
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2110
|
+
throw new Error(
|
|
2111
|
+
`invalid auth mode "${String(auth)}" for ${normalizedMethod} ${path} \u2014 use "local", "token", or "none"`
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
if (existingRoutes.some(
|
|
2115
|
+
(route) => route.method === normalizedMethod && route.path === path
|
|
2116
|
+
)) {
|
|
2117
|
+
throw new Error(
|
|
2118
|
+
`http route ${normalizedMethod} ${path} is already registered`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
return { method: normalizedMethod, path, auth };
|
|
2122
|
+
}
|
|
2123
|
+
function normalizeWebSocketRouteRegistration(path, handler, opts, existingRoutes) {
|
|
2124
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
2125
|
+
throw new Error(
|
|
2126
|
+
`websocket route path must be a string starting with "/", got ${JSON.stringify(path)}`
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
if (typeof handler !== "function") {
|
|
2130
|
+
throw new Error(`websocket route handler for ${path} must be a function`);
|
|
2131
|
+
}
|
|
2132
|
+
const auth = opts?.auth ?? "local";
|
|
2133
|
+
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2134
|
+
throw new Error(
|
|
2135
|
+
`invalid auth mode "${String(auth)}" for websocket ${path} \u2014 use "local", "token", or "none"`
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
if (existingRoutes.some((route) => route.path === path)) {
|
|
2139
|
+
throw new Error(`websocket route ${path} is already registered`);
|
|
2140
|
+
}
|
|
2141
|
+
return { path, auth };
|
|
2142
|
+
}
|
|
2143
|
+
function normalizeRpcRegistration(contract, handlers, registered) {
|
|
2144
|
+
if (typeof contract !== "object" || contract === null || Array.isArray(contract)) {
|
|
2145
|
+
throw new Error("rpc.register contract must be an object");
|
|
2146
|
+
}
|
|
2147
|
+
if (typeof handlers !== "object" || handlers === null || Array.isArray(handlers)) {
|
|
2148
|
+
throw new Error("rpc.register handlers must be an object");
|
|
2149
|
+
}
|
|
2150
|
+
const pending = [];
|
|
2151
|
+
const contractEntries = Object.entries(contract);
|
|
2152
|
+
const contractNames = new Set(contractEntries.map(([name]) => name));
|
|
2153
|
+
for (const extraName of Object.keys(handlers)) {
|
|
2154
|
+
if (!contractNames.has(extraName)) {
|
|
2155
|
+
throw new Error(
|
|
2156
|
+
`rpc handler "${extraName}" has no matching contract method`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
for (const [name, methodContractValue] of contractEntries) {
|
|
2161
|
+
if (!RPC_METHOD_PATTERN.test(name)) {
|
|
2162
|
+
throw new Error(
|
|
2163
|
+
`invalid rpc method name "${name}" \u2014 use dot-separated segments with letters, digits, "-" and "_"`
|
|
2164
|
+
);
|
|
1984
2165
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
2166
|
+
const methodContract = readRpcMethodContract(name, methodContractValue);
|
|
2167
|
+
const handler = Reflect.get(handlers, name);
|
|
2168
|
+
if (typeof handler !== "function") {
|
|
2169
|
+
throw new Error(`rpc method "${name}" must provide a handler function`);
|
|
1987
2170
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
if (descriptor.type === "select" && typeof value === "string" && !descriptor.options.includes(value)) {
|
|
1991
|
-
value = void 0;
|
|
2171
|
+
if (registered.has(name)) {
|
|
2172
|
+
throw new Error(`rpc method "${name}" is already registered`);
|
|
1992
2173
|
}
|
|
1993
|
-
|
|
2174
|
+
pending.push([
|
|
2175
|
+
name,
|
|
2176
|
+
{
|
|
2177
|
+
inputSchema: methodContract.input,
|
|
2178
|
+
outputSchema: methodContract.output,
|
|
2179
|
+
handler
|
|
2180
|
+
}
|
|
2181
|
+
]);
|
|
1994
2182
|
}
|
|
1995
|
-
return
|
|
1996
|
-
}
|
|
1997
|
-
function isNeedsConfigurationError(error) {
|
|
1998
|
-
return error instanceof Error && error.name === "NeedsConfigurationError";
|
|
2183
|
+
return pending;
|
|
1999
2184
|
}
|
|
2000
|
-
function
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
if (
|
|
2185
|
+
function normalizeRealtimePayload(channel, payload) {
|
|
2186
|
+
if (typeof channel !== "string" || channel.length === 0) {
|
|
2187
|
+
throw new Error("realtime channel must be a non-empty string");
|
|
2188
|
+
}
|
|
2189
|
+
if (payload === void 0) return null;
|
|
2005
2190
|
let json;
|
|
2006
2191
|
try {
|
|
2007
|
-
json = JSON.stringify(
|
|
2192
|
+
json = JSON.stringify(payload);
|
|
2008
2193
|
} catch {
|
|
2009
2194
|
json = void 0;
|
|
2010
2195
|
}
|
|
2011
2196
|
if (json === void 0) {
|
|
2012
|
-
throw new Error(
|
|
2197
|
+
throw new Error(
|
|
2198
|
+
`realtime payload for channel "${channel}" is not JSON-serializable`
|
|
2199
|
+
);
|
|
2013
2200
|
}
|
|
2014
2201
|
return JSON.parse(json);
|
|
2015
2202
|
}
|
|
2016
|
-
function
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
});
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2203
|
+
function validateBackgroundServiceRegistration(name, service, existing) {
|
|
2204
|
+
if (typeof name !== "string" || !BACKGROUND_NAME_PATTERN.test(name)) {
|
|
2205
|
+
throw new Error(
|
|
2206
|
+
`invalid service name ${JSON.stringify(name)} \u2014 use letters, digits, "-" and "_"`
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
if (existing.some((record) => record.name === name)) {
|
|
2210
|
+
throw new Error(`background service "${name}" is already registered`);
|
|
2211
|
+
}
|
|
2212
|
+
if (typeof service?.start !== "function") {
|
|
2213
|
+
throw new Error(
|
|
2214
|
+
`background service "${name}" must provide a start(signal) function`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
return { name, start: service.start.bind(service) };
|
|
2029
2218
|
}
|
|
2030
|
-
function
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
|
|
2219
|
+
function validateScheduleRegistration(name, cron, fn, existing, parseCron) {
|
|
2220
|
+
if (typeof name !== "string" || !BACKGROUND_NAME_PATTERN.test(name)) {
|
|
2221
|
+
throw new Error(
|
|
2222
|
+
`invalid schedule name ${JSON.stringify(name)} \u2014 use letters, digits, "-" and "_"`
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
if (existing.some((record) => record.name === name)) {
|
|
2226
|
+
throw new Error(`schedule "${name}" is already registered`);
|
|
2227
|
+
}
|
|
2038
2228
|
try {
|
|
2039
|
-
|
|
2229
|
+
parseCron(String(cron));
|
|
2040
2230
|
} catch (error) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
message: `rpc ${phase} validator failed: ${message}`,
|
|
2045
|
-
issues: [{ message }]
|
|
2046
|
-
});
|
|
2231
|
+
throw new Error(
|
|
2232
|
+
`invalid cron ${JSON.stringify(cron)} for schedule "${name}": ${error instanceof Error ? error.message : String(error)}`
|
|
2233
|
+
);
|
|
2047
2234
|
}
|
|
2048
|
-
if (
|
|
2049
|
-
|
|
2050
|
-
code: phase === "input" ? "invalid_input" : "invalid_output",
|
|
2051
|
-
message: `rpc ${phase} validation failed`,
|
|
2052
|
-
issues: normalizeRpcIssues(result.issues)
|
|
2053
|
-
});
|
|
2235
|
+
if (typeof fn !== "function") {
|
|
2236
|
+
throw new Error(`schedule "${name}" must provide a function`);
|
|
2054
2237
|
}
|
|
2055
|
-
return
|
|
2238
|
+
return { name, cron: String(cron), fn };
|
|
2056
2239
|
}
|
|
2057
|
-
function
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2240
|
+
function normalizeCliRegistration(registration, alreadyRegistered) {
|
|
2241
|
+
if (alreadyRegistered) {
|
|
2242
|
+
throw new Error("cli command is already registered");
|
|
2243
|
+
}
|
|
2244
|
+
const name = registration?.name;
|
|
2245
|
+
if (typeof name !== "string" || !CLI_COMMAND_NAME_PATTERN.test(name)) {
|
|
2246
|
+
throw new Error(
|
|
2247
|
+
`invalid cli command name ${JSON.stringify(name)} \u2014 use lowercase letters, digits, and "-"`
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
if (typeof registration.summary !== "string" || registration.summary.trim().length === 0) {
|
|
2251
|
+
throw new Error(`cli command "${name}" must provide a summary`);
|
|
2252
|
+
}
|
|
2253
|
+
const commands = registration.commands ?? [];
|
|
2254
|
+
if (!Array.isArray(commands)) {
|
|
2255
|
+
throw new Error(`cli command "${name}" commands must be an array`);
|
|
2256
|
+
}
|
|
2257
|
+
const validatedCommands = commands.map((command, index) => {
|
|
2258
|
+
if (typeof command?.name !== "string" || !CLI_COMMAND_NAME_PATTERN.test(command.name) || typeof command.summary !== "string" || typeof command.usage !== "string") {
|
|
2259
|
+
throw new Error(
|
|
2260
|
+
`cli command "${name}" commands[${index}] must be { name: [a-z0-9-]+, summary, usage }`
|
|
2261
|
+
);
|
|
2071
2262
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2263
|
+
return {
|
|
2264
|
+
name: command.name,
|
|
2265
|
+
summary: command.summary,
|
|
2266
|
+
usage: command.usage
|
|
2267
|
+
};
|
|
2268
|
+
});
|
|
2269
|
+
if (typeof registration.run !== "function") {
|
|
2270
|
+
throw new Error(
|
|
2271
|
+
`cli command "${name}" must provide a run(argv, ctx) function`
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
return {
|
|
2275
|
+
name,
|
|
2276
|
+
summary: registration.summary,
|
|
2277
|
+
commands: validatedCommands,
|
|
2278
|
+
run: registration.run.bind(registration)
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function normalizeAgentToolRegistration(args) {
|
|
2282
|
+
const { pluginId, declaredIconNames, tool } = args;
|
|
2283
|
+
const name = tool?.name;
|
|
2284
|
+
if (typeof name !== "string" || !AGENT_TOOL_NAME_PATTERN.test(name)) {
|
|
2285
|
+
throw new Error(
|
|
2286
|
+
`invalid tool name ${JSON.stringify(name)} \u2014 use letters, digits, "-" and "_"`
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
if (RESERVED_AGENT_TOOL_NAMES.includes(name)) {
|
|
2290
|
+
throw new Error(
|
|
2291
|
+
`tool name "${name}" is a built-in bb tool \u2014 pick another name`
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
rejectStaleAgentToolFields(name, tool);
|
|
2295
|
+
if (typeof tool.description !== "string" || tool.description.trim().length === 0) {
|
|
2296
|
+
throw new Error(`tool "${name}" must provide a description`);
|
|
2297
|
+
}
|
|
2298
|
+
if (tool.instructions !== void 0 && typeof tool.instructions !== "string") {
|
|
2299
|
+
throw new Error(`tool "${name}" instructions must be a string`);
|
|
2300
|
+
}
|
|
2301
|
+
if (typeof tool.instructions === "string" && tool.instructions.length > PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS) {
|
|
2302
|
+
throw new Error(
|
|
2303
|
+
`tool "${name}" instructions exceed the ${PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS}-character limit`
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
const presentation = parsePluginAgentToolPresentation(
|
|
2307
|
+
name,
|
|
2308
|
+
tool.presentation
|
|
2309
|
+
);
|
|
2310
|
+
if (presentation?.icon !== void 0) {
|
|
2311
|
+
const problem = undeclaredIconProblem(
|
|
2312
|
+
pluginId,
|
|
2313
|
+
declaredIconNames,
|
|
2314
|
+
presentation.icon.glyph
|
|
2315
|
+
);
|
|
2316
|
+
if (problem !== null) {
|
|
2317
|
+
throw new Error(agentToolIconRefusalMessage(name, problem));
|
|
2077
2318
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2319
|
+
}
|
|
2320
|
+
if (typeof tool.execute !== "function") {
|
|
2321
|
+
throw new Error(
|
|
2322
|
+
`tool "${name}" must provide an execute(params, ctx) function`
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
const parameters = tool.parameters;
|
|
2326
|
+
let inputSchema;
|
|
2327
|
+
let parse;
|
|
2328
|
+
if (isZodSchemaLike(parameters)) {
|
|
2329
|
+
try {
|
|
2330
|
+
inputSchema = zodSchemaToJsonSchema(parameters);
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
throw new Error(
|
|
2333
|
+
`tool "${name}" parameters look like a zod schema but could not be converted to JSON Schema (${error instanceof Error ? error.message : String(error)}) \u2014 use zod 4, or pass a plain JSON-schema object`
|
|
2334
|
+
);
|
|
2083
2335
|
}
|
|
2084
|
-
|
|
2336
|
+
parse = (input) => {
|
|
2337
|
+
const result = parameters.safeParse(input);
|
|
2338
|
+
if (result.success) return { ok: true, value: result.data };
|
|
2339
|
+
return { ok: false, error: summarizeParseIssues(result.error) };
|
|
2340
|
+
};
|
|
2341
|
+
} else if (typeof parameters === "object" && parameters !== null && !Array.isArray(parameters)) {
|
|
2085
2342
|
try {
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2343
|
+
inputSchema = JSON.parse(JSON.stringify(parameters));
|
|
2344
|
+
} catch {
|
|
2345
|
+
throw new Error(
|
|
2346
|
+
`tool "${name}" parameters JSON schema is not JSON-serializable`
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
parse = (input) => ({ ok: true, value: input });
|
|
2350
|
+
} else {
|
|
2351
|
+
throw new Error(
|
|
2352
|
+
`tool "${name}" parameters must be a zod schema or a JSON-schema object`
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2355
|
+
assertNoRecursiveJsonSchemaReferences(
|
|
2356
|
+
inputSchema,
|
|
2357
|
+
`tool "${name}" parameters`
|
|
2358
|
+
);
|
|
2359
|
+
return {
|
|
2360
|
+
name,
|
|
2361
|
+
description: tool.description,
|
|
2362
|
+
presentation,
|
|
2363
|
+
instructions: tool.instructions !== void 0 && tool.instructions.trim().length > 0 ? tool.instructions : null,
|
|
2364
|
+
inputSchema,
|
|
2365
|
+
parse,
|
|
2366
|
+
execute: tool.execute.bind(tool)
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
function normalizeMentionProviderRegistration(provider, existing) {
|
|
2370
|
+
const id = provider?.id;
|
|
2371
|
+
if (typeof id !== "string" || !MENTION_PROVIDER_ID_PATTERN.test(id)) {
|
|
2372
|
+
throw new Error(
|
|
2373
|
+
`invalid mention provider id ${JSON.stringify(id)} \u2014 use letters, digits, "-" and "_"`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
if (existing.some((record) => record.id === id)) {
|
|
2377
|
+
throw new Error(`mention provider "${id}" is already registered`);
|
|
2378
|
+
}
|
|
2379
|
+
if (typeof provider.label !== "string" || provider.label.trim().length === 0) {
|
|
2380
|
+
throw new Error(`mention provider "${id}" must provide a label`);
|
|
2381
|
+
}
|
|
2382
|
+
if (typeof provider.search !== "function") {
|
|
2383
|
+
throw new Error(
|
|
2384
|
+
`mention provider "${id}" must provide a search({ query, projectId, threadId }) function`
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2387
|
+
if (typeof provider.resolve !== "function") {
|
|
2388
|
+
throw new Error(
|
|
2389
|
+
`mention provider "${id}" must provide a resolve(itemId) function`
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
return {
|
|
2393
|
+
id,
|
|
2394
|
+
label: provider.label.trim(),
|
|
2395
|
+
triggers: normalizeMentionProviderTriggers(id, provider.triggers),
|
|
2396
|
+
search: provider.search.bind(provider),
|
|
2397
|
+
resolve: provider.resolve.bind(provider)
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function normalizeInteractionRequest(request) {
|
|
2401
|
+
if (!request || typeof request !== "object") {
|
|
2402
|
+
throw new Error("ui.requestInput requires an options object");
|
|
2403
|
+
}
|
|
2404
|
+
if (typeof request.threadId !== "string" || request.threadId.length === 0) {
|
|
2405
|
+
throw new Error("ui.requestInput threadId must be a non-empty string");
|
|
2406
|
+
}
|
|
2407
|
+
if (typeof request.rendererId !== "string" || !/^[a-zA-Z0-9_-]+$/.test(request.rendererId)) {
|
|
2408
|
+
throw new Error(
|
|
2409
|
+
"ui.requestInput rendererId must use letters, digits, '-' or '_'"
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
if (typeof request.title !== "string" || request.title.trim().length === 0 || request.title.trim().length > PLUGIN_INTERACTION_MAX_TITLE_LENGTH) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
`ui.requestInput title must be 1-${PLUGIN_INTERACTION_MAX_TITLE_LENGTH} characters`
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
let payload;
|
|
2418
|
+
try {
|
|
2419
|
+
const json = JSON.stringify(request.payload);
|
|
2420
|
+
if (json === void 0) throw new Error();
|
|
2421
|
+
if (Buffer.byteLength(json, "utf8") > PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES) {
|
|
2422
|
+
throw new Error("ui.requestInput payload exceeds 64 KiB");
|
|
2109
2423
|
}
|
|
2424
|
+
payload = JSON.parse(json);
|
|
2425
|
+
} catch (error) {
|
|
2426
|
+
if (error instanceof Error && error.message.includes("64 KiB")) {
|
|
2427
|
+
throw error;
|
|
2428
|
+
}
|
|
2429
|
+
throw new Error("ui.requestInput payload must be JSON-serializable");
|
|
2110
2430
|
}
|
|
2111
|
-
|
|
2431
|
+
const timeoutMs = request.timeoutMs ?? 10 * 60 * 1e3;
|
|
2432
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60 * 60 * 1e3) {
|
|
2433
|
+
throw new Error("ui.requestInput timeoutMs must be between 1 and 3600000");
|
|
2434
|
+
}
|
|
2435
|
+
return {
|
|
2436
|
+
threadId: request.threadId,
|
|
2437
|
+
rendererId: request.rendererId,
|
|
2438
|
+
title: request.title.trim(),
|
|
2439
|
+
payload,
|
|
2440
|
+
timeoutMs
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function validateProviderEnvContribution(label, providerId, resolve2, registered) {
|
|
2444
|
+
if (typeof providerId !== "string" || providerId.trim().length === 0) {
|
|
2445
|
+
throw new Error(`${label} requires a provider id`);
|
|
2446
|
+
}
|
|
2447
|
+
if (registered.has(providerId)) {
|
|
2448
|
+
throw new Error(`${label} for "${providerId}" is already registered`);
|
|
2449
|
+
}
|
|
2450
|
+
if (typeof resolve2 !== "function") {
|
|
2451
|
+
throw new Error(`${label} requires a resolver function`);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
function normalizePluginAgentToolParameters(args) {
|
|
2455
|
+
const { index, value } = args;
|
|
2456
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2457
|
+
throw new Error(
|
|
2458
|
+
`configure() output.tools[${index}].parameters must be a JSON-schema object`
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
let serialized;
|
|
2462
|
+
try {
|
|
2463
|
+
serialized = JSON.stringify(value);
|
|
2464
|
+
} catch {
|
|
2465
|
+
throw new Error(
|
|
2466
|
+
`configure() output.tools[${index}].parameters is not JSON-serializable`
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
if (serialized === void 0) {
|
|
2470
|
+
throw new Error(
|
|
2471
|
+
`configure() output.tools[${index}].parameters is not JSON-serializable`
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
if (Buffer.byteLength(serialized, "utf8") > PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES) {
|
|
2475
|
+
throw new Error(
|
|
2476
|
+
`configure() output.tools[${index}].parameters exceeds the ${PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES}-byte limit`
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2479
|
+
const parameters = JSON.parse(serialized);
|
|
2480
|
+
if (parameters.type !== "object") {
|
|
2481
|
+
throw new Error(
|
|
2482
|
+
`configure() output.tools[${index}].parameters must have root type "object"`
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
assertNoRecursiveJsonSchemaReferences(
|
|
2486
|
+
parameters,
|
|
2487
|
+
`configure() output.tools[${index}].parameters`
|
|
2488
|
+
);
|
|
2489
|
+
return parameters;
|
|
2112
2490
|
}
|
|
2113
|
-
function
|
|
2491
|
+
function normalizePluginAgentToolSelections(args) {
|
|
2114
2492
|
if (!Array.isArray(args.value)) {
|
|
2115
2493
|
throw new Error("configure() output.tools must be an array");
|
|
2116
2494
|
}
|
|
@@ -2137,7 +2515,7 @@ function normalizeAgentToolSelections(args) {
|
|
|
2137
2515
|
);
|
|
2138
2516
|
}
|
|
2139
2517
|
name = typed.name;
|
|
2140
|
-
parameters =
|
|
2518
|
+
parameters = normalizePluginAgentToolParameters({
|
|
2141
2519
|
index,
|
|
2142
2520
|
value: typed.parameters
|
|
2143
2521
|
});
|
|
@@ -2167,50 +2545,13 @@ function normalizeAgentToolSelections(args) {
|
|
|
2167
2545
|
}
|
|
2168
2546
|
return { toolIds, parameterOverrides };
|
|
2169
2547
|
}
|
|
2170
|
-
function
|
|
2171
|
-
const { index, value } = args;
|
|
2172
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2173
|
-
throw new Error(
|
|
2174
|
-
`configure() output.tools[${index}].parameters must be a JSON-schema object`
|
|
2175
|
-
);
|
|
2176
|
-
}
|
|
2177
|
-
let serialized;
|
|
2178
|
-
try {
|
|
2179
|
-
serialized = JSON.stringify(value);
|
|
2180
|
-
} catch {
|
|
2181
|
-
throw new Error(
|
|
2182
|
-
`configure() output.tools[${index}].parameters is not JSON-serializable`
|
|
2183
|
-
);
|
|
2184
|
-
}
|
|
2185
|
-
if (serialized === void 0) {
|
|
2186
|
-
throw new Error(
|
|
2187
|
-
`configure() output.tools[${index}].parameters is not JSON-serializable`
|
|
2188
|
-
);
|
|
2189
|
-
}
|
|
2190
|
-
if (Buffer.byteLength(serialized, "utf8") > PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES) {
|
|
2191
|
-
throw new Error(
|
|
2192
|
-
`configure() output.tools[${index}].parameters exceeds the ${PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES}-byte limit`
|
|
2193
|
-
);
|
|
2194
|
-
}
|
|
2195
|
-
const parameters = JSON.parse(serialized);
|
|
2196
|
-
if (parameters.type !== "object") {
|
|
2197
|
-
throw new Error(
|
|
2198
|
-
`configure() output.tools[${index}].parameters must have root type "object"`
|
|
2199
|
-
);
|
|
2200
|
-
}
|
|
2201
|
-
assertNoRecursiveJsonSchemaReferences(
|
|
2202
|
-
parameters,
|
|
2203
|
-
`configure() output.tools[${index}].parameters`
|
|
2204
|
-
);
|
|
2205
|
-
return parameters;
|
|
2206
|
-
}
|
|
2207
|
-
function normalizeAgentConfigurationIds(args) {
|
|
2548
|
+
function normalizePluginAgentSelectionIds(args) {
|
|
2208
2549
|
if (!Array.isArray(args.value)) {
|
|
2209
|
-
throw new Error(
|
|
2550
|
+
throw new Error("configure() output.skills must be an array");
|
|
2210
2551
|
}
|
|
2211
2552
|
if (args.value.length > PLUGIN_AGENT_SELECTION_MAX_IDS) {
|
|
2212
2553
|
throw new Error(
|
|
2213
|
-
`configure() output
|
|
2554
|
+
`configure() output.skills exceeds the ${PLUGIN_AGENT_SELECTION_MAX_IDS}-id limit`
|
|
2214
2555
|
);
|
|
2215
2556
|
}
|
|
2216
2557
|
const selected = [];
|
|
@@ -2219,12 +2560,12 @@ function normalizeAgentConfigurationIds(args) {
|
|
|
2219
2560
|
const id = args.value[index];
|
|
2220
2561
|
if (typeof id !== "string" || id.length === 0) {
|
|
2221
2562
|
throw new Error(
|
|
2222
|
-
`configure() output
|
|
2563
|
+
`configure() output.skills[${index}] must be a non-empty string`
|
|
2223
2564
|
);
|
|
2224
2565
|
}
|
|
2225
2566
|
if (seen.has(id)) {
|
|
2226
2567
|
throw new Error(
|
|
2227
|
-
`configure() output
|
|
2568
|
+
`configure() output.skills contains duplicate id ${JSON.stringify(id)}`
|
|
2228
2569
|
);
|
|
2229
2570
|
}
|
|
2230
2571
|
if (!args.knownIds.has(id)) {
|
|
@@ -2237,7 +2578,7 @@ function normalizeAgentConfigurationIds(args) {
|
|
|
2237
2578
|
}
|
|
2238
2579
|
return selected;
|
|
2239
2580
|
}
|
|
2240
|
-
function
|
|
2581
|
+
function normalizePluginAgentConfiguration(args) {
|
|
2241
2582
|
if (typeof args.value !== "object" || args.value === null || Array.isArray(args.value)) {
|
|
2242
2583
|
throw new Error(
|
|
2243
2584
|
"configure() must return { tools: string[], skills: string[], instructions?: string }"
|
|
@@ -2253,25 +2594,281 @@ function normalizeAgentConfiguration(args) {
|
|
|
2253
2594
|
if (output.instructions !== void 0 && typeof output.instructions !== "string") {
|
|
2254
2595
|
throw new Error("configure() output.instructions must be a string");
|
|
2255
2596
|
}
|
|
2256
|
-
const
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
};
|
|
2597
|
+
const instructions = typeof output.instructions === "string" && output.instructions.trim().length > 0 ? output.instructions.slice(
|
|
2598
|
+
0,
|
|
2599
|
+
PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS
|
|
2600
|
+
) : null;
|
|
2601
|
+
const toolSelections = normalizePluginAgentToolSelections({
|
|
2602
|
+
knownIds: args.knownToolIds,
|
|
2603
|
+
pluginId: args.pluginId,
|
|
2604
|
+
value: output.tools
|
|
2605
|
+
});
|
|
2606
|
+
return {
|
|
2607
|
+
toolIds: toolSelections.toolIds,
|
|
2608
|
+
toolParameterOverrides: toolSelections.parameterOverrides,
|
|
2609
|
+
skillIds: normalizePluginAgentSelectionIds({
|
|
2610
|
+
knownIds: args.knownSkillIds,
|
|
2611
|
+
pluginId: args.pluginId,
|
|
2612
|
+
value: output.skills
|
|
2613
|
+
}),
|
|
2614
|
+
instructions
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
function normalizeRpcIssuePath(path) {
|
|
2618
|
+
if (path === void 0) return void 0;
|
|
2619
|
+
const segments = Array.isArray(path) ? path : [path];
|
|
2620
|
+
const normalized = segments.map((segment) => {
|
|
2621
|
+
const key = typeof segment === "object" && segment !== null ? Reflect.get(segment, "key") : segment;
|
|
2622
|
+
return typeof key === "number" ? key : String(key);
|
|
2623
|
+
});
|
|
2624
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
2625
|
+
}
|
|
2626
|
+
function normalizeRpcIssues(issues) {
|
|
2627
|
+
return issues.map((issue) => {
|
|
2628
|
+
const path = normalizeRpcIssuePath(issue.path);
|
|
2629
|
+
return {
|
|
2630
|
+
message: issue.message,
|
|
2631
|
+
...path !== void 0 ? { path } : {}
|
|
2632
|
+
};
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
async function validateRpcValue(schema, value, phase, fail) {
|
|
2636
|
+
let result;
|
|
2637
|
+
try {
|
|
2638
|
+
result = await schema["~standard"].validate(value);
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2641
|
+
return fail({
|
|
2642
|
+
code: phase === "input" ? "invalid_input" : "invalid_output",
|
|
2643
|
+
message: `rpc ${phase} validator failed: ${detail}`,
|
|
2644
|
+
issues: [{ message: detail }]
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
if (result.issues !== void 0) {
|
|
2648
|
+
return fail({
|
|
2649
|
+
code: phase === "input" ? "invalid_input" : "invalid_output",
|
|
2650
|
+
message: `rpc ${phase} validation failed`,
|
|
2651
|
+
issues: normalizeRpcIssues(result.issues)
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
return result.value;
|
|
2655
|
+
}
|
|
2656
|
+
function normalizeRpcJsonResult(value, fail) {
|
|
2657
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
2658
|
+
function visit(current, path) {
|
|
2659
|
+
if (current === null || typeof current === "string" || typeof current === "boolean") {
|
|
2660
|
+
return current;
|
|
2661
|
+
}
|
|
2662
|
+
if (typeof current === "number") {
|
|
2663
|
+
if (!Number.isFinite(current)) {
|
|
2664
|
+
return fail({
|
|
2665
|
+
code: "non_json_result",
|
|
2666
|
+
message: `rpc result at ${path} contains a non-finite number`
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
return current;
|
|
2670
|
+
}
|
|
2671
|
+
if (typeof current !== "object") {
|
|
2672
|
+
return fail({
|
|
2673
|
+
code: "non_json_result",
|
|
2674
|
+
message: `rpc result at ${path} is not a JSON value (${typeof current})`
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
if (ancestors.has(current)) {
|
|
2678
|
+
return fail({
|
|
2679
|
+
code: "non_json_result",
|
|
2680
|
+
message: `rpc result at ${path} is cyclic`
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
ancestors.add(current);
|
|
2684
|
+
try {
|
|
2685
|
+
if (Array.isArray(current)) {
|
|
2686
|
+
return current.map((item, index) => visit(item, `${path}[${index}]`));
|
|
2687
|
+
}
|
|
2688
|
+
const prototype = Object.getPrototypeOf(current);
|
|
2689
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
2690
|
+
return fail({
|
|
2691
|
+
code: "non_json_result",
|
|
2692
|
+
message: `rpc result at ${path} must be a plain JSON object`
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
if (Reflect.ownKeys(current).some((key) => typeof key === "symbol")) {
|
|
2696
|
+
return fail({
|
|
2697
|
+
code: "non_json_result",
|
|
2698
|
+
message: `rpc result at ${path} contains a symbol key`
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
const normalized = {};
|
|
2702
|
+
for (const [key, child] of Object.entries(current)) {
|
|
2703
|
+
normalized[key] = visit(child, `${path}.${key}`);
|
|
2704
|
+
}
|
|
2705
|
+
return normalized;
|
|
2706
|
+
} finally {
|
|
2707
|
+
ancestors.delete(current);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
return visit(value, "$result");
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/testing/fake-plugin-host.ts
|
|
2714
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2715
|
+
import { tmpdir } from "node:os";
|
|
2716
|
+
import { join } from "node:path";
|
|
2717
|
+
import Database from "better-sqlite3";
|
|
2718
|
+
import { CronExpressionParser } from "cron-parser";
|
|
2719
|
+
import { Hono } from "hono";
|
|
2720
|
+
|
|
2721
|
+
// src/testing/fake-sdk.ts
|
|
2722
|
+
function withThreadAttribution(pluginId, args) {
|
|
2723
|
+
const [first, ...rest] = args;
|
|
2724
|
+
if (typeof first !== "object" || first === null) return args;
|
|
2725
|
+
const threadArgs = first;
|
|
2726
|
+
if (threadArgs.pluginMetadata !== void 0) {
|
|
2727
|
+
return [
|
|
2728
|
+
{
|
|
2729
|
+
...threadArgs,
|
|
2730
|
+
pluginMetadata: validatePluginMetadata(threadArgs.pluginMetadata),
|
|
2731
|
+
origin: "plugin",
|
|
2732
|
+
originPluginId: pluginId
|
|
2733
|
+
},
|
|
2734
|
+
...rest
|
|
2735
|
+
];
|
|
2736
|
+
}
|
|
2737
|
+
const origin = threadArgs.origin ?? "plugin";
|
|
2738
|
+
return [
|
|
2739
|
+
{
|
|
2740
|
+
...threadArgs,
|
|
2741
|
+
origin,
|
|
2742
|
+
...origin === "plugin" ? { originPluginId: threadArgs.originPluginId ?? pluginId } : {}
|
|
2743
|
+
},
|
|
2744
|
+
...rest
|
|
2745
|
+
];
|
|
2746
|
+
}
|
|
2747
|
+
function withPluginMetadataTarget(pluginId, args, validateSet) {
|
|
2748
|
+
const [first, ...rest] = args;
|
|
2749
|
+
if (typeof first !== "object" || first === null) return args;
|
|
2750
|
+
const input = first;
|
|
2751
|
+
return [
|
|
2752
|
+
{
|
|
2753
|
+
...input,
|
|
2754
|
+
pluginId: input.pluginId ?? pluginId,
|
|
2755
|
+
...validateSet && input.set !== void 0 ? { set: validatePluginMetadata(input.set) } : {}
|
|
2756
|
+
},
|
|
2757
|
+
...rest
|
|
2758
|
+
];
|
|
2759
|
+
}
|
|
2760
|
+
function normalizeCallArgs(pluginId, path, args) {
|
|
2761
|
+
switch (path) {
|
|
2762
|
+
case "threads.spawn":
|
|
2763
|
+
case "threads.fork":
|
|
2764
|
+
return withThreadAttribution(pluginId, args);
|
|
2765
|
+
case "threads.getPluginMetadata":
|
|
2766
|
+
return withPluginMetadataTarget(pluginId, args, false);
|
|
2767
|
+
case "threads.updatePluginMetadata":
|
|
2768
|
+
return withPluginMetadataTarget(pluginId, args, true);
|
|
2769
|
+
default:
|
|
2770
|
+
return args;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
function createFakeSdk(options) {
|
|
2774
|
+
const calls = [];
|
|
2775
|
+
const stubs = /* @__PURE__ */ new Map();
|
|
2776
|
+
function addOverrides(prefix, value) {
|
|
2777
|
+
if (typeof value === "function") {
|
|
2778
|
+
stubs.set(prefix, value);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
if (typeof value !== "object" || value === null) return;
|
|
2782
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2783
|
+
addOverrides(prefix.length === 0 ? key : `${prefix}.${key}`, child);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
addOverrides("", options.overrides ?? {});
|
|
2787
|
+
function invoke(path, rawArgs) {
|
|
2788
|
+
let args;
|
|
2789
|
+
try {
|
|
2790
|
+
args = normalizeCallArgs(options.pluginId, path, rawArgs);
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
return Promise.reject(error);
|
|
2793
|
+
}
|
|
2794
|
+
calls.push({ path, args });
|
|
2795
|
+
const stub = stubs.get(path);
|
|
2796
|
+
if (!stub) {
|
|
2797
|
+
throw new Error(
|
|
2798
|
+
`bb.sdk.${path} is not stubbed \u2014 pass an implementation via createFakePluginHost({ sdk: { ... } }) or harness.sdk.stub("${path}", fn)`
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
return stub(...args);
|
|
2802
|
+
}
|
|
2803
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2804
|
+
function node(path) {
|
|
2805
|
+
const cached = nodes.get(path);
|
|
2806
|
+
if (cached) return cached;
|
|
2807
|
+
const created = new Proxy(function() {
|
|
2808
|
+
}, {
|
|
2809
|
+
get(_target, prop) {
|
|
2810
|
+
if (typeof prop !== "string" || prop === "then") return void 0;
|
|
2811
|
+
return node(path === "" ? prop : `${path}.${prop}`);
|
|
2812
|
+
},
|
|
2813
|
+
apply(_target, _thisArg, args) {
|
|
2814
|
+
return invoke(path, args);
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2817
|
+
nodes.set(path, created);
|
|
2818
|
+
return created;
|
|
2819
|
+
}
|
|
2820
|
+
const harness = {
|
|
2821
|
+
calls,
|
|
2822
|
+
callsTo(path) {
|
|
2823
|
+
return calls.filter((call) => call.path === path).map((call) => call.args);
|
|
2824
|
+
},
|
|
2825
|
+
stub(path, implementation) {
|
|
2826
|
+
stubs.set(path, implementation);
|
|
2827
|
+
}
|
|
2828
|
+
};
|
|
2829
|
+
return { sdk: node(""), harness };
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
// src/testing/fake-plugin-host.ts
|
|
2833
|
+
var PluginContextStaleError = class extends Error {
|
|
2834
|
+
constructor(pluginId) {
|
|
2835
|
+
super(
|
|
2836
|
+
`plugin "${pluginId}" used a stale API handle \u2014 it was reloaded or disabled; re-entry happens via a fresh factory call`
|
|
2837
|
+
);
|
|
2838
|
+
this.name = "PluginContextStaleError";
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
function readSettingsValues(descriptors, stored) {
|
|
2842
|
+
const values = {};
|
|
2843
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
2844
|
+
values[key] = coerceStoredPluginSettingValue(descriptor, stored.get(key));
|
|
2845
|
+
}
|
|
2846
|
+
return values;
|
|
2847
|
+
}
|
|
2848
|
+
function isNeedsConfigurationError(error) {
|
|
2849
|
+
return error instanceof Error && error.name === "NeedsConfigurationError";
|
|
2850
|
+
}
|
|
2851
|
+
function errorMessage(error) {
|
|
2852
|
+
return error instanceof Error ? error.message : String(error);
|
|
2853
|
+
}
|
|
2854
|
+
function jsonRoundTrip(value, what) {
|
|
2855
|
+
if (value === void 0) return void 0;
|
|
2856
|
+
let json;
|
|
2857
|
+
try {
|
|
2858
|
+
json = JSON.stringify(value);
|
|
2859
|
+
} catch {
|
|
2860
|
+
json = void 0;
|
|
2861
|
+
}
|
|
2862
|
+
if (json === void 0) {
|
|
2863
|
+
throw new Error(`${what} is not JSON-serializable`);
|
|
2864
|
+
}
|
|
2865
|
+
return JSON.parse(json);
|
|
2866
|
+
}
|
|
2867
|
+
function throwRpcError(error) {
|
|
2868
|
+
const thrown = new Error(error.message);
|
|
2869
|
+
Reflect.set(thrown, "code", error.code);
|
|
2870
|
+
if (error.issues !== void 0) Reflect.set(thrown, "issues", error.issues);
|
|
2871
|
+
throw thrown;
|
|
2275
2872
|
}
|
|
2276
2873
|
var fakeHostDisposers = /* @__PURE__ */ new WeakMap();
|
|
2277
2874
|
function createFakePluginHost(options = {}) {
|
|
@@ -2353,49 +2950,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2353
2950
|
},
|
|
2354
2951
|
migrate(database, statements) {
|
|
2355
2952
|
assertLive();
|
|
2356
|
-
database
|
|
2357
|
-
"CREATE TABLE IF NOT EXISTS _bb_migrations (id INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL, statement_hash TEXT)"
|
|
2358
|
-
);
|
|
2359
|
-
const migrationColumns = database.prepare("PRAGMA table_info(_bb_migrations)").all();
|
|
2360
|
-
if (!migrationColumns.some((column) => column.name === "statement_hash")) {
|
|
2361
|
-
database.exec(
|
|
2362
|
-
"ALTER TABLE _bb_migrations ADD COLUMN statement_hash TEXT"
|
|
2363
|
-
);
|
|
2364
|
-
}
|
|
2365
|
-
const rows = database.prepare(
|
|
2366
|
-
"SELECT id, statement_hash FROM _bb_migrations ORDER BY id"
|
|
2367
|
-
).all();
|
|
2368
|
-
const applied = /* @__PURE__ */ new Map();
|
|
2369
|
-
for (const row of rows) applied.set(row.id, row.statement_hash);
|
|
2370
|
-
const statementHashes = statements.map(migrationStatementHash);
|
|
2371
|
-
statementHashes.forEach((statementHash, index) => {
|
|
2372
|
-
const recordedHash = applied.get(index);
|
|
2373
|
-
if (recordedHash !== void 0 && recordedHash !== null && recordedHash !== statementHash) {
|
|
2374
|
-
throw new Error(
|
|
2375
|
-
`migration ${index} does not match the recorded statement; append a new migration instead of changing or reusing an index`
|
|
2376
|
-
);
|
|
2377
|
-
}
|
|
2378
|
-
});
|
|
2379
|
-
const adopt = database.prepare(
|
|
2380
|
-
"UPDATE _bb_migrations SET statement_hash = ? WHERE id = ? AND statement_hash IS NULL"
|
|
2381
|
-
);
|
|
2382
|
-
const record = database.prepare(
|
|
2383
|
-
"INSERT INTO _bb_migrations (id, applied_at, statement_hash) VALUES (?, ?, ?)"
|
|
2384
|
-
);
|
|
2385
|
-
database.transaction(() => {
|
|
2386
|
-
for (const row of rows) {
|
|
2387
|
-
if (row.statement_hash !== null) continue;
|
|
2388
|
-
adopt.run(
|
|
2389
|
-
statementHashes[row.id] ?? LEGACY_UNKNOWN_MIGRATION_HASH,
|
|
2390
|
-
row.id
|
|
2391
|
-
);
|
|
2392
|
-
}
|
|
2393
|
-
statements.forEach((statement, index) => {
|
|
2394
|
-
if (applied.has(index)) return;
|
|
2395
|
-
database.exec(statement);
|
|
2396
|
-
record.run(index, Date.now(), statementHashes[index]);
|
|
2397
|
-
});
|
|
2398
|
-
})();
|
|
2953
|
+
runPluginStorageMigrations(database, statements);
|
|
2399
2954
|
}
|
|
2400
2955
|
};
|
|
2401
2956
|
const settingsDescriptors = {};
|
|
@@ -2468,107 +3023,35 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2468
3023
|
const http = {
|
|
2469
3024
|
route(method, path, handler, opts) {
|
|
2470
3025
|
assertLive();
|
|
2471
|
-
const
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
`http route path must be a string starting with "/", got ${JSON.stringify(path)}`
|
|
2480
|
-
);
|
|
2481
|
-
}
|
|
2482
|
-
if (typeof handler !== "function") {
|
|
2483
|
-
throw new Error(
|
|
2484
|
-
`http route handler for ${normalizedMethod} ${path} must be a function`
|
|
2485
|
-
);
|
|
2486
|
-
}
|
|
2487
|
-
const auth = opts?.auth ?? "local";
|
|
2488
|
-
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2489
|
-
throw new Error(
|
|
2490
|
-
`invalid auth mode "${String(auth)}" for ${normalizedMethod} ${path} \u2014 use "local", "token", or "none"`
|
|
2491
|
-
);
|
|
2492
|
-
}
|
|
2493
|
-
if (httpRoutes.some(
|
|
2494
|
-
(route) => route.method === normalizedMethod && route.path === path
|
|
2495
|
-
)) {
|
|
2496
|
-
throw new Error(
|
|
2497
|
-
`http route ${normalizedMethod} ${path} is already registered`
|
|
2498
|
-
);
|
|
2499
|
-
}
|
|
2500
|
-
httpRoutes.push({ method: normalizedMethod, path, auth, handler });
|
|
3026
|
+
const route = normalizeHttpRouteRegistration(
|
|
3027
|
+
method,
|
|
3028
|
+
path,
|
|
3029
|
+
handler,
|
|
3030
|
+
opts,
|
|
3031
|
+
httpRoutes
|
|
3032
|
+
);
|
|
3033
|
+
httpRoutes.push({ ...route, handler });
|
|
2501
3034
|
},
|
|
2502
3035
|
experimental_websocket(path, handler, opts) {
|
|
2503
3036
|
assertLive();
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
`websocket route handler for ${path} must be a function`
|
|
2512
|
-
);
|
|
2513
|
-
}
|
|
2514
|
-
const auth = opts?.auth ?? "local";
|
|
2515
|
-
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2516
|
-
throw new Error(
|
|
2517
|
-
`invalid auth mode "${String(auth)}" for websocket ${path} \u2014 use "local", "token", or "none"`
|
|
2518
|
-
);
|
|
2519
|
-
}
|
|
2520
|
-
if (websocketRoutes.some((route) => route.path === path)) {
|
|
2521
|
-
throw new Error(`websocket route ${path} is already registered`);
|
|
2522
|
-
}
|
|
2523
|
-
websocketRoutes.push({ path, auth, handler });
|
|
3037
|
+
const route = normalizeWebSocketRouteRegistration(
|
|
3038
|
+
path,
|
|
3039
|
+
handler,
|
|
3040
|
+
opts,
|
|
3041
|
+
websocketRoutes
|
|
3042
|
+
);
|
|
3043
|
+
websocketRoutes.push({ ...route, handler });
|
|
2524
3044
|
}
|
|
2525
3045
|
};
|
|
2526
3046
|
const rpcHandlers = /* @__PURE__ */ new Map();
|
|
2527
3047
|
const rpc = {
|
|
2528
3048
|
register(contract, handlers) {
|
|
2529
3049
|
assertLive();
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
}
|
|
2536
|
-
const pending = [];
|
|
2537
|
-
const contractEntries = Object.entries(contract);
|
|
2538
|
-
const contractNames = new Set(contractEntries.map(([name]) => name));
|
|
2539
|
-
for (const extraName of Object.keys(handlers)) {
|
|
2540
|
-
if (!contractNames.has(extraName)) {
|
|
2541
|
-
throw new Error(
|
|
2542
|
-
`rpc handler "${extraName}" has no matching contract method`
|
|
2543
|
-
);
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
for (const [name, contractValue] of contractEntries) {
|
|
2547
|
-
if (!RPC_METHOD_PATTERN.test(name)) {
|
|
2548
|
-
throw new Error(
|
|
2549
|
-
`invalid rpc method name "${name}" \u2014 use dot-separated segments with letters, digits, "-" and "_"`
|
|
2550
|
-
);
|
|
2551
|
-
}
|
|
2552
|
-
const methodContract = readRpcMethodContract(name, contractValue);
|
|
2553
|
-
const handler = Reflect.get(handlers, name);
|
|
2554
|
-
if (typeof handler !== "function") {
|
|
2555
|
-
throw new Error(
|
|
2556
|
-
`rpc method "${name}" must provide a handler function`
|
|
2557
|
-
);
|
|
2558
|
-
}
|
|
2559
|
-
if (rpcHandlers.has(name)) {
|
|
2560
|
-
throw new Error(`rpc method "${name}" is already registered`);
|
|
2561
|
-
}
|
|
2562
|
-
pending.push([
|
|
2563
|
-
name,
|
|
2564
|
-
{
|
|
2565
|
-
inputSchema: methodContract.input,
|
|
2566
|
-
outputSchema: methodContract.output,
|
|
2567
|
-
handler
|
|
2568
|
-
}
|
|
2569
|
-
]);
|
|
2570
|
-
}
|
|
2571
|
-
for (const [name, record] of pending) {
|
|
3050
|
+
for (const [name, record] of normalizeRpcRegistration(
|
|
3051
|
+
contract,
|
|
3052
|
+
handlers,
|
|
3053
|
+
rpcHandlers
|
|
3054
|
+
)) {
|
|
2572
3055
|
rpcHandlers.set(name, record);
|
|
2573
3056
|
}
|
|
2574
3057
|
}
|
|
@@ -2577,14 +3060,10 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2577
3060
|
const realtime = {
|
|
2578
3061
|
publish(channel, payload) {
|
|
2579
3062
|
assertLive();
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
payload,
|
|
2585
|
-
`realtime payload for channel "${channel}"`
|
|
2586
|
-
) ?? null;
|
|
2587
|
-
realtimeSignals.push({ channel, payload: normalized });
|
|
3063
|
+
realtimeSignals.push({
|
|
3064
|
+
channel,
|
|
3065
|
+
payload: normalizeRealtimePayload(channel, payload)
|
|
3066
|
+
});
|
|
2588
3067
|
}
|
|
2589
3068
|
};
|
|
2590
3069
|
const services = [];
|
|
@@ -2592,42 +3071,23 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2592
3071
|
const background = {
|
|
2593
3072
|
service(name, service) {
|
|
2594
3073
|
assertLive();
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
);
|
|
2599
|
-
}
|
|
2600
|
-
if (services.some((record) => record.name === name)) {
|
|
2601
|
-
throw new Error(`background service "${name}" is already registered`);
|
|
2602
|
-
}
|
|
2603
|
-
if (typeof service?.start !== "function") {
|
|
2604
|
-
throw new Error(
|
|
2605
|
-
`background service "${name}" must provide a start(signal) function`
|
|
2606
|
-
);
|
|
2607
|
-
}
|
|
2608
|
-
services.push({ name, start: service.start.bind(service) });
|
|
3074
|
+
services.push(
|
|
3075
|
+
validateBackgroundServiceRegistration(name, service, services)
|
|
3076
|
+
);
|
|
2609
3077
|
},
|
|
2610
3078
|
schedule(name, cron, fn) {
|
|
2611
3079
|
assertLive();
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
throw new Error(
|
|
2624
|
-
`invalid cron ${JSON.stringify(cron)} for schedule "${name}": ${errorMessage(error)}`
|
|
2625
|
-
);
|
|
2626
|
-
}
|
|
2627
|
-
if (typeof fn !== "function") {
|
|
2628
|
-
throw new Error(`schedule "${name}" must provide a function`);
|
|
2629
|
-
}
|
|
2630
|
-
schedules.push({ name, cron: String(cron), fn });
|
|
3080
|
+
schedules.push(
|
|
3081
|
+
validateScheduleRegistration(
|
|
3082
|
+
name,
|
|
3083
|
+
cron,
|
|
3084
|
+
fn,
|
|
3085
|
+
schedules,
|
|
3086
|
+
(expression) => {
|
|
3087
|
+
CronExpressionParser.parse(expression);
|
|
3088
|
+
}
|
|
3089
|
+
)
|
|
3090
|
+
);
|
|
2631
3091
|
}
|
|
2632
3092
|
};
|
|
2633
3093
|
const cliRecord = {
|
|
@@ -2636,46 +3096,12 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2636
3096
|
const cli = {
|
|
2637
3097
|
register(registration) {
|
|
2638
3098
|
assertLive();
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
`invalid cli command name ${JSON.stringify(name)} \u2014 use lowercase letters, digits, and "-"`
|
|
2646
|
-
);
|
|
2647
|
-
}
|
|
2648
|
-
if (typeof registration.summary !== "string" || registration.summary.trim().length === 0) {
|
|
2649
|
-
throw new Error(`cli command "${name}" must provide a summary`);
|
|
2650
|
-
}
|
|
2651
|
-
const commands = registration.commands ?? [];
|
|
2652
|
-
if (!Array.isArray(commands)) {
|
|
2653
|
-
throw new Error(`cli command "${name}" commands must be an array`);
|
|
2654
|
-
}
|
|
2655
|
-
const validatedCommands = commands.map((command, index) => {
|
|
2656
|
-
if (typeof command?.name !== "string" || !CLI_COMMAND_NAME_PATTERN.test(command.name) || typeof command.summary !== "string" || typeof command.usage !== "string") {
|
|
2657
|
-
throw new Error(
|
|
2658
|
-
`cli command "${name}" commands[${index}] must be { name: [a-z0-9-]+, summary, usage }`
|
|
2659
|
-
);
|
|
2660
|
-
}
|
|
2661
|
-
return {
|
|
2662
|
-
name: command.name,
|
|
2663
|
-
summary: command.summary,
|
|
2664
|
-
usage: command.usage
|
|
2665
|
-
};
|
|
2666
|
-
});
|
|
2667
|
-
if (typeof registration.run !== "function") {
|
|
2668
|
-
throw new Error(
|
|
2669
|
-
`cli command "${name}" must provide a run(argv, ctx) function`
|
|
2670
|
-
);
|
|
2671
|
-
}
|
|
2672
|
-
cliRecord.registration = {
|
|
2673
|
-
name,
|
|
2674
|
-
summary: registration.summary,
|
|
2675
|
-
commands: validatedCommands,
|
|
2676
|
-
run: registration.run.bind(registration)
|
|
2677
|
-
};
|
|
2678
|
-
const warning = pluginCliCollisionWarning(pluginId, name);
|
|
3099
|
+
const record = normalizeCliRegistration(
|
|
3100
|
+
registration,
|
|
3101
|
+
cliRecord.registration !== null
|
|
3102
|
+
);
|
|
3103
|
+
cliRecord.registration = record;
|
|
3104
|
+
const warning = pluginCliCollisionWarning(pluginId, record.name);
|
|
2679
3105
|
if (warning) emitLog("warn", warning);
|
|
2680
3106
|
}
|
|
2681
3107
|
};
|
|
@@ -2761,93 +3187,13 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2761
3187
|
},
|
|
2762
3188
|
registerTool(tool) {
|
|
2763
3189
|
assertLive();
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
throw new Error(
|
|
2772
|
-
`tool name "${name}" is a built-in bb tool \u2014 pick another name`
|
|
2773
|
-
);
|
|
2774
|
-
}
|
|
2775
|
-
rejectStaleAgentToolFields(name, tool);
|
|
2776
|
-
if (typeof tool.description !== "string" || tool.description.trim().length === 0) {
|
|
2777
|
-
throw new Error(`tool "${name}" must provide a description`);
|
|
2778
|
-
}
|
|
2779
|
-
if (tool.instructions !== void 0 && typeof tool.instructions !== "string") {
|
|
2780
|
-
throw new Error(`tool "${name}" instructions must be a string`);
|
|
2781
|
-
}
|
|
2782
|
-
if (typeof tool.instructions === "string" && tool.instructions.length > PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS) {
|
|
2783
|
-
throw new Error(
|
|
2784
|
-
`tool "${name}" instructions exceed the ${PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS}-character limit`
|
|
2785
|
-
);
|
|
2786
|
-
}
|
|
2787
|
-
const presentation = parsePluginAgentToolPresentation(
|
|
2788
|
-
name,
|
|
2789
|
-
tool.presentation
|
|
2790
|
-
);
|
|
2791
|
-
if (presentation?.icon !== void 0) {
|
|
2792
|
-
const problem = undeclaredIconProblem(
|
|
2793
|
-
pluginId,
|
|
2794
|
-
declaredIconNames,
|
|
2795
|
-
presentation.icon.glyph
|
|
2796
|
-
);
|
|
2797
|
-
if (problem !== null) {
|
|
2798
|
-
throw new Error(agentToolIconRefusalMessage(name, problem));
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
if (typeof tool.execute !== "function") {
|
|
2802
|
-
throw new Error(
|
|
2803
|
-
`tool "${name}" must provide an execute(params, ctx) function`
|
|
2804
|
-
);
|
|
2805
|
-
}
|
|
2806
|
-
const parameters = tool.parameters;
|
|
2807
|
-
let inputSchema;
|
|
2808
|
-
let parse;
|
|
2809
|
-
if (isZodSchemaLike(parameters)) {
|
|
2810
|
-
try {
|
|
2811
|
-
inputSchema = zodSchemaToJsonSchema(parameters);
|
|
2812
|
-
} catch (error) {
|
|
2813
|
-
throw new Error(
|
|
2814
|
-
`tool "${name}" parameters look like a zod schema but could not be converted to JSON Schema (${errorMessage(error)}) \u2014 use zod 4, or pass a plain JSON-schema object`
|
|
2815
|
-
);
|
|
2816
|
-
}
|
|
2817
|
-
parse = (input) => {
|
|
2818
|
-
const result = parameters.safeParse(input);
|
|
2819
|
-
if (result.success) return { ok: true, value: result.data };
|
|
2820
|
-
return { ok: false, error: summarizeParseIssues(result.error) };
|
|
2821
|
-
};
|
|
2822
|
-
} else if (typeof parameters === "object" && parameters !== null && !Array.isArray(parameters)) {
|
|
2823
|
-
try {
|
|
2824
|
-
inputSchema = JSON.parse(JSON.stringify(parameters));
|
|
2825
|
-
} catch {
|
|
2826
|
-
throw new Error(
|
|
2827
|
-
`tool "${name}" parameters JSON schema is not JSON-serializable`
|
|
2828
|
-
);
|
|
2829
|
-
}
|
|
2830
|
-
parse = (input) => ({ ok: true, value: input });
|
|
2831
|
-
} else {
|
|
2832
|
-
throw new Error(
|
|
2833
|
-
`tool "${name}" parameters must be a zod schema or a JSON-schema object`
|
|
2834
|
-
);
|
|
2835
|
-
}
|
|
2836
|
-
assertNoRecursiveJsonSchemaReferences(
|
|
2837
|
-
inputSchema,
|
|
2838
|
-
`tool "${name}" parameters`
|
|
2839
|
-
);
|
|
2840
|
-
const record = {
|
|
2841
|
-
name,
|
|
2842
|
-
description: tool.description,
|
|
2843
|
-
presentation,
|
|
2844
|
-
instructions: tool.instructions !== void 0 && tool.instructions.trim().length > 0 ? tool.instructions : null,
|
|
2845
|
-
inputSchema,
|
|
2846
|
-
parse,
|
|
2847
|
-
execute: tool.execute.bind(tool)
|
|
2848
|
-
};
|
|
2849
|
-
if (agentTools.some((existing) => existing.name === name)) {
|
|
2850
|
-
throw new Error(`tool "${name}" is already registered`);
|
|
3190
|
+
const record = normalizeAgentToolRegistration({
|
|
3191
|
+
pluginId,
|
|
3192
|
+
declaredIconNames,
|
|
3193
|
+
tool
|
|
3194
|
+
});
|
|
3195
|
+
if (agentTools.some((existing) => existing.name === record.name)) {
|
|
3196
|
+
throw new Error(`tool "${record.name}" is already registered`);
|
|
2851
3197
|
}
|
|
2852
3198
|
agentTools.push(record);
|
|
2853
3199
|
}
|
|
@@ -2857,35 +3203,9 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2857
3203
|
requestInput,
|
|
2858
3204
|
registerMentionProvider(provider) {
|
|
2859
3205
|
assertLive();
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
`invalid mention provider id ${JSON.stringify(id)} \u2014 use letters, digits, "-" and "_"`
|
|
2864
|
-
);
|
|
2865
|
-
}
|
|
2866
|
-
if (mentionProviders.some((record) => record.id === id)) {
|
|
2867
|
-
throw new Error(`mention provider "${id}" is already registered`);
|
|
2868
|
-
}
|
|
2869
|
-
if (typeof provider.label !== "string" || provider.label.trim().length === 0) {
|
|
2870
|
-
throw new Error(`mention provider "${id}" must provide a label`);
|
|
2871
|
-
}
|
|
2872
|
-
if (typeof provider.search !== "function") {
|
|
2873
|
-
throw new Error(
|
|
2874
|
-
`mention provider "${id}" must provide a search({ query, projectId, threadId }) function`
|
|
2875
|
-
);
|
|
2876
|
-
}
|
|
2877
|
-
if (typeof provider.resolve !== "function") {
|
|
2878
|
-
throw new Error(
|
|
2879
|
-
`mention provider "${id}" must provide a resolve(itemId) function`
|
|
2880
|
-
);
|
|
2881
|
-
}
|
|
2882
|
-
mentionProviders.push({
|
|
2883
|
-
id,
|
|
2884
|
-
label: provider.label.trim(),
|
|
2885
|
-
triggers: normalizeMentionProviderTriggers(id, provider.triggers),
|
|
2886
|
-
search: provider.search.bind(provider),
|
|
2887
|
-
resolve: provider.resolve.bind(provider)
|
|
2888
|
-
});
|
|
3206
|
+
mentionProviders.push(
|
|
3207
|
+
normalizeMentionProviderRegistration(provider, mentionProviders)
|
|
3208
|
+
);
|
|
2889
3209
|
}
|
|
2890
3210
|
};
|
|
2891
3211
|
let requestedDrains = 0;
|
|
@@ -2920,6 +3240,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2920
3240
|
overrides: options.sdk
|
|
2921
3241
|
});
|
|
2922
3242
|
const threadEventHandlers = {
|
|
3243
|
+
"experimental_thread.events": [],
|
|
3244
|
+
"experimental_terminal.input": [],
|
|
2923
3245
|
"thread.created": [],
|
|
2924
3246
|
"thread.active": [],
|
|
2925
3247
|
"thread.idle": [],
|
|
@@ -2936,54 +3258,20 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2936
3258
|
const hooks = {
|
|
2937
3259
|
"message.dispatch": null
|
|
2938
3260
|
};
|
|
3261
|
+
const environmentCompositions = /* @__PURE__ */ new Map();
|
|
2939
3262
|
const environmentProviders = /* @__PURE__ */ new Map();
|
|
3263
|
+
const machineProviders = /* @__PURE__ */ new Map();
|
|
3264
|
+
const serverAccessProviders = /* @__PURE__ */ new Map();
|
|
2940
3265
|
const disposeHooks = [];
|
|
2941
3266
|
const serviceControllers = [];
|
|
2942
3267
|
let nextInteractionId = 1;
|
|
2943
3268
|
const pendingInteractions = /* @__PURE__ */ new Map();
|
|
2944
3269
|
function requestInput(request, requestOptions) {
|
|
2945
3270
|
assertLive();
|
|
2946
|
-
|
|
2947
|
-
throw new Error("ui.requestInput requires an options object");
|
|
2948
|
-
}
|
|
2949
|
-
if (typeof request.threadId !== "string" || request.threadId.length === 0) {
|
|
2950
|
-
throw new Error("ui.requestInput threadId must be a non-empty string");
|
|
2951
|
-
}
|
|
2952
|
-
if (typeof request.rendererId !== "string" || !/^[a-zA-Z0-9_-]+$/.test(request.rendererId)) {
|
|
2953
|
-
throw new Error(
|
|
2954
|
-
"ui.requestInput rendererId must use letters, digits, '-' or '_'"
|
|
2955
|
-
);
|
|
2956
|
-
}
|
|
2957
|
-
if (typeof request.title !== "string" || request.title.trim().length === 0 || request.title.trim().length > PLUGIN_INTERACTION_MAX_TITLE_LENGTH) {
|
|
2958
|
-
throw new Error(
|
|
2959
|
-
`ui.requestInput title must be 1-${PLUGIN_INTERACTION_MAX_TITLE_LENGTH} characters`
|
|
2960
|
-
);
|
|
2961
|
-
}
|
|
2962
|
-
let payload;
|
|
2963
|
-
try {
|
|
2964
|
-
const json = JSON.stringify(request.payload);
|
|
2965
|
-
if (json === void 0) throw new Error();
|
|
2966
|
-
if (Buffer.byteLength(json, "utf8") > 64 * 1024) {
|
|
2967
|
-
throw new Error("ui.requestInput payload exceeds 64 KiB");
|
|
2968
|
-
}
|
|
2969
|
-
payload = JSON.parse(json);
|
|
2970
|
-
} catch (error) {
|
|
2971
|
-
if (error instanceof Error && error.message.includes("64 KiB")) {
|
|
2972
|
-
throw error;
|
|
2973
|
-
}
|
|
2974
|
-
throw new Error("ui.requestInput payload must be JSON-serializable");
|
|
2975
|
-
}
|
|
2976
|
-
const timeoutMs = request.timeoutMs ?? 10 * 60 * 1e3;
|
|
2977
|
-
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60 * 60 * 1e3) {
|
|
2978
|
-
throw new Error(
|
|
2979
|
-
"ui.requestInput timeoutMs must be between 1 and 3600000"
|
|
2980
|
-
);
|
|
2981
|
-
}
|
|
3271
|
+
const normalized = normalizeInteractionRequest(request);
|
|
2982
3272
|
const normalizedRequest = {
|
|
2983
3273
|
...request,
|
|
2984
|
-
|
|
2985
|
-
payload,
|
|
2986
|
-
timeoutMs
|
|
3274
|
+
...normalized
|
|
2987
3275
|
};
|
|
2988
3276
|
const id = `fake-interaction-${nextInteractionId++}`;
|
|
2989
3277
|
return new Promise((resolve2) => {
|
|
@@ -3000,7 +3288,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3000
3288
|
const timer = setTimeout(() => {
|
|
3001
3289
|
pendingInteractions.delete(id);
|
|
3002
3290
|
resolve2({ outcome: "cancelled", reason: "timeout" });
|
|
3003
|
-
}, timeoutMs);
|
|
3291
|
+
}, normalized.timeoutMs);
|
|
3004
3292
|
pendingInteractions.set(id, {
|
|
3005
3293
|
request: normalizedRequest,
|
|
3006
3294
|
resolve: resolve2,
|
|
@@ -3032,7 +3320,13 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3032
3320
|
});
|
|
3033
3321
|
}
|
|
3034
3322
|
const validatedInput = normalizeRpcJsonResult(
|
|
3035
|
-
await validateRpcValue(
|
|
3323
|
+
await validateRpcValue(
|
|
3324
|
+
methodContract.input,
|
|
3325
|
+
input,
|
|
3326
|
+
"input",
|
|
3327
|
+
throwRpcError
|
|
3328
|
+
),
|
|
3329
|
+
throwRpcError
|
|
3036
3330
|
);
|
|
3037
3331
|
const call = {
|
|
3038
3332
|
method: String(method),
|
|
@@ -3050,9 +3344,13 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3050
3344
|
const validatedOutput = await validateRpcValue(
|
|
3051
3345
|
methodContract.output,
|
|
3052
3346
|
rawOutput,
|
|
3053
|
-
"output"
|
|
3347
|
+
"output",
|
|
3348
|
+
throwRpcError
|
|
3349
|
+
);
|
|
3350
|
+
return normalizeRpcJsonResult(
|
|
3351
|
+
validatedOutput,
|
|
3352
|
+
throwRpcError
|
|
3054
3353
|
);
|
|
3055
|
-
return normalizeRpcJsonResult(validatedOutput);
|
|
3056
3354
|
},
|
|
3057
3355
|
experimental_onWorkerExit(handler) {
|
|
3058
3356
|
assertLive();
|
|
@@ -3154,40 +3452,22 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3154
3452
|
},
|
|
3155
3453
|
experimental_contributeEnv(providerId, resolve2) {
|
|
3156
3454
|
assertLive();
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
throw new Error(
|
|
3164
|
-
`provider environment contribution for "${providerId}" is already registered`
|
|
3165
|
-
);
|
|
3166
|
-
}
|
|
3167
|
-
if (typeof resolve2 !== "function") {
|
|
3168
|
-
throw new Error(
|
|
3169
|
-
"provider environment contribution requires a resolver function"
|
|
3170
|
-
);
|
|
3171
|
-
}
|
|
3455
|
+
validateProviderEnvContribution(
|
|
3456
|
+
"provider environment contribution",
|
|
3457
|
+
providerId,
|
|
3458
|
+
resolve2,
|
|
3459
|
+
providerEnvResolvers
|
|
3460
|
+
);
|
|
3172
3461
|
providerEnvResolvers.set(providerId, resolve2);
|
|
3173
3462
|
},
|
|
3174
3463
|
experimental_contributeEnvHealth(providerId, resolve2) {
|
|
3175
3464
|
assertLive();
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
throw new Error(
|
|
3183
|
-
`provider environment health contribution for "${providerId}" is already registered`
|
|
3184
|
-
);
|
|
3185
|
-
}
|
|
3186
|
-
if (typeof resolve2 !== "function") {
|
|
3187
|
-
throw new Error(
|
|
3188
|
-
"provider environment health contribution requires a resolver function"
|
|
3189
|
-
);
|
|
3190
|
-
}
|
|
3465
|
+
validateProviderEnvContribution(
|
|
3466
|
+
"provider environment health contribution",
|
|
3467
|
+
providerId,
|
|
3468
|
+
resolve2,
|
|
3469
|
+
providerEnvHealthResolvers
|
|
3470
|
+
);
|
|
3191
3471
|
providerEnvHealthResolvers.set(providerId, resolve2);
|
|
3192
3472
|
}
|
|
3193
3473
|
};
|
|
@@ -3206,6 +3486,26 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3206
3486
|
const experimental_environments = {
|
|
3207
3487
|
register(declaration) {
|
|
3208
3488
|
assertLive();
|
|
3489
|
+
if ("machineProviderId" in declaration) {
|
|
3490
|
+
const composition = environmentCompositionSchema.parse(declaration);
|
|
3491
|
+
const problem2 = composition.icon === null ? null : undeclaredIconProblem(
|
|
3492
|
+
pluginId,
|
|
3493
|
+
declaredIconNames,
|
|
3494
|
+
composition.icon
|
|
3495
|
+
);
|
|
3496
|
+
if (problem2 !== null)
|
|
3497
|
+
throw new Error(providerIconRefusalMessage(composition.id, problem2));
|
|
3498
|
+
if (environmentProviders.has(composition.id))
|
|
3499
|
+
throw new Error(
|
|
3500
|
+
"Environment ID is already registered as a concrete provider"
|
|
3501
|
+
);
|
|
3502
|
+
environmentCompositions.set(composition.id, composition);
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
if (environmentCompositions.has(declaration.id))
|
|
3506
|
+
throw new Error(
|
|
3507
|
+
"Environment ID is already registered as a composition"
|
|
3508
|
+
);
|
|
3209
3509
|
const target = validatePluginEnvironmentProviderDeclaration(declaration);
|
|
3210
3510
|
const problem = target.icon === null ? null : undeclaredIconProblem(pluginId, declaredIconNames, target.icon);
|
|
3211
3511
|
if (problem !== null)
|
|
@@ -3217,6 +3517,29 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3217
3517
|
requestedDrains += 1;
|
|
3218
3518
|
}
|
|
3219
3519
|
};
|
|
3520
|
+
const unavailableMachineBootstrap = () => {
|
|
3521
|
+
throw new Error(
|
|
3522
|
+
"Configure machineBootstrap in createFakePluginHost to exercise machine bootstrap"
|
|
3523
|
+
);
|
|
3524
|
+
};
|
|
3525
|
+
const experimental_machines = {
|
|
3526
|
+
async getResource(hostId) {
|
|
3527
|
+
assertLive();
|
|
3528
|
+
return options.machineResource ? options.machineResource(hostId) : null;
|
|
3529
|
+
},
|
|
3530
|
+
...options.machineBootstrap ?? {
|
|
3531
|
+
bootstrap: unavailableMachineBootstrap
|
|
3532
|
+
},
|
|
3533
|
+
register(declaration) {
|
|
3534
|
+
assertLive();
|
|
3535
|
+
const target = validatePluginMachineProviderDeclaration(declaration);
|
|
3536
|
+
const problem = target.icon === null ? null : undeclaredIconProblem(pluginId, declaredIconNames, target.icon);
|
|
3537
|
+
if (problem !== null) {
|
|
3538
|
+
throw new Error(providerIconRefusalMessage(target.id, problem));
|
|
3539
|
+
}
|
|
3540
|
+
machineProviders.set(target.id, target);
|
|
3541
|
+
}
|
|
3542
|
+
};
|
|
3220
3543
|
const bb = {
|
|
3221
3544
|
pluginId,
|
|
3222
3545
|
log,
|
|
@@ -3233,6 +3556,22 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3233
3556
|
events,
|
|
3234
3557
|
experimental_hooks,
|
|
3235
3558
|
experimental_environments,
|
|
3559
|
+
experimental_machines,
|
|
3560
|
+
experimental_serverAccess: {
|
|
3561
|
+
register(declaration) {
|
|
3562
|
+
assertLive();
|
|
3563
|
+
validateServerAccessProviderDeclaration(declaration);
|
|
3564
|
+
if (serverAccessProviders.has(declaration.id))
|
|
3565
|
+
throw new Error(
|
|
3566
|
+
`Server access provider "${declaration.id}" is already registered`
|
|
3567
|
+
);
|
|
3568
|
+
serverAccessProviders.set(declaration.id, declaration);
|
|
3569
|
+
},
|
|
3570
|
+
recheck() {
|
|
3571
|
+
assertLive();
|
|
3572
|
+
requestedDrains += 1;
|
|
3573
|
+
}
|
|
3574
|
+
},
|
|
3236
3575
|
status,
|
|
3237
3576
|
server,
|
|
3238
3577
|
hosts,
|
|
@@ -3320,6 +3659,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3320
3659
|
},
|
|
3321
3660
|
get threadEventHandlers() {
|
|
3322
3661
|
return {
|
|
3662
|
+
"experimental_thread.events": threadEventHandlers["experimental_thread.events"].length,
|
|
3663
|
+
"experimental_terminal.input": threadEventHandlers["experimental_terminal.input"].length,
|
|
3323
3664
|
"thread.created": threadEventHandlers["thread.created"].length,
|
|
3324
3665
|
"thread.active": threadEventHandlers["thread.active"].length,
|
|
3325
3666
|
"thread.idle": threadEventHandlers["thread.idle"].length,
|
|
@@ -3337,9 +3678,18 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3337
3678
|
get hooks() {
|
|
3338
3679
|
return { ...hooks };
|
|
3339
3680
|
},
|
|
3681
|
+
get environmentCompositions() {
|
|
3682
|
+
return new Map(environmentCompositions);
|
|
3683
|
+
},
|
|
3340
3684
|
get environmentProviders() {
|
|
3341
3685
|
return new Map(environmentProviders);
|
|
3342
3686
|
},
|
|
3687
|
+
get serverAccessProviders() {
|
|
3688
|
+
return new Map(serverAccessProviders);
|
|
3689
|
+
},
|
|
3690
|
+
get machineProviders() {
|
|
3691
|
+
return new Map(machineProviders);
|
|
3692
|
+
},
|
|
3343
3693
|
mentionProviders,
|
|
3344
3694
|
providerRegistrations,
|
|
3345
3695
|
providerEnvResolvers,
|
|
@@ -3408,12 +3758,19 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3408
3758
|
);
|
|
3409
3759
|
for (const subscription of subscriptions) {
|
|
3410
3760
|
const normalized = normalizeRpcJsonResult(
|
|
3411
|
-
await validateRpcValue(
|
|
3761
|
+
await validateRpcValue(
|
|
3762
|
+
subscription.payloadSchema,
|
|
3763
|
+
payload,
|
|
3764
|
+
"input",
|
|
3765
|
+
throwRpcError
|
|
3766
|
+
),
|
|
3767
|
+
throwRpcError
|
|
3412
3768
|
);
|
|
3413
3769
|
const parsed = await validateRpcValue(
|
|
3414
3770
|
subscription.payloadSchema,
|
|
3415
3771
|
normalized,
|
|
3416
|
-
"input"
|
|
3772
|
+
"input",
|
|
3773
|
+
throwRpcError
|
|
3417
3774
|
);
|
|
3418
3775
|
await subscription.handler({ hostId, payload: parsed });
|
|
3419
3776
|
}
|
|
@@ -3447,7 +3804,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3447
3804
|
const validatedInput = await validateRpcValue(
|
|
3448
3805
|
record.inputSchema,
|
|
3449
3806
|
parsedInput,
|
|
3450
|
-
"input"
|
|
3807
|
+
"input",
|
|
3808
|
+
throwRpcError
|
|
3451
3809
|
);
|
|
3452
3810
|
let result;
|
|
3453
3811
|
try {
|
|
@@ -3461,9 +3819,10 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3461
3819
|
const validatedOutput = await validateRpcValue(
|
|
3462
3820
|
record.outputSchema,
|
|
3463
3821
|
result,
|
|
3464
|
-
"output"
|
|
3822
|
+
"output",
|
|
3823
|
+
throwRpcError
|
|
3465
3824
|
);
|
|
3466
|
-
return normalizeRpcJsonResult(validatedOutput);
|
|
3825
|
+
return normalizeRpcJsonResult(validatedOutput, throwRpcError);
|
|
3467
3826
|
},
|
|
3468
3827
|
async runCli(argv, ctx = {}) {
|
|
3469
3828
|
const registration = cliRecord.registration;
|
|
@@ -3714,12 +4073,15 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3714
4073
|
instructions: null
|
|
3715
4074
|
};
|
|
3716
4075
|
}
|
|
4076
|
+
const pluginMetadata = deepFreezePluginMetadata(
|
|
4077
|
+
validatePluginMetadata(context.pluginMetadata ?? {})
|
|
4078
|
+
);
|
|
3717
4079
|
try {
|
|
3718
|
-
const normalized =
|
|
4080
|
+
const normalized = normalizePluginAgentConfiguration({
|
|
3719
4081
|
knownSkillIds: new Set(agentSkillIds),
|
|
3720
4082
|
knownToolIds: new Set(agentTools.map((tool) => tool.name)),
|
|
3721
4083
|
pluginId,
|
|
3722
|
-
value: agentConfigurationProvider(context)
|
|
4084
|
+
value: agentConfigurationProvider({ ...context, pluginMetadata })
|
|
3723
4085
|
});
|
|
3724
4086
|
const selectedTools = new Set(normalized.toolIds);
|
|
3725
4087
|
return {
|
|
@@ -3765,8 +4127,16 @@ function makeHostResponse(overrides = {}) {
|
|
|
3765
4127
|
return {
|
|
3766
4128
|
id: "host-1",
|
|
3767
4129
|
name: "Test host",
|
|
3768
|
-
status: "connected",
|
|
3769
4130
|
type: "persistent",
|
|
4131
|
+
status: "connected",
|
|
4132
|
+
machineProviderId: null,
|
|
4133
|
+
lifecycle: {
|
|
4134
|
+
phase: "active",
|
|
4135
|
+
suspendedAt: null,
|
|
4136
|
+
message: null,
|
|
4137
|
+
pendingLog: "",
|
|
4138
|
+
teardown: null
|
|
4139
|
+
},
|
|
3770
4140
|
maxPermissionMode: "full",
|
|
3771
4141
|
lastSeenAt: null,
|
|
3772
4142
|
lastRejectedProtocolVersion: null,
|
|
@@ -3806,6 +4176,7 @@ function makeThreadResponse(overrides = {}) {
|
|
|
3806
4176
|
}
|
|
3807
4177
|
function makePluginAgentConfigurationContext(overrides = {}) {
|
|
3808
4178
|
const context = {
|
|
4179
|
+
pluginMetadata: {},
|
|
3809
4180
|
thread: {
|
|
3810
4181
|
id: "thread-test",
|
|
3811
4182
|
title: null,
|
|
@@ -3834,6 +4205,7 @@ function makePluginAgentConfigurationContext(overrides = {}) {
|
|
|
3834
4205
|
origin: { kind: null, pluginId: null }
|
|
3835
4206
|
};
|
|
3836
4207
|
return {
|
|
4208
|
+
pluginMetadata: overrides.pluginMetadata ?? context.pluginMetadata,
|
|
3837
4209
|
thread: { ...context.thread, ...overrides.thread },
|
|
3838
4210
|
project: { ...context.project, ...overrides.project },
|
|
3839
4211
|
environment: { ...context.environment, ...overrides.environment },
|
|
@@ -3907,17 +4279,7 @@ function makeMessageDispatchHookContext(overrides = {}) {
|
|
|
3907
4279
|
createdAt: 0,
|
|
3908
4280
|
updatedAt: 0
|
|
3909
4281
|
};
|
|
3910
|
-
const hostDefaults =
|
|
3911
|
-
id: "host-1",
|
|
3912
|
-
name: "Test host",
|
|
3913
|
-
status: "connected",
|
|
3914
|
-
type: "persistent",
|
|
3915
|
-
maxPermissionMode: "full",
|
|
3916
|
-
lastSeenAt: null,
|
|
3917
|
-
lastRejectedProtocolVersion: null,
|
|
3918
|
-
createdAt: 0,
|
|
3919
|
-
updatedAt: 0
|
|
3920
|
-
};
|
|
4282
|
+
const hostDefaults = makeHostResponse();
|
|
3921
4283
|
const project = { ...context.project, ...overrides.project };
|
|
3922
4284
|
const host = overrides.host === void 0 ? context.host : overrides.host === null ? null : { ...hostDefaults, ...overrides.host };
|
|
3923
4285
|
const environment = overrides.environment === void 0 ? context.environment : overrides.environment === null ? null : {
|