@hachej/boring-workspace 0.1.24 → 0.1.26
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/dist/{FileTree-DjPzfDMq.js → FileTree-BZGu5Ap6.js} +1 -1
- package/dist/{MarkdownEditor-BbSy0bLV.js → MarkdownEditor-DshmttZM.js} +1 -1
- package/dist/{WorkspaceLoadingState-fccm3AQg.js → WorkspaceLoadingState-DVCLcOQu.js} +1 -1
- package/dist/{WorkspaceProvider-BW4wzbpR.js → WorkspaceProvider-DQ-325Qs.js} +2319 -2216
- package/dist/app-front.js +361 -328
- package/dist/app-server.d.ts +9 -2
- package/dist/app-server.js +667 -566
- package/dist/createInMemoryBridge--ZFPAgXy.d.ts +161 -0
- package/dist/server.d.ts +41 -68
- package/dist/server.js +155 -26
- package/dist/testing.js +1 -1
- package/dist/workspace.js +5 -5
- package/package.json +3 -3
- package/dist/createInMemoryBridge-DLckqafe.d.ts +0 -61
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { B as BoringPackageBoringField, a as BoringPackagePiField } from './manifest-C2vVgH_e.js';
|
|
2
|
+
import { PiPackageSource, PluginSkillSource, ProvisionWorkspaceRuntimeOptions } from '@hachej/boring-agent/server';
|
|
3
|
+
import { FastifyPluginAsync } from 'fastify';
|
|
4
|
+
import { A as AgentTool, U as UiBridge } from './ui-bridge-Bdgl2hR8.js';
|
|
5
|
+
|
|
6
|
+
type BoringPluginNativeFrontTargetTrust$1 = "local-trusted-native";
|
|
7
|
+
/**
|
|
8
|
+
* Host-owned runtime import target for a plugin front entry.
|
|
9
|
+
*
|
|
10
|
+
* Only the trusted native/browser-module case exists today. Future
|
|
11
|
+
* target kinds (iframe/artifact/...) can extend the union without
|
|
12
|
+
* rewriting list/event payload shapes.
|
|
13
|
+
*/
|
|
14
|
+
interface BoringPluginNativeFrontTarget$1 {
|
|
15
|
+
kind: "native";
|
|
16
|
+
entryUrl: string;
|
|
17
|
+
revision: number;
|
|
18
|
+
trust: BoringPluginNativeFrontTargetTrust$1;
|
|
19
|
+
}
|
|
20
|
+
type BoringPluginFrontTarget$1 = BoringPluginNativeFrontTarget$1;
|
|
21
|
+
type BoringPluginEvent$1 = {
|
|
22
|
+
type: "boring.plugin.load";
|
|
23
|
+
id: string;
|
|
24
|
+
boring: BoringPackageBoringField;
|
|
25
|
+
version: string;
|
|
26
|
+
revision: number;
|
|
27
|
+
frontUrl?: string;
|
|
28
|
+
frontTarget?: BoringPluginFrontTarget$1;
|
|
29
|
+
} | {
|
|
30
|
+
type: "boring.plugin.unload";
|
|
31
|
+
id: string;
|
|
32
|
+
revision: number;
|
|
33
|
+
} | {
|
|
34
|
+
type: "boring.plugin.error";
|
|
35
|
+
id: string;
|
|
36
|
+
revision: number;
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
interface BoringPluginListEntry$1 {
|
|
40
|
+
id: string;
|
|
41
|
+
boring: BoringPackageBoringField;
|
|
42
|
+
pi?: BoringPackagePiField;
|
|
43
|
+
version: string;
|
|
44
|
+
revision: number;
|
|
45
|
+
frontUrl?: string;
|
|
46
|
+
frontTarget?: BoringPluginFrontTarget$1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface BoringServerPluginManifest {
|
|
50
|
+
id: string;
|
|
51
|
+
rootDir: string;
|
|
52
|
+
version: string;
|
|
53
|
+
boring: BoringPackageBoringField;
|
|
54
|
+
pi?: BoringPackagePiField;
|
|
55
|
+
frontPath?: string;
|
|
56
|
+
/** Legacy Vite-dev browser import fallback (`/@fs/...`). */
|
|
57
|
+
frontUrl?: string;
|
|
58
|
+
serverPath?: string;
|
|
59
|
+
extensionPaths?: string[];
|
|
60
|
+
skillPaths?: string[];
|
|
61
|
+
}
|
|
62
|
+
type BoringPluginNativeFrontTargetTrust = BoringPluginNativeFrontTargetTrust$1;
|
|
63
|
+
type BoringPluginNativeFrontTarget = BoringPluginNativeFrontTarget$1;
|
|
64
|
+
type BoringPluginFrontTarget = BoringPluginFrontTarget$1;
|
|
65
|
+
type BoringPluginListEntry = BoringPluginListEntry$1;
|
|
66
|
+
interface BoringPluginFrontTargetResolverContext {
|
|
67
|
+
revision: number;
|
|
68
|
+
/** Plugin-root-relative front entry path normalized for URL-like consumers. */
|
|
69
|
+
frontEntrySubpath: string;
|
|
70
|
+
}
|
|
71
|
+
type BoringPluginFrontTargetResolver = (plugin: BoringServerPluginManifest, context: BoringPluginFrontTargetResolverContext) => BoringPluginFrontTarget | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Surfaces whose changes the hot-reload pipeline can't re-load mid-
|
|
74
|
+
* session — set when a plugin's load DID succeed but a sub-surface
|
|
75
|
+
* (the agent-tools registry, Fastify routes) carries stale code from
|
|
76
|
+
* the previous revision. The /reload caller (chat UI, verify-plugin,
|
|
77
|
+
* etc.) should surface a "restart needed for X" warning.
|
|
78
|
+
*
|
|
79
|
+
* - `'routes'`: a `WorkspaceServerPlugin.routes` function changed. The
|
|
80
|
+
* workspace's Fastify instance can't unregister + re-register routes
|
|
81
|
+
* mid-flight; the previous routes stay live until next boot.
|
|
82
|
+
* - `'agentTools'`: a `WorkspaceServerPlugin.agentTools` array changed.
|
|
83
|
+
* The current Pi session still has the old tool list; new sessions
|
|
84
|
+
* get the new list.
|
|
85
|
+
* - Multiple surfaces: order is deterministic (`routes` before
|
|
86
|
+
* `agentTools`) so subscribers can format consistently.
|
|
87
|
+
*/
|
|
88
|
+
type PluginRestartSurface = "routes" | "agentTools";
|
|
89
|
+
type BoringPluginEvent = (Extract<BoringPluginEvent$1, {
|
|
90
|
+
type: "boring.plugin.load";
|
|
91
|
+
}> & {
|
|
92
|
+
/**
|
|
93
|
+
* Non-empty when the plugin loaded but one or more server-side
|
|
94
|
+
* surfaces still hold pre-load code. UI consumers should render
|
|
95
|
+
* a "restart needed: <surfaces>" hint. Empty/omitted = fully
|
|
96
|
+
* live.
|
|
97
|
+
*/
|
|
98
|
+
requiresRestart?: PluginRestartSurface[];
|
|
99
|
+
}) | Extract<BoringPluginEvent$1, {
|
|
100
|
+
type: "boring.plugin.unload";
|
|
101
|
+
}> | Extract<BoringPluginEvent$1, {
|
|
102
|
+
type: "boring.plugin.error";
|
|
103
|
+
}>;
|
|
104
|
+
|
|
105
|
+
type WorkspaceRuntimeProvisioning = NonNullable<ProvisionWorkspaceRuntimeOptions["plugins"][number]["provisioning"]>;
|
|
106
|
+
interface WorkspaceServerPlugin {
|
|
107
|
+
id: string;
|
|
108
|
+
label?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Native Pi package sources required by this workspace integration.
|
|
111
|
+
* Workspace declares them; @hachej/boring-agent applies them through Pi's native
|
|
112
|
+
* resource loader without asking Pi packages to export Boring adapters.
|
|
113
|
+
*/
|
|
114
|
+
piPackages?: PiPackageSource[];
|
|
115
|
+
/**
|
|
116
|
+
* Native pi extension entrypoints contributed by this plugin.
|
|
117
|
+
* Passed to DefaultResourceLoader.additionalExtensionPaths so pi owns jiti
|
|
118
|
+
* loading and ctx.reload() re-imports fresh source.
|
|
119
|
+
*/
|
|
120
|
+
extensionPaths?: string[];
|
|
121
|
+
systemPrompt?: string;
|
|
122
|
+
skills?: PluginSkillSource[];
|
|
123
|
+
agentTools?: AgentTool[];
|
|
124
|
+
provisioning?: WorkspaceRuntimeProvisioning;
|
|
125
|
+
routes?: FastifyPluginAsync;
|
|
126
|
+
/** UI state keys owned by this plugin that browser state PUTs must not overwrite. */
|
|
127
|
+
preservedUiStateKeys?: string[];
|
|
128
|
+
}
|
|
129
|
+
declare function validateServerPlugin(plugin: WorkspaceServerPlugin): void;
|
|
130
|
+
declare function defineServerPlugin<T extends WorkspaceServerPlugin>(plugin: T): T;
|
|
131
|
+
|
|
132
|
+
interface ServerBootstrapOptions {
|
|
133
|
+
plugins?: WorkspaceServerPlugin[];
|
|
134
|
+
defaults?: WorkspaceServerPlugin[];
|
|
135
|
+
excludeDefaults?: string[];
|
|
136
|
+
}
|
|
137
|
+
type WorkspaceRuntimeProvisioningInput = ProvisionWorkspaceRuntimeOptions["plugins"][number];
|
|
138
|
+
type WorkspaceProvisioningContribution = {
|
|
139
|
+
id: string;
|
|
140
|
+
provisioning: NonNullable<WorkspaceRuntimeProvisioningInput["provisioning"]>;
|
|
141
|
+
};
|
|
142
|
+
type WorkspaceRouteContribution = {
|
|
143
|
+
id: string;
|
|
144
|
+
routes: FastifyPluginAsync;
|
|
145
|
+
};
|
|
146
|
+
interface ServerBootstrapResult {
|
|
147
|
+
registered: string[];
|
|
148
|
+
systemPromptAppend: string;
|
|
149
|
+
piPackages: PiPackageSource[];
|
|
150
|
+
extensionPaths: string[];
|
|
151
|
+
agentTools: AgentTool[];
|
|
152
|
+
runtimePlugins: WorkspaceRuntimeProvisioningInput[];
|
|
153
|
+
provisioningContributions: WorkspaceProvisioningContribution[];
|
|
154
|
+
routeContributions: WorkspaceRouteContribution[];
|
|
155
|
+
preservedUiStateKeys: string[];
|
|
156
|
+
}
|
|
157
|
+
declare function bootstrapServer(options: ServerBootstrapOptions): ServerBootstrapResult;
|
|
158
|
+
|
|
159
|
+
declare function createInMemoryBridge(): UiBridge;
|
|
160
|
+
|
|
161
|
+
export { type BoringPluginFrontTargetResolver as B, type PluginRestartSurface as P, type ServerBootstrapOptions as S, type WorkspaceServerPlugin as W, type WorkspaceProvisioningContribution as a, type WorkspaceRouteContribution as b, createInMemoryBridge as c, type WorkspaceRuntimeProvisioningInput as d, type BoringServerPluginManifest as e, type BoringPluginListEntry as f, type BoringPluginFrontTarget as g, type BoringPluginEvent as h, type BoringPluginFrontTargetResolverContext as i, type BoringPluginNativeFrontTarget as j, type BoringPluginNativeFrontTargetTrust as k, type ServerBootstrapResult as l, bootstrapServer as m, defineServerPlugin as n, validateServerPlugin as v };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { e as BoringServerPluginManifest, B as BoringPluginFrontTargetResolver, f as BoringPluginListEntry, g as BoringPluginFrontTarget, h as BoringPluginEvent, P as PluginRestartSurface } from './createInMemoryBridge--ZFPAgXy.js';
|
|
2
|
+
export { i as BoringPluginFrontTargetResolverContext, j as BoringPluginNativeFrontTarget, k as BoringPluginNativeFrontTargetTrust, S as ServerBootstrapOptions, l as ServerBootstrapResult, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, W as WorkspaceServerPlugin, m as bootstrapServer, c as createInMemoryBridge, n as defineServerPlugin, v as validateServerPlugin } from './createInMemoryBridge--ZFPAgXy.js';
|
|
2
3
|
import { FastifyRequest, FastifyInstance } from 'fastify';
|
|
3
4
|
import { U as UiBridge, A as AgentTool } from './ui-bridge-Bdgl2hR8.js';
|
|
4
5
|
export { C as CommandResult, a as UiCommand, b as UiState } from './ui-bridge-Bdgl2hR8.js';
|
|
5
|
-
import {
|
|
6
|
+
import { PiPackageSource } from '@hachej/boring-agent/server';
|
|
6
7
|
export { PiPackageSource as WorkspacePiPackageSource } from '@hachej/boring-agent/server';
|
|
8
|
+
import './manifest-C2vVgH_e.js';
|
|
7
9
|
|
|
8
10
|
interface UiRoutesOptions {
|
|
9
11
|
bridge?: UiBridge;
|
|
@@ -69,9 +71,9 @@ declare function createWorkspaceUiTools(uiBridge: UiBridge, opts?: ExecUiToolOpt
|
|
|
69
71
|
|
|
70
72
|
/**
|
|
71
73
|
* boring-ui system prompt — workflow steps + a Pi-style docs pointer
|
|
72
|
-
* block (per DECISIONS.md #17). The block lists
|
|
73
|
-
* installed `@hachej/boring-pi` package so the agent's `read` tool
|
|
74
|
-
* fetch the SKILL.md + reference docs on demand, without inlining their
|
|
74
|
+
* block (per DECISIONS.md #17). The block lists workspace-readable paths
|
|
75
|
+
* into the installed `@hachej/boring-pi` package so the agent's `read` tool
|
|
76
|
+
* can fetch the SKILL.md + reference docs on demand, without inlining their
|
|
75
77
|
* ~12-30 KB of markdown into every system prompt.
|
|
76
78
|
*
|
|
77
79
|
* `@hachej/boring-pi` is a runtime dep of `@hachej/boring-workspace`;
|
|
@@ -102,68 +104,6 @@ interface BuildBoringSystemPromptOptions {
|
|
|
102
104
|
}
|
|
103
105
|
declare function buildBoringSystemPrompt(opts: BuildBoringSystemPromptOptions): string;
|
|
104
106
|
|
|
105
|
-
interface BoringServerPluginManifest {
|
|
106
|
-
id: string;
|
|
107
|
-
rootDir: string;
|
|
108
|
-
version: string;
|
|
109
|
-
boring: BoringPackageBoringField;
|
|
110
|
-
pi?: BoringPackagePiField;
|
|
111
|
-
frontPath?: string;
|
|
112
|
-
frontUrl?: string;
|
|
113
|
-
serverPath?: string;
|
|
114
|
-
extensionPaths?: string[];
|
|
115
|
-
skillPaths?: string[];
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Surfaces whose changes the hot-reload pipeline can't re-load mid-
|
|
119
|
-
* session — set when a plugin's load DID succeed but a sub-surface
|
|
120
|
-
* (the agent-tools registry, Fastify routes) carries stale code from
|
|
121
|
-
* the previous revision. The /reload caller (chat UI, verify-plugin,
|
|
122
|
-
* etc.) should surface a "restart needed for X" warning.
|
|
123
|
-
*
|
|
124
|
-
* - `'routes'`: a `WorkspaceServerPlugin.routes` function changed. The
|
|
125
|
-
* workspace's Fastify instance can't unregister + re-register routes
|
|
126
|
-
* mid-flight; the previous routes stay live until next boot.
|
|
127
|
-
* - `'agentTools'`: a `WorkspaceServerPlugin.agentTools` array changed.
|
|
128
|
-
* The current Pi session still has the old tool list; new sessions
|
|
129
|
-
* get the new list.
|
|
130
|
-
* - Multiple surfaces: order is deterministic (`routes` before
|
|
131
|
-
* `agentTools`) so subscribers can format consistently.
|
|
132
|
-
*/
|
|
133
|
-
type PluginRestartSurface = "routes" | "agentTools";
|
|
134
|
-
type BoringPluginEvent = {
|
|
135
|
-
type: "boring.plugin.load";
|
|
136
|
-
id: string;
|
|
137
|
-
boring: BoringPackageBoringField;
|
|
138
|
-
version: string;
|
|
139
|
-
revision: number;
|
|
140
|
-
frontUrl?: string;
|
|
141
|
-
/**
|
|
142
|
-
* Non-empty when the plugin loaded but one or more server-side
|
|
143
|
-
* surfaces still hold pre-load code. UI consumers should render
|
|
144
|
-
* a "restart needed: <surfaces>" hint. Empty/omitted = fully
|
|
145
|
-
* live.
|
|
146
|
-
*/
|
|
147
|
-
requiresRestart?: PluginRestartSurface[];
|
|
148
|
-
} | {
|
|
149
|
-
type: "boring.plugin.unload";
|
|
150
|
-
id: string;
|
|
151
|
-
revision: number;
|
|
152
|
-
} | {
|
|
153
|
-
type: "boring.plugin.error";
|
|
154
|
-
id: string;
|
|
155
|
-
revision: number;
|
|
156
|
-
message: string;
|
|
157
|
-
};
|
|
158
|
-
interface BoringPluginListEntry {
|
|
159
|
-
id: string;
|
|
160
|
-
boring: BoringPackageBoringField;
|
|
161
|
-
pi?: BoringPackagePiField;
|
|
162
|
-
version: string;
|
|
163
|
-
revision: number;
|
|
164
|
-
frontUrl?: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
107
|
interface BoringPluginPreflightIssue {
|
|
168
108
|
pluginDir: string;
|
|
169
109
|
pluginId?: string;
|
|
@@ -191,6 +131,16 @@ interface BoringPluginAssetManagerOptions {
|
|
|
191
131
|
* the default assumes workspace root equals `process.cwd()`.
|
|
192
132
|
*/
|
|
193
133
|
errorRoot?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Optional host-owned runtime front-target resolver. When omitted, list/event
|
|
136
|
+
* payloads preserve the existing `frontUrl` (`/@fs/...`) fallback only.
|
|
137
|
+
*/
|
|
138
|
+
frontTargetResolver?: BoringPluginFrontTargetResolver;
|
|
139
|
+
/**
|
|
140
|
+
* Keep legacy `/@fs/...` frontUrl payloads alongside frontTarget. Defaults
|
|
141
|
+
* to true for back-compat; packaged CLI folder/workspaces mode can disable it.
|
|
142
|
+
*/
|
|
143
|
+
includeLegacyFrontUrl?: boolean;
|
|
194
144
|
}
|
|
195
145
|
interface LoadBoringAssetsError {
|
|
196
146
|
id: string;
|
|
@@ -202,25 +152,48 @@ interface LoadBoringAssetsResult {
|
|
|
202
152
|
events: BoringPluginEvent[];
|
|
203
153
|
errors: LoadBoringAssetsError[];
|
|
204
154
|
}
|
|
155
|
+
interface LoadedBoringPluginInspection {
|
|
156
|
+
id: string;
|
|
157
|
+
version: string;
|
|
158
|
+
revision: number;
|
|
159
|
+
rootDir: string;
|
|
160
|
+
frontPath?: string;
|
|
161
|
+
frontTarget?: BoringPluginFrontTarget;
|
|
162
|
+
}
|
|
163
|
+
interface LoadedBoringPluginPiSnapshot {
|
|
164
|
+
additionalSkillPaths: string[];
|
|
165
|
+
packages: PiPackageSource[];
|
|
166
|
+
extensionPaths: string[];
|
|
167
|
+
systemPromptAppend?: string;
|
|
168
|
+
}
|
|
205
169
|
type Listener = (event: BoringPluginEvent) => void;
|
|
206
170
|
declare class BoringPluginAssetManager {
|
|
207
171
|
private readonly pluginDirs;
|
|
208
172
|
private readonly errorRoot;
|
|
173
|
+
private readonly frontTargetResolver?;
|
|
174
|
+
private readonly includeLegacyFrontUrl;
|
|
209
175
|
private readonly loaded;
|
|
210
176
|
private readonly revisions;
|
|
211
177
|
private readonly listeners;
|
|
178
|
+
private readonly lastErrors;
|
|
212
179
|
private loading;
|
|
213
180
|
private reloadQueued;
|
|
214
181
|
constructor(options: BoringPluginAssetManagerOptions);
|
|
215
182
|
preflight(): BoringPluginPreflightResult;
|
|
216
183
|
list(): BoringPluginListEntry[];
|
|
217
184
|
getError(pluginId: string): string | null;
|
|
185
|
+
getErrors(): LoadBoringAssetsError[];
|
|
186
|
+
inspectLoaded(): LoadedBoringPluginInspection[];
|
|
187
|
+
inspectLoadedPiSnapshot(): LoadedBoringPluginPiSnapshot;
|
|
218
188
|
subscribe(listener: Listener): () => void;
|
|
219
189
|
load(): Promise<LoadBoringAssetsResult>;
|
|
220
190
|
private drainLoads;
|
|
221
191
|
private doLoadOnce;
|
|
222
192
|
private collectPreflightErrors;
|
|
223
193
|
private bumpRevision;
|
|
194
|
+
private toListEntry;
|
|
195
|
+
private frontUrlPayload;
|
|
196
|
+
private resolveFrontTarget;
|
|
224
197
|
private emit;
|
|
225
198
|
private errorPath;
|
|
226
199
|
private writeError;
|
|
@@ -301,4 +274,4 @@ declare function pluginFileSignature(path: string | undefined): string;
|
|
|
301
274
|
declare function writePluginSignatureCache(pluginRootDir: string, payload: Omit<PluginSignatureCachePayload, "version" | "loadedAt"> & Partial<Pick<PluginSignatureCachePayload, "loadedAt">>): void;
|
|
302
275
|
declare function readPluginSignatureCache(pluginRootDir: string): PluginSignatureCachePayload | null;
|
|
303
276
|
|
|
304
|
-
export { BoringPluginAssetManager,
|
|
277
|
+
export { BoringPluginAssetManager, BoringPluginEvent, BoringPluginFrontTarget, BoringPluginFrontTargetResolver, BoringPluginListEntry, type BoringPluginScanResult, BoringServerPluginManifest, type PluginReloadRebuild, type PluginRestartWarning, UiBridge, type UiRoutesOptions, aggregatePluginPrompts, boringPluginRoutes, buildBoringSystemPrompt, collectRestartWarnings, createExecUiTool, createGetUiStateTool, createWorkspaceUiTools, pluginFileSignature, preflightBoringPlugins, readBoringPlugins, readPluginSignatureCache, scanBoringPlugins, uiRoutes, writePluginSignatureCache };
|
package/dist/server.js
CHANGED
|
@@ -762,7 +762,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
762
762
|
if (opts.scaffoldCommand) {
|
|
763
763
|
n += 1;
|
|
764
764
|
steps.push(
|
|
765
|
-
`**${n}.
|
|
765
|
+
`**${n}. Check plugin-root support, then scaffold.** Bash \`boring-ui plugin-status --json\`; continue only if \`workspaceLocalPluginRoots\` is \`true\`. Then bash \`${opts.scaffoldCommand} <kebab-name> "$BORING_AGENT_WORKSPACE_ROOT"\`. Read generated \`package.json\` + \`front/index.tsx\`; do NOT write from memory.`
|
|
766
766
|
);
|
|
767
767
|
} else {
|
|
768
768
|
n += 1;
|
|
@@ -788,7 +788,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
788
788
|
steps.push(`**${n}. Ask the user to run \`/reload\`** to publish the change.`);
|
|
789
789
|
const docsBlock = boringPiRoot ? [
|
|
790
790
|
"## boring-ui plugin authoring documentation",
|
|
791
|
-
"Read these only when the user asks to build, modify, or debug a workspace plugin. Use your `read` tool with
|
|
791
|
+
"Read these only when the user asks to build, modify, or debug a workspace plugin. Use your `read` tool with these workspace-relative paths; the agent runtime guarantees they exist inside `$BORING_AGENT_WORKSPACE_ROOT`:",
|
|
792
792
|
...buildDocsRefs(boringPiRoot).map((r) => `- ${r.topic}: ${r.path}`),
|
|
793
793
|
"Follow .md cross-references when present (e.g. SKILL.md may link to a reference doc \u2014 read both)."
|
|
794
794
|
].join("\n") : [
|
|
@@ -796,7 +796,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
796
796
|
"The `boring-plugin-authoring` skill listed under `<available_skills>` is the authoritative reference (read its `<location>`). Additional reference docs (`panels.md`, `bridge.md`, `plugins.md`) are unavailable on this host \u2014 `@hachej/boring-pi` is not installed."
|
|
797
797
|
].join("\n");
|
|
798
798
|
return [
|
|
799
|
-
"You are operating inside boring-ui.
|
|
799
|
+
"You are operating inside boring-ui. Before `.pi/extensions/<name>/`, run `boring-ui plugin-status --json`; continue only when `workspaceLocalPluginRoots` is `true`. Default to `.pi/extensions/<name>/`. Global `~/.pi/agent/extensions/` only for explicit requests.",
|
|
800
800
|
[
|
|
801
801
|
"## Plugin authoring \u2014 required workflow",
|
|
802
802
|
"",
|
|
@@ -806,7 +806,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
806
806
|
"- API factories: `createPlugin`, `defineFrontPlugin`, `defineComponent` \u2014 use `definePlugin({id, panels, commands, ...})` from `@hachej/boring-workspace/plugin`.",
|
|
807
807
|
"- Imperative method names: `registerComponent`, `addPanel`, `registerCommand` (no `Panel`), `registerTab` \u2014 the actual names are `registerPanel`, `registerPanelCommand`, `registerLeftTab`, `registerSurfaceResolver` (and you usually express these declaratively, not as method calls).",
|
|
808
808
|
"- Import paths: `@hachej/boring-pi` (it's a skills package, not for code), `@boring-ui/*`, `@hachej/pi-sdk` \u2014 use `@hachej/boring-workspace/plugin` for front and `@hachej/boring-workspace/server` for server.",
|
|
809
|
-
'- File visualizers:
|
|
809
|
+
'- File visualizers: import `WORKSPACE_OPEN_PATH_SURFACE_KIND`/`PaneProps` from `@hachej/boring-workspace/plugin`; import `useApiBaseUrl`/`useWorkspaceRequestId` from `@hachej/boring-workspace`; read `request.target`; fetch `${apiBaseUrl}/api/v1/files/raw?...` with `credentials: "include"` and `x-boring-workspace-id` when present. Never use `/workspace/read` or string kind `"WORKSPACE_OPEN_PATH_SURFACE_KIND"`.',
|
|
810
810
|
"- Pi extension tools: `defineTool` and `export const tools` do NOT exist. Export `default function (pi) { pi.registerTool({ name, description, execute }) }`.",
|
|
811
811
|
'- Server/Pi tool method: `handler` \u2014 use `execute`. Return shape: `{ content: [{ type: "text", text }] }` (NEVER a bare string).',
|
|
812
812
|
"- Manifest values: `boring.server: true` \u2014 use `false`/omit for hot-reload user plugins, or a relative path string only for advanced boot-time/static server integration.",
|
|
@@ -820,7 +820,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
820
820
|
// src/server/agentPlugins/manager.ts
|
|
821
821
|
import { createHash } from "crypto";
|
|
822
822
|
import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, realpathSync as realpathSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
823
|
-
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join4, relative as relative3, resolve as
|
|
823
|
+
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join4, relative as relative3, resolve as resolve5 } from "path";
|
|
824
824
|
|
|
825
825
|
// src/shared/plugins/manifest.ts
|
|
826
826
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -1241,7 +1241,48 @@ function clearPluginSignatureCache(pluginRootDir) {
|
|
|
1241
1241
|
if (existsSync3(path)) rmSync(path, { force: true });
|
|
1242
1242
|
}
|
|
1243
1243
|
|
|
1244
|
+
// src/server/agentPlugins/piPackages.ts
|
|
1245
|
+
import { resolve as resolve4 } from "path";
|
|
1246
|
+
var REMOTE_PI_PACKAGE_PREFIXES2 = ["npm:", "git:", "github:", "http:", "https:", "ssh:"];
|
|
1247
|
+
function isRemotePiPackageSource2(source) {
|
|
1248
|
+
return REMOTE_PI_PACKAGE_PREFIXES2.some((prefix) => source.startsWith(prefix));
|
|
1249
|
+
}
|
|
1250
|
+
function packageLocalPathFromSource(source) {
|
|
1251
|
+
if (isRemotePiPackageSource2(source)) return null;
|
|
1252
|
+
return source.startsWith("file:") ? source.slice("file:".length) : source;
|
|
1253
|
+
}
|
|
1254
|
+
function normalizeLocalPiPackageSource(pluginRoot, source) {
|
|
1255
|
+
const localPath = packageLocalPathFromSource(source);
|
|
1256
|
+
if (localPath == null) return source;
|
|
1257
|
+
if (localPath === "." || localPath === "./") return resolve4(pluginRoot);
|
|
1258
|
+
const normalized = localPath.startsWith("./") ? localPath.slice(2) : localPath;
|
|
1259
|
+
if (!isSafePluginRelativePath(normalized)) {
|
|
1260
|
+
throw new Error(`unsafe Pi package source: ${source}`);
|
|
1261
|
+
}
|
|
1262
|
+
return resolve4(pluginRoot, normalized);
|
|
1263
|
+
}
|
|
1264
|
+
function normalizeBoringPluginPiPackageSource(pluginRoot, source) {
|
|
1265
|
+
if (typeof source === "string") return normalizeLocalPiPackageSource(pluginRoot, source);
|
|
1266
|
+
return {
|
|
1267
|
+
source: normalizeLocalPiPackageSource(pluginRoot, source.source),
|
|
1268
|
+
...source.extensions ? { extensions: source.extensions } : {},
|
|
1269
|
+
...source.skills ? { skills: source.skills } : {},
|
|
1270
|
+
...source.prompts ? { prompts: source.prompts } : {},
|
|
1271
|
+
...source.themes ? { themes: source.themes } : {}
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function normalizeBoringPluginPiPackages(plugins) {
|
|
1275
|
+
return plugins.flatMap(
|
|
1276
|
+
(plugin) => (plugin.pi?.packages ?? []).map(
|
|
1277
|
+
(source) => normalizeBoringPluginPiPackageSource(plugin.rootDir, source)
|
|
1278
|
+
)
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1244
1282
|
// src/server/agentPlugins/manager.ts
|
|
1283
|
+
function skillPathForPiLoader(path) {
|
|
1284
|
+
return existsSync4(join4(path, "SKILL.md")) ? dirname5(path) : path;
|
|
1285
|
+
}
|
|
1245
1286
|
function preflightErrorId(pluginDir) {
|
|
1246
1287
|
return `preflight-${createHash("sha256").update(pluginDir).digest("hex").slice(0, 12)}`;
|
|
1247
1288
|
}
|
|
@@ -1300,8 +1341,17 @@ function directorySignature(root) {
|
|
|
1300
1341
|
visit(root, 0);
|
|
1301
1342
|
return hash.digest("hex");
|
|
1302
1343
|
}
|
|
1344
|
+
function normalizePluginSubpath(rootDir, path) {
|
|
1345
|
+
return relative3(rootDir, path).replaceAll("\\", "/");
|
|
1346
|
+
}
|
|
1347
|
+
function frontSignatureRoot(plugin) {
|
|
1348
|
+
if (!plugin.frontPath) return void 0;
|
|
1349
|
+
const frontRoot = join4(plugin.rootDir, "front");
|
|
1350
|
+
const rel = relative3(frontRoot, plugin.frontPath);
|
|
1351
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel) ? frontRoot : dirname5(plugin.frontPath);
|
|
1352
|
+
}
|
|
1303
1353
|
function pluginSignature(plugin) {
|
|
1304
|
-
return createHash("sha256").update(JSON.stringify(plugin.boring)).update(JSON.stringify(plugin.pi ?? {})).update(plugin.version).update(plugin.frontPath ?? "").update(pluginFileSignature(plugin.frontPath)).update(directorySignature(
|
|
1354
|
+
return createHash("sha256").update(JSON.stringify(plugin.boring)).update(JSON.stringify(plugin.pi ?? {})).update(plugin.version).update(plugin.frontPath ?? "").update(pluginFileSignature(plugin.frontPath)).update(directorySignature(frontSignatureRoot(plugin))).update(directorySignature(join4(plugin.rootDir, "shared"))).update(plugin.serverPath ?? "").update(pluginFileSignature(plugin.serverPath)).update(directorySignature(plugin.serverPath ? dirname5(plugin.serverPath) : void 0)).update((plugin.extensionPaths ?? []).join("\0")).update((plugin.skillPaths ?? []).join("\0")).digest("hex");
|
|
1305
1355
|
}
|
|
1306
1356
|
function computeRequiresRestart(previous, next) {
|
|
1307
1357
|
if (!previous) return [];
|
|
@@ -1316,32 +1366,55 @@ function computeRequiresRestart(previous, next) {
|
|
|
1316
1366
|
var BoringPluginAssetManager = class {
|
|
1317
1367
|
pluginDirs;
|
|
1318
1368
|
errorRoot;
|
|
1369
|
+
frontTargetResolver;
|
|
1370
|
+
includeLegacyFrontUrl;
|
|
1319
1371
|
loaded = /* @__PURE__ */ new Map();
|
|
1320
1372
|
revisions = /* @__PURE__ */ new Map();
|
|
1321
1373
|
listeners = /* @__PURE__ */ new Set();
|
|
1374
|
+
lastErrors = /* @__PURE__ */ new Map();
|
|
1322
1375
|
loading = null;
|
|
1323
1376
|
reloadQueued = false;
|
|
1324
1377
|
constructor(options) {
|
|
1325
1378
|
this.pluginDirs = options.pluginDirs;
|
|
1326
1379
|
this.errorRoot = options.errorRoot ?? join4(process.cwd(), ".pi", "extensions");
|
|
1380
|
+
this.frontTargetResolver = options.frontTargetResolver;
|
|
1381
|
+
this.includeLegacyFrontUrl = options.includeLegacyFrontUrl ?? true;
|
|
1327
1382
|
}
|
|
1328
1383
|
preflight() {
|
|
1329
1384
|
return preflightBoringPlugins(this.pluginDirs);
|
|
1330
1385
|
}
|
|
1331
1386
|
list() {
|
|
1387
|
+
return [...this.loaded.values()].map((plugin) => this.toListEntry(plugin));
|
|
1388
|
+
}
|
|
1389
|
+
getError(pluginId) {
|
|
1390
|
+
const path = this.errorPath(pluginId);
|
|
1391
|
+
if (!path || !existsSync4(path)) return null;
|
|
1392
|
+
return readFileSync3(path, "utf8");
|
|
1393
|
+
}
|
|
1394
|
+
getErrors() {
|
|
1395
|
+
return [...this.lastErrors.values()];
|
|
1396
|
+
}
|
|
1397
|
+
inspectLoaded() {
|
|
1332
1398
|
return [...this.loaded.values()].map((plugin) => ({
|
|
1333
1399
|
id: plugin.id,
|
|
1334
|
-
boring: plugin.boring,
|
|
1335
|
-
...plugin.pi ? { pi: plugin.pi } : {},
|
|
1336
1400
|
version: plugin.version,
|
|
1337
1401
|
revision: plugin.revision,
|
|
1338
|
-
|
|
1402
|
+
rootDir: plugin.rootDir,
|
|
1403
|
+
...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
|
|
1404
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
|
|
1339
1405
|
}));
|
|
1340
1406
|
}
|
|
1341
|
-
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
return
|
|
1407
|
+
inspectLoadedPiSnapshot() {
|
|
1408
|
+
const plugins = [...this.loaded.values()];
|
|
1409
|
+
const prompts = plugins.map((plugin) => plugin.pi?.systemPrompt?.trim()).filter((prompt) => Boolean(prompt));
|
|
1410
|
+
return {
|
|
1411
|
+
additionalSkillPaths: [...new Set(plugins.flatMap((plugin) => plugin.skillPaths ?? []).map(skillPathForPiLoader))],
|
|
1412
|
+
packages: compactPiPackages(normalizeBoringPluginPiPackages(plugins)),
|
|
1413
|
+
extensionPaths: plugins.flatMap((plugin) => plugin.extensionPaths ?? []),
|
|
1414
|
+
...prompts.length > 0 ? { systemPromptAppend: `# Loaded boring-ui plugin context
|
|
1415
|
+
|
|
1416
|
+
${prompts.join("\n\n")}` } : {}
|
|
1417
|
+
};
|
|
1345
1418
|
}
|
|
1346
1419
|
subscribe(listener) {
|
|
1347
1420
|
this.listeners.add(listener);
|
|
@@ -1366,19 +1439,21 @@ var BoringPluginAssetManager = class {
|
|
|
1366
1439
|
return result;
|
|
1367
1440
|
}
|
|
1368
1441
|
async doLoadOnce() {
|
|
1442
|
+
this.lastErrors.clear();
|
|
1369
1443
|
const scan = scanBoringPlugins(this.pluginDirs);
|
|
1370
1444
|
const nextPlugins = scan.plugins;
|
|
1371
1445
|
const nextIds = new Set(nextPlugins.map((plugin) => plugin.id));
|
|
1372
|
-
const invalidPluginDirs = new Set(scan.preflight.errors.map((error) =>
|
|
1446
|
+
const invalidPluginDirs = new Set(scan.preflight.errors.map((error) => resolve5(error.pluginDir)));
|
|
1373
1447
|
const events = [];
|
|
1374
1448
|
const errors = [];
|
|
1375
1449
|
this.collectPreflightErrors(scan.preflight, events, errors);
|
|
1376
1450
|
for (const id of [...this.loaded.keys()]) {
|
|
1377
1451
|
if (nextIds.has(id)) continue;
|
|
1378
1452
|
const previous = this.loaded.get(id);
|
|
1379
|
-
if (previous && invalidPluginDirs.has(
|
|
1453
|
+
if (previous && invalidPluginDirs.has(resolve5(previous.rootDir))) continue;
|
|
1380
1454
|
const revision = this.bumpRevision(id);
|
|
1381
1455
|
this.loaded.delete(id);
|
|
1456
|
+
this.lastErrors.delete(id);
|
|
1382
1457
|
if (previous) {
|
|
1383
1458
|
try {
|
|
1384
1459
|
clearPluginSignatureCache(previous.rootDir);
|
|
@@ -1395,9 +1470,17 @@ var BoringPluginAssetManager = class {
|
|
|
1395
1470
|
const previous = this.loaded.get(plugin.id);
|
|
1396
1471
|
if (previous?.signature === signature) continue;
|
|
1397
1472
|
const revision = this.bumpRevision(plugin.id);
|
|
1473
|
+
const frontTarget = this.resolveFrontTarget(plugin, revision);
|
|
1398
1474
|
const serverSignature = plugin.serverPath ? pluginFileSignature(plugin.serverPath) : null;
|
|
1399
|
-
const record = {
|
|
1475
|
+
const record = {
|
|
1476
|
+
...plugin,
|
|
1477
|
+
revision,
|
|
1478
|
+
signature,
|
|
1479
|
+
...frontTarget ? { frontTarget } : {},
|
|
1480
|
+
serverSignature
|
|
1481
|
+
};
|
|
1400
1482
|
this.loaded.set(plugin.id, record);
|
|
1483
|
+
this.lastErrors.delete(plugin.id);
|
|
1401
1484
|
this.clearError(plugin.id);
|
|
1402
1485
|
try {
|
|
1403
1486
|
writePluginSignatureCache(plugin.rootDir, { serverSignature });
|
|
@@ -1410,7 +1493,8 @@ var BoringPluginAssetManager = class {
|
|
|
1410
1493
|
boring: plugin.boring,
|
|
1411
1494
|
version: plugin.version,
|
|
1412
1495
|
revision,
|
|
1413
|
-
...
|
|
1496
|
+
...this.frontUrlPayload(plugin.frontUrl),
|
|
1497
|
+
...frontTarget ? { frontTarget } : {},
|
|
1414
1498
|
...requiresRestart.length > 0 ? { requiresRestart } : {}
|
|
1415
1499
|
};
|
|
1416
1500
|
events.push(event);
|
|
@@ -1420,7 +1504,9 @@ var BoringPluginAssetManager = class {
|
|
|
1420
1504
|
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1421
1505
|
this.writeError(plugin.id, message);
|
|
1422
1506
|
const event = { type: "boring.plugin.error", id: plugin.id, revision, message };
|
|
1423
|
-
|
|
1507
|
+
const loadError = { id: plugin.id, revision, message };
|
|
1508
|
+
this.lastErrors.set(plugin.id, loadError);
|
|
1509
|
+
errors.push(loadError);
|
|
1424
1510
|
events.push(event);
|
|
1425
1511
|
this.emit(event);
|
|
1426
1512
|
}
|
|
@@ -1435,6 +1521,7 @@ var BoringPluginAssetManager = class {
|
|
|
1435
1521
|
|
|
1436
1522
|
Plugin dir: ${error.pluginDir}`;
|
|
1437
1523
|
const loadError = { id, revision, message };
|
|
1524
|
+
this.lastErrors.set(id, loadError);
|
|
1438
1525
|
errors.push(loadError);
|
|
1439
1526
|
this.writeError(id, message);
|
|
1440
1527
|
const event = { type: "boring.plugin.error", id, revision, message };
|
|
@@ -1447,6 +1534,31 @@ Plugin dir: ${error.pluginDir}`;
|
|
|
1447
1534
|
this.revisions.set(id, next);
|
|
1448
1535
|
return next;
|
|
1449
1536
|
}
|
|
1537
|
+
toListEntry(plugin) {
|
|
1538
|
+
return {
|
|
1539
|
+
id: plugin.id,
|
|
1540
|
+
boring: plugin.boring,
|
|
1541
|
+
...plugin.pi ? { pi: plugin.pi } : {},
|
|
1542
|
+
version: plugin.version,
|
|
1543
|
+
revision: plugin.revision,
|
|
1544
|
+
...this.frontUrlPayload(plugin.frontUrl),
|
|
1545
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
frontUrlPayload(frontUrl) {
|
|
1549
|
+
if (!this.includeLegacyFrontUrl || !frontUrl) return {};
|
|
1550
|
+
return { frontUrl };
|
|
1551
|
+
}
|
|
1552
|
+
resolveFrontTarget(plugin, revision) {
|
|
1553
|
+
if (!plugin.frontPath || !this.frontTargetResolver) return void 0;
|
|
1554
|
+
const frontEntrySubpath = typeof plugin.boring.front === "string" ? plugin.boring.front.replace(/^\.\//, "") : normalizePluginSubpath(plugin.rootDir, plugin.frontPath);
|
|
1555
|
+
const frontTarget = this.frontTargetResolver(plugin, {
|
|
1556
|
+
revision,
|
|
1557
|
+
frontEntrySubpath
|
|
1558
|
+
});
|
|
1559
|
+
if (!frontTarget) return void 0;
|
|
1560
|
+
return { ...frontTarget, revision };
|
|
1561
|
+
}
|
|
1450
1562
|
emit(event) {
|
|
1451
1563
|
for (const listener of [...this.listeners]) {
|
|
1452
1564
|
try {
|
|
@@ -1459,8 +1571,8 @@ Plugin dir: ${error.pluginDir}`;
|
|
|
1459
1571
|
}
|
|
1460
1572
|
errorPath(pluginId) {
|
|
1461
1573
|
if (!isValidBoringPluginId(pluginId)) return null;
|
|
1462
|
-
const root =
|
|
1463
|
-
const path =
|
|
1574
|
+
const root = resolve5(this.errorRoot);
|
|
1575
|
+
const path = resolve5(root, pluginId, ".error");
|
|
1464
1576
|
const rel = relative3(root, path);
|
|
1465
1577
|
if (rel.startsWith("..") || isAbsolute3(rel)) return null;
|
|
1466
1578
|
return path;
|
|
@@ -1535,27 +1647,44 @@ async function boringPluginRoutes(app, opts) {
|
|
|
1535
1647
|
res.setHeader("Connection", "keep-alive");
|
|
1536
1648
|
res.setHeader("X-Accel-Buffering", "no");
|
|
1537
1649
|
res.flushHeaders?.();
|
|
1538
|
-
const write = (
|
|
1650
|
+
const write = (eventName, payload) => {
|
|
1539
1651
|
try {
|
|
1540
|
-
res.write(`event: ${
|
|
1652
|
+
res.write(`event: ${eventName}
|
|
1541
1653
|
`);
|
|
1542
|
-
res.write(`data: ${JSON.stringify(
|
|
1654
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
1543
1655
|
|
|
1544
1656
|
`);
|
|
1545
1657
|
} catch {
|
|
1546
1658
|
}
|
|
1547
1659
|
};
|
|
1660
|
+
const liveQueue = [];
|
|
1661
|
+
let replaying = true;
|
|
1662
|
+
const unsubscribe = manager.subscribe((event) => {
|
|
1663
|
+
const payload = { ...event, replay: false };
|
|
1664
|
+
if (replaying) {
|
|
1665
|
+
liveQueue.push({ eventName: event.type, payload });
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
write(event.type, payload);
|
|
1669
|
+
});
|
|
1548
1670
|
for (const plugin of manager.list()) {
|
|
1549
|
-
write({
|
|
1671
|
+
write("boring.plugin.load", {
|
|
1550
1672
|
type: "boring.plugin.load",
|
|
1551
1673
|
id: plugin.id,
|
|
1552
1674
|
boring: plugin.boring,
|
|
1553
1675
|
version: plugin.version,
|
|
1554
1676
|
revision: plugin.revision,
|
|
1555
|
-
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {}
|
|
1677
|
+
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {},
|
|
1678
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
|
|
1679
|
+
replay: true
|
|
1556
1680
|
});
|
|
1557
1681
|
}
|
|
1558
|
-
|
|
1682
|
+
write("boring.plugin.replay-complete", {
|
|
1683
|
+
type: "boring.plugin.replay-complete",
|
|
1684
|
+
replay: true
|
|
1685
|
+
});
|
|
1686
|
+
replaying = false;
|
|
1687
|
+
for (const event of liveQueue) write(event.eventName, event.payload);
|
|
1559
1688
|
const heartbeat = setInterval(() => {
|
|
1560
1689
|
try {
|
|
1561
1690
|
res.write(": heartbeat\n\n");
|