@hachej/boring-workspace 0.1.24 → 0.1.27

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.
@@ -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
- export { S as ServerBootstrapOptions, e as ServerBootstrapResult, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, W as WorkspaceServerPlugin, f as bootstrapServer, c as createInMemoryBridge, g as defineServerPlugin, v as validateServerPlugin } from './createInMemoryBridge-DLckqafe.js';
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 { B as BoringPackageBoringField, a as BoringPackagePiField } from './manifest-C2vVgH_e.js';
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 absolute paths into the
73
- * installed `@hachej/boring-pi` package so the agent's `read` tool can
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, type BoringPluginEvent, type BoringPluginListEntry, type BoringPluginScanResult, type BoringServerPluginManifest, type PluginReloadRebuild, type PluginRestartWarning, UiBridge, type UiRoutesOptions, aggregatePluginPrompts, boringPluginRoutes, buildBoringSystemPrompt, collectRestartWarnings, createExecUiTool, createGetUiStateTool, createWorkspaceUiTools, pluginFileSignature, preflightBoringPlugins, readBoringPlugins, readPluginSignatureCache, scanBoringPlugins, uiRoutes, writePluginSignatureCache };
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}. Scaffold.** Bash \`${opts.scaffoldCommand} <kebab-name> "$BORING_AGENT_WORKSPACE_ROOT"\` \u2014 writes canonical files under \`$BORING_AGENT_WORKSPACE_ROOT/.pi/extensions/<kebab-name>/\`. Read the generated \`package.json\` + \`front/index.tsx\`. Do NOT skip this or write from memory. Never \`cd\` to a parent repo or write plugins outside \`$BORING_AGENT_WORKSPACE_ROOT/.pi/extensions/\`.`
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 the absolute path; the agent runtime guarantees these files exist on the host:",
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. Workspace root: `$BORING_AGENT_WORKSPACE_ROOT`; plugin files go under `$BORING_AGENT_WORKSPACE_ROOT/.pi/extensions/<name>/`.",
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: for `.csv`/file-tree opens, import `WORKSPACE_OPEN_PATH_SURFACE_KIND` (and `PaneProps`) from `@hachej/boring-workspace/plugin`, read `request.target`, and fetch `/api/v1/files/raw?path=${encodeURIComponent(request.target)}`. Never import these from the root package, use `/workspace/read`, or string kind `"WORKSPACE_OPEN_PATH_SURFACE_KIND"`.',
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 resolve4 } from "path";
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(plugin.frontPath ? dirname5(plugin.frontPath) : void 0)).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");
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
- ...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {}
1402
+ rootDir: plugin.rootDir,
1403
+ ...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
1404
+ ...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
1339
1405
  }));
1340
1406
  }
1341
- getError(pluginId) {
1342
- const path = this.errorPath(pluginId);
1343
- if (!path || !existsSync4(path)) return null;
1344
- return readFileSync3(path, "utf8");
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) => resolve4(error.pluginDir)));
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(resolve4(previous.rootDir))) continue;
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 = { ...plugin, revision, signature, serverSignature };
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
- ...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {},
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
- errors.push({ id: plugin.id, revision, message });
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 = resolve4(this.errorRoot);
1463
- const path = resolve4(root, pluginId, ".error");
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 = (event) => {
1650
+ const write = (eventName, payload) => {
1539
1651
  try {
1540
- res.write(`event: ${event.type}
1652
+ res.write(`event: ${eventName}
1541
1653
  `);
1542
- res.write(`data: ${JSON.stringify(event)}
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
- const unsubscribe = manager.subscribe(write);
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");