@hachej/boring-workspace 0.1.13 → 0.1.14

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 (34) hide show
  1. package/README.md +270 -42
  2. package/dist/CommandPalette-NOEOVkN2.js +5714 -0
  3. package/dist/{FileTree-BVfqs3rR.js → FileTree-Dl-qUAB0.js} +9 -9
  4. package/dist/MarkdownEditor-yc6mFsnI.js +533 -0
  5. package/dist/{WorkspaceLoadingState-BjZGQLS_.js → WorkspaceLoadingState-CSZfENWe.js} +145 -124
  6. package/dist/agent-tool-DEtfQPVB.d.ts +100 -0
  7. package/dist/app-front.d.ts +79 -67
  8. package/dist/app-front.js +253 -241
  9. package/dist/app-server.d.ts +17 -12
  10. package/dist/app-server.js +80 -10
  11. package/dist/{bootstrapServer-BRUqUpVW.d.ts → bootstrapServer-BreQ9QBc.d.ts} +8 -2
  12. package/dist/server.d.ts +10 -32
  13. package/dist/server.js +22 -127
  14. package/dist/shared.d.ts +1 -2
  15. package/dist/testing.d.ts +0 -63
  16. package/dist/testing.js +2248 -2401
  17. package/dist/workspace.css +1616 -974
  18. package/dist/workspace.d.ts +111 -450
  19. package/dist/workspace.js +417 -1635
  20. package/docs/INTERFACES.md +2 -2
  21. package/docs/PLUGIN_STRUCTURE.md +1 -1
  22. package/docs/plans/ASK_USER_QUESTIONS_PLUGIN_SPEC.md +131 -263
  23. package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +29 -27
  24. package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +12 -12
  25. package/docs/plans/PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md +366 -0
  26. package/docs/plans/README.md +2 -0
  27. package/docs/plans/archive/PLUGIN_MODEL.md +14 -14
  28. package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +2 -3
  29. package/docs/plans/archive/WORKSPACE_V2_PLAN.md +1 -1
  30. package/package.json +3 -6
  31. package/dist/CommandPalette-Dme9em28.js +0 -5506
  32. package/dist/MarkdownEditor-CcCDF65H.js +0 -502
  33. package/dist/agent-tool-NvxKfist.d.ts +0 -28
  34. package/dist/explorer-DtLUnuah.d.ts +0 -129
@@ -1,9 +1,9 @@
1
- import { CreateAgentAppOptions, PiPackageSource } from '@hachej/boring-agent/server';
1
+ import { PiPackageSource, CreateAgentAppOptions } from '@hachej/boring-agent/server';
2
2
  export { PiPackageSource as WorkspacePiPackageSource } from '@hachej/boring-agent/server';
3
3
  import { FastifyInstance } from 'fastify';
4
- import { S as ServerBootstrapOptions, W as WorkspaceProvisioningContribution, a as WorkspaceRouteContribution } from './bootstrapServer-BRUqUpVW.js';
5
- export { C as ComposeServerPluginsOptions, b as WorkspaceServerPlugin, c as composeServerPlugins, d as defineServerPlugin } from './bootstrapServer-BRUqUpVW.js';
6
- import './agent-tool-NvxKfist.js';
4
+ import { S as ServerBootstrapOptions, c as createInMemoryBridge, W as WorkspaceServerPlugin, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution } from './bootstrapServer-BreQ9QBc.js';
5
+ export { C as ComposeServerPluginsOptions, d as composeServerPlugins, e as defineServerPlugin } from './bootstrapServer-BreQ9QBc.js';
6
+ import './agent-tool-DEtfQPVB.js';
7
7
 
8
8
  /**
9
9
  * Standalone workspace + agent Fastify composition.
@@ -21,27 +21,32 @@ interface WorkspaceAgentResourceLoaderOptions {
21
21
  type WorkspaceAgentCreateOptions = Omit<CreateAgentAppOptions, "resourceLoaderOptions"> & {
22
22
  resourceLoaderOptions?: WorkspaceAgentResourceLoaderOptions;
23
23
  };
24
+ interface WorkspaceAgentServerPluginContext {
25
+ workspaceRoot: string;
26
+ bridge: ReturnType<typeof createInMemoryBridge>;
27
+ }
28
+ type WorkspaceAgentServerPluginFactory = (context: WorkspaceAgentServerPluginContext) => WorkspaceServerPlugin;
24
29
  interface CreateWorkspaceAgentServerOptions extends WorkspaceAgentCreateOptions, Pick<ServerBootstrapOptions, "plugins" | "defaults" | "excludeDefaults"> {
30
+ pluginFactories?: WorkspaceAgentServerPluginFactory[];
25
31
  provisionWorkspace?: boolean;
26
32
  workspaceProvisioning?: {
27
33
  force?: boolean;
28
34
  };
29
- /**
30
- * Whether exec_ui should stat-check file paths against the workspaceRoot
31
- * before queueing the command. Defaults to true only for local host-backed
32
- * modes (direct/local). Remote sandbox modes should leave this false because
33
- * workspace files may not exist on the host server running Fastify.
34
- */
35
35
  validateUiPaths?: boolean;
36
36
  }
37
37
 
38
38
  interface WorkspaceAgentServerPluginCollection {
39
39
  provisioningContributions: WorkspaceProvisioningContribution[];
40
40
  routeContributions: WorkspaceRouteContribution[];
41
+ preservedUiStateKeys: string[];
41
42
  agentOptions: Pick<WorkspaceAgentCreateOptions, "extraTools" | "systemPromptAppend" | "resourceLoaderOptions">;
42
43
  }
43
- interface CollectWorkspaceAgentServerPluginsOptions extends Pick<WorkspaceAgentCreateOptions, "workspaceRoot" | "systemPromptAppend" | "resourceLoaderOptions">, Pick<ServerBootstrapOptions, "plugins" | "defaults" | "excludeDefaults"> {
44
+ interface CollectWorkspaceAgentServerPluginsOptions extends Pick<ServerBootstrapOptions, "plugins" | "defaults" | "excludeDefaults"> {
45
+ workspaceRoot?: string;
46
+ systemPromptAppend?: string;
47
+ resourceLoaderOptions?: WorkspaceAgentResourceLoaderOptions;
44
48
  }
49
+ declare function buildWorkspaceContextPrompt(): string;
45
50
  declare function collectWorkspaceAgentServerPlugins(opts?: CollectWorkspaceAgentServerPluginsOptions): WorkspaceAgentServerPluginCollection;
46
51
  declare function provisionWorkspaceAgentServer(opts: {
47
52
  workspaceRoot: string;
@@ -50,4 +55,4 @@ declare function provisionWorkspaceAgentServer(opts: {
50
55
  }): Promise<void>;
51
56
  declare function createWorkspaceAgentServer(opts?: CreateWorkspaceAgentServerOptions): Promise<FastifyInstance>;
52
57
 
53
- export { type CollectWorkspaceAgentServerPluginsOptions, type CreateWorkspaceAgentServerOptions, type WorkspaceAgentResourceLoaderOptions, type WorkspaceAgentServerPluginCollection, WorkspaceProvisioningContribution, WorkspaceRouteContribution, collectWorkspaceAgentServerPlugins, createWorkspaceAgentServer, provisionWorkspaceAgentServer };
58
+ export { type CollectWorkspaceAgentServerPluginsOptions, type CreateWorkspaceAgentServerOptions, type WorkspaceAgentResourceLoaderOptions, type WorkspaceAgentServerPluginCollection, type WorkspaceAgentServerPluginContext, type WorkspaceAgentServerPluginFactory, WorkspaceProvisioningContribution, WorkspaceRouteContribution, WorkspaceServerPlugin, buildWorkspaceContextPrompt, collectWorkspaceAgentServerPlugins, createWorkspaceAgentServer, provisionWorkspaceAgentServer };
@@ -5,7 +5,54 @@ import {
5
5
  provisionRuntimeWorkspace,
6
6
  resolveMode
7
7
  } from "@hachej/boring-agent/server";
8
- import { join } from "path";
8
+ import { join as join2 } from "path";
9
+
10
+ // src/server/boringSystemPrompt.ts
11
+ import { existsSync, readFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ import { fileURLToPath } from "url";
14
+ var __dirname = dirname(fileURLToPath(import.meta.url));
15
+ function resolveDocsPath() {
16
+ const override = process.env.BORING_DOCS_PATH;
17
+ if (override) return override;
18
+ const candidates = [
19
+ join(__dirname, "../docs"),
20
+ // dist/server.js → packages/workspace/docs/
21
+ join(__dirname, "../../docs")
22
+ // src/server/*.ts → packages/workspace/docs/
23
+ ];
24
+ return candidates.find(existsSync) ?? null;
25
+ }
26
+ function readDocOrFallback(docsPath, name) {
27
+ const file = join(docsPath, name);
28
+ try {
29
+ return existsSync(file) ? readFileSync(file, "utf-8").trim() : "";
30
+ } catch {
31
+ return "";
32
+ }
33
+ }
34
+ function buildBoringSystemPrompt() {
35
+ const docsPath = resolveDocsPath();
36
+ const intro = `You are an expert agent operating inside boring-ui, an open-source workspace for building agent-powered products. You help users by reading files, executing commands, editing code, and opening workspace panels.`;
37
+ if (!docsPath) {
38
+ return intro;
39
+ }
40
+ const plugins = readDocOrFallback(docsPath, "plugins.md");
41
+ const panels = readDocOrFallback(docsPath, "panels.md");
42
+ const bridge = readDocOrFallback(docsPath, "bridge.md");
43
+ const sections = [
44
+ plugins && `<boring-ui-docs topic="plugin-system">
45
+ ${plugins}
46
+ </boring-ui-docs>`,
47
+ panels && `<boring-ui-docs topic="panel-components">
48
+ ${panels}
49
+ </boring-ui-docs>`,
50
+ bridge && `<boring-ui-docs topic="ui-bridge">
51
+ ${bridge}
52
+ </boring-ui-docs>`
53
+ ].filter(Boolean);
54
+ return [intro, ...sections].join("\n\n");
55
+ }
9
56
 
10
57
  // src/server/bridge/createInMemoryBridge.ts
11
58
  var MAX_PENDING_COMMANDS = 1e3;
@@ -222,11 +269,11 @@ function createExecUiTool(uiBridge, opts = {}) {
222
269
  " \u2014 Open a plugin-owned target through the workspace",
223
270
  " surface resolver registry. Use this when a plugin",
224
271
  " defines the mapping from domain target to panel",
225
- " component, for example a data catalog row.",
272
+ " component, for example a catalog row.",
226
273
  " Example: {kind:'openSurface', params:{",
227
- " kind:'data-catalog.open-row',",
274
+ " kind:'my-plugin.open-row',",
228
275
  " target:'orders_daily',",
229
- " meta:{catalogId:'data-catalog'}}}",
276
+ " meta:{catalogId:'my-plugin'}}}",
230
277
  "",
231
278
  " closePanel params: { id: string }",
232
279
  " closeWorkbenchLeftPane params: {}",
@@ -395,7 +442,11 @@ function uiRoutes(app, opts, done) {
395
442
  async (request, reply) => {
396
443
  const body = request.body;
397
444
  const bridge = await resolveBridge(request);
398
- await bridge.setState(body.state);
445
+ const current = await bridge.getState() ?? {};
446
+ const preserved = Object.fromEntries(
447
+ (opts.preserveStateKeys ?? []).filter((key) => !(key in body.state) && key in current).map((key) => [key, current[key]])
448
+ );
449
+ await bridge.setState({ ...body.state, ...preserved });
399
450
  return reply.code(204).send();
400
451
  }
401
452
  );
@@ -608,6 +659,11 @@ function validateServerPlugin(plugin) {
608
659
  if (plugin.routes !== void 0 && typeof plugin.routes !== "function") {
609
660
  fail(plugin.id, "routes must be a Fastify plugin function when provided");
610
661
  }
662
+ if (plugin.preservedUiStateKeys !== void 0) {
663
+ if (!Array.isArray(plugin.preservedUiStateKeys) || plugin.preservedUiStateKeys.some((key) => typeof key !== "string" || key.length === 0)) {
664
+ fail(plugin.id, "preservedUiStateKeys must be a non-empty string array when provided");
665
+ }
666
+ }
611
667
  if (plugin.provisioning !== void 0) {
612
668
  validateProvisioning(plugin.id, plugin.provisioning);
613
669
  }
@@ -663,6 +719,10 @@ function composeServerPlugins(options) {
663
719
  ...options.plugins.map((plugin) => plugin.routes),
664
720
  options.routes
665
721
  ]);
722
+ const preservedUiStateKeys = [.../* @__PURE__ */ new Set([
723
+ ...options.plugins.flatMap((plugin) => plugin.preservedUiStateKeys ?? []),
724
+ ...options.preservedUiStateKeys ?? []
725
+ ])];
666
726
  return defineServerPlugin({
667
727
  id: options.id,
668
728
  ...options.label !== void 0 ? { label: options.label } : {},
@@ -670,7 +730,8 @@ function composeServerPlugins(options) {
670
730
  ...systemPrompt ? { systemPrompt } : {},
671
731
  ...agentTools.length > 0 ? { agentTools } : {},
672
732
  ...provisioning ? { provisioning } : {},
673
- ...routes ? { routes } : {}
733
+ ...routes ? { routes } : {},
734
+ ...preservedUiStateKeys.length > 0 ? { preservedUiStateKeys } : {}
674
735
  });
675
736
  }
676
737
 
@@ -702,13 +763,15 @@ function bootstrapServer(options) {
702
763
  const piPackages = collectPiPackages(finalPlugins);
703
764
  const provisioningContributions = finalPlugins.filter((p) => p.provisioning).map((p) => ({ id: p.id, provisioning: p.provisioning }));
704
765
  const routeContributions = finalPlugins.filter((p) => p.routes).map((p) => ({ id: p.id, routes: p.routes }));
766
+ const preservedUiStateKeys = [...new Set(finalPlugins.flatMap((p) => p.preservedUiStateKeys ?? []))];
705
767
  return {
706
768
  registered: finalPlugins.map((p) => p.id),
707
769
  systemPromptAppend,
708
770
  piPackages,
709
771
  agentTools,
710
772
  provisioningContributions,
711
- routeContributions
773
+ routeContributions,
774
+ preservedUiStateKeys
712
775
  };
713
776
  }
714
777
 
@@ -728,12 +791,13 @@ function collectWorkspaceAgentServerPlugins(opts = {}) {
728
791
  plugins: opts.plugins,
729
792
  excludeDefaults: opts.excludeDefaults
730
793
  });
731
- const workspaceSkillsDir = join(workspaceRoot, ".agents", "skills");
794
+ const workspaceSkillsDir = join2(workspaceRoot, ".agents", "skills");
732
795
  const callerAdditional = opts.resourceLoaderOptions?.additionalSkillPaths ?? [];
733
796
  const callerPiPackages = opts.resourceLoaderOptions?.piPackages ?? [];
734
797
  return {
735
798
  provisioningContributions: result.provisioningContributions,
736
799
  routeContributions: result.routeContributions,
800
+ preservedUiStateKeys: result.preservedUiStateKeys,
737
801
  agentOptions: {
738
802
  extraTools: result.agentTools,
739
803
  systemPromptAppend: [opts.systemPromptAppend, result.systemPromptAppend].filter(Boolean).join("\n\n") || void 0,
@@ -762,7 +826,11 @@ async function createWorkspaceAgentServer(opts = {}) {
762
826
  const uiTools = createWorkspaceUiTools(bridge, {
763
827
  workspaceRoot: validateUiPaths ? workspaceRoot : void 0
764
828
  });
765
- const pluginCollection = collectWorkspaceAgentServerPlugins(opts);
829
+ const factoryPlugins = opts.pluginFactories?.map((factory) => factory({ workspaceRoot, bridge })) ?? [];
830
+ const pluginCollection = collectWorkspaceAgentServerPlugins({
831
+ ...opts,
832
+ plugins: [...opts.plugins ?? [], ...factoryPlugins]
833
+ });
766
834
  if (opts.provisionWorkspace !== false) {
767
835
  await provisionWorkspaceAgentServer({
768
836
  workspaceRoot,
@@ -781,17 +849,19 @@ async function createWorkspaceAgentServer(opts = {}) {
781
849
  ],
782
850
  systemPromptAppend: [
783
851
  workspaceFsCapability === "strong" ? buildWorkspaceContextPrompt() : void 0,
852
+ buildBoringSystemPrompt(),
784
853
  pluginCollection.agentOptions.systemPromptAppend
785
854
  ].filter(Boolean).join("\n\n") || void 0,
786
855
  resourceLoaderOptions: pluginCollection.agentOptions.resourceLoaderOptions
787
856
  });
788
- await app.register(uiRoutes, { bridge });
857
+ await app.register(uiRoutes, { bridge, preserveStateKeys: pluginCollection.preservedUiStateKeys });
789
858
  for (const { routes } of pluginCollection.routeContributions) {
790
859
  await app.register(routes);
791
860
  }
792
861
  return app;
793
862
  }
794
863
  export {
864
+ buildWorkspaceContextPrompt,
795
865
  collectWorkspaceAgentServerPlugins,
796
866
  composeServerPlugins,
797
867
  createWorkspaceAgentServer,
@@ -1,6 +1,8 @@
1
+ import { U as UiBridge, A as AgentTool } from './agent-tool-DEtfQPVB.js';
1
2
  import { PiPackageSource, RuntimeProvisioningContribution } from '@hachej/boring-agent/server';
2
3
  import { FastifyPluginAsync } from 'fastify';
3
- import { A as AgentTool } from './agent-tool-NvxKfist.js';
4
+
5
+ declare function createInMemoryBridge(): UiBridge;
4
6
 
5
7
  interface WorkspaceServerPlugin {
6
8
  id: string;
@@ -15,6 +17,8 @@ interface WorkspaceServerPlugin {
15
17
  agentTools?: AgentTool[];
16
18
  provisioning?: RuntimeProvisioningContribution;
17
19
  routes?: FastifyPluginAsync;
20
+ /** UI state keys owned by this plugin that browser state PUTs must not overwrite. */
21
+ preservedUiStateKeys?: string[];
18
22
  }
19
23
  declare class ServerPluginError extends Error {
20
24
  constructor(message: string);
@@ -31,6 +35,7 @@ interface ComposeServerPluginsOptions {
31
35
  agentTools?: AgentTool[];
32
36
  provisioning?: RuntimeProvisioningContribution;
33
37
  routes?: FastifyPluginAsync;
38
+ preservedUiStateKeys?: string[];
34
39
  }
35
40
  /**
36
41
  * Compose a server plugin from smaller server plugin fragments. Child
@@ -60,7 +65,8 @@ interface ServerBootstrapResult {
60
65
  agentTools: AgentTool[];
61
66
  provisioningContributions: WorkspaceProvisioningContribution[];
62
67
  routeContributions: WorkspaceRouteContribution[];
68
+ preservedUiStateKeys: string[];
63
69
  }
64
70
  declare function bootstrapServer(options: ServerBootstrapOptions): ServerBootstrapResult;
65
71
 
66
- export { type ComposeServerPluginsOptions as C, type ServerBootstrapOptions as S, type WorkspaceProvisioningContribution as W, type WorkspaceRouteContribution as a, type WorkspaceServerPlugin as b, composeServerPlugins as c, defineServerPlugin as d, type ServerBootstrapResult as e, ServerPluginError as f, bootstrapServer as g, validateServerPlugin as v };
72
+ export { type ComposeServerPluginsOptions as C, type ServerBootstrapOptions as S, type WorkspaceServerPlugin as W, type WorkspaceProvisioningContribution as a, type WorkspaceRouteContribution as b, createInMemoryBridge as c, composeServerPlugins as d, defineServerPlugin as e, type ServerBootstrapResult as f, ServerPluginError as g, bootstrapServer as h, validateServerPlugin as v };
package/dist/server.d.ts CHANGED
@@ -1,16 +1,18 @@
1
- import { U as UiBridge, E as ExplorerAdapter, S as SearchResult } from './explorer-DtLUnuah.js';
2
- export { C as CommandResult, a as UiCommand, b as UiState } from './explorer-DtLUnuah.js';
1
+ export { C as ComposeServerPluginsOptions, S as ServerBootstrapOptions, f as ServerBootstrapResult, g as ServerPluginError, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, W as WorkspaceServerPlugin, h as bootstrapServer, d as composeServerPlugins, c as createInMemoryBridge, e as defineServerPlugin, v as validateServerPlugin } from './bootstrapServer-BreQ9QBc.js';
3
2
  import { FastifyRequest, FastifyInstance } from 'fastify';
4
- import { A as AgentTool } from './agent-tool-NvxKfist.js';
5
- import { b as WorkspaceServerPlugin } from './bootstrapServer-BRUqUpVW.js';
6
- export { C as ComposeServerPluginsOptions, S as ServerBootstrapOptions, e as ServerBootstrapResult, f as ServerPluginError, W as WorkspaceProvisioningContribution, a as WorkspaceRouteContribution, g as bootstrapServer, c as composeServerPlugins, d as defineServerPlugin, v as validateServerPlugin } from './bootstrapServer-BRUqUpVW.js';
3
+ import { U as UiBridge, A as AgentTool } from './agent-tool-DEtfQPVB.js';
4
+ export { C as CommandResult, a as UiCommand, b as UiState } from './agent-tool-DEtfQPVB.js';
7
5
  export { PiPackageSource as WorkspacePiPackageSource } from '@hachej/boring-agent/server';
8
6
 
9
- declare function createInMemoryBridge(): UiBridge;
10
-
11
7
  interface UiRoutesOptions {
12
8
  bridge?: UiBridge;
13
9
  getBridge?: (request: FastifyRequest) => UiBridge | Promise<UiBridge>;
10
+ /**
11
+ * Server/plugin-owned state slots preserved across browser full-state PUTs.
12
+ * Browser UI snapshots are replace-style for normal workspace state, but
13
+ * these slots are published out-of-band by server plugins.
14
+ */
15
+ preserveStateKeys?: string[];
14
16
  }
15
17
  declare function uiRoutes(app: FastifyInstance, opts: UiRoutesOptions, done: (err?: Error) => void): void;
16
18
 
@@ -57,28 +59,4 @@ declare function createExecUiTool(uiBridge: UiBridge, opts?: ExecUiToolOptions):
57
59
  */
58
60
  declare function createWorkspaceUiTools(uiBridge: UiBridge, opts?: ExecUiToolOptions): AgentTool[];
59
61
 
60
- interface DataCatalogAgentToolOptions {
61
- name?: string;
62
- label?: string;
63
- adapter: ExplorerAdapter;
64
- defaultLimit?: number;
65
- maxLimit?: number;
66
- }
67
- interface DataCatalogSkillOptions {
68
- label?: string;
69
- toolName?: string;
70
- surfaceKind?: string;
71
- guidance?: string;
72
- }
73
- interface DataCatalogServerPluginOptions extends DataCatalogAgentToolOptions, DataCatalogSkillOptions {
74
- id?: string;
75
- }
76
- declare function formatDataCatalogSearchResult(query: string, result: SearchResult): string;
77
- declare function createDataCatalogAgentTool(options: DataCatalogAgentToolOptions): AgentTool;
78
- declare function createDataCatalogSkillPrompt(options?: DataCatalogSkillOptions): string;
79
- declare function createDataCatalogServerPlugin(options: DataCatalogServerPluginOptions): WorkspaceServerPlugin & {
80
- agentTools: AgentTool[];
81
- systemPrompt: string;
82
- };
83
-
84
- export { type DataCatalogAgentToolOptions, type DataCatalogServerPluginOptions, type DataCatalogSkillOptions, UiBridge, type UiRoutesOptions, WorkspaceServerPlugin, createDataCatalogAgentTool, createDataCatalogServerPlugin, createDataCatalogSkillPrompt, createExecUiTool, createGetUiStateTool, createInMemoryBridge, createWorkspaceUiTools, formatDataCatalogSearchResult, uiRoutes };
62
+ export { UiBridge, type UiRoutesOptions, createExecUiTool, createGetUiStateTool, createWorkspaceUiTools, uiRoutes };
package/dist/server.js CHANGED
@@ -93,7 +93,11 @@ function uiRoutes(app, opts, done) {
93
93
  async (request, reply) => {
94
94
  const body = request.body;
95
95
  const bridge = await resolveBridge(request);
96
- await bridge.setState(body.state);
96
+ const current = await bridge.getState() ?? {};
97
+ const preserved = Object.fromEntries(
98
+ (opts.preserveStateKeys ?? []).filter((key) => !(key in body.state) && key in current).map((key) => [key, current[key]])
99
+ );
100
+ await bridge.setState({ ...body.state, ...preserved });
97
101
  return reply.code(204).send();
98
102
  }
99
103
  );
@@ -333,11 +337,11 @@ function createExecUiTool(uiBridge, opts = {}) {
333
337
  " \u2014 Open a plugin-owned target through the workspace",
334
338
  " surface resolver registry. Use this when a plugin",
335
339
  " defines the mapping from domain target to panel",
336
- " component, for example a data catalog row.",
340
+ " component, for example a catalog row.",
337
341
  " Example: {kind:'openSurface', params:{",
338
- " kind:'data-catalog.open-row',",
342
+ " kind:'my-plugin.open-row',",
339
343
  " target:'orders_daily',",
340
- " meta:{catalogId:'data-catalog'}}}",
344
+ " meta:{catalogId:'my-plugin'}}}",
341
345
  "",
342
346
  " closePanel params: { id: string }",
343
347
  " closeWorkbenchLeftPane params: {}",
@@ -599,6 +603,11 @@ function validateServerPlugin(plugin) {
599
603
  if (plugin.routes !== void 0 && typeof plugin.routes !== "function") {
600
604
  fail(plugin.id, "routes must be a Fastify plugin function when provided");
601
605
  }
606
+ if (plugin.preservedUiStateKeys !== void 0) {
607
+ if (!Array.isArray(plugin.preservedUiStateKeys) || plugin.preservedUiStateKeys.some((key) => typeof key !== "string" || key.length === 0)) {
608
+ fail(plugin.id, "preservedUiStateKeys must be a non-empty string array when provided");
609
+ }
610
+ }
602
611
  if (plugin.provisioning !== void 0) {
603
612
  validateProvisioning(plugin.id, plugin.provisioning);
604
613
  }
@@ -654,6 +663,10 @@ function composeServerPlugins(options) {
654
663
  ...options.plugins.map((plugin) => plugin.routes),
655
664
  options.routes
656
665
  ]);
666
+ const preservedUiStateKeys = [.../* @__PURE__ */ new Set([
667
+ ...options.plugins.flatMap((plugin) => plugin.preservedUiStateKeys ?? []),
668
+ ...options.preservedUiStateKeys ?? []
669
+ ])];
657
670
  return defineServerPlugin({
658
671
  id: options.id,
659
672
  ...options.label !== void 0 ? { label: options.label } : {},
@@ -661,7 +674,8 @@ function composeServerPlugins(options) {
661
674
  ...systemPrompt ? { systemPrompt } : {},
662
675
  ...agentTools.length > 0 ? { agentTools } : {},
663
676
  ...provisioning ? { provisioning } : {},
664
- ...routes ? { routes } : {}
677
+ ...routes ? { routes } : {},
678
+ ...preservedUiStateKeys.length > 0 ? { preservedUiStateKeys } : {}
665
679
  });
666
680
  }
667
681
 
@@ -693,145 +707,26 @@ function bootstrapServer(options) {
693
707
  const piPackages = collectPiPackages(finalPlugins);
694
708
  const provisioningContributions = finalPlugins.filter((p) => p.provisioning).map((p) => ({ id: p.id, provisioning: p.provisioning }));
695
709
  const routeContributions = finalPlugins.filter((p) => p.routes).map((p) => ({ id: p.id, routes: p.routes }));
710
+ const preservedUiStateKeys = [...new Set(finalPlugins.flatMap((p) => p.preservedUiStateKeys ?? []))];
696
711
  return {
697
712
  registered: finalPlugins.map((p) => p.id),
698
713
  systemPromptAppend,
699
714
  piPackages,
700
715
  agentTools,
701
716
  provisioningContributions,
702
- routeContributions
703
- };
704
- }
705
-
706
- // src/plugins/dataCatalogPlugin/shared/constants.ts
707
- var DATA_CATALOG_PLUGIN_ID = "data-catalog";
708
- var DATA_CATALOG_DEFAULT_TOOL_NAME = "query_data_catalog";
709
- var DATA_CATALOG_ROW_SURFACE_KIND = "data-catalog.open-row";
710
-
711
- // src/plugins/dataCatalogPlugin/server/index.ts
712
- function textResult(text, details) {
713
- return { content: [{ type: "text", text }], details };
714
- }
715
- function errorResult(text) {
716
- return { content: [{ type: "text", text }], isError: true };
717
- }
718
- function clampLimit(value, fallback, max) {
719
- const numeric = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
720
- if (!Number.isFinite(numeric)) return fallback;
721
- return Math.max(1, Math.min(max, Math.floor(numeric)));
722
- }
723
- function normalizeLimitOptions(options) {
724
- const rawMax = options.maxLimit ?? 50;
725
- const maxLimit = typeof rawMax === "number" && Number.isFinite(rawMax) ? Math.max(1, Math.floor(rawMax)) : 50;
726
- const rawDefault = options.defaultLimit ?? 20;
727
- const defaultLimit = typeof rawDefault === "number" && Number.isFinite(rawDefault) ? Math.max(1, Math.min(maxLimit, Math.floor(rawDefault))) : Math.min(20, maxLimit);
728
- return { defaultLimit, maxLimit };
729
- }
730
- function formatBadge(row) {
731
- const parts = [
732
- row.leading?.code,
733
- ...(row.trailing ?? []).map((badge) => badge.code),
734
- row.meta
735
- ].filter(Boolean);
736
- return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
737
- }
738
- function formatDataCatalogSearchResult(query, result) {
739
- if (result.items.length === 0) {
740
- return `No ${query ? `results for "${query}"` : "catalog results"}.`;
741
- }
742
- const lines = result.items.map((row) => {
743
- const subtitle = row.subtitle ? ` \u2014 ${row.subtitle}` : "";
744
- return `${row.id}: ${row.title}${subtitle}${formatBadge(row)}`;
745
- });
746
- const total = Number.isFinite(result.total) ? result.total : result.items.length;
747
- return `Found ${total} results (showing ${result.items.length}):
748
-
749
- ${lines.join("\n")}`;
750
- }
751
- function createDataCatalogAgentTool(options) {
752
- const name = options.name ?? DATA_CATALOG_DEFAULT_TOOL_NAME;
753
- const label = options.label ?? "data catalog";
754
- const { defaultLimit, maxLimit } = normalizeLimitOptions(options);
755
- return {
756
- name,
757
- description: `Search the ${label}. Use this before opening data visualizations or asking for a specific dataset.`,
758
- parameters: {
759
- type: "object",
760
- properties: {
761
- query: {
762
- type: "string",
763
- description: "Search keywords for datasets, series, tables, or metrics."
764
- },
765
- limit: {
766
- type: "number",
767
- description: `Maximum number of results. Default ${defaultLimit}, max ${maxLimit}.`,
768
- minimum: 1,
769
- maximum: maxLimit
770
- }
771
- },
772
- required: ["query"],
773
- additionalProperties: false
774
- },
775
- async execute(params, ctx) {
776
- const query = String(params.query ?? "").trim();
777
- if (!query) return errorResult("query is required");
778
- const limit = clampLimit(params.limit, defaultLimit, maxLimit);
779
- try {
780
- const result = await options.adapter.search({
781
- query,
782
- filters: {},
783
- limit,
784
- offset: 0,
785
- signal: ctx.abortSignal
786
- });
787
- return textResult(formatDataCatalogSearchResult(query, result), result);
788
- } catch (error) {
789
- return errorResult(error instanceof Error ? error.message : String(error));
790
- }
791
- }
717
+ routeContributions,
718
+ preservedUiStateKeys
792
719
  };
793
720
  }
794
- function createDataCatalogSkillPrompt(options = {}) {
795
- const label = options.label ?? "data catalog";
796
- const toolName = options.toolName ?? DATA_CATALOG_DEFAULT_TOOL_NAME;
797
- const surfaceKind = options.surfaceKind ?? DATA_CATALOG_ROW_SURFACE_KIND;
798
- const guidance = options.guidance?.trim();
799
- return [
800
- "## Data Catalog Plugin",
801
- "",
802
- `Use \`${toolName}\` to search the ${label} before referencing datasets, series, tables, or metrics.`,
803
- `When you need to show a catalog row to the user, use the workspace UI bridge \`openSurface\` command with \`{ kind: '${surfaceKind}', target: row.id, meta: { catalogId, row } }\` so the client plugin resolver chooses the panel.`,
804
- guidance ? "" : void 0,
805
- guidance || void 0
806
- ].filter((line) => line !== void 0).join("\n");
807
- }
808
- function createDataCatalogServerPlugin(options) {
809
- const tool = createDataCatalogAgentTool(options);
810
- return defineServerPlugin({
811
- id: options.id ?? DATA_CATALOG_PLUGIN_ID,
812
- label: options.label ?? "Data Catalog",
813
- agentTools: [tool],
814
- systemPrompt: createDataCatalogSkillPrompt({
815
- label: options.label,
816
- toolName: tool.name,
817
- surfaceKind: options.surfaceKind,
818
- guidance: options.guidance
819
- })
820
- });
821
- }
822
721
  export {
823
722
  ServerPluginError,
824
723
  bootstrapServer,
825
724
  composeServerPlugins,
826
- createDataCatalogAgentTool,
827
- createDataCatalogServerPlugin,
828
- createDataCatalogSkillPrompt,
829
725
  createExecUiTool,
830
726
  createGetUiStateTool,
831
727
  createInMemoryBridge,
832
728
  createWorkspaceUiTools,
833
729
  defineServerPlugin,
834
- formatDataCatalogSearchResult,
835
730
  uiRoutes,
836
731
  validateServerPlugin
837
732
  };
package/dist/shared.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- export { C as CommandResult, E as ExplorerAdapter, c as ExplorerRow, S as SearchResult, U as UiBridge, a as UiCommand, b as UiState } from './explorer-DtLUnuah.js';
1
+ export { A as AgentTool, C as CommandResult, J as JSONSchema, T as ToolExecContext, c as ToolResult, U as UiBridge, a as UiCommand, b as UiState } from './agent-tool-DEtfQPVB.js';
2
2
  import { ComponentType } from 'react';
3
3
  import { DockviewPanelApi, DockviewApi } from 'dockview-react';
4
- export { A as AgentTool, J as JSONSchema, T as ToolExecContext, a as ToolResult } from './agent-tool-NvxKfist.js';
5
4
 
6
5
  /**
7
6
  * Shared panel and command types — no runtime deps beyond React/dockview.
package/dist/testing.d.ts CHANGED
@@ -8,12 +8,6 @@ import { ReactNode } from 'react';
8
8
  import { RenderOptions } from '@testing-library/react';
9
9
  import { RenderResult } from '@testing-library/react';
10
10
 
11
- declare type Badge = {
12
- /** 1–4 char mono code rendered as a chip. */
13
- code: string;
14
- tooltip?: string;
15
- };
16
-
17
11
  /**
18
12
  * Land on the app with a clean localStorage so persistence-sensitive tests
19
13
  * start from defaults. Pre-opens the workbench surface unless told
@@ -135,8 +129,6 @@ export declare interface CreateMockRegistryOptions {
135
129
  capabilities?: Record<string, boolean>;
136
130
  }
137
131
 
138
- export declare function createMockSeriesAdapter(): ExplorerAdapter;
139
-
140
132
  export declare function createMockSessions(opts?: CreateMockSessionsOptions): MockSessionsStore;
141
133
 
142
134
  export declare interface CreateMockSessionsOptions {
@@ -146,8 +138,6 @@ export declare interface CreateMockSessionsOptions {
146
138
  activeId?: string;
147
139
  }
148
140
 
149
- export declare function createMockTablesAdapter(): ExplorerAdapter;
150
-
151
141
  declare interface DynamicPaneConfig {
152
142
  id: string;
153
143
  component: string;
@@ -155,39 +145,6 @@ declare interface DynamicPaneConfig {
155
145
  title?: string;
156
146
  }
157
147
 
158
- declare type ExplorerAdapter = {
159
- search(args: SearchArgs): Promise<SearchResult>;
160
- /** Optional. When omitted, the explorer renders flat (no facet popover). */
161
- fetchFacets?(args: FacetsArgs): Promise<Facets>;
162
- };
163
-
164
- declare type ExplorerRow = {
165
- id: string;
166
- title: string;
167
- /** Optional muted second line (truncates with title). */
168
- subtitle?: string;
169
- /** Group key — must match one of the facet values for `groupBy`. */
170
- group?: string;
171
- /** Leading mono chip (e.g. type code, frequency). */
172
- leading?: Badge;
173
- /** Trailing mono chips for status flags (e.g. [D] derived, [LIVE]). */
174
- trailing?: Badge[];
175
- /** Right-aligned plain text for numeric metadata (e.g. "1.2M", "2.4s"). */
176
- meta?: string;
177
- };
178
-
179
- declare type Facets = Record<string, FacetValue[]>;
180
-
181
- declare type FacetsArgs = {
182
- filters: Record<string, string[]>;
183
- signal?: AbortSignal;
184
- };
185
-
186
- declare type FacetValue = {
187
- value: string;
188
- count: number;
189
- };
190
-
191
148
  export declare interface MockBridgeState {
192
149
  openPanels: PanelState[];
193
150
  activeFile: string | null;
@@ -363,26 +320,6 @@ export declare type RenderPaneResult = RenderResult & {
363
320
  registry: PanelRegistry;
364
321
  };
365
322
 
366
- declare type SearchArgs = {
367
- query: string;
368
- filters: Record<string, string[]>;
369
- /** Scope to a single group's value (only set when paginating inside a group). */
370
- group?: {
371
- key: string;
372
- value: string;
373
- };
374
- limit: number;
375
- offset: number;
376
- signal?: AbortSignal;
377
- };
378
-
379
- declare type SearchResult = {
380
- items: ExplorerRow[];
381
- /** Total count for the current scope (query + filters + optional group). */
382
- total: number;
383
- hasMore: boolean;
384
- };
385
-
386
323
  declare interface SessionItem {
387
324
  id: string;
388
325
  title: string;