@hachej/boring-workspace 0.1.40 → 0.1.42

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.
Files changed (33) hide show
  1. package/README.md +26 -261
  2. package/dist/{FileTree-DNIzusWa.js → FileTree-CVsvICGP.js} +1 -1
  3. package/dist/MarkdownEditor-BvaGmzWP.js +582 -0
  4. package/dist/{WorkspaceLoadingState-EratTJfG.js → WorkspaceLoadingState-yp4vNmrT.js} +21 -22
  5. package/dist/{WorkspaceProvider-uuxyAx3i.js → WorkspaceProvider-DkZAxsYo.js} +2902 -2741
  6. package/dist/app-front.d.ts +5 -0
  7. package/dist/app-front.js +221 -217
  8. package/dist/app-server.d.ts +2 -2
  9. package/dist/app-server.js +23 -5
  10. package/dist/{createInMemoryBridge-DSjZ9efK.d.ts → createInMemoryBridge-siFWq_R_.d.ts} +8 -0
  11. package/dist/plugin.d.ts +4 -2
  12. package/dist/server.d.ts +3 -3
  13. package/dist/server.js +23 -5
  14. package/dist/shared.d.ts +1 -1
  15. package/dist/{surface-obE7YwJk.d.ts → surface-DmIalUmP.d.ts} +2 -0
  16. package/dist/testing.d.ts +2 -0
  17. package/dist/testing.js +1 -1
  18. package/dist/workspace.css +112 -16
  19. package/dist/workspace.d.ts +8 -2
  20. package/dist/workspace.js +5 -5
  21. package/docs/INTERFACES.md +1 -1
  22. package/docs/PLUGIN_STRUCTURE.md +3 -3
  23. package/docs/PLUGIN_SYSTEM.md +46 -4
  24. package/docs/README.md +85 -22
  25. package/package.json +4 -4
  26. package/dist/MarkdownEditor-DhVfKSAq.js +0 -549
  27. /package/docs/plans/{ASK_USER_QUESTIONS_PLUGIN_SPEC.md → archive/ASK_USER_QUESTIONS_PLUGIN_SPEC.md} +0 -0
  28. /package/docs/plans/{FULL_PAGE_PANEL_ROUTE_SPEC.md → archive/FULL_PAGE_PANEL_ROUTE_SPEC.md} +0 -0
  29. /package/docs/plans/{GENERIC_EXPLORER_PLUGIN_PLAN.md → archive/GENERIC_EXPLORER_PLUGIN_PLAN.md} +0 -0
  30. /package/docs/plans/{PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md → archive/PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md} +0 -0
  31. /package/docs/plans/{PLUGIN_OUTPUTS_ISOLATION_PLAN.md → archive/PLUGIN_OUTPUTS_ISOLATION_PLAN.md} +0 -0
  32. /package/docs/plans/{README.md → archive/README.md} +0 -0
  33. /package/docs/plans/{UI_BRIDGE_OWNERSHIP_REFACTOR.md → archive/UI_BRIDGE_OWNERSHIP_REFACTOR.md} +0 -0
@@ -1,8 +1,8 @@
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-DSjZ9efK.js';
5
- export { e as ServerWorkspaceRuntimeProvisioningInput } from './createInMemoryBridge-DSjZ9efK.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-siFWq_R_.js';
5
+ export { e as ServerWorkspaceRuntimeProvisioningInput } from './createInMemoryBridge-siFWq_R_.js';
6
6
  import './manifest-C2vVgH_e.js';
7
7
  import './agent-tool-CB0RQyx9.js';
8
8
  import './ui-bridge-LeBuZqfA.js';
@@ -360,7 +360,8 @@ function normalizeBoringPluginSource(input) {
360
360
  return {
361
361
  rootDir: resolve2(input.rootDir),
362
362
  kind: input.kind,
363
- ...input.workspaceId ? { workspaceId: input.workspaceId } : {}
363
+ ...input.workspaceId ? { workspaceId: input.workspaceId } : {},
364
+ ...input.registered ? { registered: true } : {}
364
365
  };
365
366
  }
366
367
  function pluginIdFromPackageJson(pkg, rootDir) {
@@ -423,10 +424,14 @@ function packagePathContainmentIssues(rootDir, pkg) {
423
424
  function discoverBoringPluginDirs(pluginDirs) {
424
425
  const out = /* @__PURE__ */ new Map();
425
426
  const missingPackageJson = [];
427
+ const missingDirs = [];
426
428
  for (const raw of pluginDirs) {
427
429
  const source = normalizeBoringPluginSource(raw);
428
430
  const dir = source.rootDir;
429
- if (!existsSync2(dir)) continue;
431
+ if (!existsSync2(dir)) {
432
+ if (source.registered) missingDirs.push(dir);
433
+ continue;
434
+ }
430
435
  const info = statSync(dir);
431
436
  if (!info.isDirectory()) continue;
432
437
  const hasPackageJson = existsSync2(join2(dir, "package.json"));
@@ -441,13 +446,14 @@ function discoverBoringPluginDirs(pluginDirs) {
441
446
  if (!out.has(child)) out.set(child, { ...source, rootDir: child });
442
447
  }
443
448
  const collectionDirNames = /* @__PURE__ */ new Set(["extensions", "npm", "git"]);
444
- if (!hasPackageJson && childPackageDirs.length === 0 && !collectionDirNames.has(basename(dir))) {
449
+ if (!hasPackageJson && (source.registered || childPackageDirs.length === 0 && !collectionDirNames.has(basename(dir)))) {
445
450
  missingPackageJson.push(dir);
446
451
  }
447
452
  }
448
453
  return {
449
454
  sources: [...out.values()].sort((a, b) => a.rootDir.localeCompare(b.rootDir)),
450
- missingPackageJson: [...new Set(missingPackageJson)].sort()
455
+ missingPackageJson: [...new Set(missingPackageJson)].sort(),
456
+ missingDirs: [...new Set(missingDirs)].sort()
451
457
  };
452
458
  }
453
459
  function scanBoringPlugins(pluginDirs) {
@@ -455,6 +461,9 @@ function scanBoringPlugins(pluginDirs) {
455
461
  const plugins = [];
456
462
  const seenIds = /* @__PURE__ */ new Map();
457
463
  const discovered = discoverBoringPluginDirs(pluginDirs);
464
+ for (const pluginDir of discovered.missingDirs) {
465
+ errors.push({ pluginDir, code: "MISSING_PLUGIN_DIR", message: "registered plugin source directory does not exist" });
466
+ }
458
467
  for (const pluginDir of discovered.missingPackageJson) {
459
468
  errors.push({ pluginDir, code: "MISSING_PACKAGE_JSON", message: "package.json is missing" });
460
469
  }
@@ -471,7 +480,16 @@ function scanBoringPlugins(pluginDirs) {
471
480
  });
472
481
  continue;
473
482
  }
474
- if (!hasPluginMetadata(raw)) continue;
483
+ if (!hasPluginMetadata(raw)) {
484
+ if (source.registered) {
485
+ errors.push({
486
+ pluginDir: rootDir,
487
+ code: "INVALID_PLUGIN_METADATA",
488
+ message: 'package.json has no "boring" or "pi" plugin metadata'
489
+ });
490
+ }
491
+ continue;
492
+ }
475
493
  const result = validateBoringPluginManifest(raw);
476
494
  if (!result.valid) {
477
495
  const pluginId = safePluginIdFromPackageJson(raw, rootDir);
@@ -60,6 +60,14 @@ interface BoringPluginSource {
60
60
  rootDir: string;
61
61
  kind: BoringPluginSourceKind;
62
62
  workspaceId?: string;
63
+ /**
64
+ * True when the user explicitly registered this directory as a plugin
65
+ * source (e.g. a `packages` entry in Pi settings.json). Registered
66
+ * sources that are missing, lack a package.json, or carry no plugin
67
+ * metadata surface as preflight errors instead of being silently
68
+ * skipped the way speculative scan roots are.
69
+ */
70
+ registered?: boolean;
63
71
  }
64
72
  type BoringPluginSourceInput = string | BoringPluginSource;
65
73
  interface BoringServerPluginManifest {
package/dist/plugin.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ComponentType, ReactNode } from 'react';
2
- import { P as PaneProps, a as PanelConfig, S as SurfaceOpenRequest, c as SurfacePanelResolution } from './surface-obE7YwJk.js';
3
- export { W as WORKSPACE_OPEN_PATH_SURFACE_KIND } from './surface-obE7YwJk.js';
2
+ import { P as PaneProps, a as PanelConfig, S as SurfaceOpenRequest, c as SurfacePanelResolution } from './surface-DmIalUmP.js';
3
+ export { W as WORKSPACE_OPEN_PATH_SURFACE_KIND } from './surface-DmIalUmP.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
5
  import { a as UiCommand, C as CommandResult, U as UiBridge } from './ui-bridge-LeBuZqfA.js';
6
6
  import 'dockview-react';
@@ -70,6 +70,8 @@ interface LeftTabParams {
70
70
  searchQuery?: string;
71
71
  bridge?: unknown;
72
72
  chromeless?: boolean;
73
+ /** Optional DOM target for left-tab toolbar actions owned by the pane. */
74
+ chromeActionsElement?: Element | null;
73
75
  revealFileTreeRequest?: {
74
76
  path: string;
75
77
  seq: number;
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { f as WorkspaceServerPluginAsset, g as BoringServerPluginManifest, B as BoringPluginSourceInput, a as BoringPluginFrontTargetResolver, h as BoringPluginListEntry, i as BoringPluginFrontTarget, j as BoringPluginSource, k as BoringPluginEvent, P as PluginRestartSurface } from './createInMemoryBridge-DSjZ9efK.js';
2
- export { l as BoringPluginFrontTargetResolverContext, m as BoringPluginNativeFrontTarget, n as BoringPluginNativeFrontTargetTrust, o as BoringPluginSourceKind, S as ServerBootstrapOptions, p as ServerBootstrapResult, b as WorkspaceProvisioningContribution, c as WorkspaceRouteContribution, W as WorkspaceServerPlugin, q as bootstrapServer, d as createInMemoryBridge, r as defineServerPlugin, v as validateServerPlugin } from './createInMemoryBridge-DSjZ9efK.js';
1
+ import { f as WorkspaceServerPluginAsset, g as BoringServerPluginManifest, B as BoringPluginSourceInput, a as BoringPluginFrontTargetResolver, h as BoringPluginListEntry, i as BoringPluginFrontTarget, j as BoringPluginSource, k as BoringPluginEvent, P as PluginRestartSurface } from './createInMemoryBridge-siFWq_R_.js';
2
+ export { l as BoringPluginFrontTargetResolverContext, m as BoringPluginNativeFrontTarget, n as BoringPluginNativeFrontTargetTrust, o as BoringPluginSourceKind, S as ServerBootstrapOptions, p as ServerBootstrapResult, b as WorkspaceProvisioningContribution, c as WorkspaceRouteContribution, W as WorkspaceServerPlugin, q as bootstrapServer, d as createInMemoryBridge, r as defineServerPlugin, v as validateServerPlugin } from './createInMemoryBridge-siFWq_R_.js';
3
3
  import { FastifyRequest, FastifyInstance } from 'fastify';
4
4
  import { U as UiBridge } from './ui-bridge-LeBuZqfA.js';
5
5
  export { C as CommandResult, a as UiCommand, b as UiState } from './ui-bridge-LeBuZqfA.js';
@@ -164,7 +164,7 @@ declare function buildBoringSystemPrompt(opts: BuildBoringSystemPromptOptions):
164
164
  interface BoringPluginPreflightIssue {
165
165
  pluginDir: string;
166
166
  pluginId?: string;
167
- code: "MISSING_PACKAGE_JSON" | "INVALID_PACKAGE_JSON" | "INVALID_PLUGIN_METADATA";
167
+ code: "MISSING_PLUGIN_DIR" | "MISSING_PACKAGE_JSON" | "INVALID_PACKAGE_JSON" | "INVALID_PLUGIN_METADATA";
168
168
  message: string;
169
169
  }
170
170
  interface BoringPluginPreflightResult {
package/dist/server.js CHANGED
@@ -1238,7 +1238,8 @@ function normalizeBoringPluginSource(input) {
1238
1238
  return {
1239
1239
  rootDir: resolve3(input.rootDir),
1240
1240
  kind: input.kind,
1241
- ...input.workspaceId ? { workspaceId: input.workspaceId } : {}
1241
+ ...input.workspaceId ? { workspaceId: input.workspaceId } : {},
1242
+ ...input.registered ? { registered: true } : {}
1242
1243
  };
1243
1244
  }
1244
1245
  function pluginIdFromPackageJson(pkg, rootDir) {
@@ -1301,10 +1302,14 @@ function packagePathContainmentIssues(rootDir, pkg) {
1301
1302
  function discoverBoringPluginDirs(pluginDirs) {
1302
1303
  const out = /* @__PURE__ */ new Map();
1303
1304
  const missingPackageJson = [];
1305
+ const missingDirs = [];
1304
1306
  for (const raw of pluginDirs) {
1305
1307
  const source = normalizeBoringPluginSource(raw);
1306
1308
  const dir = source.rootDir;
1307
- if (!existsSync2(dir)) continue;
1309
+ if (!existsSync2(dir)) {
1310
+ if (source.registered) missingDirs.push(dir);
1311
+ continue;
1312
+ }
1308
1313
  const info = statSync(dir);
1309
1314
  if (!info.isDirectory()) continue;
1310
1315
  const hasPackageJson = existsSync2(join3(dir, "package.json"));
@@ -1319,13 +1324,14 @@ function discoverBoringPluginDirs(pluginDirs) {
1319
1324
  if (!out.has(child)) out.set(child, { ...source, rootDir: child });
1320
1325
  }
1321
1326
  const collectionDirNames = /* @__PURE__ */ new Set(["extensions", "npm", "git"]);
1322
- if (!hasPackageJson && childPackageDirs.length === 0 && !collectionDirNames.has(basename(dir))) {
1327
+ if (!hasPackageJson && (source.registered || childPackageDirs.length === 0 && !collectionDirNames.has(basename(dir)))) {
1323
1328
  missingPackageJson.push(dir);
1324
1329
  }
1325
1330
  }
1326
1331
  return {
1327
1332
  sources: [...out.values()].sort((a, b) => a.rootDir.localeCompare(b.rootDir)),
1328
- missingPackageJson: [...new Set(missingPackageJson)].sort()
1333
+ missingPackageJson: [...new Set(missingPackageJson)].sort(),
1334
+ missingDirs: [...new Set(missingDirs)].sort()
1329
1335
  };
1330
1336
  }
1331
1337
  function scanBoringPlugins(pluginDirs) {
@@ -1333,6 +1339,9 @@ function scanBoringPlugins(pluginDirs) {
1333
1339
  const plugins = [];
1334
1340
  const seenIds = /* @__PURE__ */ new Map();
1335
1341
  const discovered = discoverBoringPluginDirs(pluginDirs);
1342
+ for (const pluginDir of discovered.missingDirs) {
1343
+ errors.push({ pluginDir, code: "MISSING_PLUGIN_DIR", message: "registered plugin source directory does not exist" });
1344
+ }
1336
1345
  for (const pluginDir of discovered.missingPackageJson) {
1337
1346
  errors.push({ pluginDir, code: "MISSING_PACKAGE_JSON", message: "package.json is missing" });
1338
1347
  }
@@ -1349,7 +1358,16 @@ function scanBoringPlugins(pluginDirs) {
1349
1358
  });
1350
1359
  continue;
1351
1360
  }
1352
- if (!hasPluginMetadata(raw)) continue;
1361
+ if (!hasPluginMetadata(raw)) {
1362
+ if (source.registered) {
1363
+ errors.push({
1364
+ pluginDir: rootDir,
1365
+ code: "INVALID_PLUGIN_METADATA",
1366
+ message: 'package.json has no "boring" or "pi" plugin metadata'
1367
+ });
1368
+ }
1369
+ continue;
1370
+ }
1353
1371
  const result = validateBoringPluginManifest(raw);
1354
1372
  if (!result.valid) {
1355
1373
  const pluginId = safePluginIdFromPackageJson(raw, rootDir);
package/dist/shared.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { C as CommandResult, U as UiBridge, a as UiCommand, b as UiState } from './ui-bridge-LeBuZqfA.js';
2
- export { C as CommandConfig, P as PaneProps, a as PanelConfig, b as PanelRegistration, S as SurfaceOpenRequest, c as SurfacePanelResolution, d as SurfaceResolverConfig, e as SurfaceResolverRegistration, W as WORKSPACE_OPEN_PATH_SURFACE_KIND, f as definePanel } from './surface-obE7YwJk.js';
2
+ export { C as CommandConfig, P as PaneProps, a as PanelConfig, b as PanelRegistration, S as SurfaceOpenRequest, c as SurfacePanelResolution, d as SurfaceResolverConfig, e as SurfaceResolverRegistration, W as WORKSPACE_OPEN_PATH_SURFACE_KIND, f as definePanel } from './surface-DmIalUmP.js';
3
3
  export { A as AgentTool, J as JSONSchema, T as ToolExecContext, a as ToolResult } from './agent-tool-CB0RQyx9.js';
4
4
  import 'react';
5
5
  import 'dockview-react';
@@ -47,6 +47,8 @@ interface PanelConfig<T = any> {
47
47
  essential?: boolean;
48
48
  chromeless?: boolean;
49
49
  supportsFullPage?: boolean;
50
+ /** Center-panel id opened when this config is used as a left-tab category. */
51
+ defaultPanelId?: string;
50
52
  /** Source: "builtin" | "app" */
51
53
  source?: string;
52
54
  pluginId?: string;
package/dist/testing.d.ts CHANGED
@@ -231,6 +231,8 @@ declare interface PanelConfig<T = any> {
231
231
  essential?: boolean;
232
232
  chromeless?: boolean;
233
233
  supportsFullPage?: boolean;
234
+ /** Center-panel id opened when this config is used as a left-tab category. */
235
+ defaultPanelId?: string;
234
236
  /** Source: "builtin" | "app" */
235
237
  source?: string;
236
238
  pluginId?: string;
package/dist/testing.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as Ba } from "react/jsx-runtime";
2
2
  import * as Pa from "react";
3
3
  import { createElement as is, useMemo as wn, useLayoutEffect as us, isValidElement as ss, cloneElement as ds, useSyncExternalStore as Gi } from "react";
4
- import { h as cs, q as fs, o as ps } from "./WorkspaceProvider-uuxyAx3i.js";
4
+ import { h as cs, q as fs, o as ps } from "./WorkspaceProvider-DkZAxsYo.js";
5
5
  import { d as ms } from "./panel-DnvDNQac.js";
6
6
  import * as bs from "react-dom/test-utils";
7
7
  import ka from "react-dom";
@@ -1484,6 +1484,9 @@
1484
1484
  .-top-1\.5 {
1485
1485
  top: calc(var(--spacing) * -1.5);
1486
1486
  }
1487
+ .-top-px {
1488
+ top: -1px;
1489
+ }
1487
1490
  .top-0 {
1488
1491
  top: calc(var(--spacing) * 0);
1489
1492
  }
@@ -1508,6 +1511,9 @@
1508
1511
  .right-3 {
1509
1512
  right: calc(var(--spacing) * 3);
1510
1513
  }
1514
+ .-bottom-px {
1515
+ bottom: -1px;
1516
+ }
1511
1517
  .bottom-0 {
1512
1518
  bottom: calc(var(--spacing) * 0);
1513
1519
  }
@@ -2809,6 +2815,67 @@
2809
2815
  }
2810
2816
  }
2811
2817
  }
2818
+ .after\:absolute {
2819
+ &::after {
2820
+ content: var(--tw-content);
2821
+ position: absolute;
2822
+ }
2823
+ }
2824
+ .after\:inset-y-2 {
2825
+ &::after {
2826
+ content: var(--tw-content);
2827
+ inset-block: calc(var(--spacing) * 2);
2828
+ }
2829
+ }
2830
+ .after\:left-1\/2 {
2831
+ &::after {
2832
+ content: var(--tw-content);
2833
+ left: calc(1 / 2 * 100%);
2834
+ }
2835
+ }
2836
+ .after\:w-px {
2837
+ &::after {
2838
+ content: var(--tw-content);
2839
+ width: 1px;
2840
+ }
2841
+ }
2842
+ .after\:-translate-x-1\/2 {
2843
+ &::after {
2844
+ content: var(--tw-content);
2845
+ --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
2846
+ translate: var(--tw-translate-x) var(--tw-translate-y);
2847
+ }
2848
+ }
2849
+ .after\:rounded-full {
2850
+ &::after {
2851
+ content: var(--tw-content);
2852
+ border-radius: calc(infinity * 1px);
2853
+ }
2854
+ }
2855
+ .after\:bg-border\/55 {
2856
+ &::after {
2857
+ content: var(--tw-content);
2858
+ background-color: var(--boring-border);
2859
+ @supports (color: color-mix(in lab, red, red)) {
2860
+ background-color: color-mix(in oklab, var(--boring-border) 55%, transparent);
2861
+ }
2862
+ }
2863
+ }
2864
+ .after\:transition-\[width\,background-color\] {
2865
+ &::after {
2866
+ content: var(--tw-content);
2867
+ transition-property: width,background-color;
2868
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
2869
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
2870
+ }
2871
+ }
2872
+ .after\:duration-150 {
2873
+ &::after {
2874
+ content: var(--tw-content);
2875
+ --tw-duration: 150ms;
2876
+ transition-duration: 150ms;
2877
+ }
2878
+ }
2812
2879
  .hover\:-translate-y-0\.5 {
2813
2880
  &:hover {
2814
2881
  @media (hover: hover) {
@@ -2835,6 +2902,13 @@
2835
2902
  }
2836
2903
  }
2837
2904
  }
2905
+ .hover\:\!bg-transparent {
2906
+ &:hover {
2907
+ @media (hover: hover) {
2908
+ background-color: transparent !important;
2909
+ }
2910
+ }
2911
+ }
2838
2912
  .hover\:bg-accent {
2839
2913
  &:hover {
2840
2914
  @media (hover: hover) {
@@ -2862,16 +2936,6 @@
2862
2936
  }
2863
2937
  }
2864
2938
  }
2865
- .hover\:bg-border\/70 {
2866
- &:hover {
2867
- @media (hover: hover) {
2868
- background-color: var(--boring-border);
2869
- @supports (color: color-mix(in lab, red, red)) {
2870
- background-color: color-mix(in oklab, var(--boring-border) 70%, transparent);
2871
- }
2872
- }
2873
- }
2874
- }
2875
2939
  .hover\:bg-destructive\/10 {
2876
2940
  &:hover {
2877
2941
  @media (hover: hover) {
@@ -2985,10 +3049,26 @@
2985
3049
  }
2986
3050
  }
2987
3051
  }
2988
- .hover\:\[transition-delay\:150ms\] {
3052
+ .hover\:after\:w-1 {
3053
+ &:hover {
3054
+ @media (hover: hover) {
3055
+ &::after {
3056
+ content: var(--tw-content);
3057
+ width: calc(var(--spacing) * 1);
3058
+ }
3059
+ }
3060
+ }
3061
+ }
3062
+ .hover\:after\:bg-foreground\/35 {
2989
3063
  &:hover {
2990
3064
  @media (hover: hover) {
2991
- transition-delay: 150ms;
3065
+ &::after {
3066
+ content: var(--tw-content);
3067
+ background-color: var(--boring-foreground);
3068
+ @supports (color: color-mix(in lab, red, red)) {
3069
+ background-color: color-mix(in oklab, var(--boring-foreground) 35%, transparent);
3070
+ }
3071
+ }
2992
3072
  }
2993
3073
  }
2994
3074
  }
@@ -3063,11 +3143,27 @@
3063
3143
  cursor: grabbing;
3064
3144
  }
3065
3145
  }
3066
- .active\:bg-muted-foreground\/30 {
3146
+ .active\:\!bg-transparent {
3067
3147
  &:active {
3068
- background-color: var(--boring-muted-foreground);
3069
- @supports (color: color-mix(in lab, red, red)) {
3070
- background-color: color-mix(in oklab, var(--boring-muted-foreground) 30%, transparent);
3148
+ background-color: transparent !important;
3149
+ }
3150
+ }
3151
+ .active\:after\:w-1 {
3152
+ &:active {
3153
+ &::after {
3154
+ content: var(--tw-content);
3155
+ width: calc(var(--spacing) * 1);
3156
+ }
3157
+ }
3158
+ }
3159
+ .active\:after\:bg-foreground\/50 {
3160
+ &:active {
3161
+ &::after {
3162
+ content: var(--tw-content);
3163
+ background-color: var(--boring-foreground);
3164
+ @supports (color: color-mix(in lab, red, red)) {
3165
+ background-color: color-mix(in oklab, var(--boring-foreground) 50%, transparent);
3166
+ }
3071
3167
  }
3072
3168
  }
3073
3169
  }
@@ -994,6 +994,8 @@ export declare interface LeftTabParams {
994
994
  searchQuery?: string;
995
995
  bridge?: unknown;
996
996
  chromeless?: boolean;
997
+ /** Optional DOM target for left-tab toolbar actions owned by the pane. */
998
+ chromeActionsElement?: Element | null;
997
999
  revealFileTreeRequest?: {
998
1000
  path: string;
999
1001
  seq: number;
@@ -1146,6 +1148,8 @@ export declare interface PanelConfig<T = any> {
1146
1148
  essential?: boolean;
1147
1149
  chromeless?: boolean;
1148
1150
  supportsFullPage?: boolean;
1151
+ /** Center-panel id opened when this config is used as a left-tab category. */
1152
+ defaultPanelId?: string;
1149
1153
  /** Source: "builtin" | "app" */
1150
1154
  source?: string;
1151
1155
  pluginId?: string;
@@ -1489,7 +1493,7 @@ export declare interface SurfaceResolverRegistryLike {
1489
1493
  register(id: string, config: SurfaceResolverRegistration): void;
1490
1494
  }
1491
1495
 
1492
- export declare function SurfaceShell({ rootDir, sidebarDefaultWidth, sidebarMinWidth, sidebarMaxWidth, storageKey, onReady, onChange, onClose, extraPanels, defaultLeftTab, initialPanels, className, }: SurfaceShellProps): JSX.Element;
1496
+ export declare function SurfaceShell({ rootDir, sidebarDefaultWidth, sidebarMinWidth, sidebarMaxWidth, storageKey, onReady, onChange, onClose, extraPanels, defaultLeftTab, onReloadAgentPlugins, initialPanels, className, }: SurfaceShellProps): JSX.Element;
1493
1497
 
1494
1498
  export declare interface SurfaceShellApi {
1495
1499
  /** Open a file in the workbench. Idempotent — re-activates an existing pane for the same path. */
@@ -1536,6 +1540,7 @@ export declare interface SurfaceShellProps {
1536
1540
  */
1537
1541
  extraPanels?: string[];
1538
1542
  defaultLeftTab?: string;
1543
+ onReloadAgentPlugins?: () => void | Promise<unknown>;
1539
1544
  initialPanels?: Array<{
1540
1545
  id: string;
1541
1546
  component: string;
@@ -1849,7 +1854,7 @@ export declare function useWorkspaceContextOptional(): WorkspaceContextValue | n
1849
1854
 
1850
1855
  export declare function useWorkspaceRequestId(): string | null;
1851
1856
 
1852
- export declare function WorkbenchLeftPane({ rootDir, bridge, defaultTab, revealFileTreeRequest, onOpenPanel, onCollapse, className, }: WorkbenchLeftPaneProps): JSX.Element;
1857
+ export declare function WorkbenchLeftPane({ rootDir, bridge, defaultTab, revealFileTreeRequest, onOpenPanel, onReloadAgentPlugins, onCollapse, className, }: WorkbenchLeftPaneProps): JSX.Element;
1853
1858
 
1854
1859
  declare interface WorkbenchLeftPaneOpenPanelConfig {
1855
1860
  id: string;
@@ -1867,6 +1872,7 @@ export declare interface WorkbenchLeftPaneProps {
1867
1872
  seq: number;
1868
1873
  } | null;
1869
1874
  onOpenPanel?: (config: WorkbenchLeftPaneOpenPanelConfig) => void;
1875
+ onReloadAgentPlugins?: () => void | Promise<unknown>;
1870
1876
  onCollapse?: () => void;
1871
1877
  className?: string;
1872
1878
  }
package/dist/workspace.js CHANGED
@@ -1,17 +1,17 @@
1
1
  var V = Object.defineProperty;
2
2
  var X = (e, t, r) => t in e ? V(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
3
3
  var T = (e, t, r) => X(e, typeof t != "symbol" ? t + "" : t, r);
4
- import { u as K, p as Q, a as Y, b as Z, D as ee } from "./WorkspaceProvider-uuxyAx3i.js";
5
- import { A as Je, C as Ge, c as Ve, d as Xe, e as Qe, F as Ye, f as Ze, M as et, g as tt, P as rt, h as at, i as nt, j as st, k as ot, R as it, S as lt, l as ct, m as dt, T as ut, U as pt, W as ft, n as mt, o as ht, q as gt, r as bt, s as yt, t as vt, v as xt, w as kt, x as wt, y as Pt, z as St, B as Ct, E as Nt, G as Et, H as Rt, I as Tt, J as It, K as Ot, L as Ft, N as Lt, O as Mt, Q as Bt, V as Wt, X as Dt, Y as Kt, Z as $t, _ as jt, $ as zt, a0 as Ht, a1 as Ut, a2 as _t, a3 as qt, a4 as At, a5 as Jt, a6 as Gt, a7 as Vt, a8 as Xt, a9 as Qt, aa as Yt, ab as Zt, ac as er, ad as tr, ae as rr, af as ar, ag as nr, ah as sr, ai as or, aj as ir, ak as lr, al as cr, am as dr, an as ur, ao as pr, ap as fr, aq as mr, ar as hr, as as gr } from "./WorkspaceProvider-uuxyAx3i.js";
4
+ import { u as K, p as Q, a as Y, b as Z, D as ee } from "./WorkspaceProvider-DkZAxsYo.js";
5
+ import { A as Je, C as Ge, c as Ve, d as Xe, e as Qe, F as Ye, f as Ze, M as et, g as tt, P as rt, h as at, i as nt, j as st, k as ot, R as it, S as lt, l as ct, m as dt, T as ut, U as pt, W as ft, n as mt, o as ht, q as gt, r as bt, s as yt, t as vt, v as xt, w as kt, x as wt, y as Pt, z as St, B as Ct, E as Nt, G as Et, H as Rt, I as Tt, J as It, K as Ot, L as Ft, N as Lt, O as Mt, Q as Bt, V as Wt, X as Dt, Y as Kt, Z as $t, _ as jt, $ as zt, a0 as Ht, a1 as Ut, a2 as _t, a3 as qt, a4 as At, a5 as Jt, a6 as Gt, a7 as Vt, a8 as Xt, a9 as Qt, aa as Yt, ab as Zt, ac as er, ad as tr, ae as rr, af as ar, ag as nr, ah as sr, ai as or, aj as ir, ak as lr, al as cr, am as dr, an as ur, ao as pr, ap as fr, aq as mr, ar as hr, as as gr } from "./WorkspaceProvider-DkZAxsYo.js";
6
6
  import { c as C } from "./utils-B6yFEsav.js";
7
- import { C as yr, T as vr, W as xr, b as kr } from "./WorkspaceLoadingState-EratTJfG.js";
7
+ import { C as yr, T as vr, W as xr, b as kr } from "./WorkspaceLoadingState-yp4vNmrT.js";
8
8
  import { jsx as a, jsxs as g, Fragment as te } from "react/jsx-runtime";
9
9
  import { Button as P, Sheet as re, SheetContent as ae, SheetHeader as ne, SheetTitle as se, SheetDescription as oe, EmptyState as ie, Kbd as I, ErrorState as le, IconButton as O } from "@hachej/boring-ui-kit";
10
10
  import { Toaster as Pr, dismissToast as Sr, toast as Cr } from "@hachej/boring-ui-kit";
11
11
  import { useSyncExternalStore as $, useState as N, useEffect as k, useRef as S, useCallback as b, useMemo as w, Suspense as ce, Component as de } from "react";
12
12
  import { C as Er, c as Rr } from "./CodeEditor-DQqOn4xz.js";
13
- import { FileTree as Ir } from "./FileTree-DNIzusWa.js";
14
- import { MarkdownEditor as Fr } from "./MarkdownEditor-DhVfKSAq.js";
13
+ import { FileTree as Ir } from "./FileTree-CVsvICGP.js";
14
+ import { MarkdownEditor as Fr } from "./MarkdownEditor-BvaGmzWP.js";
15
15
  import { MenuIcon as ue, PanelLeftOpenIcon as pe, PanelLeftCloseIcon as fe, PinIcon as me, CheckIcon as he, CopyIcon as ge } from "lucide-react";
16
16
  import { d as Mr } from "./panel-DnvDNQac.js";
17
17
  function We() {
@@ -1,6 +1,6 @@
1
1
  # Workspace Interfaces
2
2
 
3
- Last updated: 2026-05-02
3
+ Last updated: 2026-06-12
4
4
 
5
5
  `@hachej/boring-workspace` is a workspace UI and bridge package. The app shell owns
6
6
  auth, routing, application persistence, and the concrete chat component.
@@ -1,9 +1,9 @@
1
1
  # Plugin Structure
2
2
 
3
3
  Canonical quick reference for boring-ui plugin layouts. For the full current
4
- contract, see [`PLUGIN_SYSTEM.md`](./PLUGIN_SYSTEM.md). For future hosted/runtime
5
- architecture, see the repo-level
6
- [`docs/runtime-plugin-v2-hot-reload-plan.md`](../../../docs/runtime-plugin-v2-hot-reload-plan.md).
4
+ contract, see [`PLUGIN_SYSTEM.md`](./PLUGIN_SYSTEM.md). For the historical
5
+ hosted/runtime architecture exploration, see the archived repo-level
6
+ [`docs/plans/archive/runtime-plugin-v2-hot-reload-plan.md`](../../../docs/plans/archive/runtime-plugin-v2-hot-reload-plan.md).
7
7
 
8
8
  ## Generated/runtime plugin
9
9
 
@@ -6,8 +6,9 @@ example `PLUGIN_SYSTEM.md §4.5`), so keep headings stable when editing.
6
6
 
7
7
  This document describes the implementation as it exists now. Historical
8
8
  implementation plans live under `packages/workspace/docs/plans/archive/`.
9
- For future generated/hosted runtime-plugin architecture, see the repo-level
10
- `docs/runtime-plugin-v2-hot-reload-plan.md`.
9
+ For the historical generated/hosted runtime-plugin architecture exploration,
10
+ see the archived repo-level
11
+ `docs/plans/archive/runtime-plugin-v2-hot-reload-plan.md`.
11
12
 
12
13
  ## Contents
13
14
 
@@ -27,7 +28,7 @@ For future generated/hosted runtime-plugin architecture, see the repo-level
27
28
  | Term | Definition |
28
29
  | --- | --- |
29
30
  | **App/internal plugin** | Trusted package composed by the app at boot. May export `boring.server`, Fastify routes, agent tools, providers, catalogs, and domain APIs. Server changes require restart/redeploy. |
30
- | **Runtime/generated plugin** | Workspace-local plugin under `.pi/extensions/<id>/`, usually produced by `boring-ui-plugin scaffold`. It is hot-loaded for front/Pi resources, but must not rely on dynamic backend routes. |
31
+ | **Runtime/generated plugin** | Workspace-local plugin under `.pi/extensions/<id>/`, usually produced by `boring-ui-plugin scaffold`. Also called an **external** plugin in trust-model terms. It is hot-loaded for front/Pi resources, but must not rely on dynamic backend routes. |
31
32
  | **Boring plugin package** | Node package with `package.json#boring` and/or `package.json#pi`. App-default packages are declared in `package.json#boring.defaultPluginPackages` or passed to `createWorkspaceAgentServer`. |
32
33
  | **Boring front factory** | Default export of `boring.front`: `(api: BoringFrontAPI) => void | Promise<void>`. Usually created with `definePlugin({ ... })`. |
33
34
  | **Workspace server plugin** | Trusted boot-time server contribution returned by `defineServerPlugin({ ... })` or a compatible object. May include routes, tools, system prompt, Pi resources, and provisioning. |
@@ -35,6 +36,25 @@ For future generated/hosted runtime-plugin architecture, see the repo-level
35
36
  | **Revision** | Per-plugin monotonic integer. It bumps on signature changes and is appended to browser front imports for cache busting. |
36
37
  | **Surface resolver** | Front contribution that maps a typed request such as `open-path` to a panel id/title/params. File opens should route through this path. |
37
38
 
39
+ ### 1.1 Trust model (internal vs external)
40
+
41
+ The two tiers differ by **provenance and trust**, not just lifecycle:
42
+
43
+ | | App/internal plugin | Runtime/generated ("external") plugin |
44
+ | --- | --- | --- |
45
+ | Provenance | App-owned package, composed at boot | Workspace-local `.pi/extensions/`, often agent-generated |
46
+ | Trust | Trusted app module | Trusted **only** as local developer/workspace code |
47
+ | Front | Native React in the host tree | Native React (local trusted context) |
48
+ | Server/routes/tools | Full power: Fastify routes, static `agentTools`, providers, domain APIs | Route-free; no `boring.server`; backend work goes through Pi tools |
49
+ | Reload | Restart/redeploy | `/reload` hot-swaps front + Pi resources |
50
+
51
+ Plugin tools' `execute()` run in the **host Node process and bypass the
52
+ sandbox by design**; plugin loading is local-mode-only (skipped under
53
+ `vercel-sandbox`). Hosted/marketplace plugins — untrusted external code that
54
+ would need iframe fronts and sandbox-proxied tools — are **not implemented**;
55
+ that provenance/permission model is a future phase (see §7 and the archived
56
+ repo-level `docs/plans/archive/runtime-plugin-trust-modes-plan.md`).
57
+
38
58
  ---
39
59
 
40
60
  ## 2. End-to-end behaviour
@@ -241,6 +261,27 @@ boring-ui-plugin verify <name>
241
261
  Do not teach agents to copy `packages/plugin-cli/templates/plugin` for generated runtime
242
262
  plugins. That template is an app/internal publishable package example.
243
263
 
264
+ ### 4.7 Chat slash commands
265
+
266
+ Plugins can contribute **chat `/slash` commands** — a separate surface from the
267
+ command-palette `commands` output. There is no `!` bang-command; the composer
268
+ parses `/name args` only.
269
+
270
+ - **Front registry** (`packages/agent/src/front/slashCommands/registry.ts`):
271
+ `SlashCommand { name, description, kind?, source?, sourcePlugin?, handler }`.
272
+ `kind: 'local'` runs in the browser; `kind: 'skill'` is forwarded to the Pi
273
+ agent as `skill: <name>\n\n<args>`. The chat composer (`PiChatPanel`)
274
+ intercepts `/` input before it reaches the harness.
275
+ - **Server commands** are listed from the harness via
276
+ `GET /api/v1/agent/commands` (consumed by `useServerCommands`) and executed
277
+ via `POST /api/v1/agent/commands/execute` — this route **bypasses the
278
+ harness chat loop**; Pi executes the command natively. `source`
279
+ (`extension`/`prompt`/`skill`) and `sourcePlugin` tag where a command came
280
+ from in the picker.
281
+ - **How a plugin contributes one**: ship a Pi extension/prompt/skill in its
282
+ `package.json#pi` block — Pi-sourced commands appear automatically. No
283
+ harness changes required.
284
+
244
285
  ---
245
286
 
246
287
  ## 5. Key algorithms
@@ -327,7 +368,8 @@ while capturing the front factory.
327
368
  plugin RPC for purity.
328
369
  - Dynamic provider/binding trees for runtime hot-loaded plugins in this PR.
329
370
  - Marketplace signing/provenance/permissions. Those belong to the next runtime
330
- plugin architecture phase.
371
+ plugin architecture phase. Promotion (external → internal) is designed as an
372
+ explicit admin action with pinned provenance + restart — not implemented yet.
331
373
 
332
374
  ---
333
375