@hachej/boring-workspace 0.1.23 → 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-D8Rmj8Bo.js → FileTree-BZGu5Ap6.js} +114 -104
- package/dist/{MarkdownEditor-DKC4gNT4.js → MarkdownEditor-DshmttZM.js} +9 -9
- package/dist/{WorkspaceLoadingState-hKrnYCL3.js → WorkspaceLoadingState-DVCLcOQu.js} +167 -146
- package/dist/WorkspaceProvider-DQ-325Qs.js +6367 -0
- package/dist/app-front.d.ts +114 -2
- package/dist/app-front.js +787 -333
- package/dist/app-server.d.ts +10 -3
- package/dist/app-server.js +744 -579
- package/dist/createInMemoryBridge--ZFPAgXy.d.ts +161 -0
- package/dist/events.d.ts +3 -0
- package/dist/{manifest-CyNNdfYz.d.ts → manifest-C2vVgH_e.d.ts} +2 -0
- package/dist/plugin.d.ts +8 -3
- package/dist/plugin.js +3 -2
- package/dist/server.d.ts +50 -70
- package/dist/server.js +192 -44
- package/dist/shared.d.ts +2 -2
- package/dist/{surface-COYagY2m.d.ts → surface-CEEkd81D.d.ts} +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +409 -404
- package/dist/{ui-bridge-CT18yqwN.d.ts → ui-bridge-Bdgl2hR8.d.ts} +2 -0
- package/dist/workspace.css +73 -0
- package/dist/workspace.d.ts +228 -6
- package/dist/workspace.js +188 -179
- package/docs/INTERFACES.md +6 -0
- package/docs/plans/FULL_PAGE_PANEL_ROUTE_SPEC.md +633 -0
- package/package.json +6 -6
- package/dist/WorkspaceProvider-Cn0sPgaB.js +0 -5976
- package/dist/createInMemoryBridge-CYNW1h_o.d.ts +0 -61
package/dist/server.js
CHANGED
|
@@ -165,7 +165,7 @@ data: ${JSON.stringify({ v: UI_BRIDGE_PROTOCOL_VERSION })}
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// src/server/ui-control/tools/uiTools.ts
|
|
168
|
-
import {
|
|
168
|
+
import { stat } from "fs/promises";
|
|
169
169
|
import { resolve, isAbsolute, relative, win32 } from "path";
|
|
170
170
|
function makeError(message) {
|
|
171
171
|
return {
|
|
@@ -203,7 +203,7 @@ function validatePathSyntax(relPath, workspaceRoot) {
|
|
|
203
203
|
}
|
|
204
204
|
return { ok: true };
|
|
205
205
|
}
|
|
206
|
-
async function
|
|
206
|
+
async function validateExistingPath(workspaceRoot, relPath) {
|
|
207
207
|
const syntax = validatePathSyntax(relPath, workspaceRoot);
|
|
208
208
|
if (!syntax.ok) return syntax;
|
|
209
209
|
const resolved = resolve(workspaceRoot, relPath);
|
|
@@ -215,8 +215,8 @@ async function validatePath(workspaceRoot, relPath) {
|
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
217
|
try {
|
|
218
|
-
await
|
|
219
|
-
return { ok: true };
|
|
218
|
+
const fileStat = await stat(resolved);
|
|
219
|
+
return { ok: true, kind: fileStat.isDirectory() ? "dir" : "file" };
|
|
220
220
|
} catch {
|
|
221
221
|
return {
|
|
222
222
|
ok: false,
|
|
@@ -227,6 +227,7 @@ async function validatePath(workspaceRoot, relPath) {
|
|
|
227
227
|
function createGetUiStateTool(uiBridge) {
|
|
228
228
|
return {
|
|
229
229
|
name: "get_ui_state",
|
|
230
|
+
readinessRequirements: ["ui-bridge"],
|
|
230
231
|
description: [
|
|
231
232
|
"Read the current workspace UI state. Returns a JSON object with:",
|
|
232
233
|
"- workbenchOpen (boolean): is the right-side workbench pane visible?",
|
|
@@ -280,12 +281,13 @@ function isVerified(kind, params, state) {
|
|
|
280
281
|
return true;
|
|
281
282
|
}
|
|
282
283
|
function createExecUiTool(uiBridge, opts = {}) {
|
|
283
|
-
const { workspaceRoot } = opts;
|
|
284
|
+
const { workspaceRoot, resolvePathKind } = opts;
|
|
284
285
|
const verifyDelayMs = opts.verifyDelayMs ?? 200;
|
|
285
286
|
const verifyRetries = opts.verifyRetries ?? 2;
|
|
286
287
|
const verifyIntervalMs = opts.verifyIntervalMs ?? 200;
|
|
287
288
|
return {
|
|
288
289
|
name: "exec_ui",
|
|
290
|
+
readinessRequirements: ["ui-bridge"],
|
|
289
291
|
description: [
|
|
290
292
|
"Execute a UI command in the workspace. Use this to open files, panels,",
|
|
291
293
|
"navigate to lines, or show notifications.",
|
|
@@ -319,6 +321,8 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
319
321
|
" returned \u2014 don't give up and don't switch to the read",
|
|
320
322
|
" tool. Repeat until openFile succeeds or no candidate",
|
|
321
323
|
" is found.",
|
|
324
|
+
" If the path is a folder, openFile reveals/selects it in",
|
|
325
|
+
" the file tree instead of opening an editor tab.",
|
|
322
326
|
" Example: {kind:'openFile', params:{path:'README.md'}}",
|
|
323
327
|
"",
|
|
324
328
|
" openPanel params: { id: string, component: string,",
|
|
@@ -402,6 +406,7 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
402
406
|
return makeError("openSurface: meta must be an object when provided");
|
|
403
407
|
}
|
|
404
408
|
}
|
|
409
|
+
let effectiveKind = kind;
|
|
405
410
|
if (PATH_BEARING_KINDS.has(kind)) {
|
|
406
411
|
const relPath = getPathParam(kind, cmdParams);
|
|
407
412
|
if (!relPath) {
|
|
@@ -412,14 +417,25 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
412
417
|
const syntax = validatePathSyntax(relPath, workspaceRoot);
|
|
413
418
|
if (!syntax.ok) return makeError(syntax.reason);
|
|
414
419
|
if (workspaceRoot) {
|
|
415
|
-
const check = await
|
|
420
|
+
const check = await validateExistingPath(workspaceRoot, relPath);
|
|
416
421
|
if (!check.ok) {
|
|
417
422
|
return makeError(check.reason);
|
|
418
423
|
}
|
|
424
|
+
if (kind === "openFile" && check.kind === "dir") {
|
|
425
|
+
effectiveKind = "expandToFile";
|
|
426
|
+
}
|
|
427
|
+
} else if (resolvePathKind) {
|
|
428
|
+
const pathKind = await resolvePathKind(relPath);
|
|
429
|
+
if (!pathKind) {
|
|
430
|
+
return makeError(`file not found at "${relPath}". Try find or grep to locate the file before retrying openFile.`);
|
|
431
|
+
}
|
|
432
|
+
if (kind === "openFile" && pathKind === "dir") {
|
|
433
|
+
effectiveKind = "expandToFile";
|
|
434
|
+
}
|
|
419
435
|
}
|
|
420
436
|
}
|
|
421
437
|
try {
|
|
422
|
-
const command = { kind, params: cmdParams };
|
|
438
|
+
const command = { kind: effectiveKind, params: cmdParams };
|
|
423
439
|
const result = await uiBridge.postCommand(command);
|
|
424
440
|
if (result.status === "error") {
|
|
425
441
|
return {
|
|
@@ -428,11 +444,11 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
428
444
|
details: result
|
|
429
445
|
};
|
|
430
446
|
}
|
|
431
|
-
if (verifyDelayMs > 0 && VERIFIABLE_KINDS.has(
|
|
447
|
+
if (verifyDelayMs > 0 && VERIFIABLE_KINDS.has(effectiveKind)) {
|
|
432
448
|
await new Promise((r) => setTimeout(r, verifyDelayMs));
|
|
433
449
|
let uiState = await uiBridge.getState();
|
|
434
450
|
for (let i = 0; i < verifyRetries; i++) {
|
|
435
|
-
if (isVerified(
|
|
451
|
+
if (isVerified(effectiveKind, cmdParams, uiState)) break;
|
|
436
452
|
await new Promise((r) => setTimeout(r, verifyIntervalMs));
|
|
437
453
|
uiState = await uiBridge.getState();
|
|
438
454
|
}
|
|
@@ -746,7 +762,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
746
762
|
if (opts.scaffoldCommand) {
|
|
747
763
|
n += 1;
|
|
748
764
|
steps.push(
|
|
749
|
-
`**${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.`
|
|
750
766
|
);
|
|
751
767
|
} else {
|
|
752
768
|
n += 1;
|
|
@@ -772,7 +788,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
772
788
|
steps.push(`**${n}. Ask the user to run \`/reload\`** to publish the change.`);
|
|
773
789
|
const docsBlock = boringPiRoot ? [
|
|
774
790
|
"## boring-ui plugin authoring documentation",
|
|
775
|
-
"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`:",
|
|
776
792
|
...buildDocsRefs(boringPiRoot).map((r) => `- ${r.topic}: ${r.path}`),
|
|
777
793
|
"Follow .md cross-references when present (e.g. SKILL.md may link to a reference doc \u2014 read both)."
|
|
778
794
|
].join("\n") : [
|
|
@@ -780,7 +796,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
780
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."
|
|
781
797
|
].join("\n");
|
|
782
798
|
return [
|
|
783
|
-
"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.",
|
|
784
800
|
[
|
|
785
801
|
"## Plugin authoring \u2014 required workflow",
|
|
786
802
|
"",
|
|
@@ -790,7 +806,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
790
806
|
"- API factories: `createPlugin`, `defineFrontPlugin`, `defineComponent` \u2014 use `definePlugin({id, panels, commands, ...})` from `@hachej/boring-workspace/plugin`.",
|
|
791
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).",
|
|
792
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.",
|
|
793
|
-
'- 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"`.',
|
|
794
810
|
"- Pi extension tools: `defineTool` and `export const tools` do NOT exist. Export `default function (pi) { pi.registerTool({ name, description, execute }) }`.",
|
|
795
811
|
'- Server/Pi tool method: `handler` \u2014 use `execute`. Return shape: `{ content: [{ type: "text", text }] }` (NEVER a bare string).',
|
|
796
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.",
|
|
@@ -804,7 +820,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
804
820
|
// src/server/agentPlugins/manager.ts
|
|
805
821
|
import { createHash } from "crypto";
|
|
806
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";
|
|
807
|
-
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";
|
|
808
824
|
|
|
809
825
|
// src/shared/plugins/manifest.ts
|
|
810
826
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -854,8 +870,8 @@ function validateBoringField(issues, boring) {
|
|
|
854
870
|
));
|
|
855
871
|
}
|
|
856
872
|
}
|
|
857
|
-
if (boring.id !== void 0) {
|
|
858
|
-
issues.push(issue("
|
|
873
|
+
if (boring.id !== void 0 && (typeof boring.id !== "string" || !isValidBoringPluginId(boring.id))) {
|
|
874
|
+
issues.push(issue("INVALID_ID", "boring.id", "boring.id must start with a letter or number and use only letters, numbers, dot, underscore, colon, or dash"));
|
|
859
875
|
}
|
|
860
876
|
const front = boring.front;
|
|
861
877
|
if (front !== void 0 && (typeof front !== "string" || !isSafePluginRelativePath(front))) {
|
|
@@ -869,6 +885,7 @@ function validateBoringField(issues, boring) {
|
|
|
869
885
|
issues.push(issue("INVALID_FIELD", "boring.label", "boring.label must be a string when provided"));
|
|
870
886
|
}
|
|
871
887
|
return {
|
|
888
|
+
...typeof boring.id === "string" ? { id: boring.id } : {},
|
|
872
889
|
...typeof boring.front === "string" ? { front: boring.front } : {},
|
|
873
890
|
...typeof boring.server === "string" || boring.server === false ? { server: boring.server } : {},
|
|
874
891
|
...typeof boring.label === "string" ? { label: boring.label } : {}
|
|
@@ -998,6 +1015,8 @@ function resolveContainedPluginPath(rootDir, value, options = {}) {
|
|
|
998
1015
|
|
|
999
1016
|
// src/server/agentPlugins/scan.ts
|
|
1000
1017
|
function pluginIdFromPackageJson(pkg, rootDir) {
|
|
1018
|
+
const explicitId = typeof pkg.boring?.id === "string" && pkg.boring.id.trim() ? pkg.boring.id.trim() : void 0;
|
|
1019
|
+
if (explicitId) return explicitId;
|
|
1001
1020
|
const name = typeof pkg.name === "string" && pkg.name.trim() ? pkg.name.trim() : void 0;
|
|
1002
1021
|
return (name ?? rootDir.split(/[\\/]/).at(-1) ?? "plugin").replace(/^@/, "").replaceAll("/", "-");
|
|
1003
1022
|
}
|
|
@@ -1177,8 +1196,8 @@ import { dirname as dirname4, join as join3 } from "path";
|
|
|
1177
1196
|
var PLUGIN_SIGNATURE_CACHE_FILE = ".boring-signature.json";
|
|
1178
1197
|
function pluginFileSignature(path) {
|
|
1179
1198
|
if (!path || !existsSync3(path)) return "missing";
|
|
1180
|
-
const
|
|
1181
|
-
return `${
|
|
1199
|
+
const stat2 = statSync2(path);
|
|
1200
|
+
return `${stat2.mtimeMs}:${stat2.size}`;
|
|
1182
1201
|
}
|
|
1183
1202
|
function cachePath(pluginRootDir) {
|
|
1184
1203
|
return join3(pluginRootDir, PLUGIN_SIGNATURE_CACHE_FILE);
|
|
@@ -1222,7 +1241,48 @@ function clearPluginSignatureCache(pluginRootDir) {
|
|
|
1222
1241
|
if (existsSync3(path)) rmSync(path, { force: true });
|
|
1223
1242
|
}
|
|
1224
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
|
+
|
|
1225
1282
|
// src/server/agentPlugins/manager.ts
|
|
1283
|
+
function skillPathForPiLoader(path) {
|
|
1284
|
+
return existsSync4(join4(path, "SKILL.md")) ? dirname5(path) : path;
|
|
1285
|
+
}
|
|
1226
1286
|
function preflightErrorId(pluginDir) {
|
|
1227
1287
|
return `preflight-${createHash("sha256").update(pluginDir).digest("hex").slice(0, 12)}`;
|
|
1228
1288
|
}
|
|
@@ -1245,8 +1305,8 @@ function directorySignature(root) {
|
|
|
1245
1305
|
count++;
|
|
1246
1306
|
const path = join4(dir, entry.name);
|
|
1247
1307
|
const rel = relative3(root, path);
|
|
1248
|
-
const
|
|
1249
|
-
if (
|
|
1308
|
+
const stat2 = lstatSync(path);
|
|
1309
|
+
if (stat2.isSymbolicLink()) {
|
|
1250
1310
|
let target;
|
|
1251
1311
|
try {
|
|
1252
1312
|
target = realpathSync2(path);
|
|
@@ -1271,9 +1331,9 @@ function directorySignature(root) {
|
|
|
1271
1331
|
continue;
|
|
1272
1332
|
}
|
|
1273
1333
|
hash.update(rel);
|
|
1274
|
-
hash.update(String(
|
|
1275
|
-
hash.update(String(
|
|
1276
|
-
if (
|
|
1334
|
+
hash.update(String(stat2.mtimeMs));
|
|
1335
|
+
hash.update(String(stat2.size));
|
|
1336
|
+
if (stat2.isDirectory()) {
|
|
1277
1337
|
visit(path, depth + 1);
|
|
1278
1338
|
}
|
|
1279
1339
|
}
|
|
@@ -1281,8 +1341,17 @@ function directorySignature(root) {
|
|
|
1281
1341
|
visit(root, 0);
|
|
1282
1342
|
return hash.digest("hex");
|
|
1283
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
|
+
}
|
|
1284
1353
|
function pluginSignature(plugin) {
|
|
1285
|
-
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");
|
|
1286
1355
|
}
|
|
1287
1356
|
function computeRequiresRestart(previous, next) {
|
|
1288
1357
|
if (!previous) return [];
|
|
@@ -1297,32 +1366,55 @@ function computeRequiresRestart(previous, next) {
|
|
|
1297
1366
|
var BoringPluginAssetManager = class {
|
|
1298
1367
|
pluginDirs;
|
|
1299
1368
|
errorRoot;
|
|
1369
|
+
frontTargetResolver;
|
|
1370
|
+
includeLegacyFrontUrl;
|
|
1300
1371
|
loaded = /* @__PURE__ */ new Map();
|
|
1301
1372
|
revisions = /* @__PURE__ */ new Map();
|
|
1302
1373
|
listeners = /* @__PURE__ */ new Set();
|
|
1374
|
+
lastErrors = /* @__PURE__ */ new Map();
|
|
1303
1375
|
loading = null;
|
|
1304
1376
|
reloadQueued = false;
|
|
1305
1377
|
constructor(options) {
|
|
1306
1378
|
this.pluginDirs = options.pluginDirs;
|
|
1307
1379
|
this.errorRoot = options.errorRoot ?? join4(process.cwd(), ".pi", "extensions");
|
|
1380
|
+
this.frontTargetResolver = options.frontTargetResolver;
|
|
1381
|
+
this.includeLegacyFrontUrl = options.includeLegacyFrontUrl ?? true;
|
|
1308
1382
|
}
|
|
1309
1383
|
preflight() {
|
|
1310
1384
|
return preflightBoringPlugins(this.pluginDirs);
|
|
1311
1385
|
}
|
|
1312
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() {
|
|
1313
1398
|
return [...this.loaded.values()].map((plugin) => ({
|
|
1314
1399
|
id: plugin.id,
|
|
1315
|
-
boring: plugin.boring,
|
|
1316
|
-
...plugin.pi ? { pi: plugin.pi } : {},
|
|
1317
1400
|
version: plugin.version,
|
|
1318
1401
|
revision: plugin.revision,
|
|
1319
|
-
|
|
1402
|
+
rootDir: plugin.rootDir,
|
|
1403
|
+
...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
|
|
1404
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
|
|
1320
1405
|
}));
|
|
1321
1406
|
}
|
|
1322
|
-
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
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
|
+
};
|
|
1326
1418
|
}
|
|
1327
1419
|
subscribe(listener) {
|
|
1328
1420
|
this.listeners.add(listener);
|
|
@@ -1347,19 +1439,21 @@ var BoringPluginAssetManager = class {
|
|
|
1347
1439
|
return result;
|
|
1348
1440
|
}
|
|
1349
1441
|
async doLoadOnce() {
|
|
1442
|
+
this.lastErrors.clear();
|
|
1350
1443
|
const scan = scanBoringPlugins(this.pluginDirs);
|
|
1351
1444
|
const nextPlugins = scan.plugins;
|
|
1352
1445
|
const nextIds = new Set(nextPlugins.map((plugin) => plugin.id));
|
|
1353
|
-
const invalidPluginDirs = new Set(scan.preflight.errors.map((error) =>
|
|
1446
|
+
const invalidPluginDirs = new Set(scan.preflight.errors.map((error) => resolve5(error.pluginDir)));
|
|
1354
1447
|
const events = [];
|
|
1355
1448
|
const errors = [];
|
|
1356
1449
|
this.collectPreflightErrors(scan.preflight, events, errors);
|
|
1357
1450
|
for (const id of [...this.loaded.keys()]) {
|
|
1358
1451
|
if (nextIds.has(id)) continue;
|
|
1359
1452
|
const previous = this.loaded.get(id);
|
|
1360
|
-
if (previous && invalidPluginDirs.has(
|
|
1453
|
+
if (previous && invalidPluginDirs.has(resolve5(previous.rootDir))) continue;
|
|
1361
1454
|
const revision = this.bumpRevision(id);
|
|
1362
1455
|
this.loaded.delete(id);
|
|
1456
|
+
this.lastErrors.delete(id);
|
|
1363
1457
|
if (previous) {
|
|
1364
1458
|
try {
|
|
1365
1459
|
clearPluginSignatureCache(previous.rootDir);
|
|
@@ -1376,9 +1470,17 @@ var BoringPluginAssetManager = class {
|
|
|
1376
1470
|
const previous = this.loaded.get(plugin.id);
|
|
1377
1471
|
if (previous?.signature === signature) continue;
|
|
1378
1472
|
const revision = this.bumpRevision(plugin.id);
|
|
1473
|
+
const frontTarget = this.resolveFrontTarget(plugin, revision);
|
|
1379
1474
|
const serverSignature = plugin.serverPath ? pluginFileSignature(plugin.serverPath) : null;
|
|
1380
|
-
const record = {
|
|
1475
|
+
const record = {
|
|
1476
|
+
...plugin,
|
|
1477
|
+
revision,
|
|
1478
|
+
signature,
|
|
1479
|
+
...frontTarget ? { frontTarget } : {},
|
|
1480
|
+
serverSignature
|
|
1481
|
+
};
|
|
1381
1482
|
this.loaded.set(plugin.id, record);
|
|
1483
|
+
this.lastErrors.delete(plugin.id);
|
|
1382
1484
|
this.clearError(plugin.id);
|
|
1383
1485
|
try {
|
|
1384
1486
|
writePluginSignatureCache(plugin.rootDir, { serverSignature });
|
|
@@ -1391,7 +1493,8 @@ var BoringPluginAssetManager = class {
|
|
|
1391
1493
|
boring: plugin.boring,
|
|
1392
1494
|
version: plugin.version,
|
|
1393
1495
|
revision,
|
|
1394
|
-
...
|
|
1496
|
+
...this.frontUrlPayload(plugin.frontUrl),
|
|
1497
|
+
...frontTarget ? { frontTarget } : {},
|
|
1395
1498
|
...requiresRestart.length > 0 ? { requiresRestart } : {}
|
|
1396
1499
|
};
|
|
1397
1500
|
events.push(event);
|
|
@@ -1401,7 +1504,9 @@ var BoringPluginAssetManager = class {
|
|
|
1401
1504
|
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1402
1505
|
this.writeError(plugin.id, message);
|
|
1403
1506
|
const event = { type: "boring.plugin.error", id: plugin.id, revision, message };
|
|
1404
|
-
|
|
1507
|
+
const loadError = { id: plugin.id, revision, message };
|
|
1508
|
+
this.lastErrors.set(plugin.id, loadError);
|
|
1509
|
+
errors.push(loadError);
|
|
1405
1510
|
events.push(event);
|
|
1406
1511
|
this.emit(event);
|
|
1407
1512
|
}
|
|
@@ -1416,6 +1521,7 @@ var BoringPluginAssetManager = class {
|
|
|
1416
1521
|
|
|
1417
1522
|
Plugin dir: ${error.pluginDir}`;
|
|
1418
1523
|
const loadError = { id, revision, message };
|
|
1524
|
+
this.lastErrors.set(id, loadError);
|
|
1419
1525
|
errors.push(loadError);
|
|
1420
1526
|
this.writeError(id, message);
|
|
1421
1527
|
const event = { type: "boring.plugin.error", id, revision, message };
|
|
@@ -1428,6 +1534,31 @@ Plugin dir: ${error.pluginDir}`;
|
|
|
1428
1534
|
this.revisions.set(id, next);
|
|
1429
1535
|
return next;
|
|
1430
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
|
+
}
|
|
1431
1562
|
emit(event) {
|
|
1432
1563
|
for (const listener of [...this.listeners]) {
|
|
1433
1564
|
try {
|
|
@@ -1440,8 +1571,8 @@ Plugin dir: ${error.pluginDir}`;
|
|
|
1440
1571
|
}
|
|
1441
1572
|
errorPath(pluginId) {
|
|
1442
1573
|
if (!isValidBoringPluginId(pluginId)) return null;
|
|
1443
|
-
const root =
|
|
1444
|
-
const path =
|
|
1574
|
+
const root = resolve5(this.errorRoot);
|
|
1575
|
+
const path = resolve5(root, pluginId, ".error");
|
|
1445
1576
|
const rel = relative3(root, path);
|
|
1446
1577
|
if (rel.startsWith("..") || isAbsolute3(rel)) return null;
|
|
1447
1578
|
return path;
|
|
@@ -1516,27 +1647,44 @@ async function boringPluginRoutes(app, opts) {
|
|
|
1516
1647
|
res.setHeader("Connection", "keep-alive");
|
|
1517
1648
|
res.setHeader("X-Accel-Buffering", "no");
|
|
1518
1649
|
res.flushHeaders?.();
|
|
1519
|
-
const write = (
|
|
1650
|
+
const write = (eventName, payload) => {
|
|
1520
1651
|
try {
|
|
1521
|
-
res.write(`event: ${
|
|
1652
|
+
res.write(`event: ${eventName}
|
|
1522
1653
|
`);
|
|
1523
|
-
res.write(`data: ${JSON.stringify(
|
|
1654
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
1524
1655
|
|
|
1525
1656
|
`);
|
|
1526
1657
|
} catch {
|
|
1527
1658
|
}
|
|
1528
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
|
+
});
|
|
1529
1670
|
for (const plugin of manager.list()) {
|
|
1530
|
-
write({
|
|
1671
|
+
write("boring.plugin.load", {
|
|
1531
1672
|
type: "boring.plugin.load",
|
|
1532
1673
|
id: plugin.id,
|
|
1533
1674
|
boring: plugin.boring,
|
|
1534
1675
|
version: plugin.version,
|
|
1535
1676
|
revision: plugin.revision,
|
|
1536
|
-
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {}
|
|
1677
|
+
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {},
|
|
1678
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
|
|
1679
|
+
replay: true
|
|
1537
1680
|
});
|
|
1538
1681
|
}
|
|
1539
|
-
|
|
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);
|
|
1540
1688
|
const heartbeat = setInterval(() => {
|
|
1541
1689
|
try {
|
|
1542
1690
|
res.write(": heartbeat\n\n");
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AgentTool, C as CommandResult, J as JSONSchema, T as ToolExecContext, c as ToolResult, U as UiBridge, a as UiCommand, b as UiState } from './ui-bridge-
|
|
2
|
-
export { C as CommandConfig, P as PaneProps, a as PanelConfig, b as PanelRegistration, S as SurfaceOpenRequest, c as SurfacePanelResolution, d as SurfaceResolverConfig, e as SurfaceResolverRegistration, W as WORKSPACE_OPEN_PATH_SURFACE_KIND, f as definePanel } from './surface-
|
|
1
|
+
export { A as AgentTool, C as CommandResult, J as JSONSchema, T as ToolExecContext, c as ToolResult, U as UiBridge, a as UiCommand, b as UiState } from './ui-bridge-Bdgl2hR8.js';
|
|
2
|
+
export { C as CommandConfig, P as PaneProps, a as PanelConfig, b as PanelRegistration, S as SurfaceOpenRequest, c as SurfacePanelResolution, d as SurfaceResolverConfig, e as SurfaceResolverRegistration, W as WORKSPACE_OPEN_PATH_SURFACE_KIND, f as definePanel } from './surface-CEEkd81D.js';
|
|
3
3
|
import 'react';
|
|
4
4
|
import 'dockview-react';
|
|
5
5
|
|
package/dist/testing.d.ts
CHANGED