@hachej/boring-workspace 0.1.35 → 0.1.37

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.
@@ -1,10 +1,11 @@
1
1
  import { PiPackageSource, PiExtensionFactory, CreateAgentAppOptions, ProvisionWorkspaceRuntimeOptions } from '@hachej/boring-agent/server';
2
2
  export { PiPackageSource as WorkspacePiPackageSource } from '@hachej/boring-agent/server';
3
3
  import { FastifyInstance } from 'fastify';
4
- import { W as WorkspaceServerPlugin, S as ServerBootstrapOptions, B as BoringPluginSourceInput, a as BoringPluginFrontTargetResolver, b as WorkspaceProvisioningContribution, c as WorkspaceRouteContribution, d as createInMemoryBridge } from './createInMemoryBridge-zb8MpO60.js';
5
- export { e as ServerWorkspaceRuntimeProvisioningInput } from './createInMemoryBridge-zb8MpO60.js';
4
+ import { W as WorkspaceServerPlugin, S as ServerBootstrapOptions, B as BoringPluginSourceInput, a as BoringPluginFrontTargetResolver, b as WorkspaceProvisioningContribution, c as WorkspaceRouteContribution, d as createInMemoryBridge } from './createInMemoryBridge-DSjZ9efK.js';
5
+ export { e as ServerWorkspaceRuntimeProvisioningInput } from './createInMemoryBridge-DSjZ9efK.js';
6
6
  import './manifest-C2vVgH_e.js';
7
- import './ui-bridge-DFNem0df.js';
7
+ import './agent-tool-CB0RQyx9.js';
8
+ import './ui-bridge-LeBuZqfA.js';
8
9
 
9
10
  /**
10
11
  * Directory-source entry: `{ dir, options?, hotReload? }`. Resolved via
@@ -89,14 +90,6 @@ interface CreateWorkspaceAgentServerOptions extends WorkspaceAgentCreateOptions,
89
90
  force?: boolean;
90
91
  };
91
92
  validateUiPaths?: boolean;
92
- /**
93
- * Whether the canonical agent reload refreshes discovered package plugins.
94
- * When true, chat `/reload` refreshes package front/Pi metadata and reports
95
- * static-server restart warnings; dir-source server entries are re-imported
96
- * for diagnostics only. When false, initial static discovery still runs, but
97
- * dynamic Pi/system-prompt refresh is disabled. Defaults to true.
98
- */
99
- pluginHotReload?: boolean;
100
93
  /**
101
94
  * App-default plugin packages (by npm name OR absolute filesystem path).
102
95
  * Each entry is resolved at boot, registered as a Pi package (so Pi sees
@@ -112,28 +105,12 @@ interface CreateWorkspaceAgentServerOptions extends WorkspaceAgentCreateOptions,
112
105
  */
113
106
  defaultPluginPackages?: string[];
114
107
  /**
115
- * Absolute path to the app's `package.json`. When passed, the workspace
116
- * reads `package.json#boring.defaultPluginPackages: string[]` from it
117
- * and merges those entries with anything passed in
118
- * `defaultPluginPackages`. Relative entries in package.json resolve
119
- * against the package.json's own directory.
120
- *
121
- * Example app `package.json`:
122
- *
123
- * {
124
- * "name": "my-app",
125
- * "boring": {
126
- * "defaultPluginPackages": [
127
- * "@hachej/boring-ask-user",
128
- * "./src/plugins/playgroundDataCatalog"
129
- * ]
130
- * }
131
- * }
132
- *
133
- * Lets apps declare their plugin set in the canonical app manifest
134
- * instead of inside the server boot path.
108
+ * The host app's package root. Anchors npm-name resolution of
109
+ * `defaultPluginPackages` at the app's own node_modules (in addition to a
110
+ * walk-up from `workspaceRoot`). Pass when the workspace root does not
111
+ * live under the app directory.
135
112
  */
136
- appPackageJsonPath?: string;
113
+ appRoot?: string;
137
114
  /** Additional plugin collection roots to scan alongside workspace .pi/extensions and package/plugin-derived roots. */
138
115
  additionalBoringPluginDirs?: BoringPluginSourceInput[];
139
116
  /**
@@ -151,8 +128,6 @@ interface CreateWorkspaceAgentServerOptions extends WorkspaceAgentCreateOptions,
151
128
  installPluginAuthoring?: boolean;
152
129
  /** Optional host-owned front-target override for boring plugin list/event payloads. */
153
130
  boringPluginFrontTargetResolver?: BoringPluginFrontTargetResolver;
154
- /** Preserve legacy `/@fs/...` frontUrl payloads alongside frontTarget. Defaults to true. */
155
- boringPluginIncludeLegacyFrontUrl?: boolean;
156
131
  }
157
132
  declare const PLUGIN_AUTHORING_PROVISIONING_IDS: Set<string>;
158
133
  declare function omitPluginAuthoringProvisioning(plugins: WorkspaceRuntimeProvisioningInput[]): WorkspaceRuntimeProvisioningInput[];
@@ -190,15 +165,27 @@ declare function createWorkspaceAgentServer(opts?: CreateWorkspaceAgentServerOpt
190
165
 
191
166
  interface ResolveDefaultWorkspacePluginPackagePathsOptions {
192
167
  workspaceRoot?: string;
168
+ /**
169
+ * Internal plugin packages, listed explicitly in host boot code — npm
170
+ * package names (resolved via require.resolve) or absolute directory
171
+ * paths. Mirrors the front side, where internal plugins are statically
172
+ * imported in the app: both sides declare the same explicit list.
173
+ */
193
174
  defaultPluginPackages?: string[];
194
- appPackageJsonPath?: string;
175
+ /**
176
+ * Directory whose node_modules anchors npm-name resolution (the host
177
+ * package's own root). Defaults to walking up from `workspaceRoot`,
178
+ * then from this module's location.
179
+ */
180
+ anchorDir?: string;
195
181
  }
196
182
  /**
197
- * Resolve app-default plugin package declarations exactly once for app hosts.
198
- * This is shared by standalone workspace-agent and core composition so both
199
- * read `package.json#boring.defaultPluginPackages` with the same relative-path
200
- * and package-name semantics.
183
+ * Resolve each entry in `defaultPluginPackages` to an absolute package
184
+ * directory. Accepts either an npm-style name (resolved via
185
+ * `require.resolve('<name>/package.json')`) or an absolute filesystem
186
+ * path. THROWS on unresolved entries — a typo or missing dependency
187
+ * is an app boot-time error, not something to silently drop.
201
188
  */
202
- declare function resolveDefaultWorkspacePluginPackagePaths({ workspaceRoot, defaultPluginPackages, appPackageJsonPath, }?: ResolveDefaultWorkspacePluginPackagePathsOptions): string[];
189
+ declare function resolveDefaultWorkspacePluginPackagePaths({ workspaceRoot, defaultPluginPackages, anchorDir, }?: ResolveDefaultWorkspacePluginPackagePathsOptions): string[];
203
190
 
204
191
  export { type CollectWorkspaceAgentServerPluginsOptions, type CreateWorkspaceAgentServerOptions, type DirPluginEntry, PLUGIN_AUTHORING_PROVISIONING_IDS, type PluginResolveContext, type ResolveDefaultWorkspacePluginPackagePathsOptions, type WorkspaceAgentPiOptions, type WorkspaceAgentServerPluginCollection, type WorkspaceAgentServerPluginContext, type WorkspacePluginEntry, type WorkspacePluginPackagePiSnapshot, WorkspaceProvisioningContribution, WorkspaceRouteContribution, type WorkspaceRuntimeProvisioningInput, buildWorkspaceContextPrompt, collectWorkspaceAgentServerPlugins, createWorkspaceAgentServer, hasDirServerPlugin, omitPluginAuthoringProvisioning, provisionWorkspaceAgentServer, readWorkspacePluginPackagePiSnapshot, readWorkspacePluginPackageRuntimePlugins, resolveDefaultWorkspacePluginPackagePaths, resolveOnePluginEntry };
@@ -8,7 +8,7 @@ import {
8
8
  resolveMode,
9
9
  VERCEL_SANDBOX_WORKSPACE_ROOT
10
10
  } from "@hachej/boring-agent/server";
11
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
11
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
12
12
  import { dirname as dirname7, isAbsolute as isAbsolute5, join as join7, resolve as resolve7 } from "path";
13
13
  import { homedir } from "os";
14
14
  import { createRequire as createRequire4 } from "module";
@@ -987,7 +987,6 @@ var BoringPluginAssetManager = class {
987
987
  pluginDirs;
988
988
  errorRoot;
989
989
  frontTargetResolver;
990
- includeLegacyFrontUrl;
991
990
  loaded = /* @__PURE__ */ new Map();
992
991
  revisions = /* @__PURE__ */ new Map();
993
992
  listeners = /* @__PURE__ */ new Set();
@@ -998,7 +997,16 @@ var BoringPluginAssetManager = class {
998
997
  this.pluginDirs = options.pluginDirs;
999
998
  this.errorRoot = options.errorRoot ?? join4(process.cwd(), ".pi", "extensions");
1000
999
  this.frontTargetResolver = options.frontTargetResolver;
1001
- this.includeLegacyFrontUrl = options.includeLegacyFrontUrl ?? true;
1000
+ }
1001
+ /**
1002
+ * Replace the scanned source roots. Lets hosts re-resolve discovery inputs
1003
+ * (e.g. workspace `.pi/settings.json` package sources, which can gain
1004
+ * entries after `boring-ui-plugin install`) before the next load() so
1005
+ * `/reload` picks up newly installed plugin sources without a process
1006
+ * restart.
1007
+ */
1008
+ setPluginDirs(pluginDirs) {
1009
+ this.pluginDirs = pluginDirs;
1002
1010
  }
1003
1011
  preflight() {
1004
1012
  return preflightBoringPlugins(this.pluginDirs);
@@ -1006,6 +1014,17 @@ var BoringPluginAssetManager = class {
1006
1014
  list() {
1007
1015
  return [...this.loaded.values()].map((plugin) => this.toListEntry(plugin));
1008
1016
  }
1017
+ /**
1018
+ * Plugins whose front lifecycle the SSE channel owns. Internal plugins are
1019
+ * app code — their front is statically bundled by the host app and must
1020
+ * never be re-imported through the hot-reload pipeline (a second module
1021
+ * instance would carry a fresh React context identity, breaking
1022
+ * provider ↔ panel lookups). They are loaded server-side (routes, agent
1023
+ * tools, Pi snapshot) but excluded from SSE replay and live events.
1024
+ */
1025
+ listExternal() {
1026
+ return [...this.loaded.values()].filter((plugin) => plugin.source.kind === "external").map((plugin) => this.toListEntry(plugin));
1027
+ }
1009
1028
  getError(pluginId) {
1010
1029
  const path = this.errorPath(pluginId);
1011
1030
  if (!path || !existsSync4(path)) return null;
@@ -1082,9 +1101,7 @@ ${prompts.join("\n\n")}` } : {}
1082
1101
  } catch {
1083
1102
  }
1084
1103
  }
1085
- const event = { type: "boring.plugin.unload", id, revision };
1086
- events.push(event);
1087
- this.emit(event);
1104
+ this.record(events, { type: "boring.plugin.unload", id, revision }, previous?.source);
1088
1105
  }
1089
1106
  for (const plugin of nextPlugins) {
1090
1107
  try {
@@ -1115,22 +1132,18 @@ ${prompts.join("\n\n")}` } : {}
1115
1132
  boring: plugin.boring,
1116
1133
  version: plugin.version,
1117
1134
  revision,
1118
- ...this.frontUrlPayload(plugin.frontUrl),
1119
1135
  ...frontTarget ? { frontTarget } : {},
1120
1136
  ...requiresRestart.length > 0 ? { requiresRestart } : {}
1121
1137
  };
1122
- events.push(event);
1123
- this.emit(event);
1138
+ this.record(events, event, plugin.source);
1124
1139
  } catch (error) {
1125
1140
  const revision = this.bumpRevision(plugin.id);
1126
1141
  const message = error instanceof Error ? error.stack ?? error.message : String(error);
1127
1142
  this.writeError(plugin.id, message);
1128
- const event = { type: "boring.plugin.error", id: plugin.id, revision, message };
1129
1143
  const loadError = { id: plugin.id, revision, message };
1130
1144
  this.lastErrors.set(plugin.id, loadError);
1131
1145
  errors.push(loadError);
1132
- events.push(event);
1133
- this.emit(event);
1146
+ this.record(events, { type: "boring.plugin.error", id: plugin.id, revision, message }, plugin.source);
1134
1147
  }
1135
1148
  }
1136
1149
  return { loaded: this.list(), events, errors };
@@ -1163,16 +1176,14 @@ Plugin dir: ${error.pluginDir}`;
1163
1176
  ...plugin.pi ? { pi: plugin.pi } : {},
1164
1177
  version: plugin.version,
1165
1178
  revision: plugin.revision,
1166
- ...this.frontUrlPayload(plugin.frontUrl),
1167
1179
  ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
1168
1180
  };
1169
1181
  }
1170
- frontUrlPayload(frontUrl) {
1171
- if (!this.includeLegacyFrontUrl || !frontUrl) return {};
1172
- return { frontUrl };
1173
- }
1174
1182
  resolveFrontTarget(plugin, revision) {
1175
- if (!plugin.frontPath || !this.frontTargetResolver) return void 0;
1183
+ if (!plugin.frontPath) return void 0;
1184
+ if (!this.frontTargetResolver) {
1185
+ return plugin.frontUrl ? { kind: "module-url", entryUrl: plugin.frontUrl, revision } : void 0;
1186
+ }
1176
1187
  const frontEntrySubpath = typeof plugin.boring.front === "string" ? plugin.boring.front.replace(/^\.\//, "") : normalizePluginSubpath(plugin.rootDir, plugin.frontPath);
1177
1188
  const frontTarget = this.frontTargetResolver(plugin, {
1178
1189
  revision,
@@ -1181,6 +1192,16 @@ Plugin dir: ${error.pluginDir}`;
1181
1192
  if (!frontTarget) return void 0;
1182
1193
  return { ...frontTarget, revision };
1183
1194
  }
1195
+ /**
1196
+ * Append to the load result's events array and emit on the SSE channel —
1197
+ * unless the source is internal. Internal plugins are app code: their
1198
+ * events stay in the load result (for /reload diagnostics and restart
1199
+ * warnings) but never reach SSE subscribers (see listExternal).
1200
+ */
1201
+ record(events, event, source) {
1202
+ events.push(event);
1203
+ if (source?.kind !== "internal") this.emit(event);
1204
+ }
1184
1205
  emit(event) {
1185
1206
  for (const listener of [...this.listeners]) {
1186
1207
  try {
@@ -1265,14 +1286,13 @@ async function boringPluginRoutes(app, opts) {
1265
1286
  }
1266
1287
  write(event.type, payload);
1267
1288
  });
1268
- for (const plugin of manager.list()) {
1289
+ for (const plugin of manager.listExternal()) {
1269
1290
  write("boring.plugin.load", {
1270
1291
  type: "boring.plugin.load",
1271
1292
  id: plugin.id,
1272
1293
  boring: plugin.boring,
1273
1294
  version: plugin.version,
1274
1295
  revision: plugin.revision,
1275
- ...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {},
1276
1296
  ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
1277
1297
  replay: true
1278
1298
  });
@@ -1872,31 +1892,18 @@ async function rebuildServerPlugins(opts) {
1872
1892
  }
1873
1893
 
1874
1894
  // src/app/server/defaultPluginPackages.ts
1875
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1895
+ import { existsSync as existsSync6 } from "fs";
1876
1896
  import { createRequire as createRequire3 } from "module";
1877
1897
  import { dirname as dirname6, isAbsolute as isAbsolute3, join as join6 } from "path";
1878
- function readAppManifestDefaultPlugins(appPackageJsonPath) {
1879
- if (!appPackageJsonPath || !existsSync6(appPackageJsonPath)) return [];
1880
- let pkg;
1881
- try {
1882
- pkg = JSON.parse(readFileSync5(appPackageJsonPath, "utf8"));
1883
- } catch {
1884
- return [];
1885
- }
1886
- const entries = pkg.boring?.defaultPluginPackages;
1887
- if (!Array.isArray(entries)) return [];
1888
- const pkgDir = dirname6(appPackageJsonPath);
1889
- return entries.filter((e) => typeof e === "string").map((entry) => {
1890
- if (entry.startsWith("./") || entry.startsWith("../")) {
1891
- return join6(pkgDir, entry);
1892
- }
1893
- return entry;
1894
- });
1895
- }
1896
- function resolveDefaultPluginPackagePaths(workspaceRoot, defaultPluginPackages) {
1898
+ function resolveDefaultWorkspacePluginPackagePaths({
1899
+ workspaceRoot = process.cwd(),
1900
+ defaultPluginPackages = [],
1901
+ anchorDir
1902
+ } = {}) {
1897
1903
  if (defaultPluginPackages.length === 0) return [];
1898
- const require5 = createRequire3(join6(workspaceRoot, "package.json"));
1899
- const requireFromHere = createRequire3(import.meta.url);
1904
+ const requireFromAnchor = anchorDir ? createRequire3(join6(anchorDir, "package.json")) : null;
1905
+ const requireFromWorkspace = createRequire3(join6(workspaceRoot, "package.json"));
1906
+ const resolvers = [requireFromAnchor, requireFromWorkspace].filter((req) => req !== null);
1900
1907
  const resolved = [];
1901
1908
  for (const entry of defaultPluginPackages) {
1902
1909
  if (isAbsolute3(entry)) {
@@ -1909,32 +1916,22 @@ function resolveDefaultPluginPackagePaths(workspaceRoot, defaultPluginPackages)
1909
1916
  continue;
1910
1917
  }
1911
1918
  let resolvedPath = null;
1912
- try {
1913
- resolvedPath = dirname6(require5.resolve(`${entry}/package.json`));
1914
- } catch {
1919
+ for (const req of resolvers) {
1915
1920
  try {
1916
- resolvedPath = dirname6(requireFromHere.resolve(`${entry}/package.json`));
1921
+ resolvedPath = dirname6(req.resolve(`${entry}/package.json`));
1922
+ break;
1917
1923
  } catch {
1918
- throw new Error(
1919
- `defaultPluginPackages: cannot resolve "${entry}" \u2014 install it as a dep of the app (or workspace root) so require.resolve can find its package.json. Pass an absolute path instead if the package lives outside node_modules.`
1920
- );
1921
1924
  }
1922
1925
  }
1926
+ if (!resolvedPath) {
1927
+ throw new Error(
1928
+ `defaultPluginPackages: cannot resolve "${entry}" \u2014 install it as a dep of the app so require.resolve can find its package.json. Pass an absolute path instead if the package lives outside node_modules.`
1929
+ );
1930
+ }
1923
1931
  resolved.push(resolvedPath);
1924
1932
  }
1925
1933
  return resolved;
1926
1934
  }
1927
- function resolveDefaultWorkspacePluginPackagePaths({
1928
- workspaceRoot = process.cwd(),
1929
- defaultPluginPackages = [],
1930
- appPackageJsonPath
1931
- } = {}) {
1932
- const manifestPluginPackages = readAppManifestDefaultPlugins(appPackageJsonPath);
1933
- return resolveDefaultPluginPackagePaths(workspaceRoot, [
1934
- ...manifestPluginPackages,
1935
- ...defaultPluginPackages
1936
- ]);
1937
- }
1938
1935
 
1939
1936
  // src/server/bridge/createInMemoryBridge.ts
1940
1937
  var MAX_PENDING_COMMANDS = 1e3;
@@ -1979,6 +1976,20 @@ function createInMemoryBridge() {
1979
1976
  };
1980
1977
  }
1981
1978
 
1979
+ // src/shared/plugins/uiBridgeRegistry.ts
1980
+ var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@hachej/boring-workspace:active-ui-bridge");
1981
+ function slot() {
1982
+ return globalThis;
1983
+ }
1984
+ function registerWorkspaceUiBridge(bridge) {
1985
+ slot()[REGISTRY_KEY] = bridge;
1986
+ return () => {
1987
+ if (slot()[REGISTRY_KEY] === bridge) {
1988
+ slot()[REGISTRY_KEY] = void 0;
1989
+ }
1990
+ };
1991
+ }
1992
+
1982
1993
  // src/server/ui-control/tools/uiTools.ts
1983
1994
  import { stat } from "fs/promises";
1984
1995
  import { resolve as resolve6, isAbsolute as isAbsolute4, relative as relative3, win32 } from "path";
@@ -2598,7 +2609,7 @@ function resolveWorkspacePackageRoot() {
2598
2609
  ];
2599
2610
  for (const candidate of candidates) {
2600
2611
  try {
2601
- const pkg = JSON.parse(readFileSync6(join7(candidate, "package.json"), "utf8"));
2612
+ const pkg = JSON.parse(readFileSync5(join7(candidate, "package.json"), "utf8"));
2602
2613
  if (pkg.name === "@hachej/boring-workspace") return candidate;
2603
2614
  } catch {
2604
2615
  }
@@ -2608,7 +2619,7 @@ function resolveWorkspacePackageRoot() {
2608
2619
  function readPackageVersion(packageRoot) {
2609
2620
  if (!packageRoot) return void 0;
2610
2621
  try {
2611
- const pkg = JSON.parse(readFileSync6(join7(packageRoot, "package.json"), "utf8"));
2622
+ const pkg = JSON.parse(readFileSync5(join7(packageRoot, "package.json"), "utf8"));
2612
2623
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : void 0;
2613
2624
  } catch {
2614
2625
  return void 0;
@@ -2625,7 +2636,7 @@ function resolveBoringPiPackageRoot() {
2625
2636
  ];
2626
2637
  for (const candidate of candidates) {
2627
2638
  try {
2628
- const pkg = JSON.parse(readFileSync6(join7(candidate, "package.json"), "utf8"));
2639
+ const pkg = JSON.parse(readFileSync5(join7(candidate, "package.json"), "utf8"));
2629
2640
  if (pkg.name === "@hachej/boring-pi") return candidate;
2630
2641
  } catch {
2631
2642
  }
@@ -2638,7 +2649,7 @@ function resolveBoringPiPackageRoot() {
2638
2649
  }
2639
2650
  function isUsableBoringUiPluginCliPackageRoot(candidate) {
2640
2651
  try {
2641
- const pkg = JSON.parse(readFileSync6(join7(candidate, "package.json"), "utf8"));
2652
+ const pkg = JSON.parse(readFileSync5(join7(candidate, "package.json"), "utf8"));
2642
2653
  return pkg.name === "@hachej/boring-ui-plugin-cli" && existsSync7(join7(candidate, "dist", "bin.js"));
2643
2654
  } catch {
2644
2655
  return false;
@@ -2776,7 +2787,7 @@ function resolveLocalPiPackageSource(settingsDir, source) {
2776
2787
  function readPiSettingsBoringPluginSources(settingsPath, workspaceId) {
2777
2788
  let raw;
2778
2789
  try {
2779
- raw = JSON.parse(readFileSync6(settingsPath, "utf8"));
2790
+ raw = JSON.parse(readFileSync5(settingsPath, "utf8"));
2780
2791
  } catch {
2781
2792
  return [];
2782
2793
  }
@@ -2885,6 +2896,7 @@ function readWorkspacePluginPackagePiSnapshot(pluginDirs) {
2885
2896
  async function createWorkspaceAgentServer(opts = {}) {
2886
2897
  const workspaceRoot = opts.workspaceRoot ?? process.cwd();
2887
2898
  const bridge = createInMemoryBridge();
2899
+ const unregisterUiBridge = registerWorkspaceUiBridge(bridge);
2888
2900
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
2889
2901
  const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
2890
2902
  const workspaceFsCapability = modeAdapter.workspaceFsCapability ?? "best-effort";
@@ -2895,11 +2907,10 @@ async function createWorkspaceAgentServer(opts = {}) {
2895
2907
  const ctx = { workspaceRoot, bridge };
2896
2908
  const defaultPluginPackagePaths = resolveDefaultWorkspacePluginPackagePaths({
2897
2909
  workspaceRoot,
2898
- appPackageJsonPath: opts.appPackageJsonPath,
2899
- defaultPluginPackages: opts.defaultPluginPackages
2910
+ defaultPluginPackages: opts.defaultPluginPackages,
2911
+ anchorDir: opts.appRoot
2900
2912
  });
2901
- const pluginHotReload = opts.pluginHotReload ?? true;
2902
- const defaultPluginDirEntries = defaultPluginPackagePaths.map((dir) => ({ dir, hotReload: pluginHotReload })).filter((entry) => hasDirServerPlugin(entry));
2913
+ const defaultPluginDirEntries = defaultPluginPackagePaths.map((dir) => ({ dir, hotReload: true })).filter((entry) => hasDirServerPlugin(entry));
2903
2914
  const allPluginEntries = [
2904
2915
  ...defaultPluginDirEntries,
2905
2916
  ...opts.plugins ?? []
@@ -2933,7 +2944,7 @@ async function createWorkspaceAgentServer(opts = {}) {
2933
2944
  return boringPluginDirs;
2934
2945
  };
2935
2946
  refreshBoringPluginDirs();
2936
- const staticPluginPackagePiSnapshot = pluginHotReload ? emptyPackageJsonPiSnapshot() : readWorkspacePluginPackagePiSnapshot(refreshBoringPluginDirs());
2947
+ const staticPluginPackagePiSnapshot = emptyPackageJsonPiSnapshot();
2937
2948
  const staticPiSkillPaths = [
2938
2949
  ...baseStaticPiSkillPaths,
2939
2950
  ...staticPluginPackagePiSnapshot.additionalSkillPaths
@@ -2946,12 +2957,11 @@ async function createWorkspaceAgentServer(opts = {}) {
2946
2957
  ...baseStaticPiExtensionPaths,
2947
2958
  ...staticPluginPackagePiSnapshot.extensionPaths
2948
2959
  ];
2949
- const getHotReloadablePiResources = pluginHotReload ? () => readWorkspacePluginPackagePiSnapshot(refreshBoringPluginDirs()) : void 0;
2960
+ const getHotReloadablePiResources = () => readWorkspacePluginPackagePiSnapshot(refreshBoringPluginDirs());
2950
2961
  const boringAssetManager = new BoringPluginAssetManager({
2951
2962
  pluginDirs: boringPluginDirs,
2952
2963
  errorRoot: join7(workspaceRoot, ".pi", "extensions"),
2953
- frontTargetResolver: opts.boringPluginFrontTargetResolver,
2954
- includeLegacyFrontUrl: opts.boringPluginIncludeLegacyFrontUrl
2964
+ frontTargetResolver: opts.boringPluginFrontTargetResolver
2955
2965
  });
2956
2966
  const runtimeBackendRegistry = new RuntimeBackendRegistry();
2957
2967
  const buildRuntimeProvisioningInputs = () => {
@@ -3019,19 +3029,17 @@ async function createWorkspaceAgentServer(opts = {}) {
3019
3029
  beforeReload: async () => {
3020
3030
  let restart_warnings = [];
3021
3031
  let diagnostics = [];
3022
- if (pluginHotReload) {
3023
- refreshBoringPluginDirs();
3024
- const scan = await boringAssetManager.load();
3025
- const backendReload = await runtimeBackendRegistry.reloadFromLoadedPlugins(boringAssetManager.inspectLoaded());
3026
- restart_warnings = collectRestartWarnings(scan.events);
3027
- const scanDiagnostics = scan.errors.map((error) => ({
3028
- source: `boring plugin asset scan (${error.id})`,
3029
- message: error.message,
3030
- pluginId: error.id
3031
- }));
3032
- const rebuild = await rebuildPlugins();
3033
- diagnostics = [...scanDiagnostics, ...backendReload.diagnostics, ...rebuild.diagnostics];
3034
- }
3032
+ refreshBoringPluginDirs();
3033
+ const scan = await boringAssetManager.load();
3034
+ const backendReload = await runtimeBackendRegistry.reloadFromLoadedPlugins(boringAssetManager.inspectLoaded());
3035
+ restart_warnings = collectRestartWarnings(scan.events);
3036
+ const scanDiagnostics = scan.errors.map((error) => ({
3037
+ source: `boring plugin asset scan (${error.id})`,
3038
+ message: error.message,
3039
+ pluginId: error.id
3040
+ }));
3041
+ const rebuild = await rebuildPlugins();
3042
+ diagnostics = [...scanDiagnostics, ...backendReload.diagnostics, ...rebuild.diagnostics];
3035
3043
  await runRuntimeProvisioning();
3036
3044
  const callerResult = await opts.beforeReload?.();
3037
3045
  const callerRestartWarnings = callerResult && typeof callerResult === "object" ? callerResult.restart_warnings ?? [] : [];
@@ -3054,7 +3062,7 @@ async function createWorkspaceAgentServer(opts = {}) {
3054
3062
  extensionFactories: pluginCollection.agentOptions.pi?.extensionFactories,
3055
3063
  getHotReloadableResources: getHotReloadablePiResources
3056
3064
  },
3057
- systemPromptDynamic: pluginHotReload ? () => aggregatePluginPrompts(boringAssetManager) : void 0
3065
+ systemPromptDynamic: () => aggregatePluginPrompts(boringAssetManager)
3058
3066
  });
3059
3067
  refreshBoringPluginDirs();
3060
3068
  await boringAssetManager.load();
@@ -3062,6 +3070,7 @@ async function createWorkspaceAgentServer(opts = {}) {
3062
3070
  if (typeof app.addHook === "function") {
3063
3071
  app.addHook("onClose", async () => {
3064
3072
  await runtimeBackendRegistry.close();
3073
+ unregisterUiBridge();
3065
3074
  });
3066
3075
  }
3067
3076
  await app.register(uiRoutes, { bridge, preserveStateKeys: pluginCollection.preservedUiStateKeys });
@@ -1 +1 @@
1
- .dv-shell{--dv-background-color: var(--background);--dv-paneview-header-border-color: var(--border);--dv-tabs-and-actions-container-font-size: .8125rem;--dv-tabs-and-actions-container-height: 52px;--dv-tab-close-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E");--dv-group-view-background-color: var(--background);--dv-tabs-and-actions-container-background-color: var(--background);--dv-activegroup-visiblepanel-tab-background-color: var(--background);--dv-activegroup-hiddenpanel-tab-background-color: var(--muted);--dv-inactivegroup-visiblepanel-tab-background-color: var(--background);--dv-inactivegroup-hiddenpanel-tab-background-color: var(--muted);--dv-activegroup-visiblepanel-tab-color: var(--foreground);--dv-activegroup-hiddenpanel-tab-color: var(--muted-foreground);--dv-inactivegroup-visiblepanel-tab-color: var(--foreground);--dv-inactivegroup-hiddenpanel-tab-color: var(--muted-foreground);--dv-tab-divider-color: transparent;--dv-drag-over-background-color: oklch(from var(--accent) l c h / .3);--dv-drag-over-border-color: var(--accent);--dv-separator-border: var(--border);color:var(--foreground);background-color:var(--background)}.dv-shell .dv-tabs-and-actions-container,.dv-shell .tabs-and-actions-container{background-color:var(--background)!important;border-bottom:1px solid oklch(from var(--border) l c h / .4);padding:6px 8px 0;gap:4px;align-items:flex-end}.dv-shell .dv-tabs-container,.dv-shell .tab-container{background-color:transparent!important;gap:4px;align-self:stretch;display:flex;align-items:flex-end}.workbench-dockview .dv-shell .dv-tabs-and-actions-container,.workbench-dockview .dv-shell .tabs-and-actions-container,.workbench-dockview .dv-tabs-and-actions-container,.workbench-dockview .tabs-and-actions-container{height:44px!important}.workbench-dockview[data-collapsed-sources=true] .dv-tabs-and-actions-container,.workbench-dockview[data-collapsed-sources=true] .tabs-and-actions-container{padding-left:44px!important}.workbench-dockview .dv-tabs-and-actions-container,.workbench-dockview .tabs-and-actions-container{padding-right:44px!important}.dv-shell .dv-tab,.dv-shell .tab{color:var(--muted-foreground)!important;background-color:transparent!important;border:1px solid transparent;border-top-left-radius:8px;border-top-right-radius:8px;transition:color .18s cubic-bezier(.22,1,.36,1),background-color .18s cubic-bezier(.22,1,.36,1),border-color .18s cubic-bezier(.22,1,.36,1);min-width:120px;max-width:220px;padding:0;height:34px;align-self:end;font-size:12.5px;letter-spacing:-.01em}.dv-shell .dv-tab:hover,.dv-shell .tab:hover{color:var(--foreground)!important;background-color:oklch(from var(--foreground) l c h / .04)!important}.dv-shell .dv-tab>*,.dv-shell .tab>*{width:100%;height:100%}.dv-shell .dv-tab.dv-active-tab,.dv-shell .tab.active-tab{color:var(--foreground)!important;background-color:var(--background)!important;border-color:oklch(from var(--border) l c h / .5);border-bottom-color:var(--background);margin-bottom:-1px;position:relative;z-index:1;font-weight:500}.dv-shell .dv-tab.dv-active-tab:before,.dv-shell .tab.active-tab:before{content:"";position:absolute;left:10px;right:10px;top:-1px;height:2px;background:var(--accent);border-radius:0 0 2px 2px;opacity:0;transition:opacity .2s cubic-bezier(.22,1,.36,1)}.dv-shell .dv-tab.dv-active-tab:hover:before,.dv-shell .tab.active-tab:hover:before{opacity:.7}.dv-shell .sash-container .sash,.dv-shell .dv-sash-container .dv-sash{transition:background-color .2s}.dv-shell .sash-container .sash:hover,.dv-shell .sash-container .sash.active,.dv-shell .dv-sash-container .dv-sash:hover,.dv-shell .dv-sash-container .dv-sash.dv-active{background-color:var(--primary);transition-delay:.15s}.dv-shell .drop-target-dropzone>.drop-target-selection,.dv-shell .dv-drop-target-dropzone>.dv-drop-target-selection{background-color:oklch(from var(--primary) l c h / .15);border:2px dashed var(--primary);border-radius:calc(var(--radius) - 2px)}.dv-shell .watermark,.dv-shell .dv-watermark{color:var(--muted-foreground)}.dv-shell,.dv-shell .dv-dockview,.dv-shell .dv-groupview,.dv-shell .groupview,.dv-shell .groupview>.content-container,.dv-shell .dv-view-container,.dv-shell .view-container{background-color:var(--background)}.dv-shell .dv-groupview,.dv-shell .groupview{color:var(--foreground)}.dv-shell .right-actions-container,.dv-shell .left-actions-container,.dv-shell .dv-right-actions-container,.dv-shell .dv-left-actions-container{color:var(--muted-foreground);display:flex;align-items:center;align-self:stretch}.dv-shell .right-actions-container:hover,.dv-shell .left-actions-container:hover,.dv-shell .dv-right-actions-container:hover,.dv-shell .dv-left-actions-container:hover{color:var(--foreground)}.dv-shell .dv-drag-image{opacity:.85;border:1px solid var(--border);border-radius:calc(var(--radius) - 2px);box-shadow:0 4px 12px #00000026}.dv-chat-stage{--dv-tabs-and-actions-container-height: 32px;--dv-tabs-and-actions-container-font-size: .75rem;--dv-activegroup-visiblepanel-tab-background-color: transparent;--dv-activegroup-hiddenpanel-tab-background-color: transparent;--dv-inactivegroup-visiblepanel-tab-background-color: transparent;--dv-inactivegroup-hiddenpanel-tab-background-color: transparent}.dv-shell.dv-chat-stage .dv-tabs-and-actions-container{border-bottom:none;padding:0;gap:0;align-items:stretch}.dv-shell.dv-chat-stage .dv-tabs-and-actions-container.dv-single-tab .dv-tabs-container{width:100%}.dv-shell.dv-chat-stage .dv-tab,.dv-shell.dv-chat-stage .dv-tab.dv-active-tab{width:100%;height:100%;min-width:0;max-width:none;flex:1 1 auto;align-self:stretch;margin-bottom:0;padding:0;border:none;border-radius:0;background-color:transparent!important}.dv-shell.dv-chat-stage .dv-tab.dv-active-tab:before{content:none}.dv-chat-stage .dv-default-tab-action{display:none}.dv-chat-stage .dv-groupview{position:relative}.dv-chat-stage .dv-groupview:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:40;pointer-events:none;background:transparent;transition:background-color .22s cubic-bezier(.22,1,.36,1),box-shadow .22s cubic-bezier(.22,1,.36,1)}[data-boring-workspace-part=chat-pane-stage][data-multi-pane=true] .dv-groupview:not(.dv-active-group):after{background:oklch(from var(--foreground) l c h / .035)}
1
+ .dv-shell{--dv-background-color: var(--background);--dv-paneview-header-border-color: var(--border);--dv-tabs-and-actions-container-font-size: .8125rem;--dv-tabs-and-actions-container-height: 52px;--dv-tab-close-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M18 6 6 18M6 6l12 12'/%3E%3C/svg%3E");--dv-group-view-background-color: var(--background);--dv-tabs-and-actions-container-background-color: var(--background);--dv-activegroup-visiblepanel-tab-background-color: var(--background);--dv-activegroup-hiddenpanel-tab-background-color: var(--muted);--dv-inactivegroup-visiblepanel-tab-background-color: var(--background);--dv-inactivegroup-hiddenpanel-tab-background-color: var(--muted);--dv-activegroup-visiblepanel-tab-color: var(--foreground);--dv-activegroup-hiddenpanel-tab-color: var(--muted-foreground);--dv-inactivegroup-visiblepanel-tab-color: var(--foreground);--dv-inactivegroup-hiddenpanel-tab-color: var(--muted-foreground);--dv-tab-divider-color: transparent;--dv-drag-over-background-color: oklch(from var(--accent) l c h / .3);--dv-drag-over-border-color: var(--accent);--dv-separator-border: var(--border);color:var(--foreground);background-color:var(--background)}.dv-shell .dv-tabs-and-actions-container,.dv-shell .tabs-and-actions-container{background-color:var(--background)!important;border-bottom:1px solid oklch(from var(--border) l c h / .4);padding:6px 8px 0;gap:4px;align-items:flex-end}.dv-shell .dv-tabs-container,.dv-shell .tab-container{background-color:transparent!important;gap:4px;align-self:stretch;display:flex;align-items:flex-end}.workbench-dockview .dv-shell .dv-tabs-and-actions-container,.workbench-dockview .dv-shell .tabs-and-actions-container,.workbench-dockview .dv-tabs-and-actions-container,.workbench-dockview .tabs-and-actions-container{height:44px!important}.workbench-dockview[data-collapsed-sources=true] .dv-tabs-and-actions-container,.workbench-dockview[data-collapsed-sources=true] .tabs-and-actions-container{padding-left:44px!important}.workbench-dockview .dv-tabs-and-actions-container,.workbench-dockview .tabs-and-actions-container{padding-right:44px!important}.dv-shell .dv-tab,.dv-shell .tab{color:var(--muted-foreground)!important;background-color:transparent!important;border:1px solid transparent;border-top-left-radius:8px;border-top-right-radius:8px;transition:color .18s cubic-bezier(.22,1,.36,1),background-color .18s cubic-bezier(.22,1,.36,1),border-color .18s cubic-bezier(.22,1,.36,1);min-width:120px;max-width:220px;padding:0;height:34px;align-self:end;font-size:12.5px;letter-spacing:-.01em}.dv-shell .dv-tab:hover,.dv-shell .tab:hover{color:var(--foreground)!important;background-color:oklch(from var(--foreground) l c h / .04)!important}.dv-shell .dv-tab>*,.dv-shell .tab>*{width:100%;height:100%}.dv-shell .dv-tab.dv-active-tab,.dv-shell .tab.active-tab{color:var(--foreground)!important;background-color:var(--background)!important;border-color:oklch(from var(--border) l c h / .5);border-bottom-color:var(--background);margin-bottom:-1px;position:relative;z-index:1;font-weight:500}.dv-shell .dv-tab.dv-active-tab:before,.dv-shell .tab.active-tab:before{content:"";position:absolute;left:10px;right:10px;top:-1px;height:2px;background:var(--accent);border-radius:0 0 2px 2px;opacity:0;transition:opacity .2s cubic-bezier(.22,1,.36,1)}.dv-shell .dv-tab.dv-active-tab:hover:before,.dv-shell .tab.active-tab:hover:before{opacity:.7}.dv-shell{--dv-sash-color: transparent;--dv-active-sash-color: transparent;--dv-active-sash-transition-delay: 0s}.dv-shell .sash-container .sash,.dv-shell .dv-sash-container .dv-sash{transition:box-shadow .16s cubic-bezier(.22,1,.36,1),background-color .16s cubic-bezier(.22,1,.36,1)}.dv-shell .dv-split-view-container.dv-horizontal>.dv-sash-container>.dv-sash{width:12px;margin-left:-4px}.dv-shell .dv-split-view-container.dv-vertical>.dv-sash-container>.dv-sash{height:12px;margin-top:-4px}.dv-shell .dv-split-view-container.dv-horizontal>.dv-sash-container>.dv-sash:hover,.dv-shell .dv-split-view-container.dv-horizontal>.dv-sash-container>.dv-sash:active{box-shadow:inset 0 0 0 1px oklch(from var(--primary) l c h / .45),inset 5px 0 oklch(from var(--primary) l c h / .35),inset 6px 0 oklch(from var(--primary) l c h / .35)}.dv-shell .dv-split-view-container.dv-vertical>.dv-sash-container>.dv-sash:hover,.dv-shell .dv-split-view-container.dv-vertical>.dv-sash-container>.dv-sash:active{box-shadow:inset 0 0 0 1px oklch(from var(--primary) l c h / .45),inset 0 5px oklch(from var(--primary) l c h / .35),inset 0 6px oklch(from var(--primary) l c h / .35)}.dv-shell .drop-target-dropzone>.drop-target-selection,.dv-shell .dv-drop-target-dropzone>.dv-drop-target-selection{background-color:oklch(from var(--primary) l c h / .15);border:2px dashed var(--primary);border-radius:calc(var(--radius) - 2px)}.dv-shell .watermark,.dv-shell .dv-watermark{color:var(--muted-foreground)}.dv-shell,.dv-shell .dv-dockview,.dv-shell .dv-groupview,.dv-shell .groupview,.dv-shell .groupview>.content-container,.dv-shell .dv-view-container,.dv-shell .view-container{background-color:var(--background)}.dv-shell .dv-groupview,.dv-shell .groupview{color:var(--foreground)}.dv-shell .right-actions-container,.dv-shell .left-actions-container,.dv-shell .dv-right-actions-container,.dv-shell .dv-left-actions-container{color:var(--muted-foreground);display:flex;align-items:center;align-self:stretch}.dv-shell .right-actions-container:hover,.dv-shell .left-actions-container:hover,.dv-shell .dv-right-actions-container:hover,.dv-shell .dv-left-actions-container:hover{color:var(--foreground)}.dv-shell .dv-drag-image{opacity:.85;border:1px solid var(--border);border-radius:calc(var(--radius) - 2px);box-shadow:0 4px 12px #00000026}.dv-chat-stage{--dv-tabs-and-actions-container-height: 32px;--dv-tabs-and-actions-container-font-size: .75rem;--dv-activegroup-visiblepanel-tab-background-color: transparent;--dv-activegroup-hiddenpanel-tab-background-color: transparent;--dv-inactivegroup-visiblepanel-tab-background-color: transparent;--dv-inactivegroup-hiddenpanel-tab-background-color: transparent}.dv-shell.dv-chat-stage .dv-tabs-and-actions-container{border-bottom:none;padding:0;gap:0;align-items:stretch}.dv-shell.dv-chat-stage .dv-tabs-and-actions-container.dv-single-tab .dv-tabs-container{width:100%}.dv-shell.dv-chat-stage .dv-tab,.dv-shell.dv-chat-stage .dv-tab.dv-active-tab{width:100%;height:100%;min-width:0;max-width:none;flex:1 1 auto;align-self:stretch;margin-bottom:0;padding:0;border:none;border-radius:0;background-color:transparent!important}.dv-shell.dv-chat-stage .dv-tab.dv-active-tab:before{content:none}.dv-chat-stage .dv-default-tab-action{display:none}.dv-chat-stage .dv-groupview{position:relative}.dv-chat-stage .dv-groupview:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;z-index:40;pointer-events:none;background:transparent;transition:background-color .22s cubic-bezier(.22,1,.36,1),box-shadow .22s cubic-bezier(.22,1,.36,1)}[data-boring-workspace-part=chat-pane-stage][data-multi-pane=true] .dv-groupview:not(.dv-active-group):after{background:oklch(from var(--foreground) l c h / .035)}
@@ -1,7 +1,8 @@
1
1
  import { B as BoringPackageBoringField, a as BoringPackagePiField } from './manifest-C2vVgH_e.js';
2
2
  import { PiPackageSource, PluginSkillSource, ProvisionWorkspaceRuntimeOptions } from '@hachej/boring-agent/server';
3
3
  import { FastifyPluginAsync } from 'fastify';
4
- import { A as AgentTool, U as UiBridge } from './ui-bridge-DFNem0df.js';
4
+ import { A as AgentTool } from './agent-tool-CB0RQyx9.js';
5
+ import { U as UiBridge } from './ui-bridge-LeBuZqfA.js';
5
6
 
6
7
  type BoringPluginNativeFrontTargetTrust$1 = "local-trusted-native";
7
8
  /**
@@ -17,14 +18,23 @@ interface BoringPluginNativeFrontTarget$1 {
17
18
  revision: number;
18
19
  trust: BoringPluginNativeFrontTargetTrust$1;
19
20
  }
20
- type BoringPluginFrontTarget$1 = BoringPluginNativeFrontTarget$1;
21
+ /**
22
+ * Plugin front served as a plain browser module URL — the Vite-dev transport
23
+ * (`/@fs/...`). Hosts running a Vite dev server let Vite transform the entry;
24
+ * the CLI's runtime host mints `native` targets instead.
25
+ */
26
+ interface BoringPluginModuleUrlFrontTarget {
27
+ kind: "module-url";
28
+ entryUrl: string;
29
+ revision: number;
30
+ }
31
+ type BoringPluginFrontTarget$1 = BoringPluginNativeFrontTarget$1 | BoringPluginModuleUrlFrontTarget;
21
32
  type BoringPluginEvent$1 = {
22
33
  type: "boring.plugin.load";
23
34
  id: string;
24
35
  boring: BoringPackageBoringField;
25
36
  version: string;
26
37
  revision: number;
27
- frontUrl?: string;
28
38
  frontTarget?: BoringPluginFrontTarget$1;
29
39
  } | {
30
40
  type: "boring.plugin.unload";
@@ -42,7 +52,6 @@ interface BoringPluginListEntry$1 {
42
52
  pi?: BoringPackagePiField;
43
53
  version: string;
44
54
  revision: number;
45
- frontUrl?: string;
46
55
  frontTarget?: BoringPluginFrontTarget$1;
47
56
  }
48
57
 
package/dist/plugin.d.ts CHANGED
@@ -2,6 +2,7 @@ import { ComponentType, ReactNode } from 'react';
2
2
  import { P as PaneProps, a as PanelConfig, S as SurfaceOpenRequest, c as SurfacePanelResolution } from './surface-obE7YwJk.js';
3
3
  export { W as WORKSPACE_OPEN_PATH_SURFACE_KIND } from './surface-obE7YwJk.js';
4
4
  export { B as BoringPackageBoringField, a as BoringPackagePiField, b as BoringPackagePiSource, c as BoringPackagePiSourceObject, d as BoringPluginManifestErrorCode, e as BoringPluginManifestIssue, f as BoringPluginManifestValidationResult, g as BoringPluginPackageJson, i as isSafePluginRelativePath, h as isValidBoringPluginId, v as validateBoringPluginManifest } from './manifest-C2vVgH_e.js';
5
+ import { a as UiCommand, C as CommandResult, U as UiBridge } from './ui-bridge-LeBuZqfA.js';
5
6
  import 'dockview-react';
6
7
 
7
8
  type CatalogBadge = {
@@ -209,4 +210,67 @@ declare function createCapturingBoringFrontAPI(options?: {
209
210
  }): CapturingBoringFrontAPIHandle;
210
211
  declare function captureFrontPlugin(plugin: BoringFrontFactoryWithId): CapturedFrontPlugin;
211
212
 
212
- export { type BoringFrontAPI, type BoringFrontBindingRegistration, type BoringFrontFactory, type BoringFrontFactoryWithId, type BoringFrontLeftTabRegistration, type BoringFrontPanelCommandRegistration, type BoringFrontPanelRegistration, type BoringFrontProviderRegistration, type BoringFrontSetup, type BoringFrontSurfaceResolverRegistration, type CapturedBoringFrontRegistrations, type CapturedFrontPlugin, type CapturingBoringFrontAPIHandle, type DefinePluginConfig, PaneProps, captureFrontPlugin, createCapturingBoringFrontAPI, definePlugin };
213
+ /**
214
+ * In-process registry that lets a plugin's Pi slash command reach the live
215
+ * workspace `UiBridge` WITHOUT an HTTP round-trip or a `BORING_UI_URL` env var.
216
+ *
217
+ * Why this exists
218
+ * ---------------
219
+ * A Pi extension slash-command handler only receives Pi's terminal-oriented
220
+ * `ctx.ui` (notify/select/confirm). It has no concept of the boring-ui
221
+ * workspace UI bridge, so the old canonical template resorted to
222
+ * `fetch(BORING_UI_URL + "/api/v1/ui/commands")` — fragile, and it silently
223
+ * no-op'd whenever the env var was unset (which is the common case). The bridge
224
+ * is already an in-process object owned by the agent server, so the right thing
225
+ * is to call `bridge.postCommand(...)` directly — the exact same path the
226
+ * agent's `exec_ui` tool uses.
227
+ *
228
+ * Why globalThis
229
+ * --------------
230
+ * Hot-reloadable plugins are loaded by Pi through jiti, which keeps its own
231
+ * module cache. A plain module-level singleton populated by the server is NOT
232
+ * guaranteed to be the same instance the plugin imports. `globalThis` is shared
233
+ * across every module realm in the process, so a `Symbol.for`-keyed slot is the
234
+ * one storage that both the server and a jiti-loaded plugin observe identically.
235
+ *
236
+ * This module is browser-safe (only `globalThis` + types), so it can live on
237
+ * the `@hachej/boring-workspace/plugin` authoring surface.
238
+ */
239
+
240
+ /** The active workspace `UiBridge`, or `undefined` when none is registered. */
241
+ declare function getWorkspaceUiBridge(): UiBridge | undefined;
242
+ /**
243
+ * Thrown by the plugin-facing helpers when no workspace bridge is active — for
244
+ * example when plugin code runs under a bare Pi CLI with no workspace UI
245
+ * attached. The message is deliberately actionable.
246
+ */
247
+ declare class NoWorkspaceUiBridgeError extends Error {
248
+ constructor();
249
+ }
250
+ /**
251
+ * Dispatch an arbitrary UI command through the active workspace bridge. This is
252
+ * the same call the agent's `exec_ui` tool makes; the connected browser drains
253
+ * the command. Prefer the named helpers below for common actions.
254
+ */
255
+ declare function execWorkspaceUi(command: UiCommand): Promise<CommandResult>;
256
+ interface OpenPanelArgs {
257
+ /** Tab instance id. Reuse the same id to re-activate an existing tab. */
258
+ id: string;
259
+ /** Panel component id (one of the workspace's registered panels). */
260
+ component: string;
261
+ /** Optional params forwarded to the panel component. */
262
+ params?: Record<string, unknown>;
263
+ }
264
+ /**
265
+ * Open an app/plugin panel in the workspace from a plugin slash command.
266
+ * In-process — no URL, no env. Throws `NoWorkspaceUiBridgeError` if no bridge.
267
+ */
268
+ declare function openPanel(args: OpenPanelArgs): Promise<CommandResult>;
269
+ /**
270
+ * Show a workspace notification (toast) from a plugin slash command. Unlike
271
+ * Pi's `ctx.ui.notify` (a terminal notification that is swallowed in
272
+ * server/headless mode), this surfaces in the browser via the UI bridge.
273
+ */
274
+ declare function notify(msg: string, level?: "info" | "warn" | "error"): Promise<CommandResult>;
275
+
276
+ export { type BoringFrontAPI, type BoringFrontBindingRegistration, type BoringFrontFactory, type BoringFrontFactoryWithId, type BoringFrontLeftTabRegistration, type BoringFrontPanelCommandRegistration, type BoringFrontPanelRegistration, type BoringFrontProviderRegistration, type BoringFrontSetup, type BoringFrontSurfaceResolverRegistration, type CapturedBoringFrontRegistrations, type CapturedFrontPlugin, type CapturingBoringFrontAPIHandle, type DefinePluginConfig, NoWorkspaceUiBridgeError, type OpenPanelArgs, PaneProps, captureFrontPlugin, createCapturingBoringFrontAPI, definePlugin, execWorkspaceUi, getWorkspaceUiBridge, notify, openPanel };