bb.workspace and bb.workspace-ui

The headless bb.workspace plugin owns workspace data and routing. The bb.workspace-ui plugin presents projects and environments.

Purpose

bb.workspace gives app and server plugins one stable workspace model. It owns projects, environments, routing, and environment providers.

bb.workspace-ui supplies the default project switcher and environment pages. It requires the bb.workspace service.

The data plugin has consumers beyond its own UI. It therefore ships headless and carries no presentation assumptions.

The bb.workspace plugin (data)

bb.workspace declares no surfaces. A service replacement does not need to implement React components, pages, or shell controls.

Surfaces

ID kind replaceable props contract sketch notes
This plugin declares no surfaces.

bb.workspace-ui owns workspace presentation. The data service returns no React nodes, icons, colors, or renderer choices.

Services

ID kind replaceable default provider edge use notes
bb.workspace single yes bb.workspace required, optional, or watched Owns projects, environments, and pure route functions.
bb.workspace.envProvider keyed yes first-party claimant for each provider ID used by bb.workspace as a watched keyed edge Joins a server provider to one private host role.

bb.workspace

The service token matches by ID and semver. A required consumer restarts after a winner change. The first-party implementation uses kernel storage, realtime, host RPC, and HTTP ports.

import { defineService } from "@get-bb/plugin/server";

export const workspaceService = defineService<WorkspaceService>(
  "bb.workspace",
  "1.0",
);

export interface WorkspaceService {
  readonly projects: WorkspaceProjects;
  readonly environments: WorkspaceEnvironments;
  readonly hosts: WorkspaceHosts;
  readonly terminals: WorkspaceTerminals;
  readonly routing: WorkspaceRouting;
  watch(listener: (event: WorkspaceSystemChangeEvent) => void): Dispose;
}

export interface WorkspaceProjects {
  list(query?: ProjectListQuery): Promise<ProjectPage>;
  get(projectId: ProjectId): Promise<Project>;
  create(input: ProjectCreateInput): Promise<Project>;
  update(projectId: ProjectId, patch: ProjectPatch): Promise<Project>;
  delete(projectId: ProjectId): Promise<{ deleted: true }>;
  reorder(projectIds: readonly ProjectId[]): Promise<readonly ProjectId[]>;

  branches(input: ProjectBranchesInput): Promise<readonly BranchInfo[]>;
  commands(projectId: ProjectId): Promise<readonly ProjectCommand[]>;
  defaultExecutionOptions(projectId: ProjectId): Promise<ExecutionDefaults>;
  files(input: ProjectFilesInput): Promise<ProjectFilePage>;
  fileContent(input: ProjectFileContentInput): Promise<ProjectFileContent>;
  paths(projectId: ProjectId): Promise<readonly WorkspacePath[]>;
  promptHistory(input: ProjectPromptHistoryInput): Promise<PromptHistoryPage>;
  catalogSnapshot(query?: ProjectListQuery): Promise<ProjectCatalogSnapshot>;

  readonly attachments: WorkspaceProjectAttachments;
  readonly sources: WorkspaceProjectSources;

  watch(
    selector: { projectId?: ProjectId },
    listener: (event: ProjectChangeEvent) => void,
  ): Dispose;
}

export interface WorkspaceProjectAttachments {
  copy(input: ProjectAttachmentCopyInput): Promise<void>;
  read(input: ProjectAttachmentReadInput): Promise<ProjectAttachmentContent>;
  upload(input: ProjectAttachmentUploadInput): Promise<ProjectAttachment>;
}

export interface WorkspaceProjectSources {
  add(projectId: ProjectId, input: ProjectSourceCreateInput): Promise<ProjectSource>;
  update(sourceId: SourceId, patch: ProjectSourcePatch): Promise<ProjectSource>;
  delete(sourceId: SourceId): Promise<{ deleted: true }>;
}

export interface WorkspaceEnvironments {
  list(query?: EnvironmentListQuery): Promise<EnvironmentPage>;
  get(environmentId: EnvironmentId): Promise<Environment>;
  update(environmentId: EnvironmentId, patch: EnvironmentPatch): Promise<Environment>;
  archiveThreads(environmentId: EnvironmentId): Promise<{ archivedThreadIds: ThreadId[] }>;

  providers(hostId: HostId): Promise<readonly EnvironmentProviderDescriptor[]>;
  provision(
    input: EnvironmentProvisionInput,
    options?: { signal?: AbortSignal },
  ): AsyncIterable<EnvironmentProvisionEvent>;
  reconnect(environmentId: EnvironmentId): Promise<Environment>;
  destroy(
    environmentId: EnvironmentId,
    options?: { signal?: AbortSignal },
  ): Promise<void>;

  status(input: EnvironmentStatusInput): Promise<EnvironmentStatus>;
  paths(environmentId: EnvironmentId): Promise<readonly WorkspacePath[]>;
  commit(input: EnvironmentCommitInput): Promise<CommitResult>;
  squashMerge(input: EnvironmentSquashMergeInput): Promise<SquashMergeResult>;
  diff(input: EnvironmentDiffInput): Promise<EnvironmentDiff>;
  diffBranches(environmentId: EnvironmentId): Promise<readonly BranchInfo[]>;
  diffFiles(input: EnvironmentDiffInput): Promise<EnvironmentDiffFilePage>;
  diffFile(input: EnvironmentDiffFileInput): Promise<EnvironmentDiffFile>;
  diffPatch(input: EnvironmentDiffPatchInput): Promise<EnvironmentPatchResult>;

  pullRequest(environmentId: EnvironmentId): Promise<EnvironmentPullRequest | null>;
  markPullRequestDraft(environmentId: EnvironmentId): Promise<EnvironmentPullRequest>;
  markPullRequestReady(environmentId: EnvironmentId): Promise<EnvironmentPullRequest>;
  mergePullRequest(input: EnvironmentPullRequestMergeInput): Promise<MergeResult>;

  watch(
    selector: { environmentId?: EnvironmentId },
    listener: (event: EnvironmentChangeEvent) => void,
  ): Dispose;
}

export interface WorkspaceHosts {
  list(): Promise<readonly HostRecord[]>;
  get(hostId: HostId): Promise<HostRecord>;
  update(hostId: HostId, patch: HostPatch): Promise<HostRecord>;
  delete(hostId: HostId): Promise<{ deleted: true }>;
  directory(input: HostDirectoryInput): Promise<HostDirectoryPage>;
  pathsExist(hostId: HostId, paths: readonly string[]): Promise<readonly boolean[]>;
  pickFolder(hostId: HostId, options?: FolderPickerOptions): Promise<string | null>;
  cloneDefaultPath(hostId: HostId, projectId: ProjectId): Promise<string>;
  createJoinCode(): Promise<HostJoinCode>;
  retryUpdate(hostId: HostId): Promise<HostRecord>;
  providerCliStatus(hostId: HostId, providerId: string): Promise<ProviderCliStatus>;
  installProviderCli(hostId: HostId, providerId: string): Promise<ProviderCliInstallResult>;
  watch(listener: (event: HostChangeEvent) => void): Dispose;
}

export interface WorkspaceTerminals {
  list(scope: TerminalScope): Promise<readonly TerminalRecord[]>;
  create(input: TerminalCreateInput): Promise<TerminalRecord>;
  get(terminalId: string): Promise<TerminalRecord>;
  input(terminalId: string, data: string): Promise<void>;
  output(terminalId: string, cursor?: string): Promise<TerminalOutput>;
  resize(terminalId: string, columns: number, rows: number): Promise<void>;
  rename(terminalId: string, title: string): Promise<TerminalRecord>;
  restart(terminalId: string): Promise<TerminalRecord>;
  close(terminalId: string): Promise<void>;
}

export type WorkspaceSystemChangeEvent =
  | { kind: "host"; hostId: HostId | null }
  | { kind: "environment"; environmentId: EnvironmentId | null }
  | { kind: "project"; projectId: ProjectId | null };

export interface WorkspaceRouting {
  parse(url: URL): WorkspaceRoute | null;
  href(target: WorkspaceTarget): string;
  context(route: WorkspaceRoute): WorkspaceContext;
}

The watch methods replace entity filters on the broad old realtime subscription. A watched service edge receives provider availability changes without a plugin restart.

The service uses the following record shapes.

export type ProjectId = string & { readonly __projectId: unique symbol };
export type EnvironmentId = string & { readonly __environmentId: unique symbol };
export type HostId = string & { readonly __hostId: unique symbol };
export type SourceId = string & { readonly __sourceId: unique symbol };
export type ThreadId = string & { readonly __threadId: unique symbol };

export interface Project {
  id: ProjectId;
  name: string;
  description: string | null;
  personal: boolean;
  order: number;
  sources: readonly ProjectSource[];
  createdAt: string;
  updatedAt: string;
}

export interface ProjectSource {
  id: SourceId;
  projectId: ProjectId;
  hostId: HostId;
  path: string;
  gitRemoteUrl: string | null;
  default: boolean;
}

export interface Environment {
  id: EnvironmentId;
  projectId: ProjectId;
  hostId: HostId;
  providerId: EnvironmentProviderId;
  state: "provisioning" | "ready" | "retiring" | "destroying" | "destroyed" | "failed";
  managed: boolean;
  handle: JsonObject | null;
  summary: EnvironmentSummary | null;
  createdAt: string;
  updatedAt: string;
}

export interface EnvironmentSummary {
  label: string;
  path: string | null;
  isRepo: boolean;
  isWorktree: boolean;
  branch: string | null;
  baseBranch: string | null;
  defaultBranch: string | null;
}

export type EnvironmentProvisionEvent =
  | { kind: "progress"; step: string; text: string; status: "started" | "completed" | "failed" }
  | { kind: "output"; line: string }
  | { kind: "ready"; environment: Environment };

export type WorkspaceTarget =
  | { kind: "compose"; projectId?: ProjectId; initialPrompt?: string; focusPrompt?: boolean }
  | { kind: "project"; projectId: ProjectId }
  | { kind: "environment"; environmentId: EnvironmentId; subPath?: string }
  | { kind: "thread"; threadId: ThreadId }
  | { kind: "plugin-panel"; pluginId: string; path: string; subPath?: string }
  | { kind: "thread-panel"; threadId: ThreadId; panelId: string; params?: JsonValue }
  | { kind: "file-preview"; source: WorkspaceFileSource; path: string }
  | { kind: "file-external"; source: WorkspaceFileSource; path: string }
  | { kind: "url"; url: string };

export type WorkspaceRoute = Exclude<
  WorkspaceTarget,
  { kind: "file-external" | "url" }
>;

export interface WorkspaceContext {
  projectId: ProjectId | null;
  environmentId: EnvironmentId | null;
  threadId: ThreadId | null;
  route: WorkspaceRoute;
}

Methods use explicit input records for operations with more than one value. All list methods return bounded pages and a continuation token. The generated contract.json contains the complete schemas and limits.

bb.workspace.envProvider

This keyed contract uses EnvironmentProviderId as its key. The key has the form <pluginId>/<name>. One winner serves each key.

export type EnvironmentProviderId = `${string}/${string}`;

export const environmentProvider = defineKeyedService<
  EnvironmentProviderId,
  EnvironmentProviderService
>("bb.workspace.envProvider", "1.0");

export interface EnvironmentProviderService {
  describe(): Promise<EnvironmentProviderDescriptor>;
  provision(
    request: ProviderProvisionRequest,
    options?: { signal?: AbortSignal },
  ): AsyncIterable<ProviderProvisionEvent>;
  reconnect(request: ProviderReconnectRequest): Promise<EnvironmentHandle>;
  destroy(
    request: ProviderDestroyRequest,
    options?: { signal?: AbortSignal },
  ): Promise<void>;
  summarize(handle: EnvironmentHandle): Promise<EnvironmentSummary>;
}

export interface EnvironmentProviderDescriptor {
  id: EnvironmentProviderId;
  label: string;
  ownsRoot: boolean;
  optionsSchema: JsonSchema;
}

export type EnvironmentHandle =
  | {
      kind: "local-path";
      root: string;
      writeRoots: readonly string[];
      persisted: JsonObject;
    }
  | {
      kind: "custom";
      writeRoots: readonly string[];
      persisted: JsonObject;
    };

export interface ProviderProvisionRequest<Options extends JsonObject = JsonObject> {
  environmentId: EnvironmentId;
  projectId: ProjectId;
  hostId: HostId;
  source: ProjectSource | null;
  options: Options;
}

export interface ProviderReconnectRequest {
  environmentId: EnvironmentId;
  hostId: HostId;
  handle: EnvironmentHandle;
}

export interface ProviderDestroyRequest extends ProviderReconnectRequest {}

export type ProviderProvisionEvent =
  | { kind: "progress"; step: string; text: string; status: "started" | "completed" | "failed" }
  | { kind: "output"; line: string }
  | { kind: "done"; handle: EnvironmentHandle };

The public provider service stays on the server. Its server half calls a private typed host role for all host work. The provider must reject a handle that does not match its environment and owned root.

Exports

Module imports select exact first-party code. Service calls follow the current winner.

Module Export Use
@bb/workspace/contracts workspaceService, environmentProvider, all service records Declares server edges and shared schemas.
@bb/workspace/app useWorkspace, useWorkspaceContext, useWorkspaceNavigate Reads app state and starts route actions.
@bb/workspace/routing parseWorkspaceRoute, workspaceHref Uses pure route functions without React.
@bb/workspace/host defineEnvironmentProvider, provisionWorkspace, personalWorkspaceRoot, validatePersonalWorkspacePath Implements a provider host role.

The app contracts replace the broad old BbContext and BbNavigate names.

export interface WorkspaceNavigation {
  toThread(threadId: ThreadId): void;
  toProject(projectId: ProjectId): void;
  toEnvironment(environmentId: EnvironmentId, subPath?: string): void;
  toPluginPanel(path: string, options?: { subPath?: string; replace?: boolean }): void;
  toCompose(options?: { projectId?: ProjectId; initialPrompt?: string; focusPrompt?: boolean }): void;
  openThreadPanel(options: ThreadPanelTarget): boolean;
  openUrl(url: string): boolean;
  openFilePreview(options: WorkspaceFileOpenOptions): boolean;
  openFileExternally(options: WorkspaceFileOpenOptions): boolean;
}

export function useWorkspaceContext(): WorkspaceContext;
export function useWorkspaceNavigate(): WorkspaceNavigation;

export interface WorkspaceAppClient {
  readonly projects: WorkspaceProjectClient;
  readonly environments: WorkspaceEnvironmentClient;
  readonly navigation: WorkspaceNavigation;
}

export function useWorkspace(): WorkspaceAppClient;

The app client follows the current bb.workspace service winner. It keeps request state and cache details outside the public contract.

Host roles

Each bb.workspace.envProvider claimant declares one private role. The role ID is bb.workspace.envProvider.host, and its name supplies the public key suffix.

export interface EnvironmentProvider<Options extends JsonObject> {
  name: string;
  label: string;
  ownsRoot: boolean;
  options: StandardSchemaV1<Options>;
  provision(request: HostProvisionRequest<Options>, context: EnvironmentProviderContext): Promise<EnvironmentHandle>;
  reconnect(request: HostReconnectRequest, context: EnvironmentProviderContext): Promise<EnvironmentHandle>;
  destroy(request: HostDestroyRequest, context: EnvironmentProviderContext): Promise<void>;
  summarize(handle: EnvironmentHandle, context: EnvironmentProviderContext): Promise<EnvironmentSummary>;
}

export interface EnvironmentProviderContext {
  signal: AbortSignal;
  lifecycle: { signal: AbortSignal };
  paths: { dataDir: string; tempDir: string };
  progress: EnvironmentProgressSink;
  workspace: EnvironmentWorkspaceFactory;
}

export interface EnvironmentProgressSink {
  step(key: string, text: string, status: "started" | "completed" | "failed"): void;
  output(line: string): void;
}

The host package supplies a contained workspace helper. It absorbs the old host workspace object and keeps filesystem paths private to the host.

export interface EnvironmentWorkspaceFactory {
  provision(options: ProvisionWorkspaceInput): Promise<EnvironmentWorkspace>;
  personalRoot(dataDir: string): string;
  validatePersonalPath(input: PersonalPathInput): string;
}

export interface EnvironmentWorkspace {
  readonly path: string;
  readonly managed: boolean;
  readonly isGitRepo: boolean;
  readonly isWorktree: boolean;

  getDefaultBranch(): Promise<string | null>;
  getCurrentBranch(): Promise<string | null>;
  getHeadSha(): Promise<string | null>;
  getLocalStateFingerprint(): Promise<string>;
  getSharedGitRefsFingerprint(): Promise<string>;
  getAdditionalWorkspaceWriteRoots(): Promise<readonly string[]>;
  getStatus(options?: WorkspaceStatusOptions): Promise<EnvironmentStatus>;
  getDiff(options?: WorkspaceDiffOptions): Promise<EnvironmentDiff>;
  diffFiles(options: WorkspaceDiffFilesInput): Promise<EnvironmentDiffFilePage>;
  diffPatch(options: WorkspaceDiffPatchInput): Promise<EnvironmentPatchResult>;
  getPullRequest(): Promise<EnvironmentPullRequest | null>;
  listFiles(): Promise<readonly WorkspacePath[]>;

  commit(options: WorkspaceCommitOptions): Promise<CommitResult>;
  reset(): Promise<void>;
  squashMerge(options: WorkspaceSquashMergeOptions): Promise<SquashMergeResult>;
  runPullRequestAction(action: PullRequestAction): Promise<EnvironmentPullRequest>;
  destroy(): Promise<void>;
}

export type ProvisionWorkspaceInput =
  | { mode: "unmanaged"; path: string }
  | { mode: "managed-worktree"; sourcePath: string; environmentId: EnvironmentId; branch?: string }
  | { mode: "personal"; environmentId: EnvironmentId; targetPath?: string }
  | { mode: "reconnect-managed-worktree"; environmentId: EnvironmentId; path: string };

export interface WorkspaceStatusOptions {
  mergeBaseBranch?: string;
  maxUntrackedLineStatFiles?: number;
  maxUntrackedLineStatBytes?: number;
}

export interface WorkspaceDiffOptions {
  target?: WorkspaceDiffTarget;
  maxDiffBytes?: number;
  maxFileListBytes?: number;
  maxUntrackedFiles?: number;
}

export interface WorkspaceDiffFilesInput {
  target: WorkspaceDiffTarget;
  maxFiles: number;
}

export interface WorkspaceDiffPatchInput {
  target: WorkspaceDiffTarget;
  paths: readonly string[];
  maxBytesPerFile: number;
}

export interface WorkspaceCommitOptions {
  message: string;
  noVerify: boolean;
}

export interface WorkspaceSquashMergeOptions {
  targetBranch: string;
  commitMessage: string;
}

export type PullRequestAction = "mark-draft" | "mark-ready" | "merge";

The server half owns the public provider token. Consumers never call the private host role. The kernel inspector joins both records under one provider key.

Example

This plugin adds a scratch environment provider. It consumes the workspace service and adds no UI claim.

{
  "id": "acme.scratch-workspace",
  "version": "2.0.0",
  "claims": [
    {
      "service": "bb.workspace.envProvider",
      "key": "acme.scratch-workspace/scratch",
      "version": "1.0.0"
    }
  ],
  "requires": [{ "service": "bb.workspace", "range": "^1" }],
  "artifacts": {
    "server": "./dist/server.js",
    "host": "./dist/host.js"
  }
}
// server.ts
import { defineServerPlugin } from "@get-bb/plugin/server";
import { environmentProvider, workspaceService } from "@bb/workspace/contracts";

export default defineServerPlugin(async (api) => {
  const workspace = await api.services.use(workspaceService);
  const host = api.hostRoles.client(scratchHostRole);

  api.services.provide(environmentProvider.key("acme.scratch-workspace/scratch"), {
    describe: () => host.call("describe", {}),
    provision: (request, options) => host.stream("provision", request, options),
    reconnect: (request) => host.call("reconnect", request),
    destroy: (request, options) => host.call("destroy", request, options),
    summarize: (handle) => host.call("summarize", { handle }),
  });

  return workspace.environments.watch({}, (event) => {
    api.log.debug(`environment ${event.environmentId} changed`);
  });
});

The host artifact uses defineEnvironmentProvider. It returns a contained local-path handle.

The bb.workspace-ui plugin

bb.workspace-ui is the default presentation for the workspace domain. It requires the bb.workspace service through a required edge.

The plugin renders project and environment data. It does not own, copy, or wrap the data service.

Surfaces

ID kind replaceable props contract sketch notes
bb.workspace-ui.projectSwitcher single yes ProjectSwitcherProps Shows the current project and starts project selection. The winner receives Original.
bb.workspace-ui.environmentPage keyed yes EnvironmentPageProps Uses a page ID as the key. First-party keys are overview, changes, and pull-request.

These surfaces exist only while bb.workspace-ui declares them. A different presentation can omit either surface.

bb.workspace-ui.projectSwitcher

The shell supplies route state and callbacks. The surface does not own project data.

export interface ProjectSwitcherProps {
  activeProjectId: ProjectId | null;
  presentation: "compact" | "full";
  disabled?: boolean;
  onSelect(projectId: ProjectId): void;
  onCreate(): void;
}

export type ProjectSwitcherSurface = SingleSurface<ProjectSwitcherProps>;

api.surfaces.provide(
  "bb.workspace-ui.projectSwitcher",
  ({ Original, props }) => <Original {...props} presentation="compact" />,
);

A winner calls onSelect after a user choice. The router then changes the route.

bb.workspace-ui.environmentPage

The page key selects one environment view. Each key has one global winner.

export type EnvironmentPageId = "overview" | "changes" | "pull-request";

export interface EnvironmentPageProps {
  page: EnvironmentPageId;
  environmentId: EnvironmentId;
  projectId: ProjectId;
  threadId: ThreadId | null;
  compact: boolean;
  navigate: WorkspaceNavigation;
}

export type EnvironmentPageSurface = KeyedSurface<
  EnvironmentPageId,
  EnvironmentPageProps
>;

api.surfaces.provide("bb.workspace-ui.environmentPage", {
  key: "changes",
  component: ChangesPage,
});

A keyed winner does not receive Original. A failed winner returns to the default claimant for that key.

Required service edge

The manifest declares a required edge on bb.workspace. The loader activates bb.workspace-ui only after the data service becomes ready.

{
  "id": "bb.workspace-ui",
  "requires": [{ "service": "bb.workspace", "range": "^1" }]
}

A workspace service winner change restarts bb.workspace-ui with a fresh service handle.

Services

ID kind replaceable notes
This presentation plugin provides no service.

Exports

Module Export Use
@bb/workspace-ui/components ProjectSwitcher, EnvironmentOverviewPage, EnvironmentChangesPage, EnvironmentPullRequestPage Imports exact first-party presentation components.

Surface claims follow the current winner. These module imports always select the first-party implementation.

Host roles

bb.workspace-ui declares no host role. It gets all workspace data through its required service edge.

Example

This app plugin replaces only the project switcher. It requires bb.workspace for project data and route actions.

{
  "id": "acme.compact-workspace-ui",
  "version": "2.0.0",
  "claims": [{ "surface": "bb.workspace-ui.projectSwitcher" }],
  "requires": [{ "service": "bb.workspace", "range": "^1" }],
  "artifacts": { "app": "./dist/app.js" }
}
// app.tsx
import { definePlugin } from "@get-bb/plugin/app";
import { useWorkspace, useWorkspaceNavigate } from "@bb/workspace/app";

function CompactSwitcher() {
  const workspace = useWorkspace();
  const navigate = useWorkspaceNavigate();
  const projects = workspace.projects.useList();

  return (
    <select
      value={workspace.projects.activeId ?? ""}
      onChange={(event) => navigate.toProject(event.target.value as ProjectId)}
    >
      {projects.data.map((project) => (
        <option key={project.id} value={project.id}>{project.name}</option>
      ))}
    </select>
  );
}

export default definePlugin((api) => {
  api.surfaces.provide("bb.workspace-ui.projectSwitcher", () => (
    <CompactSwitcher />
  ));
});

Covers

old item ID new contract/verb note
server.sdk.environments bb.workspace service: environments Replaces the broad SDK area.
server.sdk.environments.archiveThreads bb.workspace service: environments.archiveThreads() The implementation uses its required bb.threads edge.
server.sdk.environments.commit bb.workspace service: environments.commit() Keeps an explicit commit input.
server.sdk.environments.diff bb.workspace service: environments.diff() Returns a bounded diff record.
server.sdk.environments.diffBranches bb.workspace service: environments.diffBranches() Lists valid comparison branches.
server.sdk.environments.diffFile bb.workspace service: environments.diffFile() Returns one changed file.
server.sdk.environments.diffFiles bb.workspace service: environments.diffFiles() Returns a bounded file page.
server.sdk.environments.diffPatch bb.workspace service: environments.diffPatch() Returns bounded patches for selected paths.
server.sdk.environments.get bb.workspace service: environments.get() Reads one environment.
server.sdk.environments.pullRequest bb.workspace service: environments.pullRequest() Returns pull request state or null.
server.sdk.environments.markPullRequestDraft bb.workspace service: environments.markPullRequestDraft() Uses a typed environment ID.
server.sdk.environments.markPullRequestReady bb.workspace service: environments.markPullRequestReady() Uses a typed environment ID.
server.sdk.environments.mergePullRequest bb.workspace service: environments.mergePullRequest() Keeps merge options in one input record.
server.sdk.environments.paths bb.workspace service: environments.paths() Lists roots for one environment.
server.sdk.environments.squashMerge bb.workspace service: environments.squashMerge() Keeps target and message options.
server.sdk.environments.status bb.workspace service: environments.status() Returns workspace and Git status.
server.sdk.environments.update bb.workspace service: environments.update() Updates environment metadata.
server.sdk.projects bb.workspace service: projects Replaces the broad SDK area.
server.sdk.projects.attachments bb.workspace service: projects.attachments Keeps attachment operations under projects.
server.sdk.projects.attachments.copy bb.workspace service: projects.attachments.copy() Copies one attachment.
server.sdk.projects.attachments.read bb.workspace service: projects.attachments.read() Reads one attachment.
server.sdk.projects.attachments.upload bb.workspace service: projects.attachments.upload() Uploads one attachment.
server.sdk.projects.branches bb.workspace service: projects.branches() Lists source branches.
server.sdk.projects.commands bb.workspace service: projects.commands() Lists project commands.
server.sdk.projects.create bb.workspace service: projects.create() Creates one project.
server.sdk.projects.defaultExecutionOptions bb.workspace service: projects.defaultExecutionOptions() Reads project execution defaults.
server.sdk.projects.delete bb.workspace service: projects.delete() Deletes one project.
server.sdk.projects.fileContent bb.workspace service: projects.fileContent() Reads source content through workspace containment.
server.sdk.projects.files bb.workspace service: projects.files() Lists source files through workspace containment.
server.sdk.projects.get bb.workspace service: projects.get() Reads one project.
server.sdk.projects.list bb.workspace service: projects.list() Returns a bounded project page.
server.sdk.projects.paths bb.workspace service: projects.paths() Lists project source roots.
server.sdk.projects.promptHistory bb.workspace service: projects.promptHistory() Reads bounded prompt history.
server.sdk.projects.reorder bb.workspace service: projects.reorder() Applies one explicit project order.
server.sdk.projects.sidebarBootstrap bb.workspace service: projects.catalogSnapshot() Replaces sidebar-specific output with a presentation-neutral project catalog.
server.sdk.projects.sources bb.workspace service: projects.sources Keeps source operations under projects.
server.sdk.projects.sources.add bb.workspace service: projects.sources.add() Adds one source.
server.sdk.projects.sources.delete bb.workspace service: projects.sources.delete() Deletes one source.
server.sdk.projects.sources.update bb.workspace service: projects.sources.update() Updates one source.
server.sdk.projects.update bb.workspace service: projects.update() Updates project metadata.
server.sdk.subscribe.projectChanged bb.workspace service: projects.watch() Replaces the broad realtime subscription filter.
server.sdk.subscribe.environmentChanged bb.workspace service: environments.watch() Replaces the broad realtime subscription filter.
app.hooks.useBbContext @bb/workspace/app: useWorkspaceContext() Adds environment and typed route state.
app.hooks.useBbNavigate @bb/workspace/app: useWorkspaceNavigate() Returns typed workspace route actions.
app.contracts.BbContext @bb/workspace/contracts: WorkspaceContext Replaces the project-and-thread-only route record.
app.contracts.BbNavigate @bb/workspace/contracts: WorkspaceNavigation Replaces the broad navigation type.
app.contracts.BbNavigate.toThread WorkspaceNavigation.toThread() Keeps thread route navigation.
app.contracts.BbNavigate.toProject WorkspaceNavigation.toProject() Keeps project route navigation.
app.contracts.BbNavigate.toPluginPanel WorkspaceNavigation.toPluginPanel() Keeps plugin panel routes.
app.contracts.BbNavigate.toCompose WorkspaceNavigation.toCompose() Adds an optional typed project ID.
app.contracts.BbNavigate.openThreadPanel WorkspaceNavigation.openThreadPanel() Keeps thread panel routing.
app.contracts.BbNavigate.openUrl WorkspaceNavigation.openUrl() Keeps host-controlled URL opening.
host.workspace @bb/workspace/host: EnvironmentWorkspace Keeps the host-only workspace object.
host.workspace.queries EnvironmentWorkspace query methods Keeps all status, diff, branch, fingerprint, pull request, and file queries.
host.workspace.mutations EnvironmentWorkspace mutation methods Keeps commit, reset, merge, pull request, and destroy operations.
host.workspace.provision EnvironmentWorkspaceFactory.provision() Creates or reconnects a contained workspace.
host.workspace.provisionArgs ProvisionWorkspaceInput Keeps all four provisioning modes.
host.workspace.personalRoot EnvironmentWorkspaceFactory.personalRoot() Returns the managed personal root.
host.workspace.validatePersonalPath EnvironmentWorkspaceFactory.validatePersonalPath() Validates a personal target path.
host.workspace.statusOptions WorkspaceStatusOptions Keeps status work limits.
host.workspace.diffOptions WorkspaceDiffOptions Keeps diff work limits.
host.workspace.diffFilesArgs WorkspaceDiffFilesInput Keeps file count limits.
host.workspace.diffPatchArgs WorkspaceDiffPatchInput Keeps path and byte limits.
host.workspace.commitOptions WorkspaceCommitOptions Keeps the message and hook choice.
host.workspace.squashMergeOptions WorkspaceSquashMergeOptions Keeps the target branch and commit message.
host.workspace.pullRequestAction PullRequestAction Keeps the typed pull request action.
app.contracts.PluginHomepageSectionProps bb.thread-ui.composer.actions: ComposerActionContext The default composer declares and renders this presentation surface.
app.contracts.PluginHomepageSectionProps.projectId ComposerActionContext.scope A new-thread scope carries the nullable project ID.
app.contracts.PluginHomepageSectionRegistration bb.thread-ui.composer.actions: ComposerActionContribution A stack contribution replaces the fixed homepage slot.
app.contracts.PluginHomepageSectionRegistration.component ComposerActionContribution.component The component receives the composer action context.
app.contracts.PluginHomepageSectionRegistration.id ComposerActionContribution.id The contribution keeps one stable ID.
app.contracts.PluginHomepageSectionRegistration.title ComposerActionContribution.component The component renders its own heading.
app.slots.homepageSection bb.thread-ui.composer.actions surface with placement: "stack" The default composer owns this presentation-only affordance.
server.sdk.hosts bb.workspace service: hosts The named workspace service owns enrolled host operations.
server.sdk.hosts.cloneDefaultPath bb.workspace service: hosts.cloneDefaultPath() The workspace owner keeps explicit host identity.
server.sdk.hosts.createJoinCode bb.workspace service: hosts.createJoinCode() The workspace owner keeps explicit host identity.
server.sdk.hosts.delete bb.workspace service: hosts.delete() The workspace owner keeps explicit host identity.
server.sdk.hosts.directory bb.workspace service: hosts.directory() The workspace owner keeps explicit host identity.
server.sdk.hosts.get bb.workspace service: hosts.get() The workspace owner keeps explicit host identity.
server.sdk.hosts.installProviderCli bb.workspace service: hosts.installProviderCli() The workspace owner keeps explicit host identity.
server.sdk.hosts.list bb.workspace service: hosts.list() The workspace owner keeps explicit host identity.
server.sdk.hosts.pathsExist bb.workspace service: hosts.pathsExist() The workspace owner keeps explicit host identity.
server.sdk.hosts.pickFolder bb.workspace service: hosts.pickFolder() The workspace owner keeps explicit host identity.
server.sdk.hosts.providerCliStatus bb.workspace service: hosts.providerCliStatus() The workspace owner keeps explicit host identity.
server.sdk.hosts.retryUpdate bb.workspace service: hosts.retryUpdate() The workspace owner keeps explicit host identity.
server.sdk.hosts.update bb.workspace service: hosts.update() The workspace owner keeps explicit host identity.
server.sdk.subscribe.hostChanged bb.workspace service: hosts.watch() The host watcher replaces the broad realtime selector.
server.sdk.subscribe.systemChanged bb.workspace service: watch() The workspace watcher reports host, environment, and project changes.
server.sdk.terminals bb.workspace service: terminals The named workspace service owns terminal sessions.
server.sdk.terminals.close bb.workspace service: terminals.close() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.create bb.workspace service: terminals.create() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.get bb.workspace service: terminals.get() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.input bb.workspace service: terminals.input() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.list bb.workspace service: terminals.list() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.output bb.workspace service: terminals.output() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.rename bb.workspace service: terminals.rename() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.resize bb.workspace service: terminals.resize() The workspace owner keeps terminal identity and scope.
server.sdk.terminals.restart bb.workspace service: terminals.restart() The workspace owner keeps terminal identity and scope.