@get-bb/plugin-sdk 0.4.45 → 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.
@@ -406,6 +406,11 @@ interface PluginPendingInteractionProps {
406
406
  */
407
407
  interface PluginSidebarFooterActionProps {
408
408
  }
409
+ /** Props passed to an experimental sidebar-footer disclosure component. */
410
+ interface ExperimentalSidebarFooterDisclosureProps {
411
+ /** Hide this disclosure without affecting another plugin's open disclosure. */
412
+ dismiss(): void;
413
+ }
409
414
  /** Display and accessibility metadata for a host-owned sidebar shortcut. */
410
415
  interface ExperimentalSidebarNavigationShortcut {
411
416
  label: string;
@@ -942,6 +947,46 @@ interface PluginSidebarFooterActionRegistration {
942
947
  */
943
948
  run(context: PluginSidebarFooterActionContext): void | Promise<void>;
944
949
  }
950
+ /** Context handed to an experimental sidebar-footer action. */
951
+ interface ExperimentalSidebarFooterActionContext {
952
+ /** Navigate to this plugin's detail page in Tools. */
953
+ openPluginDetails(): void;
954
+ }
955
+ /** Fields shared by both experimental sidebar-footer item behaviors. */
956
+ interface ExperimentalSidebarFooterItemBase {
957
+ /** Unique within the plugin's unified sidebar footer; letters, digits, `-`, `_`. */
958
+ id: string;
959
+ /** Tooltip and accessible label for the host-rendered icon button. */
960
+ label: string;
961
+ /** BB icon-name hint; unknown names fall back to a generic icon. */
962
+ icon: string;
963
+ }
964
+ /** A sidebar-footer item that runs a callback when activated. */
965
+ interface ExperimentalSidebarFooterActionRegistration extends ExperimentalSidebarFooterItemBase {
966
+ kind: "action";
967
+ onActivate(context: ExperimentalSidebarFooterActionContext): void | Promise<void>;
968
+ }
969
+ /** A sidebar-footer item that reveals plugin-rendered content above the row. */
970
+ interface ExperimentalSidebarFooterDisclosureRegistration extends ExperimentalSidebarFooterItemBase {
971
+ kind: "disclosure";
972
+ component: ComponentType<ExperimentalSidebarFooterDisclosureProps>;
973
+ }
974
+ /** One host-rendered item in the app sidebar footer. */
975
+ type ExperimentalSidebarFooterItemRegistration = ExperimentalSidebarFooterActionRegistration | ExperimentalSidebarFooterDisclosureRegistration;
976
+ /** Live controls for an experimental sidebar-footer disclosure. */
977
+ interface ExperimentalSidebarFooterDisclosureController {
978
+ /** Request that the host open this disclosure, replacing any open sibling. */
979
+ open(): void;
980
+ /** Close this disclosure if it is currently open. */
981
+ close(): void;
982
+ /** Open this disclosure, or close it when it is currently open. */
983
+ toggle(): void;
984
+ }
985
+ /** Managed registration surface for items in the app sidebar footer. */
986
+ interface ExperimentalSidebarFooter {
987
+ register(registration: ExperimentalSidebarFooterActionRegistration): void;
988
+ register(registration: ExperimentalSidebarFooterDisclosureRegistration): ExperimentalSidebarFooterDisclosureController;
989
+ }
945
990
  /**
946
991
  * The one status bb would paint for a thread, already resolved through the
947
992
  * host's precedence (attention before work; plan and goal before the generic
@@ -1664,6 +1709,8 @@ interface PluginAppBuilder {
1664
1709
  slots: PluginAppSlots;
1665
1710
  composer: PluginAppComposer;
1666
1711
  contentScripts: PluginAppContentScripts;
1712
+ /** Experimental managed region for actions and disclosures in the sidebar footer. */
1713
+ experimental_sidebarFooter: ExperimentalSidebarFooter;
1667
1714
  }
1668
1715
  type PluginAppSetup = (app: PluginAppBuilder) => void;
1669
1716
  /**
@@ -2470,4 +2517,4 @@ declare const experimental_useProviders: () => PluginProvidersState;
2470
2517
  declare const experimental_useCodeTheme: () => PluginCodeThemeState;
2471
2518
 
2472
2519
  export { Markdown, ThreadChat, UrlLink, definePluginApp, experimental_Diff, experimental_FileLink, experimental_NewThreadComposer, experimental_PermissionModePicker, experimental_ProviderModelPicker, experimental_SourceCode, experimental_useAppPanel, experimental_useCodeTheme, experimental_useFixedTabTarget, experimental_useProviders, experimental_useSidebarThreadActions, experimental_useSidebarThreadPullRequest, experimental_useSidebarThreadSplit, experimental_useSidebarThreads, useBbContext, useBbNavigate, useComposer, useComposerView, useRealtime, useRealtimeConnectionState, useRpc, useSettings };
2473
- export type { BbContext, BbNavigate, CodeOverflowMode, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, DiffProps, DiffViewMode, ExperimentalAppOverlayProps, ExperimentalAppOverlayRegistration, ExperimentalAppPanel, ExperimentalAppPanelSurface, ExperimentalComposerSubmitOptions, ExperimentalDiffFileContent, ExperimentalDiffFullFileContents, ExperimentalFileLinkProps, ExperimentalFileLocation, ExperimentalFileOpenOptions, ExperimentalFixedTabTargetContract, ExperimentalFixedTabTargetState, ExperimentalLiveFileTarget, ExperimentalOpenFixedTabOptions, ExperimentalPermissionModePickerProps, ExperimentalPluginFixedTabReference, ExperimentalProviderModelPickerProps, ExperimentalProviderModelPickerRouting, ExperimentalProviderModelPickerValue, ExperimentalSidebarNavigationAction, ExperimentalSidebarNavigationActivationOptions, ExperimentalSidebarNavigationIcon, ExperimentalSidebarNavigationItem, ExperimentalSidebarNavigationProps, ExperimentalSidebarNavigationRegistration, ExperimentalSidebarNavigationShortcut, JsonValue, MarkdownProps, NewThreadComposerProps, NewThreadRequest, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginCodeThemeData, PluginCodeThemeState, PluginCodeThemeTokenRule, PluginCommandPaletteActionContext, PluginCommandPaletteActionRegistration, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginDiffRendererProps, PluginDiffRendererRegistration, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginFixedTabDeclaration, PluginFixedTabRegistration, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginNewThreadPanelActionContext, PluginNewThreadPanelActionRegistration, PluginNewThreadPanelProps, PluginPanelActionOpenOptions, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginProviderIconRegistration, PluginProvidersState, PluginRealtimeConnectionState, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginSourceCodeRendererProps, PluginSourceCodeRendererRegistration, PluginTargetedPanelActionOpenOptions, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, PluginTimelineRendererProps, PluginTimelineRendererRegistration, PluginTimelineRendererRow, PluginTimelineRowPresentation, PluginTimelineRowStatus, SourceCodeLineRange, SourceCodeProps, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps, UrlLinkProps };
2520
+ export type { BbContext, BbNavigate, CodeOverflowMode, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, DiffProps, DiffViewMode, ExperimentalAppOverlayProps, ExperimentalAppOverlayRegistration, ExperimentalAppPanel, ExperimentalAppPanelSurface, ExperimentalComposerSubmitOptions, ExperimentalDiffFileContent, ExperimentalDiffFullFileContents, ExperimentalFileLinkProps, ExperimentalFileLocation, ExperimentalFileOpenOptions, ExperimentalFixedTabTargetContract, ExperimentalFixedTabTargetState, ExperimentalLiveFileTarget, ExperimentalOpenFixedTabOptions, ExperimentalPermissionModePickerProps, ExperimentalPluginFixedTabReference, ExperimentalProviderModelPickerProps, ExperimentalProviderModelPickerRouting, ExperimentalProviderModelPickerValue, ExperimentalSidebarFooter, ExperimentalSidebarFooterActionContext, ExperimentalSidebarFooterActionRegistration, ExperimentalSidebarFooterDisclosureController, ExperimentalSidebarFooterDisclosureProps, ExperimentalSidebarFooterDisclosureRegistration, ExperimentalSidebarFooterItemBase, ExperimentalSidebarFooterItemRegistration, ExperimentalSidebarNavigationAction, ExperimentalSidebarNavigationActivationOptions, ExperimentalSidebarNavigationIcon, ExperimentalSidebarNavigationItem, ExperimentalSidebarNavigationProps, ExperimentalSidebarNavigationRegistration, ExperimentalSidebarNavigationShortcut, JsonValue, MarkdownProps, NewThreadComposerProps, NewThreadRequest, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginCodeThemeData, PluginCodeThemeState, PluginCodeThemeTokenRule, PluginCommandPaletteActionContext, PluginCommandPaletteActionRegistration, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginDiffRendererProps, PluginDiffRendererRegistration, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginFixedTabDeclaration, PluginFixedTabRegistration, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginNewThreadPanelActionContext, PluginNewThreadPanelActionRegistration, PluginNewThreadPanelProps, PluginPanelActionOpenOptions, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginProviderIconRegistration, PluginProvidersState, PluginRealtimeConnectionState, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginSourceCodeRendererProps, PluginSourceCodeRendererRegistration, PluginTargetedPanelActionOpenOptions, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, PluginTimelineRendererProps, PluginTimelineRendererRegistration, PluginTimelineRendererRow, PluginTimelineRowPresentation, PluginTimelineRowStatus, SourceCodeLineRange, SourceCodeProps, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps, UrlLinkProps };
@@ -5,8 +5,37 @@
5
5
  // Confused by the API, or need a symbol that isn't here? Clone the BB repo
6
6
  // and read the real source: https://github.com/get-bb/bb
7
7
 
8
- import { PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, ExperimentalAppOverlayRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, ExperimentalSidebarNavigationRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginCommandPaletteActionRegistration, PluginProviderIconRegistration, PluginTimelineRendererRegistration, PluginContentScriptRegistration, PluginAppDefinition } from '@get-bb/plugin-sdk';
8
+ import { ExperimentalSidebarFooterActionContext, ExperimentalSidebarFooterItemRegistration, PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, ExperimentalAppOverlayRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, ExperimentalSidebarNavigationRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginCommandPaletteActionRegistration, PluginProviderIconRegistration, PluginTimelineRendererRegistration, PluginContentScriptRegistration, PluginAppDefinition } from '@get-bb/plugin-sdk';
9
9
 
10
+ type ExperimentalSidebarFooterCommandKind = "close" | "open" | "toggle";
11
+ interface ExperimentalSidebarFooterRuntimeSnapshot {
12
+ command: {
13
+ sequence: number;
14
+ kind: ExperimentalSidebarFooterCommandKind;
15
+ } | null;
16
+ }
17
+ interface ExperimentalSidebarFooterItemRuntime {
18
+ subscribe(listener: () => void): () => void;
19
+ getSnapshot(): ExperimentalSidebarFooterRuntimeSnapshot;
20
+ acknowledgeCommand(sequence: number): void;
21
+ }
22
+ type CollectedExperimentalSidebarFooterItem = ExperimentalSidebarFooterItemRegistration & {
23
+ runtime: ExperimentalSidebarFooterItemRuntime;
24
+ };
25
+ type CollectedManagedSidebarFooterItem = CollectedExperimentalSidebarFooterItem & {
26
+ source: "experimental_sidebarFooter";
27
+ };
28
+ interface CollectedCompatibilitySidebarFooterItem {
29
+ source: "sidebarFooterAction";
30
+ id: string;
31
+ label: string;
32
+ icon: string;
33
+ kind: "action";
34
+ onActivate(context: ExperimentalSidebarFooterActionContext): void | Promise<void>;
35
+ runtime: ExperimentalSidebarFooterItemRuntime;
36
+ }
37
+ type CollectedSidebarFooterItem = CollectedCompatibilitySidebarFooterItem | CollectedManagedSidebarFooterItem;
38
+ declare function adaptSidebarFooterAction(registration: PluginSidebarFooterActionRegistration): CollectedCompatibilitySidebarFooterItem;
10
39
  /** Validated registrations produced by one plugin app setup execution. */
11
40
  interface CollectedPluginAppRegistrations {
12
41
  homepageSections: PluginHomepageSectionRegistration[];
@@ -18,6 +47,7 @@ interface CollectedPluginAppRegistrations {
18
47
  composerCustomizations: ComposerCustomization[];
19
48
  pendingInteractions: PluginPendingInteractionRegistration[];
20
49
  sidebarFooterActions: PluginSidebarFooterActionRegistration[];
50
+ experimentalSidebarFooterItems: CollectedExperimentalSidebarFooterItem[];
21
51
  experimentalSidebarNavigations: ExperimentalSidebarNavigationRegistration[];
22
52
  threadLists: PluginThreadListRegistration[];
23
53
  threadHeaderActions: PluginThreadHeaderActionRegistration[];
@@ -31,6 +61,7 @@ interface CollectedPluginAppRegistrations {
31
61
  timelineRenderers: PluginTimelineRendererRegistration[];
32
62
  contentScripts: PluginContentScriptRegistration[];
33
63
  }
64
+ declare function getCollectedSidebarFooterItems(registrations: object): readonly CollectedSidebarFooterItem[] | null;
34
65
  /**
35
66
  * Run a plugin app definition against the canonical validating collector.
36
67
  * Both the BB app and the public test harness use this implementation so a
@@ -39,5 +70,5 @@ interface CollectedPluginAppRegistrations {
39
70
  */
40
71
  declare function collectPluginAppRegistrations(definition: PluginAppDefinition, onComposerCustomizationRejected?: (reason: string) => void): CollectedPluginAppRegistrations;
41
72
 
42
- export { collectPluginAppRegistrations };
43
- export type { CollectedPluginAppRegistrations };
73
+ export { adaptSidebarFooterAction, collectPluginAppRegistrations, getCollectedSidebarFooterItems };
74
+ export type { CollectedCompatibilitySidebarFooterItem, CollectedExperimentalSidebarFooterItem, CollectedManagedSidebarFooterItem, CollectedPluginAppRegistrations, CollectedSidebarFooterItem, ExperimentalSidebarFooterCommandKind, ExperimentalSidebarFooterItemRuntime, ExperimentalSidebarFooterRuntimeSnapshot };
@@ -7,7 +7,23 @@
7
7
 
8
8
  import { ReactNode, ComponentType } from 'react';
9
9
  import { RenderResult } from '@testing-library/react';
10
- import { PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, ExperimentalAppOverlayRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, ExperimentalSidebarNavigationRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginProviderIconRegistration, PluginTimelineRendererRegistration, PluginContentScriptRegistration, PluginComposerScope, PluginComposerTextEffect, PluginComposerMention, PluginComposerThreadRowStatus, ExperimentalOpenFixedTabOptions, JsonValue, BbNavigate, ExperimentalFileOpenOptions, PluginAppDefinition, PluginRpcContract, StandardSchemaV1InferInput, PluginRpcResult, PluginRealtimeConnectionState, PluginSidebarThreadsState, PluginProvidersState, PluginCodeThemeState, PluginSidebarPullRequest, PluginSidebarThreadActions } from '@get-bb/plugin-sdk';
10
+ import { ExperimentalSidebarFooterItemRegistration, PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, ExperimentalAppOverlayRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, ExperimentalSidebarNavigationRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginProviderIconRegistration, PluginTimelineRendererRegistration, PluginContentScriptRegistration, PluginComposerScope, PluginComposerTextEffect, PluginComposerMention, PluginComposerThreadRowStatus, ExperimentalOpenFixedTabOptions, JsonValue, BbNavigate, ExperimentalFileOpenOptions, PluginAppDefinition, PluginRpcContract, StandardSchemaV1InferInput, PluginRpcResult, PluginRealtimeConnectionState, PluginSidebarThreadsState, PluginProvidersState, PluginCodeThemeState, PluginSidebarPullRequest, PluginSidebarThreadActions } from '@get-bb/plugin-sdk';
11
+
12
+ type ExperimentalSidebarFooterCommandKind = "close" | "open" | "toggle";
13
+ interface ExperimentalSidebarFooterRuntimeSnapshot {
14
+ command: {
15
+ sequence: number;
16
+ kind: ExperimentalSidebarFooterCommandKind;
17
+ } | null;
18
+ }
19
+ interface ExperimentalSidebarFooterItemRuntime {
20
+ subscribe(listener: () => void): () => void;
21
+ getSnapshot(): ExperimentalSidebarFooterRuntimeSnapshot;
22
+ acknowledgeCommand(sequence: number): void;
23
+ }
24
+ type CollectedExperimentalSidebarFooterItem = ExperimentalSidebarFooterItemRegistration & {
25
+ runtime: ExperimentalSidebarFooterItemRuntime;
26
+ };
11
27
 
12
28
  /**
13
29
  * `@get-bb/plugin-sdk/testing/app` — the frontend plugin test harness. Tests a
@@ -125,6 +141,7 @@ interface CapturedPluginApp {
125
141
  composerCustomizations: ComposerCustomization[];
126
142
  pendingInteractions: PluginPendingInteractionRegistration[];
127
143
  sidebarFooterActions: PluginSidebarFooterActionRegistration[];
144
+ experimentalSidebarFooterItems: CollectedExperimentalSidebarFooterItem[];
128
145
  experimentalSidebarNavigations: ExperimentalSidebarNavigationRegistration[];
129
146
  threadLists: PluginThreadListRegistration[];
130
147
  threadHeaderActions: PluginThreadHeaderActionRegistration[];
@@ -6,7 +6,7 @@
6
6
  // and read the real source: https://github.com/get-bb/bb
7
7
 
8
8
  import { z } from 'zod';
9
- import { BbPluginApi, PluginSettingValue as PluginSettingValue$1, PluginSharedPortTunnelIdentity, PluginAgentToolPresentation, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, JsonValue as JsonValue$1, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, ExperimentalPluginProviderEnvContext, ExperimentalPluginProviderEnvEntry, ExperimentalPluginProviderEnvHealthContext, ExperimentalPluginProviderEnvHealth, PluginSettingDescriptors, PluginAgentConfiguration, PluginHookName, PluginHookHandler, PluginAiServiceDeclaration, PluginInteractionRequest, MessageDispatchHookContext } from '@get-bb/plugin-sdk';
9
+ import { BbPluginApi, PluginSettingValue as PluginSettingValue$1, PluginSharedPortTunnelIdentity, PluginAgentToolPresentation, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, JsonValue as JsonValue$1, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, ExperimentalPluginProviderEnvContext, ExperimentalPluginProviderEnvEntry, ExperimentalPluginProviderEnvHealthContext, ExperimentalPluginProviderEnvHealth, PluginSettingDescriptors, ExperimentalPluginWebSocketHandler, PluginAgentConfiguration, PluginHookName, PluginHookHandler, PluginAiServiceDeclaration, PluginInteractionRequest, MessageDispatchHookContext } from '@get-bb/plugin-sdk';
10
10
 
11
11
  /** Input-form entry: a path, or a path with options. */
12
12
  declare const providerNativeRootInputSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
@@ -555,6 +555,22 @@ interface FakeHttpRouteRecord {
555
555
  auth: PluginHttpAuthMode;
556
556
  handler: PluginHttpHandler;
557
557
  }
558
+ interface ExperimentalFakeWebSocketRouteRecord {
559
+ path: string;
560
+ auth: PluginHttpAuthMode;
561
+ handler: ExperimentalPluginWebSocketHandler;
562
+ }
563
+ interface ExperimentalFakeWebSocketSession {
564
+ readonly sent: readonly (string | Uint8Array)[];
565
+ readonly closeCalls: readonly {
566
+ code: number | null;
567
+ reason: string | null;
568
+ }[];
569
+ readonly readyState: number;
570
+ receive(data: string | Uint8Array): Promise<void>;
571
+ close(code?: number, reason?: string): Promise<void>;
572
+ error(error: Error): Promise<void>;
573
+ }
558
574
  interface FakeScheduleRecord {
559
575
  name: string;
560
576
  cron: string;
@@ -618,6 +634,7 @@ interface ExperimentalFakeHostRpcCall {
618
634
  interface FakePluginRegistrations {
619
635
  settingsDescriptors: PluginSettingDescriptors;
620
636
  httpRoutes: FakeHttpRouteRecord[];
637
+ websocketRoutes: ExperimentalFakeWebSocketRouteRecord[];
621
638
  rpcMethods: string[];
622
639
  services: FakeServiceRecord[];
623
640
  schedules: FakeScheduleRecord[];
@@ -707,6 +724,8 @@ interface FakePluginBehaviorDrivers {
707
724
  * `{ ok: false, error: "plugin route failed: …" }` response.
708
725
  */
709
726
  fetchHttp(method: string, path: string, init?: RequestInit): Promise<Response>;
727
+ /** Open and drive an exact-match `bb.http.experimental_websocket` route. */
728
+ experimental_openWebSocket(path: string, init?: RequestInit): Promise<ExperimentalFakeWebSocketSession>;
710
729
  /**
711
730
  * Start a registered background service once, deterministically. `done`
712
731
  * settles when `start` returns; abort `controller` to signal shutdown.
@@ -312,6 +312,7 @@ declare const experimentsSchema: z$1.ZodRecord<z$1.ZodEnum<{
312
312
  changelogPreview: "changelogPreview";
313
313
  editMessages: "editMessages";
314
314
  mobileApp: "mobileApp";
315
+ sidebarProgressiveDisclosure: "sidebarProgressiveDisclosure";
315
316
  timelineWindowing: "timelineWindowing";
316
317
  }>, z$1.ZodBoolean>;
317
318
  type Experiments = z$1.infer<typeof experimentsSchema>;
@@ -8887,6 +8888,7 @@ declare const systemConfigResponseSchema: z$1.ZodObject<{
8887
8888
  changelogPreview: "changelogPreview";
8888
8889
  editMessages: "editMessages";
8889
8890
  mobileApp: "mobileApp";
8891
+ sidebarProgressiveDisclosure: "sidebarProgressiveDisclosure";
8890
8892
  timelineWindowing: "timelineWindowing";
8891
8893
  }>, z$1.ZodBoolean>;
8892
8894
  featureFlags: z$1.ZodObject<{
@@ -12767,6 +12769,7 @@ declare const threadTimelineResponseSchema: z$1.ZodObject<{
12767
12769
  }, z$1.core.$strip>>;
12768
12770
  workflowName: z$1.ZodNullable<z$1.ZodString>;
12769
12771
  }, z$1.core.$strip>>;
12772
+ contextBoundarySeq: z$1.ZodNullable<z$1.ZodNumber>;
12770
12773
  contextWindowUsage: z$1.ZodOptional<z$1.ZodObject<{
12771
12774
  estimated: z$1.ZodBoolean;
12772
12775
  modelContextWindow: z$1.ZodNumber;
@@ -13297,6 +13300,11 @@ interface PluginPendingInteractionProps {
13297
13300
  */
13298
13301
  interface PluginSidebarFooterActionProps {
13299
13302
  }
13303
+ /** Props passed to an experimental sidebar-footer disclosure component. */
13304
+ interface ExperimentalSidebarFooterDisclosureProps {
13305
+ /** Hide this disclosure without affecting another plugin's open disclosure. */
13306
+ dismiss(): void;
13307
+ }
13300
13308
  /** Display and accessibility metadata for a host-owned sidebar shortcut. */
13301
13309
  interface ExperimentalSidebarNavigationShortcut {
13302
13310
  label: string;
@@ -13833,6 +13841,46 @@ interface PluginSidebarFooterActionRegistration {
13833
13841
  */
13834
13842
  run(context: PluginSidebarFooterActionContext): void | Promise<void>;
13835
13843
  }
13844
+ /** Context handed to an experimental sidebar-footer action. */
13845
+ interface ExperimentalSidebarFooterActionContext {
13846
+ /** Navigate to this plugin's detail page in Tools. */
13847
+ openPluginDetails(): void;
13848
+ }
13849
+ /** Fields shared by both experimental sidebar-footer item behaviors. */
13850
+ interface ExperimentalSidebarFooterItemBase {
13851
+ /** Unique within the plugin's unified sidebar footer; letters, digits, `-`, `_`. */
13852
+ id: string;
13853
+ /** Tooltip and accessible label for the host-rendered icon button. */
13854
+ label: string;
13855
+ /** BB icon-name hint; unknown names fall back to a generic icon. */
13856
+ icon: string;
13857
+ }
13858
+ /** A sidebar-footer item that runs a callback when activated. */
13859
+ interface ExperimentalSidebarFooterActionRegistration extends ExperimentalSidebarFooterItemBase {
13860
+ kind: "action";
13861
+ onActivate(context: ExperimentalSidebarFooterActionContext): void | Promise<void>;
13862
+ }
13863
+ /** A sidebar-footer item that reveals plugin-rendered content above the row. */
13864
+ interface ExperimentalSidebarFooterDisclosureRegistration extends ExperimentalSidebarFooterItemBase {
13865
+ kind: "disclosure";
13866
+ component: ComponentType<ExperimentalSidebarFooterDisclosureProps>;
13867
+ }
13868
+ /** One host-rendered item in the app sidebar footer. */
13869
+ type ExperimentalSidebarFooterItemRegistration = ExperimentalSidebarFooterActionRegistration | ExperimentalSidebarFooterDisclosureRegistration;
13870
+ /** Live controls for an experimental sidebar-footer disclosure. */
13871
+ interface ExperimentalSidebarFooterDisclosureController {
13872
+ /** Request that the host open this disclosure, replacing any open sibling. */
13873
+ open(): void;
13874
+ /** Close this disclosure if it is currently open. */
13875
+ close(): void;
13876
+ /** Open this disclosure, or close it when it is currently open. */
13877
+ toggle(): void;
13878
+ }
13879
+ /** Managed registration surface for items in the app sidebar footer. */
13880
+ interface ExperimentalSidebarFooter {
13881
+ register(registration: ExperimentalSidebarFooterActionRegistration): void;
13882
+ register(registration: ExperimentalSidebarFooterDisclosureRegistration): ExperimentalSidebarFooterDisclosureController;
13883
+ }
13836
13884
  /**
13837
13885
  * The one status bb would paint for a thread, already resolved through the
13838
13886
  * host's precedence (attention before work; plan and goal before the generic
@@ -14555,6 +14603,8 @@ interface PluginAppBuilder {
14555
14603
  slots: PluginAppSlots;
14556
14604
  composer: PluginAppComposer;
14557
14605
  contentScripts: PluginAppContentScripts;
14606
+ /** Experimental managed region for actions and disclosures in the sidebar footer. */
14607
+ experimental_sidebarFooter: ExperimentalSidebarFooter;
14558
14608
  }
14559
14609
  type PluginAppSetup = (app: PluginAppBuilder) => void;
14560
14610
  /**
@@ -16550,6 +16600,7 @@ interface ThreadsArea {
16550
16600
  childSummary(args: ThreadStatusArgs): Promise<ThreadChildSummaryResult>;
16551
16601
  compact(args: ThreadActionArgs): Promise<ThreadCompactResult>;
16552
16602
  cancelPlan(args: ThreadActionArgs): Promise<ThreadBannerActionResult>;
16603
+ clearContext(args: ThreadActionArgs): Promise<ThreadBannerActionResult>;
16553
16604
  clearGoal(args: ThreadActionArgs): Promise<ThreadBannerActionResult>;
16554
16605
  conversationOutline(args: ThreadStatusArgs): Promise<ThreadConversationOutlineResult>;
16555
16606
  count(args?: ThreadCountArgs): Promise<ThreadCountResult>;
@@ -17189,6 +17240,26 @@ interface PluginHooks {
17189
17240
  }
17190
17241
  type PluginHttpAuthMode = "local" | "none" | "token";
17191
17242
  type PluginHttpHandler = (context: Context) => Response | Promise<Response>;
17243
+ interface ExperimentalPluginWebSocket {
17244
+ send(data: string | Uint8Array): void;
17245
+ close(code?: number, reason?: string): void;
17246
+ readonly readyState: number;
17247
+ }
17248
+ interface ExperimentalPluginWebSocketContext {
17249
+ request: Request;
17250
+ url: URL;
17251
+ headers: Headers;
17252
+ }
17253
+ interface ExperimentalPluginWebSocketHandlers {
17254
+ onOpen?(socket: ExperimentalPluginWebSocket): void | Promise<void>;
17255
+ onMessage?(socket: ExperimentalPluginWebSocket, data: string | Uint8Array): void | Promise<void>;
17256
+ onClose?(socket: ExperimentalPluginWebSocket, event: {
17257
+ code: number;
17258
+ reason: string;
17259
+ }): void | Promise<void>;
17260
+ onError?(socket: ExperimentalPluginWebSocket, error: Error): void;
17261
+ }
17262
+ type ExperimentalPluginWebSocketHandler = (context: ExperimentalPluginWebSocketContext) => ExperimentalPluginWebSocketHandlers;
17192
17263
  interface PluginHttp {
17193
17264
  /**
17194
17265
  * Register an HTTP route, mounted at
@@ -17202,6 +17273,14 @@ interface PluginHttp {
17202
17273
  route(method: string, path: string, handler: PluginHttpHandler, opts?: {
17203
17274
  auth?: PluginHttpAuthMode;
17204
17275
  }): void;
17276
+ /**
17277
+ * Register a WebSocket route in the same `/http/` namespace as `route`.
17278
+ * A GET request upgrades only when it carries `Upgrade: websocket`.
17279
+ * Auth modes and exact-path matching are identical to HTTP routes.
17280
+ */
17281
+ experimental_websocket(path: string, handler: ExperimentalPluginWebSocketHandler, opts?: {
17282
+ auth?: PluginHttpAuthMode;
17283
+ }): void;
17205
17284
  }
17206
17285
  interface PluginRpc {
17207
17286
  /**
@@ -18158,4 +18237,4 @@ interface BbPluginApi {
18158
18237
  }
18159
18238
 
18160
18239
  export { PLUGIN_CLI_OUTPUT_MAX_BYTES, defineRpcContract, experimental_defineHostEntry };
18161
- export type { BbContext, BbNavigate, BbPluginApi, CodeOverflowMode, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, DiffProps, DiffViewMode, ExperimentalAppOverlayProps, ExperimentalAppOverlayRegistration, ExperimentalAppPanel, ExperimentalAppPanelSurface, ExperimentalComposerSubmitOptions, ExperimentalDiffFileContent, ExperimentalDiffFullFileContents, ExperimentalFileLinkProps, ExperimentalFileLocation, ExperimentalFileOpenOptions, ExperimentalFixedTabTargetContract, ExperimentalFixedTabTargetState, ExperimentalHostCallOptions, ExperimentalHostClient, ExperimentalHostEntry, ExperimentalHostPaths, ExperimentalHostRpcContext, ExperimentalHostRpcHandlers, ExperimentalHostSignalContract, ExperimentalHostSignalEvent, ExperimentalHostSignals, ExperimentalHostWatchChange, ExperimentalHostWatchChangeType, ExperimentalHostWatchEvent, ExperimentalHostWatchListener, ExperimentalHostWatchOptions, ExperimentalHostWatchSubscription, ExperimentalHostWorkerLease, ExperimentalLiveFileTarget, ExperimentalOpenFixedTabOptions, ExperimentalPermissionModePickerProps, ExperimentalPluginFixedTabReference, ExperimentalPluginProviderEnvContext, ExperimentalPluginProviderEnvEntry, ExperimentalPluginProviderEnvHealth, ExperimentalPluginProviderEnvHealthContext, ExperimentalProviderModelPickerProps, ExperimentalProviderModelPickerRouting, ExperimentalProviderModelPickerValue, ExperimentalSidebarNavigationAction, ExperimentalSidebarNavigationActivationOptions, ExperimentalSidebarNavigationIcon, ExperimentalSidebarNavigationItem, ExperimentalSidebarNavigationProps, ExperimentalSidebarNavigationRegistration, ExperimentalSidebarNavigationShortcut, JsonValue, MarkdownProps, MessageDispatchHookContext, MessageDispatchHookDecision, NewThreadComposerProps, NewThreadRequest, PluginAgentConfiguration, PluginAgentConfigurationContext, PluginAgentToolContentPart, PluginAgentToolContext, PluginAgentToolLabels, PluginAgentToolPresentation, PluginAgentToolRegistrationBase, PluginAgentToolResult, PluginAgentToolSelection, PluginAgents, PluginAiServiceDeclaration, PluginAiServiceKind, PluginAiServices, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginBackground, PluginCli, PluginCliCommandInfo, PluginCliContext, PluginCliExecutionResult, PluginCliOutputLimitError, PluginCliRegistration, PluginCliResult, PluginCodeThemeData, PluginCodeThemeState, PluginCodeThemeTokenRule, PluginCommandPaletteActionContext, PluginCommandPaletteActionRegistration, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginDiffRendererProps, PluginDiffRendererRegistration, PluginDispatchAttemptKind, PluginDispatchExecution, PluginDispatchExecutionSources, PluginDispatchInput, PluginEvents, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginFixedTabDeclaration, PluginFixedTabRegistration, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginHookHandler, PluginHookName, PluginHookSignatures, PluginHooks, PluginHosts, PluginHttp, PluginHttpAuthMode, PluginHttpHandler, PluginInteractionCancelReason, PluginInteractionRequest, PluginInteractionResult, PluginKvStorage, PluginLogger, PluginMentionItem, PluginMentionProviderRegistration, PluginMentionSearchContext, PluginMentionTrigger, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginNewThreadPanelActionContext, PluginNewThreadPanelActionRegistration, PluginNewThreadPanelProps, PluginPanelActionOpenOptions, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginProviderCapabilities, PluginProviderComposerAction, PluginProviderDeclaration, PluginProviderExtensionKindDeclaration, PluginProviderFallbackModel, PluginProviderIconRegistration, PluginProviderMaintenance, PluginProviderModelCatalogScope, PluginProviderNativeRootEntry, PluginProviderNativeRoots, PluginProviderOptionDescriptor, PluginProviderOptionsContext, PluginProviderPermissionMode, PluginProviderReasoningLevel, PluginProviderStrings, PluginProviders, PluginProvidersState, PluginRealtime, PluginRealtimeConnectionState, PluginRpc, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginServerApi, PluginSettingDescriptor, PluginSettingDescriptors, PluginSettingValue, PluginSettings, PluginSettingsHandle, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSettingsValues, PluginSharedPortTunnelIdentity, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginSourceCodeRendererProps, PluginSourceCodeRendererRegistration, PluginStatusApi, PluginStorage, PluginTargetedPanelActionOpenOptions, PluginThreadEventHandler, PluginThreadEventName, PluginThreadEventPayloads, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, PluginTimelineRendererProps, PluginTimelineRendererRegistration, PluginTimelineRendererRow, PluginTimelineRowPresentation, PluginTimelineRowStatus, PluginTurnFailedEvent, PluginUi, SourceCodeLineRange, SourceCodeProps, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps, UrlLinkProps };
18240
+ export type { BbContext, BbNavigate, BbPluginApi, CodeOverflowMode, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, DiffProps, DiffViewMode, ExperimentalAppOverlayProps, ExperimentalAppOverlayRegistration, ExperimentalAppPanel, ExperimentalAppPanelSurface, ExperimentalComposerSubmitOptions, ExperimentalDiffFileContent, ExperimentalDiffFullFileContents, ExperimentalFileLinkProps, ExperimentalFileLocation, ExperimentalFileOpenOptions, ExperimentalFixedTabTargetContract, ExperimentalFixedTabTargetState, ExperimentalHostCallOptions, ExperimentalHostClient, ExperimentalHostEntry, ExperimentalHostPaths, ExperimentalHostRpcContext, ExperimentalHostRpcHandlers, ExperimentalHostSignalContract, ExperimentalHostSignalEvent, ExperimentalHostSignals, ExperimentalHostWatchChange, ExperimentalHostWatchChangeType, ExperimentalHostWatchEvent, ExperimentalHostWatchListener, ExperimentalHostWatchOptions, ExperimentalHostWatchSubscription, ExperimentalHostWorkerLease, ExperimentalLiveFileTarget, ExperimentalOpenFixedTabOptions, ExperimentalPermissionModePickerProps, ExperimentalPluginFixedTabReference, ExperimentalPluginProviderEnvContext, ExperimentalPluginProviderEnvEntry, ExperimentalPluginProviderEnvHealth, ExperimentalPluginProviderEnvHealthContext, ExperimentalPluginWebSocket, ExperimentalPluginWebSocketContext, ExperimentalPluginWebSocketHandler, ExperimentalPluginWebSocketHandlers, ExperimentalProviderModelPickerProps, ExperimentalProviderModelPickerRouting, ExperimentalProviderModelPickerValue, ExperimentalSidebarFooter, ExperimentalSidebarFooterActionContext, ExperimentalSidebarFooterActionRegistration, ExperimentalSidebarFooterDisclosureController, ExperimentalSidebarFooterDisclosureProps, ExperimentalSidebarFooterDisclosureRegistration, ExperimentalSidebarFooterItemBase, ExperimentalSidebarFooterItemRegistration, ExperimentalSidebarNavigationAction, ExperimentalSidebarNavigationActivationOptions, ExperimentalSidebarNavigationIcon, ExperimentalSidebarNavigationItem, ExperimentalSidebarNavigationProps, ExperimentalSidebarNavigationRegistration, ExperimentalSidebarNavigationShortcut, JsonValue, MarkdownProps, MessageDispatchHookContext, MessageDispatchHookDecision, NewThreadComposerProps, NewThreadRequest, PluginAgentConfiguration, PluginAgentConfigurationContext, PluginAgentToolContentPart, PluginAgentToolContext, PluginAgentToolLabels, PluginAgentToolPresentation, PluginAgentToolRegistrationBase, PluginAgentToolResult, PluginAgentToolSelection, PluginAgents, PluginAiServiceDeclaration, PluginAiServiceKind, PluginAiServices, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginBackground, PluginCli, PluginCliCommandInfo, PluginCliContext, PluginCliExecutionResult, PluginCliOutputLimitError, PluginCliRegistration, PluginCliResult, PluginCodeThemeData, PluginCodeThemeState, PluginCodeThemeTokenRule, PluginCommandPaletteActionContext, PluginCommandPaletteActionRegistration, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginDiffRendererProps, PluginDiffRendererRegistration, PluginDispatchAttemptKind, PluginDispatchExecution, PluginDispatchExecutionSources, PluginDispatchInput, PluginEvents, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginFixedTabDeclaration, PluginFixedTabRegistration, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginHookHandler, PluginHookName, PluginHookSignatures, PluginHooks, PluginHosts, PluginHttp, PluginHttpAuthMode, PluginHttpHandler, PluginInteractionCancelReason, PluginInteractionRequest, PluginInteractionResult, PluginKvStorage, PluginLogger, PluginMentionItem, PluginMentionProviderRegistration, PluginMentionSearchContext, PluginMentionTrigger, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginNewThreadPanelActionContext, PluginNewThreadPanelActionRegistration, PluginNewThreadPanelProps, PluginPanelActionOpenOptions, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginProviderCapabilities, PluginProviderComposerAction, PluginProviderDeclaration, PluginProviderExtensionKindDeclaration, PluginProviderFallbackModel, PluginProviderIconRegistration, PluginProviderMaintenance, PluginProviderModelCatalogScope, PluginProviderNativeRootEntry, PluginProviderNativeRoots, PluginProviderOptionDescriptor, PluginProviderOptionsContext, PluginProviderPermissionMode, PluginProviderReasoningLevel, PluginProviderStrings, PluginProviders, PluginProvidersState, PluginRealtime, PluginRealtimeConnectionState, PluginRpc, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginServerApi, PluginSettingDescriptor, PluginSettingDescriptors, PluginSettingValue, PluginSettings, PluginSettingsHandle, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSettingsValues, PluginSharedPortTunnelIdentity, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginSourceCodeRendererProps, PluginSourceCodeRendererRegistration, PluginStatusApi, PluginStorage, PluginTargetedPanelActionOpenOptions, PluginThreadEventHandler, PluginThreadEventName, PluginThreadEventPayloads, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, PluginTimelineRendererProps, PluginTimelineRendererRegistration, PluginTimelineRendererRow, PluginTimelineRowPresentation, PluginTimelineRowStatus, PluginTurnFailedEvent, PluginUi, SourceCodeLineRange, SourceCodeProps, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps, UrlLinkProps };
@@ -213,6 +213,127 @@ function collectComposerCustomization(registration, seenIds, onRejected) {
213
213
  }
214
214
 
215
215
  // src/internal/plugin-app-collector.ts
216
+ var sidebarFooterCommandSequence = 0;
217
+ var SIDEBAR_FOOTER_ACTION_KEYS = /* @__PURE__ */ new Set([
218
+ "id",
219
+ "label",
220
+ "icon",
221
+ "kind",
222
+ "onActivate"
223
+ ]);
224
+ var SIDEBAR_FOOTER_DISCLOSURE_KEYS = /* @__PURE__ */ new Set([
225
+ "id",
226
+ "label",
227
+ "icon",
228
+ "kind",
229
+ "component"
230
+ ]);
231
+ var SidebarFooterItemRuntime = class {
232
+ listeners = /* @__PURE__ */ new Set();
233
+ snapshot = {
234
+ command: null
235
+ };
236
+ subscribe = (listener) => {
237
+ this.listeners.add(listener);
238
+ return () => this.listeners.delete(listener);
239
+ };
240
+ getSnapshot = () => this.snapshot;
241
+ acknowledgeCommand = (sequence) => {
242
+ if (this.snapshot.command?.sequence !== sequence) return;
243
+ this.snapshot = { ...this.snapshot, command: null };
244
+ this.emit();
245
+ };
246
+ createDisclosureController() {
247
+ return Object.freeze({
248
+ open: () => this.request("open"),
249
+ close: () => this.request("close"),
250
+ toggle: () => this.request("toggle")
251
+ });
252
+ }
253
+ request(kind) {
254
+ sidebarFooterCommandSequence += 1;
255
+ this.snapshot = {
256
+ ...this.snapshot,
257
+ command: { sequence: sidebarFooterCommandSequence, kind }
258
+ };
259
+ this.emit();
260
+ }
261
+ emit() {
262
+ for (const listener of this.listeners) listener();
263
+ }
264
+ };
265
+ function adaptSidebarFooterAction(registration) {
266
+ return {
267
+ source: "sidebarFooterAction",
268
+ id: registration.id,
269
+ label: registration.title,
270
+ icon: registration.icon,
271
+ kind: "action",
272
+ onActivate: ({ openPluginDetails }) => registration.run({ openSettings: openPluginDetails }),
273
+ runtime: new SidebarFooterItemRuntime()
274
+ };
275
+ }
276
+ var SidebarFooterCollector = class {
277
+ constructor(collected, allCollected, seenIds) {
278
+ this.collected = collected;
279
+ this.allCollected = allCollected;
280
+ this.seenIds = seenIds;
281
+ }
282
+ collected;
283
+ allCollected;
284
+ seenIds;
285
+ register(registration) {
286
+ const kind = "experimental_sidebarFooter.register";
287
+ const id = requireSlotId(kind, registration?.id);
288
+ requireUniqueId(kind, this.seenIds, id);
289
+ const label = requireNonEmptyString(kind, "label", registration.label);
290
+ const icon = requireNonEmptyString(kind, "icon", registration.icon);
291
+ const runtime = new SidebarFooterItemRuntime();
292
+ if (registration.kind === "action") {
293
+ this.rejectUnknownKeys(kind, registration, SIDEBAR_FOOTER_ACTION_KEYS);
294
+ if (typeof registration.onActivate !== "function") {
295
+ throw new Error(`${kind}: "onActivate" must be a function`);
296
+ }
297
+ const item = {
298
+ id,
299
+ label,
300
+ icon,
301
+ kind: "action",
302
+ onActivate: registration.onActivate,
303
+ source: "experimental_sidebarFooter",
304
+ runtime
305
+ };
306
+ this.collected.push(item);
307
+ this.allCollected.push(item);
308
+ return;
309
+ }
310
+ if (registration.kind === "disclosure") {
311
+ this.rejectUnknownKeys(
312
+ kind,
313
+ registration,
314
+ SIDEBAR_FOOTER_DISCLOSURE_KEYS
315
+ );
316
+ const item = {
317
+ id,
318
+ label,
319
+ icon,
320
+ kind: "disclosure",
321
+ component: requireComponent(kind, registration.component),
322
+ source: "experimental_sidebarFooter",
323
+ runtime
324
+ };
325
+ this.collected.push(item);
326
+ this.allCollected.push(item);
327
+ return runtime.createDisclosureController();
328
+ }
329
+ throw new Error(`${kind}: "kind" must be "action" or "disclosure"`);
330
+ }
331
+ rejectUnknownKeys(kind, registration, allowed) {
332
+ for (const key of Object.keys(registration)) {
333
+ if (!allowed.has(key)) throw new Error(`${kind}: unknown field "${key}"`);
334
+ }
335
+ }
336
+ };
216
337
  var NAV_PANEL_REGISTRATION_KEYS = new Set(
217
338
  Object.keys({
218
339
  id: true,
@@ -242,7 +363,12 @@ function rejectStaleNavPanelKeys(kind, registration) {
242
363
  }
243
364
  }
244
365
  }
366
+ var sidebarFooterItemsByRegistrationSet = /* @__PURE__ */ new WeakMap();
367
+ function getCollectedSidebarFooterItems(registrations) {
368
+ return sidebarFooterItemsByRegistrationSet.get(registrations) ?? null;
369
+ }
245
370
  function collectPluginAppRegistrations(definition, onComposerCustomizationRejected = (reason) => console.warn(reason)) {
371
+ const sidebarFooterItems = [];
246
372
  const collected = {
247
373
  homepageSections: [],
248
374
  settingsSections: [],
@@ -253,6 +379,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
253
379
  composerCustomizations: [],
254
380
  pendingInteractions: [],
255
381
  sidebarFooterActions: [],
382
+ experimentalSidebarFooterItems: [],
256
383
  experimentalSidebarNavigations: [],
257
384
  threadLists: [],
258
385
  threadHeaderActions: [],
@@ -266,6 +393,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
266
393
  timelineRenderers: [],
267
394
  contentScripts: []
268
395
  };
396
+ sidebarFooterItemsByRegistrationSet.set(collected, sidebarFooterItems);
269
397
  const seenIds = {
270
398
  homepageSection: /* @__PURE__ */ new Set(),
271
399
  settingsSection: /* @__PURE__ */ new Set(),
@@ -275,7 +403,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
275
403
  newThreadPanelAction: /* @__PURE__ */ new Set(),
276
404
  composerCustomization: /* @__PURE__ */ new Set(),
277
405
  pendingInteraction: /* @__PURE__ */ new Set(),
278
- sidebarFooterAction: /* @__PURE__ */ new Set(),
406
+ sidebarFooterItem: /* @__PURE__ */ new Set(),
279
407
  sidebarNavigation: /* @__PURE__ */ new Set(),
280
408
  threadList: /* @__PURE__ */ new Set(),
281
409
  threadHeaderAction: /* @__PURE__ */ new Set(),
@@ -466,16 +594,18 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
466
594
  sidebarFooterAction(registration) {
467
595
  const kind = "slots.sidebarFooterAction";
468
596
  const id = requireSlotId(kind, registration?.id);
469
- requireUniqueId(kind, seenIds.sidebarFooterAction, id);
597
+ requireUniqueId(kind, seenIds.sidebarFooterItem, id);
470
598
  if (typeof registration.run !== "function") {
471
599
  throw new Error(`${kind}: "run" must be a function`);
472
600
  }
473
- collected.sidebarFooterActions.push({
601
+ const legacyRegistration = {
474
602
  id,
475
603
  title: requireNonEmptyString(kind, "title", registration.title),
476
604
  icon: requireNonEmptyString(kind, "icon", registration.icon),
477
605
  run: registration.run
478
- });
606
+ };
607
+ collected.sidebarFooterActions.push(legacyRegistration);
608
+ sidebarFooterItems.push(adaptSidebarFooterAction(legacyRegistration));
479
609
  },
480
610
  experimental_sidebarNavigation(registration) {
481
611
  const kind = "slots.experimental_sidebarNavigation";
@@ -637,6 +767,11 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
637
767
  });
638
768
  }
639
769
  },
770
+ experimental_sidebarFooter: new SidebarFooterCollector(
771
+ collected.experimentalSidebarFooterItems,
772
+ sidebarFooterItems,
773
+ seenIds.sidebarFooterItem
774
+ ),
640
775
  composer: {
641
776
  customize(registration) {
642
777
  const customization = collectComposerCustomization(
@@ -664,5 +799,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
664
799
  return collected;
665
800
  }
666
801
  export {
667
- collectPluginAppRegistrations
802
+ adaptSidebarFooterAction,
803
+ collectPluginAppRegistrations,
804
+ getCollectedSidebarFooterItems
668
805
  };
@@ -189,11 +189,11 @@ var promptInputSchema = z2.discriminatedUnion("type", [
189
189
  function isSelectedPromptCommandMention(mention, selector) {
190
190
  return mention.resource.kind === "command" && mention.resource.trigger === selector.trigger && mention.resource.name === selector.name;
191
191
  }
192
- var BUILTIN_COMPACT_COMMAND = { trigger: "/", name: "compact" };
193
- function isStandaloneBuiltinCompactCommand(input) {
192
+ function isStandaloneBuiltinCommand(input, name) {
193
+ const selector = { trigger: "/", name };
194
194
  const selected = input.flatMap(
195
195
  (item) => item.type === "text" ? item.mentions.filter(
196
- (mention2) => isSelectedPromptCommandMention(mention2, BUILTIN_COMPACT_COMMAND)
196
+ (mention2) => isSelectedPromptCommandMention(mention2, selector)
197
197
  ).map((mention2) => ({ mention: mention2, text: item.text })) : []
198
198
  );
199
199
  const standalone = selected[0];
@@ -201,13 +201,15 @@ function isStandaloneBuiltinCompactCommand(input) {
201
201
  return false;
202
202
  }
203
203
  const { mention, text } = standalone;
204
- if (mention.resource.kind !== "command" || mention.resource.source !== "command" || mention.resource.origin !== "builtin" || text.slice(mention.start, mention.end) !== "/compact") {
204
+ if (mention.resource.kind !== "command" || mention.resource.source !== "command" || mention.resource.origin !== "builtin" || text.slice(mention.start, mention.end) !== `/${name}`) {
205
205
  return false;
206
206
  }
207
- return removeCommandMentionsFromPromptInput(
208
- input,
209
- BUILTIN_COMPACT_COMMAND
210
- ).every((item) => item.type === "text" && item.text.trim() === "");
207
+ return removeCommandMentionsFromPromptInput(input, selector).every(
208
+ (item) => item.type === "text" && item.text.trim() === ""
209
+ );
210
+ }
211
+ function isStandaloneBuiltinCompactCommand(input) {
212
+ return isStandaloneBuiltinCommand(input, "compact");
211
213
  }
212
214
  function commandRemovalRanges(input, selector) {
213
215
  return input.mentions.filter((mention) => isSelectedPromptCommandMention(mention, selector)).map((mention) => ({
@@ -6404,15 +6404,6 @@ function isRequest(message) {
6404
6404
  function isResponse(message) {
6405
6405
  return message.id !== void 0 && message.method === void 0;
6406
6406
  }
6407
- function countTurnBoundaries(events) {
6408
- let started = 0;
6409
- let completed = 0;
6410
- for (const event of events) {
6411
- if (event.type === "turn/started") started += 1;
6412
- if (event.type === "turn/completed") completed += 1;
6413
- }
6414
- return { started, completed };
6415
- }
6416
6407
  function planRuntimeSteps(recording, assembler) {
6417
6408
  const steps = [];
6418
6409
  const assembled = [];
@@ -6433,7 +6424,7 @@ function planRuntimeSteps(recording, assembler) {
6433
6424
  steps.push({
6434
6425
  entry,
6435
6426
  message: parseWire(entry.line),
6436
- gate: countTurnBoundaries(assembled),
6427
+ gate: countTurns(assembled),
6437
6428
  eventsBefore: assembled.length
6438
6429
  });
6439
6430
  }
@@ -6682,7 +6673,7 @@ async function replayRecording(options) {
6682
6673
  await waitFor(
6683
6674
  `${step.gate.started} turn/started and ${step.gate.completed} turn/completed before ${method}`,
6684
6675
  () => {
6685
- const live = countTurnBoundaries(events);
6676
+ const live = countTurns(events);
6686
6677
  return live.started >= step.gate.started && live.completed >= step.gate.completed;
6687
6678
  }
6688
6679
  );
@@ -1460,11 +1460,11 @@ var promptInputSchema = z6.discriminatedUnion("type", [
1460
1460
  function isSelectedPromptCommandMention(mention, selector) {
1461
1461
  return mention.resource.kind === "command" && mention.resource.trigger === selector.trigger && mention.resource.name === selector.name;
1462
1462
  }
1463
- var BUILTIN_COMPACT_COMMAND = { trigger: "/", name: "compact" };
1464
- function isStandaloneBuiltinCompactCommand(input) {
1463
+ function isStandaloneBuiltinCommand(input, name) {
1464
+ const selector = { trigger: "/", name };
1465
1465
  const selected = input.flatMap(
1466
1466
  (item) => item.type === "text" ? item.mentions.filter(
1467
- (mention2) => isSelectedPromptCommandMention(mention2, BUILTIN_COMPACT_COMMAND)
1467
+ (mention2) => isSelectedPromptCommandMention(mention2, selector)
1468
1468
  ).map((mention2) => ({ mention: mention2, text: item.text })) : []
1469
1469
  );
1470
1470
  const standalone = selected[0];
@@ -1472,13 +1472,15 @@ function isStandaloneBuiltinCompactCommand(input) {
1472
1472
  return false;
1473
1473
  }
1474
1474
  const { mention, text } = standalone;
1475
- if (mention.resource.kind !== "command" || mention.resource.source !== "command" || mention.resource.origin !== "builtin" || text.slice(mention.start, mention.end) !== "/compact") {
1475
+ if (mention.resource.kind !== "command" || mention.resource.source !== "command" || mention.resource.origin !== "builtin" || text.slice(mention.start, mention.end) !== `/${name}`) {
1476
1476
  return false;
1477
1477
  }
1478
- return removeCommandMentionsFromPromptInput(
1479
- input,
1480
- BUILTIN_COMPACT_COMMAND
1481
- ).every((item) => item.type === "text" && item.text.trim() === "");
1478
+ return removeCommandMentionsFromPromptInput(input, selector).every(
1479
+ (item) => item.type === "text" && item.text.trim() === ""
1480
+ );
1481
+ }
1482
+ function isStandaloneBuiltinCompactCommand(input) {
1483
+ return isStandaloneBuiltinCommand(input, "compact");
1482
1484
  }
1483
1485
  function commandRemovalRanges(input, selector) {
1484
1486
  return input.mentions.filter((mention) => isSelectedPromptCommandMention(mention, selector)).map((mention) => ({
@@ -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
- sidebarFooterAction: /* @__PURE__ */ new Set(),
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.sidebarFooterAction, id);
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
- collected.sidebarFooterActions.push({
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(
@@ -2311,6 +2311,8 @@ function createFakePluginHostInternal(options, sharedState) {
2311
2311
  }
2312
2312
  };
2313
2313
  const httpRoutes = [];
2314
+ const websocketRoutes = [];
2315
+ const websocketSessions = /* @__PURE__ */ new Set();
2314
2316
  const http = {
2315
2317
  route(method, path, handler, opts) {
2316
2318
  assertLive();
@@ -2344,6 +2346,29 @@ function createFakePluginHostInternal(options, sharedState) {
2344
2346
  );
2345
2347
  }
2346
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 });
2347
2372
  }
2348
2373
  };
2349
2374
  const rpcHandlers = /* @__PURE__ */ new Map();
@@ -3059,6 +3084,9 @@ function createFakePluginHostInternal(options, sharedState) {
3059
3084
  pendingInteractions.delete(id);
3060
3085
  pending.resolve({ outcome: "cancelled", reason: "plugin-disposed" });
3061
3086
  }
3087
+ for (const session of [...websocketSessions]) {
3088
+ await session.closeForReload();
3089
+ }
3062
3090
  for (const controller of serviceControllers) controller.abort();
3063
3091
  for (const hook of [...disposeHooks].reverse()) {
3064
3092
  try {
@@ -3104,6 +3132,7 @@ function createFakePluginHostInternal(options, sharedState) {
3104
3132
  registrations: {
3105
3133
  settingsDescriptors,
3106
3134
  httpRoutes,
3135
+ websocketRoutes,
3107
3136
  get rpcMethods() {
3108
3137
  return [...rpcHandlers.keys()];
3109
3138
  },
@@ -3321,6 +3350,127 @@ function createFakePluginHostInternal(options, sharedState) {
3321
3350
  });
3322
3351
  return app.request(path, { ...init, method: normalizedMethod });
3323
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
+ },
3324
3474
  runService(name) {
3325
3475
  const service = services.find((record) => record.name === name);
3326
3476
  if (!service) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.45",
3
+ "version": "0.4.47",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"