@hachej/boring-workspace 0.1.17 → 0.1.20
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.
- package/README.md +36 -34
- package/dist/{FileTree-Dvaud3jU.js → FileTree-DHVB9rpk.js} +15 -15
- package/dist/{MarkdownEditor-sLkqTXDj.js → MarkdownEditor-L1KDH0bM.js} +1 -1
- package/dist/{WorkspaceLoadingState-zLzh1tGc.js → WorkspaceLoadingState-DYDxUYnx.js} +114 -110
- package/dist/WorkspaceProvider-CDPaAO5u.js +5971 -0
- package/dist/app-front.d.ts +94 -107
- package/dist/app-front.js +243 -233
- package/dist/app-server.d.ts +130 -15
- package/dist/app-server.js +1569 -304
- package/dist/{bootstrapServer-BreQ9QBc.d.ts → createInMemoryBridge-BDxDzihm.d.ts} +11 -26
- package/dist/manifest-CyNNdfYz.d.ts +58 -0
- package/dist/plugin.d.ts +199 -0
- package/dist/plugin.js +300 -0
- package/dist/server.d.ts +239 -4
- package/dist/server.js +901 -78
- package/dist/shared.d.ts +4 -112
- package/dist/surface-COYagY2m.d.ts +111 -0
- package/dist/testing.d.ts +19 -1
- package/dist/testing.js +2 -2
- package/dist/{agent-tool-DEtfQPVB.d.ts → ui-bridge-Gfh1MMgl.d.ts} +30 -30
- package/dist/workspace.css +36 -0
- package/dist/workspace.d.ts +165 -120
- package/dist/workspace.js +330 -377
- package/docs/INTERFACES.md +9 -9
- package/docs/PLUGIN_STRUCTURE.md +39 -145
- package/docs/PLUGIN_SYSTEM.md +355 -0
- package/docs/README.md +6 -1
- package/docs/plans/README.md +1 -0
- package/docs/plans/archive/HOT_RELOADABLE_AGENT_PLUGINS_PLAN.md +218 -0
- package/docs/plans/archive/RELOAD_PLUGGABILITY_PLAN.md +174 -0
- package/docs/plans/archive/UNIFIED_PLUGIN_SYSTEM_PLAN.md +769 -0
- package/package.json +11 -5
- package/dist/CommandPalette-CJHuTJlD.js +0 -5716
- package/docs/bridge.md +0 -135
- package/docs/panels.md +0 -102
- package/docs/plugins.md +0 -158
- /package/docs/plans/{MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md → archive/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md} +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { U as UiBridge, A as AgentTool } from './agent-tool-DEtfQPVB.js';
|
|
2
1
|
import { PiPackageSource, RuntimeProvisioningContribution } from '@hachej/boring-agent/server';
|
|
3
2
|
import { FastifyPluginAsync } from 'fastify';
|
|
4
|
-
|
|
5
|
-
declare function createInMemoryBridge(): UiBridge;
|
|
3
|
+
import { A as AgentTool, U as UiBridge } from './ui-bridge-Gfh1MMgl.js';
|
|
6
4
|
|
|
7
5
|
interface WorkspaceServerPlugin {
|
|
8
6
|
id: string;
|
|
@@ -13,6 +11,12 @@ interface WorkspaceServerPlugin {
|
|
|
13
11
|
* resource loader without asking Pi packages to export Boring adapters.
|
|
14
12
|
*/
|
|
15
13
|
piPackages?: PiPackageSource[];
|
|
14
|
+
/**
|
|
15
|
+
* Native pi extension entrypoints contributed by this plugin.
|
|
16
|
+
* Passed to DefaultResourceLoader.additionalExtensionPaths so pi owns jiti
|
|
17
|
+
* loading and ctx.reload() re-imports fresh source.
|
|
18
|
+
*/
|
|
19
|
+
extensionPaths?: string[];
|
|
16
20
|
systemPrompt?: string;
|
|
17
21
|
agentTools?: AgentTool[];
|
|
18
22
|
provisioning?: RuntimeProvisioningContribution;
|
|
@@ -20,31 +24,9 @@ interface WorkspaceServerPlugin {
|
|
|
20
24
|
/** UI state keys owned by this plugin that browser state PUTs must not overwrite. */
|
|
21
25
|
preservedUiStateKeys?: string[];
|
|
22
26
|
}
|
|
23
|
-
declare class ServerPluginError extends Error {
|
|
24
|
-
constructor(message: string);
|
|
25
|
-
}
|
|
26
27
|
declare function validateServerPlugin(plugin: WorkspaceServerPlugin): void;
|
|
27
28
|
declare function defineServerPlugin<T extends WorkspaceServerPlugin>(plugin: T): T;
|
|
28
29
|
|
|
29
|
-
interface ComposeServerPluginsOptions {
|
|
30
|
-
id: string;
|
|
31
|
-
label?: string;
|
|
32
|
-
plugins: WorkspaceServerPlugin[];
|
|
33
|
-
piPackages?: PiPackageSource[];
|
|
34
|
-
systemPrompt?: string;
|
|
35
|
-
agentTools?: AgentTool[];
|
|
36
|
-
provisioning?: RuntimeProvisioningContribution;
|
|
37
|
-
routes?: FastifyPluginAsync;
|
|
38
|
-
preservedUiStateKeys?: string[];
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Compose a server plugin from smaller server plugin fragments. Child
|
|
42
|
-
* contributions are concatenated before parent contributions. Composed routes
|
|
43
|
-
* are registered without extra Fastify options; use an explicit routes plugin
|
|
44
|
-
* when a child needs scoped registration options.
|
|
45
|
-
*/
|
|
46
|
-
declare function composeServerPlugins(options: ComposeServerPluginsOptions): WorkspaceServerPlugin;
|
|
47
|
-
|
|
48
30
|
interface ServerBootstrapOptions {
|
|
49
31
|
plugins?: WorkspaceServerPlugin[];
|
|
50
32
|
defaults?: WorkspaceServerPlugin[];
|
|
@@ -62,6 +44,7 @@ interface ServerBootstrapResult {
|
|
|
62
44
|
registered: string[];
|
|
63
45
|
systemPromptAppend: string;
|
|
64
46
|
piPackages: PiPackageSource[];
|
|
47
|
+
extensionPaths: string[];
|
|
65
48
|
agentTools: AgentTool[];
|
|
66
49
|
provisioningContributions: WorkspaceProvisioningContribution[];
|
|
67
50
|
routeContributions: WorkspaceRouteContribution[];
|
|
@@ -69,4 +52,6 @@ interface ServerBootstrapResult {
|
|
|
69
52
|
}
|
|
70
53
|
declare function bootstrapServer(options: ServerBootstrapOptions): ServerBootstrapResult;
|
|
71
54
|
|
|
72
|
-
|
|
55
|
+
declare function createInMemoryBridge(): UiBridge;
|
|
56
|
+
|
|
57
|
+
export { type ServerBootstrapOptions as S, type WorkspaceServerPlugin as W, type WorkspaceProvisioningContribution as a, type WorkspaceRouteContribution as b, createInMemoryBridge as c, type ServerBootstrapResult as d, bootstrapServer as e, defineServerPlugin as f, validateServerPlugin as v };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical package.json plugin shape for boring-ui workspace packages.
|
|
3
|
+
*
|
|
4
|
+
* Browser-safe: no node:* imports, no fs, no path.
|
|
5
|
+
*
|
|
6
|
+
* A plugin package has one package boundary and two namespaces:
|
|
7
|
+
* - `pi`: agent/Pi runtime contributions (extensions, skills, prompt, Pi packages)
|
|
8
|
+
* - `boring`: workspace/UI package discovery (front/server entrypoints and labels)
|
|
9
|
+
*/
|
|
10
|
+
interface BoringPackageBoringField {
|
|
11
|
+
/** Browser entry that default-exports a BoringFrontFactory. */
|
|
12
|
+
front?: string;
|
|
13
|
+
/** Workspace/UI support server entry. Set false to disable convention lookup. */
|
|
14
|
+
server?: string | false;
|
|
15
|
+
label?: string;
|
|
16
|
+
}
|
|
17
|
+
interface BoringPackagePiSourceObject {
|
|
18
|
+
source: string;
|
|
19
|
+
extensions?: string[];
|
|
20
|
+
skills?: string[];
|
|
21
|
+
themes?: string[];
|
|
22
|
+
prompts?: string[];
|
|
23
|
+
}
|
|
24
|
+
type BoringPackagePiSource = string | BoringPackagePiSourceObject;
|
|
25
|
+
interface BoringPackagePiField {
|
|
26
|
+
/** Native Pi extension entrypoints, relative to the package root. */
|
|
27
|
+
extensions?: string[];
|
|
28
|
+
/** Skill directories/files, relative to the package root. */
|
|
29
|
+
skills?: string[];
|
|
30
|
+
/** Additional Pi package sources to inject into Pi settings. */
|
|
31
|
+
packages?: BoringPackagePiSource[];
|
|
32
|
+
/** Agent context injected by the boring Pi extension. Prefer skills for large docs. */
|
|
33
|
+
systemPrompt?: string;
|
|
34
|
+
}
|
|
35
|
+
interface BoringPluginPackageJson {
|
|
36
|
+
name?: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
boring?: BoringPackageBoringField;
|
|
39
|
+
pi?: BoringPackagePiField;
|
|
40
|
+
}
|
|
41
|
+
type BoringPluginManifestErrorCode = "INVALID_ID" | "INVALID_VERSION" | "INVALID_FIELD" | "INVALID_PATH" | "MISSING_REQUIRED_FIELD";
|
|
42
|
+
interface BoringPluginManifestIssue {
|
|
43
|
+
code: BoringPluginManifestErrorCode;
|
|
44
|
+
field: string;
|
|
45
|
+
message: string;
|
|
46
|
+
}
|
|
47
|
+
type BoringPluginManifestValidationResult = {
|
|
48
|
+
valid: true;
|
|
49
|
+
packageJson: BoringPluginPackageJson;
|
|
50
|
+
} | {
|
|
51
|
+
valid: false;
|
|
52
|
+
issues: BoringPluginManifestIssue[];
|
|
53
|
+
};
|
|
54
|
+
declare function isValidBoringPluginId(id: string): boolean;
|
|
55
|
+
declare function isSafePluginRelativePath(value: string): boolean;
|
|
56
|
+
declare function validateBoringPluginManifest(raw: unknown): BoringPluginManifestValidationResult;
|
|
57
|
+
|
|
58
|
+
export { type BoringPackageBoringField as B, type BoringPackagePiField as a, type BoringPackagePiSource as b, type BoringPackagePiSourceObject as c, type BoringPluginManifestErrorCode as d, type BoringPluginManifestIssue as e, type BoringPluginManifestValidationResult as f, type BoringPluginPackageJson as g, isValidBoringPluginId as h, isSafePluginRelativePath as i, validateBoringPluginManifest as v };
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { P as PaneProps, a as PanelConfig, S as SurfaceOpenRequest, c as SurfacePanelResolution } from './surface-COYagY2m.js';
|
|
3
|
+
export { W as WORKSPACE_OPEN_PATH_SURFACE_KIND } from './surface-COYagY2m.js';
|
|
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-CyNNdfYz.js';
|
|
5
|
+
import 'dockview-react';
|
|
6
|
+
|
|
7
|
+
type CatalogBadge = {
|
|
8
|
+
/** 1–4 char mono code rendered as a chip. */
|
|
9
|
+
code: string;
|
|
10
|
+
tooltip?: string;
|
|
11
|
+
};
|
|
12
|
+
type CatalogRow = {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
group?: string;
|
|
17
|
+
leading?: CatalogBadge;
|
|
18
|
+
trailing?: CatalogBadge[];
|
|
19
|
+
meta?: string;
|
|
20
|
+
};
|
|
21
|
+
type CatalogFacetValue = {
|
|
22
|
+
value: string;
|
|
23
|
+
count: number;
|
|
24
|
+
};
|
|
25
|
+
type CatalogFacets = Record<string, CatalogFacetValue[]>;
|
|
26
|
+
type CatalogSearchArgs = {
|
|
27
|
+
query: string;
|
|
28
|
+
filters: Record<string, string[]>;
|
|
29
|
+
group?: {
|
|
30
|
+
key: string;
|
|
31
|
+
value: string;
|
|
32
|
+
};
|
|
33
|
+
limit: number;
|
|
34
|
+
offset: number;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
};
|
|
37
|
+
type CatalogSearchResult = {
|
|
38
|
+
items: CatalogRow[];
|
|
39
|
+
total: number;
|
|
40
|
+
hasMore: boolean;
|
|
41
|
+
};
|
|
42
|
+
type CatalogFacetsArgs = {
|
|
43
|
+
filters: Record<string, string[]>;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
};
|
|
46
|
+
type CatalogAdapter = {
|
|
47
|
+
search(args: CatalogSearchArgs): Promise<CatalogSearchResult>;
|
|
48
|
+
fetchFacets?(args: CatalogFacetsArgs): Promise<CatalogFacets>;
|
|
49
|
+
};
|
|
50
|
+
type PluginBinding = ComponentType<unknown>;
|
|
51
|
+
interface PluginProviderProps {
|
|
52
|
+
apiBaseUrl: string;
|
|
53
|
+
authHeaders?: Record<string, string>;
|
|
54
|
+
onAuthError?: (statusCode: number) => void;
|
|
55
|
+
apiTimeout?: number;
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
}
|
|
58
|
+
type PluginProvider = ComponentType<PluginProviderProps>;
|
|
59
|
+
interface CatalogConfig {
|
|
60
|
+
id: string;
|
|
61
|
+
label: string;
|
|
62
|
+
adapter: CatalogAdapter;
|
|
63
|
+
onSelect: (row: CatalogRow) => void;
|
|
64
|
+
pluginId?: string;
|
|
65
|
+
}
|
|
66
|
+
interface LeftTabParams {
|
|
67
|
+
rootDir?: string;
|
|
68
|
+
query?: string;
|
|
69
|
+
searchQuery?: string;
|
|
70
|
+
bridge?: unknown;
|
|
71
|
+
chromeless?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface BoringFrontPanelRegistration<T = unknown> {
|
|
75
|
+
id: string;
|
|
76
|
+
component: ComponentType<PaneProps<T>> | (() => Promise<{
|
|
77
|
+
default: ComponentType<PaneProps<T>>;
|
|
78
|
+
}>);
|
|
79
|
+
label?: string;
|
|
80
|
+
icon?: ComponentType<{
|
|
81
|
+
className?: string;
|
|
82
|
+
}>;
|
|
83
|
+
placement?: string;
|
|
84
|
+
requiresCapabilities?: string[];
|
|
85
|
+
essential?: boolean;
|
|
86
|
+
lazy?: boolean;
|
|
87
|
+
chromeless?: boolean;
|
|
88
|
+
source?: string;
|
|
89
|
+
}
|
|
90
|
+
interface BoringFrontPanelCommandRegistration {
|
|
91
|
+
id: string;
|
|
92
|
+
title: string;
|
|
93
|
+
panelId?: string;
|
|
94
|
+
run?: () => void;
|
|
95
|
+
keywords?: string[];
|
|
96
|
+
shortcut?: string;
|
|
97
|
+
when?: () => boolean;
|
|
98
|
+
}
|
|
99
|
+
interface BoringFrontLeftTabRegistration<T = LeftTabParams> {
|
|
100
|
+
id: string;
|
|
101
|
+
title: string;
|
|
102
|
+
panelId: string;
|
|
103
|
+
icon?: ComponentType<{
|
|
104
|
+
className?: string;
|
|
105
|
+
}>;
|
|
106
|
+
component?: PanelConfig<T>["component"];
|
|
107
|
+
lazy?: boolean;
|
|
108
|
+
chromeless?: boolean;
|
|
109
|
+
requiresCapabilities?: string[];
|
|
110
|
+
source?: string;
|
|
111
|
+
}
|
|
112
|
+
interface BoringFrontProviderRegistration {
|
|
113
|
+
id: string;
|
|
114
|
+
component: PluginProvider;
|
|
115
|
+
}
|
|
116
|
+
interface BoringFrontBindingRegistration {
|
|
117
|
+
id: string;
|
|
118
|
+
component: PluginBinding;
|
|
119
|
+
}
|
|
120
|
+
interface BoringFrontSurfaceResolverRegistration {
|
|
121
|
+
id?: string;
|
|
122
|
+
kind: string;
|
|
123
|
+
source?: string;
|
|
124
|
+
resolve: (request: SurfaceOpenRequest) => SurfacePanelResolution | null | undefined;
|
|
125
|
+
}
|
|
126
|
+
interface BoringFrontAPI {
|
|
127
|
+
registerProvider(registration: BoringFrontProviderRegistration): void;
|
|
128
|
+
registerBinding(registration: BoringFrontBindingRegistration): void;
|
|
129
|
+
registerCatalog(registration: CatalogConfig): void;
|
|
130
|
+
registerPanel<T = unknown>(registration: BoringFrontPanelRegistration<T>): void;
|
|
131
|
+
registerPanelCommand(registration: BoringFrontPanelCommandRegistration): void;
|
|
132
|
+
registerLeftTab<T = LeftTabParams>(registration: BoringFrontLeftTabRegistration<T>): void;
|
|
133
|
+
registerSurfaceResolver(registration: BoringFrontSurfaceResolverRegistration): void;
|
|
134
|
+
}
|
|
135
|
+
type BoringFrontFactory = (api: BoringFrontAPI) => void | Promise<void>;
|
|
136
|
+
type BoringFrontSetup = (api: BoringFrontAPI) => void;
|
|
137
|
+
type RejectAsyncSetup<C> = C extends {
|
|
138
|
+
setup?: infer Setup;
|
|
139
|
+
} ? Setup extends (...args: any[]) => infer Return ? Extract<Return, PromiseLike<unknown>> extends never ? unknown : never : unknown : unknown;
|
|
140
|
+
/**
|
|
141
|
+
* A `BoringFrontFactory` that carries its own plugin id (and optional
|
|
142
|
+
* label) as static properties. Produced by `definePlugin({ ... })` and used
|
|
143
|
+
* directly by `WorkspaceProvider.plugins`.
|
|
144
|
+
*/
|
|
145
|
+
type BoringFrontFactoryWithId = BoringFrontFactory & {
|
|
146
|
+
pluginId: string;
|
|
147
|
+
pluginLabel?: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Declarative plugin config — the canonical shape for `definePlugin`.
|
|
151
|
+
*/
|
|
152
|
+
interface DefinePluginConfig {
|
|
153
|
+
id: string;
|
|
154
|
+
label?: string;
|
|
155
|
+
panels?: ReadonlyArray<BoringFrontPanelRegistration<any>>;
|
|
156
|
+
commands?: ReadonlyArray<BoringFrontPanelCommandRegistration>;
|
|
157
|
+
leftTabs?: ReadonlyArray<BoringFrontLeftTabRegistration<any>>;
|
|
158
|
+
surfaceResolvers?: ReadonlyArray<BoringFrontSurfaceResolverRegistration>;
|
|
159
|
+
providers?: ReadonlyArray<BoringFrontProviderRegistration>;
|
|
160
|
+
bindings?: ReadonlyArray<BoringFrontBindingRegistration>;
|
|
161
|
+
catalogs?: ReadonlyArray<CatalogConfig>;
|
|
162
|
+
/**
|
|
163
|
+
* Escape hatch for registrations that can't be expressed declaratively.
|
|
164
|
+
* Called LAST, after every declarative field has been registered.
|
|
165
|
+
*/
|
|
166
|
+
setup?: BoringFrontSetup;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Define a boring-ui plugin. Takes a single declarative config object and
|
|
170
|
+
* returns a branded front factory.
|
|
171
|
+
*
|
|
172
|
+
* Older positional signatures are not supported. The `setup` field is
|
|
173
|
+
* synchronous so statically composed plugins cannot return a Promise during
|
|
174
|
+
* provider bootstrap.
|
|
175
|
+
*/
|
|
176
|
+
declare function definePlugin<const Config extends DefinePluginConfig>(config: Config & RejectAsyncSetup<Config>): BoringFrontFactoryWithId;
|
|
177
|
+
interface CapturedBoringFrontRegistrations {
|
|
178
|
+
providers: BoringFrontProviderRegistration[];
|
|
179
|
+
bindings: BoringFrontBindingRegistration[];
|
|
180
|
+
catalogs: CatalogConfig[];
|
|
181
|
+
panels: BoringFrontPanelRegistration<any>[];
|
|
182
|
+
panelCommands: BoringFrontPanelCommandRegistration[];
|
|
183
|
+
leftTabs: BoringFrontLeftTabRegistration<any>[];
|
|
184
|
+
surfaceResolvers: BoringFrontSurfaceResolverRegistration[];
|
|
185
|
+
}
|
|
186
|
+
interface CapturedFrontPlugin {
|
|
187
|
+
id: string;
|
|
188
|
+
label?: string;
|
|
189
|
+
registrations: CapturedBoringFrontRegistrations;
|
|
190
|
+
}
|
|
191
|
+
interface CapturingBoringFrontAPIHandle extends BoringFrontAPI {
|
|
192
|
+
flush(): CapturedBoringFrontRegistrations;
|
|
193
|
+
}
|
|
194
|
+
declare function createCapturingBoringFrontAPI(options?: {
|
|
195
|
+
pluginId?: string;
|
|
196
|
+
}): CapturingBoringFrontAPIHandle;
|
|
197
|
+
declare function captureFrontPlugin(plugin: BoringFrontFactoryWithId): CapturedFrontPlugin;
|
|
198
|
+
|
|
199
|
+
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 };
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// src/shared/plugins/errors.ts
|
|
2
|
+
var PluginError = class extends Error {
|
|
3
|
+
constructor(kind, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.kind = kind;
|
|
6
|
+
this.name = "PluginError";
|
|
7
|
+
}
|
|
8
|
+
kind;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/shared/plugins/frontFactory.ts
|
|
12
|
+
function definePlugin(config) {
|
|
13
|
+
if (typeof config !== "object" || config === null) {
|
|
14
|
+
if (typeof config === "string" || typeof config === "function") {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"definePlugin now takes a single declarative config object: definePlugin({ id, label?, panels, commands, leftTabs, surfaceResolvers, setup? }). The legacy positional form was removed \u2014 use the new shape."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
throw new Error("definePlugin: expected a config object");
|
|
20
|
+
}
|
|
21
|
+
if (typeof config.id !== "string" || config.id.length === 0) {
|
|
22
|
+
throw new Error("definePlugin: `id` is required and must be a non-empty string");
|
|
23
|
+
}
|
|
24
|
+
const factory = (api) => {
|
|
25
|
+
for (const panel of config.panels ?? []) api.registerPanel(panel);
|
|
26
|
+
for (const command of config.commands ?? []) api.registerPanelCommand(command);
|
|
27
|
+
for (const tab of config.leftTabs ?? []) api.registerLeftTab(tab);
|
|
28
|
+
for (const resolver of config.surfaceResolvers ?? []) api.registerSurfaceResolver(resolver);
|
|
29
|
+
for (const provider of config.providers ?? []) api.registerProvider(provider);
|
|
30
|
+
for (const binding of config.bindings ?? []) api.registerBinding(binding);
|
|
31
|
+
for (const catalog of config.catalogs ?? []) api.registerCatalog(catalog);
|
|
32
|
+
if (config.setup) config.setup(api);
|
|
33
|
+
return void 0;
|
|
34
|
+
};
|
|
35
|
+
return brandFactoryWithPluginId(config.id, factory, { label: config.label });
|
|
36
|
+
}
|
|
37
|
+
function brandFactoryWithPluginId(id, factory, options) {
|
|
38
|
+
const existing = factory.pluginId;
|
|
39
|
+
if (existing !== void 0 && existing !== id) {
|
|
40
|
+
throw new Error(`definePlugin: factory already branded as "${existing}", cannot rebrand as "${id}"`);
|
|
41
|
+
}
|
|
42
|
+
const wrapper = ((api) => factory(api));
|
|
43
|
+
Object.defineProperty(wrapper, "pluginId", { value: id, enumerable: true });
|
|
44
|
+
if (options.label !== void 0) {
|
|
45
|
+
Object.defineProperty(wrapper, "pluginLabel", { value: options.label, enumerable: true });
|
|
46
|
+
}
|
|
47
|
+
return wrapper;
|
|
48
|
+
}
|
|
49
|
+
function clone(items) {
|
|
50
|
+
return [...items];
|
|
51
|
+
}
|
|
52
|
+
function createCapturingBoringFrontAPI(options = {}) {
|
|
53
|
+
const providers = [];
|
|
54
|
+
const bindings = [];
|
|
55
|
+
const catalogs = [];
|
|
56
|
+
const panels = [];
|
|
57
|
+
const panelCommands = [];
|
|
58
|
+
const leftTabs = [];
|
|
59
|
+
const surfaceResolvers = [];
|
|
60
|
+
const seen = /* @__PURE__ */ new Map();
|
|
61
|
+
const claim = (kind, id) => {
|
|
62
|
+
const key = `${kind}:${id}`;
|
|
63
|
+
const prior = seen.get(key);
|
|
64
|
+
if (prior !== void 0) {
|
|
65
|
+
const owner = options.pluginId ?? "<plugin>";
|
|
66
|
+
throw new PluginError(
|
|
67
|
+
"duplicate-id",
|
|
68
|
+
`plugin "${owner}" registers ${kind} "${id}" twice (first as ${prior}, then again). If you are composing kits, two of them are registering the same id \u2014 namespace one of them.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
seen.set(key, `${kind} "${id}"`);
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
registerProvider(registration) {
|
|
75
|
+
claim("provider", registration.id);
|
|
76
|
+
providers.push(registration);
|
|
77
|
+
},
|
|
78
|
+
registerBinding(registration) {
|
|
79
|
+
claim("binding", registration.id);
|
|
80
|
+
bindings.push(registration);
|
|
81
|
+
},
|
|
82
|
+
registerCatalog(registration) {
|
|
83
|
+
claim("catalog", registration.id);
|
|
84
|
+
catalogs.push(registration);
|
|
85
|
+
},
|
|
86
|
+
registerPanel(registration) {
|
|
87
|
+
claim("panel", registration.id);
|
|
88
|
+
panels.push(registration);
|
|
89
|
+
},
|
|
90
|
+
registerPanelCommand(registration) {
|
|
91
|
+
claim("command", registration.id);
|
|
92
|
+
panelCommands.push(registration);
|
|
93
|
+
},
|
|
94
|
+
registerLeftTab(registration) {
|
|
95
|
+
claim("left-tab", registration.id);
|
|
96
|
+
leftTabs.push(registration);
|
|
97
|
+
},
|
|
98
|
+
registerSurfaceResolver(registration) {
|
|
99
|
+
const id = registration.id ?? `${options.pluginId ?? "anon"}:${registration.kind}`;
|
|
100
|
+
claim("surface-resolver", id);
|
|
101
|
+
if (registration.id === void 0) {
|
|
102
|
+
;
|
|
103
|
+
registration.id = id;
|
|
104
|
+
}
|
|
105
|
+
surfaceResolvers.push(registration);
|
|
106
|
+
},
|
|
107
|
+
flush() {
|
|
108
|
+
return {
|
|
109
|
+
providers: clone(providers),
|
|
110
|
+
bindings: clone(bindings),
|
|
111
|
+
catalogs: clone(catalogs),
|
|
112
|
+
panels: clone(panels),
|
|
113
|
+
panelCommands: clone(panelCommands),
|
|
114
|
+
leftTabs: clone(leftTabs),
|
|
115
|
+
surfaceResolvers: clone(surfaceResolvers)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function captureFrontPlugin(plugin) {
|
|
121
|
+
if (typeof plugin !== "function" || typeof plugin.pluginId !== "string" || plugin.pluginId.length === 0) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"WorkspaceProvider.plugins accepts plugins created by definePlugin({ id, ... }). Received a front plugin without a pluginId."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const api = createCapturingBoringFrontAPI({ pluginId: plugin.pluginId });
|
|
127
|
+
const result = plugin(api);
|
|
128
|
+
if (result && typeof result.then === "function") {
|
|
129
|
+
throw new Error(`captureFrontPlugin(${plugin.pluginId}) requires a synchronous factory`);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
id: plugin.pluginId,
|
|
133
|
+
...plugin.pluginLabel !== void 0 ? { label: plugin.pluginLabel } : {},
|
|
134
|
+
registrations: api.flush()
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/shared/plugins/manifest.ts
|
|
139
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
140
|
+
var PLUGIN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
141
|
+
function isValidBoringPluginId(id) {
|
|
142
|
+
return typeof id === "string" && id.length > 0 && PLUGIN_ID_RE.test(id);
|
|
143
|
+
}
|
|
144
|
+
function isSafePluginRelativePath(value) {
|
|
145
|
+
return typeof value === "string" && value.length > 0 && value !== "." && !value.includes("\0") && !value.includes("\\") && !value.startsWith("/") && !value.startsWith("//") && !/^[A-Za-z]:[\\/]/.test(value) && !value.split("/").includes("..");
|
|
146
|
+
}
|
|
147
|
+
function issue(code, field, message) {
|
|
148
|
+
return { code, field, message };
|
|
149
|
+
}
|
|
150
|
+
function isRecord(value) {
|
|
151
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
152
|
+
}
|
|
153
|
+
function validateStringArray(issues, value, field, pathLike) {
|
|
154
|
+
if (value === void 0) return;
|
|
155
|
+
if (!Array.isArray(value)) {
|
|
156
|
+
issues.push(issue("INVALID_FIELD", field, `${field} must be an array`));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
value.forEach((entry, index) => {
|
|
160
|
+
const itemField = `${field}[${index}]`;
|
|
161
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
162
|
+
issues.push(issue("INVALID_FIELD", itemField, `${itemField} must be a non-empty string`));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (pathLike && !isSafePluginRelativePath(entry)) {
|
|
166
|
+
issues.push(issue("INVALID_PATH", itemField, `${itemField} must be a safe relative path`));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
var REMOVED_BORING_UI_FIELDS = ["outputs", "panels", "commands", "leftTabs", "surfaceResolvers", "providers", "bindings", "catalogs"];
|
|
171
|
+
function validateBoringField(issues, boring) {
|
|
172
|
+
if (boring === void 0) return void 0;
|
|
173
|
+
if (!isRecord(boring)) {
|
|
174
|
+
issues.push(issue("INVALID_FIELD", "boring", "boring must be an object when provided"));
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
for (const field of REMOVED_BORING_UI_FIELDS) {
|
|
178
|
+
if (boring[field] !== void 0) {
|
|
179
|
+
issues.push(issue(
|
|
180
|
+
"INVALID_FIELD",
|
|
181
|
+
`boring.${field}`,
|
|
182
|
+
`boring.${field} is not supported; declare front contributions in boring.front via definePlugin({ ... })`
|
|
183
|
+
));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (boring.id !== void 0) {
|
|
187
|
+
issues.push(issue("INVALID_FIELD", "boring.id", "boring.id is not supported; package discovery identity comes from package.json#name"));
|
|
188
|
+
}
|
|
189
|
+
const front = boring.front;
|
|
190
|
+
if (front !== void 0 && (typeof front !== "string" || !isSafePluginRelativePath(front))) {
|
|
191
|
+
issues.push(issue("INVALID_PATH", "boring.front", "boring.front must be a safe relative path"));
|
|
192
|
+
}
|
|
193
|
+
const server = boring.server;
|
|
194
|
+
if (server !== void 0 && server !== false && (typeof server !== "string" || !isSafePluginRelativePath(server))) {
|
|
195
|
+
issues.push(issue("INVALID_PATH", "boring.server", "boring.server must be a safe relative path or false"));
|
|
196
|
+
}
|
|
197
|
+
if (boring.label !== void 0 && typeof boring.label !== "string") {
|
|
198
|
+
issues.push(issue("INVALID_FIELD", "boring.label", "boring.label must be a string when provided"));
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
...typeof boring.front === "string" ? { front: boring.front } : {},
|
|
202
|
+
...typeof boring.server === "string" || boring.server === false ? { server: boring.server } : {},
|
|
203
|
+
...typeof boring.label === "string" ? { label: boring.label } : {}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
var REMOTE_PI_PACKAGE_PREFIXES = ["npm:", "git:", "github:", "http:", "https:", "ssh:"];
|
|
207
|
+
function isRemotePiPackageSource(value) {
|
|
208
|
+
return REMOTE_PI_PACKAGE_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
209
|
+
}
|
|
210
|
+
function isSafePiPackageSource(value) {
|
|
211
|
+
if (value.length === 0) return false;
|
|
212
|
+
if (isRemotePiPackageSource(value)) return true;
|
|
213
|
+
const path = value.startsWith("file:") ? value.slice("file:".length) : value;
|
|
214
|
+
if (path === "." || path === "./") return true;
|
|
215
|
+
const normalized = path.startsWith("./") ? path.slice(2) : path;
|
|
216
|
+
return isSafePluginRelativePath(normalized);
|
|
217
|
+
}
|
|
218
|
+
function validatePiPackages(issues, value) {
|
|
219
|
+
if (value === void 0) return;
|
|
220
|
+
if (!Array.isArray(value)) {
|
|
221
|
+
issues.push(issue("INVALID_FIELD", "pi.packages", "pi.packages must be an array when provided"));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
value.forEach((entry, index) => {
|
|
225
|
+
const field = `pi.packages[${index}]`;
|
|
226
|
+
if (typeof entry === "string") {
|
|
227
|
+
if (!isSafePiPackageSource(entry)) {
|
|
228
|
+
issues.push(issue("INVALID_PATH", field, `${field} must be a safe package source`));
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (!isRecord(entry)) {
|
|
233
|
+
issues.push(issue("INVALID_FIELD", field, `${field} must be a string or package source object`));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (typeof entry.source !== "string" || entry.source.length === 0) {
|
|
237
|
+
issues.push(issue("INVALID_FIELD", `${field}.source`, `${field}.source must be a non-empty string`));
|
|
238
|
+
} else if (!isSafePiPackageSource(entry.source)) {
|
|
239
|
+
issues.push(issue("INVALID_PATH", `${field}.source`, `${field}.source must be a safe package source`));
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function validatePiField(issues, pi) {
|
|
244
|
+
if (pi === void 0) return void 0;
|
|
245
|
+
if (!isRecord(pi)) {
|
|
246
|
+
issues.push(issue("INVALID_FIELD", "pi", "pi must be an object when provided"));
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
validateStringArray(issues, pi.extensions, "pi.extensions", true);
|
|
250
|
+
validateStringArray(issues, pi.skills, "pi.skills", true);
|
|
251
|
+
validatePiPackages(issues, pi.packages);
|
|
252
|
+
if (pi.systemPrompt !== void 0 && typeof pi.systemPrompt !== "string") {
|
|
253
|
+
issues.push(issue("INVALID_FIELD", "pi.systemPrompt", "pi.systemPrompt must be a string when provided"));
|
|
254
|
+
}
|
|
255
|
+
return pi;
|
|
256
|
+
}
|
|
257
|
+
function validateBoringPluginManifest(raw) {
|
|
258
|
+
const issues = [];
|
|
259
|
+
if (!isRecord(raw)) {
|
|
260
|
+
return {
|
|
261
|
+
valid: false,
|
|
262
|
+
issues: [issue("INVALID_FIELD", "<root>", "package.json manifest must be an object")]
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (raw.name !== void 0 && typeof raw.name !== "string") {
|
|
266
|
+
issues.push(issue("INVALID_FIELD", "name", "name must be a string when provided"));
|
|
267
|
+
}
|
|
268
|
+
if (raw.version !== void 0 && typeof raw.version !== "string") {
|
|
269
|
+
issues.push(issue("INVALID_VERSION", "version", "version must be a string when provided"));
|
|
270
|
+
} else if (typeof raw.version === "string" && raw.version.length > 0 && !SEMVER_RE.test(raw.version)) {
|
|
271
|
+
issues.push(issue("INVALID_VERSION", "version", "version must be a valid semver string"));
|
|
272
|
+
}
|
|
273
|
+
const boring = validateBoringField(issues, raw.boring);
|
|
274
|
+
const pi = validatePiField(issues, raw.pi);
|
|
275
|
+
if (!boring && !pi) {
|
|
276
|
+
issues.push(issue("MISSING_REQUIRED_FIELD", "boring|pi", "package.json must include boring and/or pi plugin metadata"));
|
|
277
|
+
}
|
|
278
|
+
if (issues.length > 0) return { valid: false, issues };
|
|
279
|
+
return {
|
|
280
|
+
valid: true,
|
|
281
|
+
packageJson: {
|
|
282
|
+
...typeof raw.name === "string" ? { name: raw.name } : {},
|
|
283
|
+
...typeof raw.version === "string" ? { version: raw.version } : {},
|
|
284
|
+
...boring ? { boring } : {},
|
|
285
|
+
...pi ? { pi } : {}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/shared/types/surface.ts
|
|
291
|
+
var WORKSPACE_OPEN_PATH_SURFACE_KIND = "workspace.open.path";
|
|
292
|
+
export {
|
|
293
|
+
WORKSPACE_OPEN_PATH_SURFACE_KIND,
|
|
294
|
+
captureFrontPlugin,
|
|
295
|
+
createCapturingBoringFrontAPI,
|
|
296
|
+
definePlugin,
|
|
297
|
+
isSafePluginRelativePath,
|
|
298
|
+
isValidBoringPluginId,
|
|
299
|
+
validateBoringPluginManifest
|
|
300
|
+
};
|