@get-bb/plugin-sdk 0.4.40 → 0.4.47
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 +16 -1
- package/bundled-types/bb-plugin-sdk-app.d.ts +48 -1
- package/bundled-types/bb-plugin-sdk-internal-host-policy.d.ts +12 -1
- package/bundled-types/bb-plugin-sdk-internal-plugin-app-collector.d.ts +34 -3
- package/bundled-types/bb-plugin-sdk-provider-bridge-acp.d.ts +9 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts +590 -0
- package/bundled-types/bb-plugin-sdk-provider-bridge.d.ts +135 -0
- package/bundled-types/bb-plugin-sdk-testing-app.d.ts +18 -1
- package/bundled-types/bb-plugin-sdk-testing.d.ts +59 -2
- package/bundled-types/bb-plugin-sdk.d.ts +711 -9
- package/dist/internal/host-policy.js +39 -0
- package/dist/internal/plugin-app-collector.js +142 -5
- package/dist/provider-bridge-acp.js +44 -10
- package/dist/provider-bridge-testing.js +36 -13
- package/dist/provider-bridge.js +44 -10
- package/dist/testing/app.js +136 -4
- package/dist/testing/index.js +415 -1
- package/package.json +1 -1
package/dist/testing/app.js
CHANGED
|
@@ -392,6 +392,127 @@ function normalizeExperimentalFileOpenOptions(value) {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
// src/internal/plugin-app-collector.ts
|
|
395
|
+
var sidebarFooterCommandSequence = 0;
|
|
396
|
+
var SIDEBAR_FOOTER_ACTION_KEYS = /* @__PURE__ */ new Set([
|
|
397
|
+
"id",
|
|
398
|
+
"label",
|
|
399
|
+
"icon",
|
|
400
|
+
"kind",
|
|
401
|
+
"onActivate"
|
|
402
|
+
]);
|
|
403
|
+
var SIDEBAR_FOOTER_DISCLOSURE_KEYS = /* @__PURE__ */ new Set([
|
|
404
|
+
"id",
|
|
405
|
+
"label",
|
|
406
|
+
"icon",
|
|
407
|
+
"kind",
|
|
408
|
+
"component"
|
|
409
|
+
]);
|
|
410
|
+
var SidebarFooterItemRuntime = class {
|
|
411
|
+
listeners = /* @__PURE__ */ new Set();
|
|
412
|
+
snapshot = {
|
|
413
|
+
command: null
|
|
414
|
+
};
|
|
415
|
+
subscribe = (listener) => {
|
|
416
|
+
this.listeners.add(listener);
|
|
417
|
+
return () => this.listeners.delete(listener);
|
|
418
|
+
};
|
|
419
|
+
getSnapshot = () => this.snapshot;
|
|
420
|
+
acknowledgeCommand = (sequence) => {
|
|
421
|
+
if (this.snapshot.command?.sequence !== sequence) return;
|
|
422
|
+
this.snapshot = { ...this.snapshot, command: null };
|
|
423
|
+
this.emit();
|
|
424
|
+
};
|
|
425
|
+
createDisclosureController() {
|
|
426
|
+
return Object.freeze({
|
|
427
|
+
open: () => this.request("open"),
|
|
428
|
+
close: () => this.request("close"),
|
|
429
|
+
toggle: () => this.request("toggle")
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
request(kind) {
|
|
433
|
+
sidebarFooterCommandSequence += 1;
|
|
434
|
+
this.snapshot = {
|
|
435
|
+
...this.snapshot,
|
|
436
|
+
command: { sequence: sidebarFooterCommandSequence, kind }
|
|
437
|
+
};
|
|
438
|
+
this.emit();
|
|
439
|
+
}
|
|
440
|
+
emit() {
|
|
441
|
+
for (const listener of this.listeners) listener();
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
function adaptSidebarFooterAction(registration) {
|
|
445
|
+
return {
|
|
446
|
+
source: "sidebarFooterAction",
|
|
447
|
+
id: registration.id,
|
|
448
|
+
label: registration.title,
|
|
449
|
+
icon: registration.icon,
|
|
450
|
+
kind: "action",
|
|
451
|
+
onActivate: ({ openPluginDetails }) => registration.run({ openSettings: openPluginDetails }),
|
|
452
|
+
runtime: new SidebarFooterItemRuntime()
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
var SidebarFooterCollector = class {
|
|
456
|
+
constructor(collected, allCollected, seenIds) {
|
|
457
|
+
this.collected = collected;
|
|
458
|
+
this.allCollected = allCollected;
|
|
459
|
+
this.seenIds = seenIds;
|
|
460
|
+
}
|
|
461
|
+
collected;
|
|
462
|
+
allCollected;
|
|
463
|
+
seenIds;
|
|
464
|
+
register(registration) {
|
|
465
|
+
const kind = "experimental_sidebarFooter.register";
|
|
466
|
+
const id = requireSlotId(kind, registration?.id);
|
|
467
|
+
requireUniqueId(kind, this.seenIds, id);
|
|
468
|
+
const label = requireNonEmptyString(kind, "label", registration.label);
|
|
469
|
+
const icon = requireNonEmptyString(kind, "icon", registration.icon);
|
|
470
|
+
const runtime = new SidebarFooterItemRuntime();
|
|
471
|
+
if (registration.kind === "action") {
|
|
472
|
+
this.rejectUnknownKeys(kind, registration, SIDEBAR_FOOTER_ACTION_KEYS);
|
|
473
|
+
if (typeof registration.onActivate !== "function") {
|
|
474
|
+
throw new Error(`${kind}: "onActivate" must be a function`);
|
|
475
|
+
}
|
|
476
|
+
const item = {
|
|
477
|
+
id,
|
|
478
|
+
label,
|
|
479
|
+
icon,
|
|
480
|
+
kind: "action",
|
|
481
|
+
onActivate: registration.onActivate,
|
|
482
|
+
source: "experimental_sidebarFooter",
|
|
483
|
+
runtime
|
|
484
|
+
};
|
|
485
|
+
this.collected.push(item);
|
|
486
|
+
this.allCollected.push(item);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (registration.kind === "disclosure") {
|
|
490
|
+
this.rejectUnknownKeys(
|
|
491
|
+
kind,
|
|
492
|
+
registration,
|
|
493
|
+
SIDEBAR_FOOTER_DISCLOSURE_KEYS
|
|
494
|
+
);
|
|
495
|
+
const item = {
|
|
496
|
+
id,
|
|
497
|
+
label,
|
|
498
|
+
icon,
|
|
499
|
+
kind: "disclosure",
|
|
500
|
+
component: requireComponent(kind, registration.component),
|
|
501
|
+
source: "experimental_sidebarFooter",
|
|
502
|
+
runtime
|
|
503
|
+
};
|
|
504
|
+
this.collected.push(item);
|
|
505
|
+
this.allCollected.push(item);
|
|
506
|
+
return runtime.createDisclosureController();
|
|
507
|
+
}
|
|
508
|
+
throw new Error(`${kind}: "kind" must be "action" or "disclosure"`);
|
|
509
|
+
}
|
|
510
|
+
rejectUnknownKeys(kind, registration, allowed) {
|
|
511
|
+
for (const key of Object.keys(registration)) {
|
|
512
|
+
if (!allowed.has(key)) throw new Error(`${kind}: unknown field "${key}"`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
395
516
|
var NAV_PANEL_REGISTRATION_KEYS = new Set(
|
|
396
517
|
Object.keys({
|
|
397
518
|
id: true,
|
|
@@ -421,7 +542,9 @@ function rejectStaleNavPanelKeys(kind, registration) {
|
|
|
421
542
|
}
|
|
422
543
|
}
|
|
423
544
|
}
|
|
545
|
+
var sidebarFooterItemsByRegistrationSet = /* @__PURE__ */ new WeakMap();
|
|
424
546
|
function collectPluginAppRegistrations(definition, onComposerCustomizationRejected = (reason) => console.warn(reason)) {
|
|
547
|
+
const sidebarFooterItems = [];
|
|
425
548
|
const collected = {
|
|
426
549
|
homepageSections: [],
|
|
427
550
|
settingsSections: [],
|
|
@@ -432,6 +555,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
432
555
|
composerCustomizations: [],
|
|
433
556
|
pendingInteractions: [],
|
|
434
557
|
sidebarFooterActions: [],
|
|
558
|
+
experimentalSidebarFooterItems: [],
|
|
435
559
|
experimentalSidebarNavigations: [],
|
|
436
560
|
threadLists: [],
|
|
437
561
|
threadHeaderActions: [],
|
|
@@ -445,6 +569,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
445
569
|
timelineRenderers: [],
|
|
446
570
|
contentScripts: []
|
|
447
571
|
};
|
|
572
|
+
sidebarFooterItemsByRegistrationSet.set(collected, sidebarFooterItems);
|
|
448
573
|
const seenIds = {
|
|
449
574
|
homepageSection: /* @__PURE__ */ new Set(),
|
|
450
575
|
settingsSection: /* @__PURE__ */ new Set(),
|
|
@@ -454,7 +579,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
454
579
|
newThreadPanelAction: /* @__PURE__ */ new Set(),
|
|
455
580
|
composerCustomization: /* @__PURE__ */ new Set(),
|
|
456
581
|
pendingInteraction: /* @__PURE__ */ new Set(),
|
|
457
|
-
|
|
582
|
+
sidebarFooterItem: /* @__PURE__ */ new Set(),
|
|
458
583
|
sidebarNavigation: /* @__PURE__ */ new Set(),
|
|
459
584
|
threadList: /* @__PURE__ */ new Set(),
|
|
460
585
|
threadHeaderAction: /* @__PURE__ */ new Set(),
|
|
@@ -645,16 +770,18 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
645
770
|
sidebarFooterAction(registration) {
|
|
646
771
|
const kind = "slots.sidebarFooterAction";
|
|
647
772
|
const id = requireSlotId(kind, registration?.id);
|
|
648
|
-
requireUniqueId(kind, seenIds.
|
|
773
|
+
requireUniqueId(kind, seenIds.sidebarFooterItem, id);
|
|
649
774
|
if (typeof registration.run !== "function") {
|
|
650
775
|
throw new Error(`${kind}: "run" must be a function`);
|
|
651
776
|
}
|
|
652
|
-
|
|
777
|
+
const legacyRegistration = {
|
|
653
778
|
id,
|
|
654
779
|
title: requireNonEmptyString(kind, "title", registration.title),
|
|
655
780
|
icon: requireNonEmptyString(kind, "icon", registration.icon),
|
|
656
781
|
run: registration.run
|
|
657
|
-
}
|
|
782
|
+
};
|
|
783
|
+
collected.sidebarFooterActions.push(legacyRegistration);
|
|
784
|
+
sidebarFooterItems.push(adaptSidebarFooterAction(legacyRegistration));
|
|
658
785
|
},
|
|
659
786
|
experimental_sidebarNavigation(registration) {
|
|
660
787
|
const kind = "slots.experimental_sidebarNavigation";
|
|
@@ -816,6 +943,11 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
|
|
|
816
943
|
});
|
|
817
944
|
}
|
|
818
945
|
},
|
|
946
|
+
experimental_sidebarFooter: new SidebarFooterCollector(
|
|
947
|
+
collected.experimentalSidebarFooterItems,
|
|
948
|
+
sidebarFooterItems,
|
|
949
|
+
seenIds.sidebarFooterItem
|
|
950
|
+
),
|
|
819
951
|
composer: {
|
|
820
952
|
customize(registration) {
|
|
821
953
|
const customization = collectComposerCustomization(
|
package/dist/testing/index.js
CHANGED
|
@@ -293,6 +293,42 @@ var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
|
|
|
293
293
|
var BACKGROUND_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
294
294
|
var CLI_COMMAND_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
295
295
|
var AGENT_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
296
|
+
var PLUGIN_PROVIDER_ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
297
|
+
var PLUGIN_PROVIDER_ENV_MAX_ENTRIES = 32;
|
|
298
|
+
var pluginProviderEnvEntrySchema = z3.object({
|
|
299
|
+
name: z3.string().regex(PLUGIN_PROVIDER_ENV_NAME_PATTERN),
|
|
300
|
+
value: z3.union([
|
|
301
|
+
z3.string(),
|
|
302
|
+
z3.object({ serverPath: z3.string().startsWith("/") }).strict()
|
|
303
|
+
]),
|
|
304
|
+
reason: z3.string(),
|
|
305
|
+
secret: z3.boolean()
|
|
306
|
+
}).strict();
|
|
307
|
+
var pluginProviderEnvEntriesSchema = z3.array(pluginProviderEnvEntrySchema).max(PLUGIN_PROVIDER_ENV_MAX_ENTRIES).superRefine((entries, context) => {
|
|
308
|
+
const names = /* @__PURE__ */ new Set();
|
|
309
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
310
|
+
const name = entries[index]?.name;
|
|
311
|
+
if (name !== void 0 && names.has(name)) {
|
|
312
|
+
context.addIssue({
|
|
313
|
+
code: "custom",
|
|
314
|
+
path: [index, "name"],
|
|
315
|
+
message: "must be unique within one resolver"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (name !== void 0) names.add(name);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
function validatePluginProviderEnvEntries(value) {
|
|
322
|
+
const parsed = pluginProviderEnvEntriesSchema.safeParse(value);
|
|
323
|
+
if (!parsed.success) {
|
|
324
|
+
const issue = parsed.error.issues[0];
|
|
325
|
+
const path = issue?.path.length ? `[${issue.path.join(".")}] ` : "";
|
|
326
|
+
throw new Error(
|
|
327
|
+
`provider environment contribution ${path}${issue?.message ?? "is invalid"}`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return parsed.data;
|
|
331
|
+
}
|
|
296
332
|
var PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS = 4096;
|
|
297
333
|
var PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS = 80;
|
|
298
334
|
var PLUGIN_AGENT_SELECTION_MAX_IDS = 256;
|
|
@@ -2174,7 +2210,9 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2174
2210
|
"ALTER TABLE _bb_migrations ADD COLUMN statement_hash TEXT"
|
|
2175
2211
|
);
|
|
2176
2212
|
}
|
|
2177
|
-
const rows = database.prepare(
|
|
2213
|
+
const rows = database.prepare(
|
|
2214
|
+
"SELECT id, statement_hash FROM _bb_migrations ORDER BY id"
|
|
2215
|
+
).all();
|
|
2178
2216
|
const applied = /* @__PURE__ */ new Map();
|
|
2179
2217
|
for (const row of rows) applied.set(row.id, row.statement_hash);
|
|
2180
2218
|
const statementHashes = statements.map(migrationStatementHash);
|
|
@@ -2273,6 +2311,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2273
2311
|
}
|
|
2274
2312
|
};
|
|
2275
2313
|
const httpRoutes = [];
|
|
2314
|
+
const websocketRoutes = [];
|
|
2315
|
+
const websocketSessions = /* @__PURE__ */ new Set();
|
|
2276
2316
|
const http = {
|
|
2277
2317
|
route(method, path, handler, opts) {
|
|
2278
2318
|
assertLive();
|
|
@@ -2306,6 +2346,29 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2306
2346
|
);
|
|
2307
2347
|
}
|
|
2308
2348
|
httpRoutes.push({ method: normalizedMethod, path, auth, handler });
|
|
2349
|
+
},
|
|
2350
|
+
experimental_websocket(path, handler, opts) {
|
|
2351
|
+
assertLive();
|
|
2352
|
+
if (typeof path !== "string" || !path.startsWith("/")) {
|
|
2353
|
+
throw new Error(
|
|
2354
|
+
`websocket route path must be a string starting with "/", got ${JSON.stringify(path)}`
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
if (typeof handler !== "function") {
|
|
2358
|
+
throw new Error(
|
|
2359
|
+
`websocket route handler for ${path} must be a function`
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
const auth = opts?.auth ?? "local";
|
|
2363
|
+
if (auth !== "local" && auth !== "token" && auth !== "none") {
|
|
2364
|
+
throw new Error(
|
|
2365
|
+
`invalid auth mode "${String(auth)}" for websocket ${path} \u2014 use "local", "token", or "none"`
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
if (websocketRoutes.some((route) => route.path === path)) {
|
|
2369
|
+
throw new Error(`websocket route ${path} is already registered`);
|
|
2370
|
+
}
|
|
2371
|
+
websocketRoutes.push({ path, auth, handler });
|
|
2309
2372
|
}
|
|
2310
2373
|
};
|
|
2311
2374
|
const rpcHandlers = /* @__PURE__ */ new Map();
|
|
@@ -2466,6 +2529,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2466
2529
|
};
|
|
2467
2530
|
const agentTools = [];
|
|
2468
2531
|
const providerRegistrations = [];
|
|
2532
|
+
const providerEnvResolvers = /* @__PURE__ */ new Map();
|
|
2533
|
+
const providerEnvHealthResolvers = /* @__PURE__ */ new Map();
|
|
2469
2534
|
let agentConfigurationProvider = null;
|
|
2470
2535
|
let instructionProvider = null;
|
|
2471
2536
|
function registerProviderDeclaration(declaration) {
|
|
@@ -2931,6 +2996,44 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2931
2996
|
const providers = {
|
|
2932
2997
|
register(declaration) {
|
|
2933
2998
|
return registerProviderDeclaration(declaration);
|
|
2999
|
+
},
|
|
3000
|
+
experimental_contributeEnv(providerId, resolve2) {
|
|
3001
|
+
assertLive();
|
|
3002
|
+
if (typeof providerId !== "string" || providerId.trim().length === 0) {
|
|
3003
|
+
throw new Error(
|
|
3004
|
+
"provider environment contribution requires a provider id"
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
if (providerEnvResolvers.has(providerId)) {
|
|
3008
|
+
throw new Error(
|
|
3009
|
+
`provider environment contribution for "${providerId}" is already registered`
|
|
3010
|
+
);
|
|
3011
|
+
}
|
|
3012
|
+
if (typeof resolve2 !== "function") {
|
|
3013
|
+
throw new Error(
|
|
3014
|
+
"provider environment contribution requires a resolver function"
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
providerEnvResolvers.set(providerId, resolve2);
|
|
3018
|
+
},
|
|
3019
|
+
experimental_contributeEnvHealth(providerId, resolve2) {
|
|
3020
|
+
assertLive();
|
|
3021
|
+
if (typeof providerId !== "string" || providerId.trim().length === 0) {
|
|
3022
|
+
throw new Error(
|
|
3023
|
+
"provider environment health contribution requires a provider id"
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
if (providerEnvHealthResolvers.has(providerId)) {
|
|
3027
|
+
throw new Error(
|
|
3028
|
+
`provider environment health contribution for "${providerId}" is already registered`
|
|
3029
|
+
);
|
|
3030
|
+
}
|
|
3031
|
+
if (typeof resolve2 !== "function") {
|
|
3032
|
+
throw new Error(
|
|
3033
|
+
"provider environment health contribution requires a resolver function"
|
|
3034
|
+
);
|
|
3035
|
+
}
|
|
3036
|
+
providerEnvHealthResolvers.set(providerId, resolve2);
|
|
2934
3037
|
}
|
|
2935
3038
|
};
|
|
2936
3039
|
const experimental_hooks = {
|
|
@@ -2981,6 +3084,9 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
2981
3084
|
pendingInteractions.delete(id);
|
|
2982
3085
|
pending.resolve({ outcome: "cancelled", reason: "plugin-disposed" });
|
|
2983
3086
|
}
|
|
3087
|
+
for (const session of [...websocketSessions]) {
|
|
3088
|
+
await session.closeForReload();
|
|
3089
|
+
}
|
|
2984
3090
|
for (const controller of serviceControllers) controller.abort();
|
|
2985
3091
|
for (const hook of [...disposeHooks].reverse()) {
|
|
2986
3092
|
try {
|
|
@@ -3026,6 +3132,7 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3026
3132
|
registrations: {
|
|
3027
3133
|
settingsDescriptors,
|
|
3028
3134
|
httpRoutes,
|
|
3135
|
+
websocketRoutes,
|
|
3029
3136
|
get rpcMethods() {
|
|
3030
3137
|
return [...rpcHandlers.keys()];
|
|
3031
3138
|
},
|
|
@@ -3060,6 +3167,8 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3060
3167
|
},
|
|
3061
3168
|
mentionProviders,
|
|
3062
3169
|
providerRegistrations,
|
|
3170
|
+
providerEnvResolvers,
|
|
3171
|
+
providerEnvHealthResolvers,
|
|
3063
3172
|
aiServiceRegistrations
|
|
3064
3173
|
},
|
|
3065
3174
|
get pendingInteractions() {
|
|
@@ -3077,6 +3186,43 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3077
3186
|
await handler({ hostId });
|
|
3078
3187
|
}
|
|
3079
3188
|
},
|
|
3189
|
+
async resolveProviderEnv(providerId, context) {
|
|
3190
|
+
assertLive();
|
|
3191
|
+
const resolve2 = providerEnvResolvers.get(providerId);
|
|
3192
|
+
if (resolve2 === void 0) return [];
|
|
3193
|
+
try {
|
|
3194
|
+
return validatePluginProviderEnvEntries(await resolve2(context));
|
|
3195
|
+
} catch (error) {
|
|
3196
|
+
emitLog(
|
|
3197
|
+
"warn",
|
|
3198
|
+
`provider environment contribution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3199
|
+
);
|
|
3200
|
+
return [];
|
|
3201
|
+
}
|
|
3202
|
+
},
|
|
3203
|
+
async resolveProviderEnvHealth(providerId, context) {
|
|
3204
|
+
assertLive();
|
|
3205
|
+
if (!providerEnvResolvers.has(providerId)) return null;
|
|
3206
|
+
const resolve2 = providerEnvHealthResolvers.get(providerId);
|
|
3207
|
+
if (resolve2 === void 0) return null;
|
|
3208
|
+
try {
|
|
3209
|
+
const value = await resolve2(context);
|
|
3210
|
+
if (value === null) return null;
|
|
3211
|
+
if (value.label.trim().length === 0) {
|
|
3212
|
+
throw new Error("label must not be empty");
|
|
3213
|
+
}
|
|
3214
|
+
if (value.statusMessage.trim().length === 0) {
|
|
3215
|
+
throw new Error("statusMessage must not be empty");
|
|
3216
|
+
}
|
|
3217
|
+
return value;
|
|
3218
|
+
} catch (error) {
|
|
3219
|
+
emitLog(
|
|
3220
|
+
"warn",
|
|
3221
|
+
`provider environment health contribution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
3222
|
+
);
|
|
3223
|
+
return null;
|
|
3224
|
+
}
|
|
3225
|
+
},
|
|
3080
3226
|
async experimental_emitHostSignal(hostId, signal, payload) {
|
|
3081
3227
|
assertLive();
|
|
3082
3228
|
if (hostId.trim().length === 0) {
|
|
@@ -3204,6 +3350,127 @@ function createFakePluginHostInternal(options, sharedState) {
|
|
|
3204
3350
|
});
|
|
3205
3351
|
return app.request(path, { ...init, method: normalizedMethod });
|
|
3206
3352
|
},
|
|
3353
|
+
async experimental_openWebSocket(path, init) {
|
|
3354
|
+
assertLive();
|
|
3355
|
+
const url = new URL(path, "http://plugin.test");
|
|
3356
|
+
const route = websocketRoutes.find(
|
|
3357
|
+
(candidate) => candidate.path === url.pathname
|
|
3358
|
+
);
|
|
3359
|
+
if (!route) {
|
|
3360
|
+
throw new Error(
|
|
3361
|
+
`no websocket route ${url.pathname} is registered \u2014 registered: ${websocketRoutes.map((candidate) => candidate.path).join(", ") || "(none)"}`
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
const request = new Request(url, { ...init, method: "GET" });
|
|
3365
|
+
let handlers;
|
|
3366
|
+
try {
|
|
3367
|
+
handlers = route.handler({
|
|
3368
|
+
request,
|
|
3369
|
+
url,
|
|
3370
|
+
headers: request.headers
|
|
3371
|
+
});
|
|
3372
|
+
if (typeof handlers !== "object" || handlers === null || Array.isArray(handlers)) {
|
|
3373
|
+
throw new Error("websocket route handler must return an object");
|
|
3374
|
+
}
|
|
3375
|
+
for (const name of [
|
|
3376
|
+
"onOpen",
|
|
3377
|
+
"onMessage",
|
|
3378
|
+
"onClose",
|
|
3379
|
+
"onError"
|
|
3380
|
+
]) {
|
|
3381
|
+
const callback = handlers[name];
|
|
3382
|
+
if (callback !== void 0 && typeof callback !== "function") {
|
|
3383
|
+
throw new Error(
|
|
3384
|
+
`websocket route handler ${name} must be a function`
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
} catch (error) {
|
|
3389
|
+
emitLog(
|
|
3390
|
+
"warn",
|
|
3391
|
+
`websocket ${route.path} connect failed: ${errorMessage(error)}`
|
|
3392
|
+
);
|
|
3393
|
+
throw error;
|
|
3394
|
+
}
|
|
3395
|
+
const sent = [];
|
|
3396
|
+
const closeCalls = [];
|
|
3397
|
+
let readyState = 0;
|
|
3398
|
+
let closeNotified = false;
|
|
3399
|
+
let eventQueue = Promise.resolve();
|
|
3400
|
+
const invoke = async (event, run) => {
|
|
3401
|
+
eventQueue = eventQueue.then(async () => {
|
|
3402
|
+
try {
|
|
3403
|
+
await run();
|
|
3404
|
+
} catch (error) {
|
|
3405
|
+
emitLog(
|
|
3406
|
+
"warn",
|
|
3407
|
+
`websocket ${route.path} ${event} failed: ${errorMessage(error)}`
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3410
|
+
});
|
|
3411
|
+
await eventQueue;
|
|
3412
|
+
};
|
|
3413
|
+
const socket = {
|
|
3414
|
+
send(data) {
|
|
3415
|
+
sent.push(typeof data === "string" ? data : new Uint8Array(data));
|
|
3416
|
+
},
|
|
3417
|
+
close(code, reason) {
|
|
3418
|
+
closeCalls.push({ code: code ?? null, reason: reason ?? null });
|
|
3419
|
+
if (readyState < 2) readyState = 2;
|
|
3420
|
+
},
|
|
3421
|
+
get readyState() {
|
|
3422
|
+
return readyState;
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
const notifyClose = async (code, reason) => {
|
|
3426
|
+
if (closeNotified) return;
|
|
3427
|
+
closeNotified = true;
|
|
3428
|
+
readyState = 3;
|
|
3429
|
+
websocketSessions.delete(session);
|
|
3430
|
+
if (handlers.onClose !== void 0) {
|
|
3431
|
+
await invoke(
|
|
3432
|
+
"close",
|
|
3433
|
+
() => handlers.onClose?.(socket, { code, reason })
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
const session = {
|
|
3438
|
+
sent,
|
|
3439
|
+
closeCalls,
|
|
3440
|
+
get readyState() {
|
|
3441
|
+
return readyState;
|
|
3442
|
+
},
|
|
3443
|
+
async receive(data) {
|
|
3444
|
+
if (readyState !== 1) {
|
|
3445
|
+
throw new Error(
|
|
3446
|
+
"cannot receive a websocket message while not open"
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
if (handlers.onMessage !== void 0) {
|
|
3450
|
+
await invoke("message", () => handlers.onMessage?.(socket, data));
|
|
3451
|
+
}
|
|
3452
|
+
},
|
|
3453
|
+
async close(code = 1e3, reason = "") {
|
|
3454
|
+
if (readyState < 2) socket.close(code, reason);
|
|
3455
|
+
await notifyClose(code, reason);
|
|
3456
|
+
},
|
|
3457
|
+
async error(error) {
|
|
3458
|
+
if (handlers.onError !== void 0) {
|
|
3459
|
+
await invoke("error", () => handlers.onError?.(socket, error));
|
|
3460
|
+
}
|
|
3461
|
+
},
|
|
3462
|
+
async closeForReload() {
|
|
3463
|
+
socket.close(1012, "Plugin reloaded or disabled");
|
|
3464
|
+
await notifyClose(1012, "Plugin reloaded or disabled");
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
websocketSessions.add(session);
|
|
3468
|
+
readyState = 1;
|
|
3469
|
+
if (handlers.onOpen !== void 0) {
|
|
3470
|
+
await invoke("open", () => handlers.onOpen?.(socket));
|
|
3471
|
+
}
|
|
3472
|
+
return session;
|
|
3473
|
+
},
|
|
3207
3474
|
runService(name) {
|
|
3208
3475
|
const service = services.find((record) => record.name === name);
|
|
3209
3476
|
if (!service) {
|
|
@@ -3348,6 +3615,151 @@ function makeThreadResponse(overrides = {}) {
|
|
|
3348
3615
|
...overrides
|
|
3349
3616
|
};
|
|
3350
3617
|
}
|
|
3618
|
+
function makePluginAgentConfigurationContext(overrides = {}) {
|
|
3619
|
+
const context = {
|
|
3620
|
+
thread: {
|
|
3621
|
+
id: "thread-test",
|
|
3622
|
+
title: null,
|
|
3623
|
+
parentThreadId: null,
|
|
3624
|
+
sourceThreadId: null
|
|
3625
|
+
},
|
|
3626
|
+
project: {
|
|
3627
|
+
id: "project-test",
|
|
3628
|
+
kind: "standard",
|
|
3629
|
+
name: "Test project",
|
|
3630
|
+
gitRemoteUrl: null
|
|
3631
|
+
},
|
|
3632
|
+
environment: {
|
|
3633
|
+
id: "environment-test",
|
|
3634
|
+
name: null,
|
|
3635
|
+
path: "/tmp/test",
|
|
3636
|
+
workspaceProvisionType: "unmanaged",
|
|
3637
|
+
branchName: null
|
|
3638
|
+
},
|
|
3639
|
+
host: { id: "host-test", name: "Test host" },
|
|
3640
|
+
provider: {
|
|
3641
|
+
id: "test-provider",
|
|
3642
|
+
model: "test-model",
|
|
3643
|
+
capabilities: { supportsNativeUserQuestion: false }
|
|
3644
|
+
},
|
|
3645
|
+
origin: { kind: null, pluginId: null }
|
|
3646
|
+
};
|
|
3647
|
+
return {
|
|
3648
|
+
thread: { ...context.thread, ...overrides.thread },
|
|
3649
|
+
project: { ...context.project, ...overrides.project },
|
|
3650
|
+
environment: { ...context.environment, ...overrides.environment },
|
|
3651
|
+
host: { ...context.host, ...overrides.host },
|
|
3652
|
+
provider: {
|
|
3653
|
+
...context.provider,
|
|
3654
|
+
...overrides.provider,
|
|
3655
|
+
capabilities: {
|
|
3656
|
+
...context.provider.capabilities,
|
|
3657
|
+
...overrides.provider?.capabilities
|
|
3658
|
+
}
|
|
3659
|
+
},
|
|
3660
|
+
origin: { ...context.origin, ...overrides.origin }
|
|
3661
|
+
};
|
|
3662
|
+
}
|
|
3663
|
+
function makeMessageDispatchHookContext(overrides = {}) {
|
|
3664
|
+
const context = {
|
|
3665
|
+
thread: makeThreadResponse({ status: "pending" }),
|
|
3666
|
+
project: {
|
|
3667
|
+
id: "project-1",
|
|
3668
|
+
kind: "standard",
|
|
3669
|
+
name: "Test project",
|
|
3670
|
+
gitRemoteUrl: null,
|
|
3671
|
+
createdAt: 0,
|
|
3672
|
+
updatedAt: 0
|
|
3673
|
+
},
|
|
3674
|
+
environment: null,
|
|
3675
|
+
host: null,
|
|
3676
|
+
input: { blocks: [], text: "Test message" },
|
|
3677
|
+
requestedExecution: {
|
|
3678
|
+
providerId: "test-provider",
|
|
3679
|
+
model: null,
|
|
3680
|
+
reasoningLevel: null,
|
|
3681
|
+
serviceTier: null,
|
|
3682
|
+
permissionMode: null
|
|
3683
|
+
},
|
|
3684
|
+
executionSources: {
|
|
3685
|
+
providerId: null,
|
|
3686
|
+
model: null,
|
|
3687
|
+
reasoningLevel: null,
|
|
3688
|
+
serviceTier: null,
|
|
3689
|
+
permissionMode: null
|
|
3690
|
+
},
|
|
3691
|
+
attempt: "start-turn",
|
|
3692
|
+
queuedMessage: null,
|
|
3693
|
+
origin: null,
|
|
3694
|
+
originPluginId: null,
|
|
3695
|
+
startedOnBehalfOf: null,
|
|
3696
|
+
parentThreadId: null
|
|
3697
|
+
};
|
|
3698
|
+
const environmentDefaults = {
|
|
3699
|
+
id: "environment-1",
|
|
3700
|
+
name: null,
|
|
3701
|
+
projectId: "project-1",
|
|
3702
|
+
hostId: "host-1",
|
|
3703
|
+
path: "/tmp/test",
|
|
3704
|
+
managed: true,
|
|
3705
|
+
isGitRepo: true,
|
|
3706
|
+
isWorktree: false,
|
|
3707
|
+
workspaceProvisionType: "unmanaged",
|
|
3708
|
+
branchName: "main",
|
|
3709
|
+
baseBranch: null,
|
|
3710
|
+
defaultBranch: "main",
|
|
3711
|
+
mergeBaseBranch: null,
|
|
3712
|
+
status: "ready",
|
|
3713
|
+
createdAt: 0,
|
|
3714
|
+
updatedAt: 0
|
|
3715
|
+
};
|
|
3716
|
+
const hostDefaults = {
|
|
3717
|
+
id: "host-1",
|
|
3718
|
+
name: "Test host",
|
|
3719
|
+
type: "persistent",
|
|
3720
|
+
status: "connected",
|
|
3721
|
+
maxPermissionMode: "full",
|
|
3722
|
+
lastSeenAt: null,
|
|
3723
|
+
lastRejectedProtocolVersion: null,
|
|
3724
|
+
createdAt: 0,
|
|
3725
|
+
updatedAt: 0
|
|
3726
|
+
};
|
|
3727
|
+
const project = { ...context.project, ...overrides.project };
|
|
3728
|
+
const host = overrides.host === void 0 ? context.host : overrides.host === null ? null : { ...hostDefaults, ...overrides.host };
|
|
3729
|
+
const environment = overrides.environment === void 0 ? context.environment : overrides.environment === null ? null : {
|
|
3730
|
+
...environmentDefaults,
|
|
3731
|
+
projectId: project.id,
|
|
3732
|
+
hostId: host?.id ?? environmentDefaults.hostId,
|
|
3733
|
+
...overrides.environment
|
|
3734
|
+
};
|
|
3735
|
+
const thread = {
|
|
3736
|
+
...context.thread,
|
|
3737
|
+
projectId: project.id,
|
|
3738
|
+
environmentId: environment?.id ?? null,
|
|
3739
|
+
...overrides.thread
|
|
3740
|
+
};
|
|
3741
|
+
return {
|
|
3742
|
+
...context,
|
|
3743
|
+
...overrides,
|
|
3744
|
+
thread,
|
|
3745
|
+
project,
|
|
3746
|
+
environment,
|
|
3747
|
+
host,
|
|
3748
|
+
input: { ...context.input, ...overrides.input },
|
|
3749
|
+
requestedExecution: {
|
|
3750
|
+
...context.requestedExecution,
|
|
3751
|
+
...overrides.requestedExecution
|
|
3752
|
+
},
|
|
3753
|
+
executionSources: {
|
|
3754
|
+
...context.executionSources,
|
|
3755
|
+
...overrides.executionSources
|
|
3756
|
+
},
|
|
3757
|
+
queuedMessage: overrides.queuedMessage === void 0 ? context.queuedMessage : overrides.queuedMessage === null ? null : makeQueueEntry({
|
|
3758
|
+
threadId: thread.id,
|
|
3759
|
+
...overrides.queuedMessage
|
|
3760
|
+
})
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3351
3763
|
function makeQueueEntry(overrides = {}) {
|
|
3352
3764
|
return {
|
|
3353
3765
|
id: "queued_1",
|
|
@@ -3485,6 +3897,8 @@ export {
|
|
|
3485
3897
|
createFakePluginHost,
|
|
3486
3898
|
createFakeSdk,
|
|
3487
3899
|
scanPublicSdkOnly as experimental_scanPublicSdkOnly,
|
|
3900
|
+
makeMessageDispatchHookContext,
|
|
3901
|
+
makePluginAgentConfigurationContext,
|
|
3488
3902
|
makeQueueEntry,
|
|
3489
3903
|
makeThreadResponse,
|
|
3490
3904
|
makeTurnFailedEvent
|