@hachej/boring-workspace 0.1.12 → 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-CPQBxYFG.js → WorkspaceLoadingState-CSZfENWe.js} +178 -157
  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 +105 -23
  11. package/dist/{bootstrapServer-BRUqUpVW.d.ts → bootstrapServer-BreQ9QBc.d.ts} +8 -2
  12. package/dist/server.d.ts +13 -35
  13. package/dist/server.js +39 -137
  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 -977
  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
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
  );
@@ -162,7 +166,7 @@ data: ${JSON.stringify({ v: UI_BRIDGE_PROTOCOL_VERSION })}
162
166
 
163
167
  // src/server/ui-control/tools/uiTools.ts
164
168
  import { access } from "fs/promises";
165
- import { resolve, isAbsolute, relative } from "path";
169
+ import { resolve, isAbsolute, relative, win32 } from "path";
166
170
  function makeError(message) {
167
171
  return {
168
172
  content: [{ type: "text", text: message }],
@@ -174,9 +178,15 @@ function getPathParam(kind, params) {
174
178
  const raw = kind === "navigateToLine" ? params.file : params.path;
175
179
  return typeof raw === "string" && raw.length > 0 ? raw : void 0;
176
180
  }
181
+ function isPathAbsolute(filePath) {
182
+ return isAbsolute(filePath) || win32.isAbsolute(filePath);
183
+ }
184
+ function isOutsideWorkspaceRel(rel) {
185
+ return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isPathAbsolute(rel);
186
+ }
177
187
  function validatePathSyntax(relPath, workspaceRoot) {
178
188
  const rootHint = workspaceRoot ? ` (${workspaceRoot})` : "";
179
- if (isAbsolute(relPath)) {
189
+ if (isPathAbsolute(relPath)) {
180
190
  return {
181
191
  ok: false,
182
192
  reason: `path "${relPath}" is absolute \u2014 pass a path relative to the workspace root${rootHint}.`
@@ -198,7 +208,7 @@ async function validatePath(workspaceRoot, relPath) {
198
208
  if (!syntax.ok) return syntax;
199
209
  const resolved = resolve(workspaceRoot, relPath);
200
210
  const rel = relative(workspaceRoot, resolved);
201
- if (rel.startsWith("..") || isAbsolute(rel)) {
211
+ if (isOutsideWorkspaceRel(rel)) {
202
212
  return {
203
213
  ok: false,
204
214
  reason: `path "${relPath}" escapes the workspace root (${workspaceRoot}).`
@@ -301,13 +311,14 @@ function createExecUiTool(uiBridge, opts = {}) {
301
311
  " auto-opens if collapsed. Path must be relative to the",
302
312
  " workspace root (e.g. `src/foo.ts`, not `foo.ts` if it",
303
313
  " lives under src/).",
304
- " Recovery on file-not-found: this tool stat-checks the",
305
- " path server-side and returns an error if it doesn't",
306
- " exist. On that error, immediately call find (or",
307
- " grep) to locate the file, then call exec_ui",
308
- " openFile AGAIN using the EXACT path returned \u2014 don't",
309
- " give up and don't switch to the read tool. Repeat",
310
- " until openFile succeeds or no candidate is found.",
314
+ " Recovery on file-not-found: when the server has local",
315
+ " filesystem access, this tool stat-checks the path and",
316
+ " returns an error if it doesn't exist. On that error,",
317
+ " immediately call find (or grep) to locate the file,",
318
+ " then call exec_ui openFile AGAIN using the EXACT path",
319
+ " returned \u2014 don't give up and don't switch to the read",
320
+ " tool. Repeat until openFile succeeds or no candidate",
321
+ " is found.",
311
322
  " Example: {kind:'openFile', params:{path:'README.md'}}",
312
323
  "",
313
324
  " openPanel params: { id: string, component: string,",
@@ -326,11 +337,11 @@ function createExecUiTool(uiBridge, opts = {}) {
326
337
  " \u2014 Open a plugin-owned target through the workspace",
327
338
  " surface resolver registry. Use this when a plugin",
328
339
  " defines the mapping from domain target to panel",
329
- " component, for example a data catalog row.",
340
+ " component, for example a catalog row.",
330
341
  " Example: {kind:'openSurface', params:{",
331
- " kind:'data-catalog.open-row',",
342
+ " kind:'my-plugin.open-row',",
332
343
  " target:'orders_daily',",
333
- " meta:{catalogId:'data-catalog'}}}",
344
+ " meta:{catalogId:'my-plugin'}}}",
334
345
  "",
335
346
  " closePanel params: { id: string }",
336
347
  " closeWorkbenchLeftPane params: {}",
@@ -592,6 +603,11 @@ function validateServerPlugin(plugin) {
592
603
  if (plugin.routes !== void 0 && typeof plugin.routes !== "function") {
593
604
  fail(plugin.id, "routes must be a Fastify plugin function when provided");
594
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
+ }
595
611
  if (plugin.provisioning !== void 0) {
596
612
  validateProvisioning(plugin.id, plugin.provisioning);
597
613
  }
@@ -647,6 +663,10 @@ function composeServerPlugins(options) {
647
663
  ...options.plugins.map((plugin) => plugin.routes),
648
664
  options.routes
649
665
  ]);
666
+ const preservedUiStateKeys = [.../* @__PURE__ */ new Set([
667
+ ...options.plugins.flatMap((plugin) => plugin.preservedUiStateKeys ?? []),
668
+ ...options.preservedUiStateKeys ?? []
669
+ ])];
650
670
  return defineServerPlugin({
651
671
  id: options.id,
652
672
  ...options.label !== void 0 ? { label: options.label } : {},
@@ -654,7 +674,8 @@ function composeServerPlugins(options) {
654
674
  ...systemPrompt ? { systemPrompt } : {},
655
675
  ...agentTools.length > 0 ? { agentTools } : {},
656
676
  ...provisioning ? { provisioning } : {},
657
- ...routes ? { routes } : {}
677
+ ...routes ? { routes } : {},
678
+ ...preservedUiStateKeys.length > 0 ? { preservedUiStateKeys } : {}
658
679
  });
659
680
  }
660
681
 
@@ -686,145 +707,26 @@ function bootstrapServer(options) {
686
707
  const piPackages = collectPiPackages(finalPlugins);
687
708
  const provisioningContributions = finalPlugins.filter((p) => p.provisioning).map((p) => ({ id: p.id, provisioning: p.provisioning }));
688
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 ?? []))];
689
711
  return {
690
712
  registered: finalPlugins.map((p) => p.id),
691
713
  systemPromptAppend,
692
714
  piPackages,
693
715
  agentTools,
694
716
  provisioningContributions,
695
- routeContributions
696
- };
697
- }
698
-
699
- // src/plugins/dataCatalogPlugin/shared/constants.ts
700
- var DATA_CATALOG_PLUGIN_ID = "data-catalog";
701
- var DATA_CATALOG_DEFAULT_TOOL_NAME = "query_data_catalog";
702
- var DATA_CATALOG_ROW_SURFACE_KIND = "data-catalog.open-row";
703
-
704
- // src/plugins/dataCatalogPlugin/server/index.ts
705
- function textResult(text, details) {
706
- return { content: [{ type: "text", text }], details };
707
- }
708
- function errorResult(text) {
709
- return { content: [{ type: "text", text }], isError: true };
710
- }
711
- function clampLimit(value, fallback, max) {
712
- const numeric = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
713
- if (!Number.isFinite(numeric)) return fallback;
714
- return Math.max(1, Math.min(max, Math.floor(numeric)));
715
- }
716
- function normalizeLimitOptions(options) {
717
- const rawMax = options.maxLimit ?? 50;
718
- const maxLimit = typeof rawMax === "number" && Number.isFinite(rawMax) ? Math.max(1, Math.floor(rawMax)) : 50;
719
- const rawDefault = options.defaultLimit ?? 20;
720
- const defaultLimit = typeof rawDefault === "number" && Number.isFinite(rawDefault) ? Math.max(1, Math.min(maxLimit, Math.floor(rawDefault))) : Math.min(20, maxLimit);
721
- return { defaultLimit, maxLimit };
722
- }
723
- function formatBadge(row) {
724
- const parts = [
725
- row.leading?.code,
726
- ...(row.trailing ?? []).map((badge) => badge.code),
727
- row.meta
728
- ].filter(Boolean);
729
- return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
730
- }
731
- function formatDataCatalogSearchResult(query, result) {
732
- if (result.items.length === 0) {
733
- return `No ${query ? `results for "${query}"` : "catalog results"}.`;
734
- }
735
- const lines = result.items.map((row) => {
736
- const subtitle = row.subtitle ? ` \u2014 ${row.subtitle}` : "";
737
- return `${row.id}: ${row.title}${subtitle}${formatBadge(row)}`;
738
- });
739
- const total = Number.isFinite(result.total) ? result.total : result.items.length;
740
- return `Found ${total} results (showing ${result.items.length}):
741
-
742
- ${lines.join("\n")}`;
743
- }
744
- function createDataCatalogAgentTool(options) {
745
- const name = options.name ?? DATA_CATALOG_DEFAULT_TOOL_NAME;
746
- const label = options.label ?? "data catalog";
747
- const { defaultLimit, maxLimit } = normalizeLimitOptions(options);
748
- return {
749
- name,
750
- description: `Search the ${label}. Use this before opening data visualizations or asking for a specific dataset.`,
751
- parameters: {
752
- type: "object",
753
- properties: {
754
- query: {
755
- type: "string",
756
- description: "Search keywords for datasets, series, tables, or metrics."
757
- },
758
- limit: {
759
- type: "number",
760
- description: `Maximum number of results. Default ${defaultLimit}, max ${maxLimit}.`,
761
- minimum: 1,
762
- maximum: maxLimit
763
- }
764
- },
765
- required: ["query"],
766
- additionalProperties: false
767
- },
768
- async execute(params, ctx) {
769
- const query = String(params.query ?? "").trim();
770
- if (!query) return errorResult("query is required");
771
- const limit = clampLimit(params.limit, defaultLimit, maxLimit);
772
- try {
773
- const result = await options.adapter.search({
774
- query,
775
- filters: {},
776
- limit,
777
- offset: 0,
778
- signal: ctx.abortSignal
779
- });
780
- return textResult(formatDataCatalogSearchResult(query, result), result);
781
- } catch (error) {
782
- return errorResult(error instanceof Error ? error.message : String(error));
783
- }
784
- }
717
+ routeContributions,
718
+ preservedUiStateKeys
785
719
  };
786
720
  }
787
- function createDataCatalogSkillPrompt(options = {}) {
788
- const label = options.label ?? "data catalog";
789
- const toolName = options.toolName ?? DATA_CATALOG_DEFAULT_TOOL_NAME;
790
- const surfaceKind = options.surfaceKind ?? DATA_CATALOG_ROW_SURFACE_KIND;
791
- const guidance = options.guidance?.trim();
792
- return [
793
- "## Data Catalog Plugin",
794
- "",
795
- `Use \`${toolName}\` to search the ${label} before referencing datasets, series, tables, or metrics.`,
796
- `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.`,
797
- guidance ? "" : void 0,
798
- guidance || void 0
799
- ].filter((line) => line !== void 0).join("\n");
800
- }
801
- function createDataCatalogServerPlugin(options) {
802
- const tool = createDataCatalogAgentTool(options);
803
- return defineServerPlugin({
804
- id: options.id ?? DATA_CATALOG_PLUGIN_ID,
805
- label: options.label ?? "Data Catalog",
806
- agentTools: [tool],
807
- systemPrompt: createDataCatalogSkillPrompt({
808
- label: options.label,
809
- toolName: tool.name,
810
- surfaceKind: options.surfaceKind,
811
- guidance: options.guidance
812
- })
813
- });
814
- }
815
721
  export {
816
722
  ServerPluginError,
817
723
  bootstrapServer,
818
724
  composeServerPlugins,
819
- createDataCatalogAgentTool,
820
- createDataCatalogServerPlugin,
821
- createDataCatalogSkillPrompt,
822
725
  createExecUiTool,
823
726
  createGetUiStateTool,
824
727
  createInMemoryBridge,
825
728
  createWorkspaceUiTools,
826
729
  defineServerPlugin,
827
- formatDataCatalogSearchResult,
828
730
  uiRoutes,
829
731
  validateServerPlugin
830
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;