@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.
- package/dist/{FileTree-DjPzfDMq.js → FileTree-DDO-RrZ8.js} +1 -1
- package/dist/{MarkdownEditor-BbSy0bLV.js → MarkdownEditor-Bx5InUeG.js} +1 -1
- package/dist/{WorkspaceLoadingState-fccm3AQg.js → WorkspaceLoadingState-DuLadpix.js} +1 -1
- package/dist/{WorkspaceProvider-BW4wzbpR.js → WorkspaceProvider-CpMMNtLh.js} +2319 -2215
- 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
package/dist/app-server.js
CHANGED
|
@@ -54,7 +54,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
54
54
|
if (opts.scaffoldCommand) {
|
|
55
55
|
n += 1;
|
|
56
56
|
steps.push(
|
|
57
|
-
`**${n}.
|
|
57
|
+
`**${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.`
|
|
58
58
|
);
|
|
59
59
|
} else {
|
|
60
60
|
n += 1;
|
|
@@ -80,7 +80,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
80
80
|
steps.push(`**${n}. Ask the user to run \`/reload\`** to publish the change.`);
|
|
81
81
|
const docsBlock = boringPiRoot ? [
|
|
82
82
|
"## boring-ui plugin authoring documentation",
|
|
83
|
-
"Read these only when the user asks to build, modify, or debug a workspace plugin. Use your `read` tool with
|
|
83
|
+
"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`:",
|
|
84
84
|
...buildDocsRefs(boringPiRoot).map((r) => `- ${r.topic}: ${r.path}`),
|
|
85
85
|
"Follow .md cross-references when present (e.g. SKILL.md may link to a reference doc \u2014 read both)."
|
|
86
86
|
].join("\n") : [
|
|
@@ -88,7 +88,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
88
88
|
"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."
|
|
89
89
|
].join("\n");
|
|
90
90
|
return [
|
|
91
|
-
"You are operating inside boring-ui.
|
|
91
|
+
"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.",
|
|
92
92
|
[
|
|
93
93
|
"## Plugin authoring \u2014 required workflow",
|
|
94
94
|
"",
|
|
@@ -98,7 +98,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
98
98
|
"- API factories: `createPlugin`, `defineFrontPlugin`, `defineComponent` \u2014 use `definePlugin({id, panels, commands, ...})` from `@hachej/boring-workspace/plugin`.",
|
|
99
99
|
"- 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).",
|
|
100
100
|
"- 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.",
|
|
101
|
-
'- File visualizers:
|
|
101
|
+
'- 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"`.',
|
|
102
102
|
"- Pi extension tools: `defineTool` and `export const tools` do NOT exist. Export `default function (pi) { pi.registerTool({ name, description, execute }) }`.",
|
|
103
103
|
'- Server/Pi tool method: `handler` \u2014 use `execute`. Return shape: `{ content: [{ type: "text", text }] }` (NEVER a bare string).',
|
|
104
104
|
"- 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.",
|
|
@@ -112,7 +112,7 @@ function buildBoringSystemPrompt(opts) {
|
|
|
112
112
|
// src/server/agentPlugins/manager.ts
|
|
113
113
|
import { createHash } from "crypto";
|
|
114
114
|
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";
|
|
115
|
-
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as
|
|
115
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as resolve4 } from "path";
|
|
116
116
|
|
|
117
117
|
// src/shared/plugins/manifest.ts
|
|
118
118
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -551,632 +551,721 @@ function clearPluginSignatureCache(pluginRootDir) {
|
|
|
551
551
|
if (existsSync3(path)) rmSync(path, { force: true });
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
// src/server/agentPlugins/
|
|
555
|
-
|
|
556
|
-
|
|
554
|
+
// src/server/agentPlugins/piPackages.ts
|
|
555
|
+
import { resolve as resolve3 } from "path";
|
|
556
|
+
var REMOTE_PI_PACKAGE_PREFIXES2 = ["npm:", "git:", "github:", "http:", "https:", "ssh:"];
|
|
557
|
+
function isRemotePiPackageSource2(source) {
|
|
558
|
+
return REMOTE_PI_PACKAGE_PREFIXES2.some((prefix) => source.startsWith(prefix));
|
|
557
559
|
}
|
|
558
|
-
function
|
|
559
|
-
if (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
560
|
+
function packageLocalPathFromSource(source) {
|
|
561
|
+
if (isRemotePiPackageSource2(source)) return null;
|
|
562
|
+
return source.startsWith("file:") ? source.slice("file:".length) : source;
|
|
563
|
+
}
|
|
564
|
+
function normalizeLocalPiPackageSource(pluginRoot, source) {
|
|
565
|
+
const localPath = packageLocalPathFromSource(source);
|
|
566
|
+
if (localPath == null) return source;
|
|
567
|
+
if (localPath === "." || localPath === "./") return resolve3(pluginRoot);
|
|
568
|
+
const normalized = localPath.startsWith("./") ? localPath.slice(2) : localPath;
|
|
569
|
+
if (!isSafePluginRelativePath(normalized)) {
|
|
570
|
+
throw new Error(`unsafe Pi package source: ${source}`);
|
|
567
571
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
if (stat2.isSymbolicLink()) {
|
|
579
|
-
let target;
|
|
580
|
-
try {
|
|
581
|
-
target = realpathSync2(path);
|
|
582
|
-
} catch {
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
if (visited.has(target)) {
|
|
586
|
-
hash.update(rel);
|
|
587
|
-
hash.update("symlink-cycle");
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
visited.add(target);
|
|
591
|
-
const targetStat = statSync3(target);
|
|
592
|
-
hash.update(rel);
|
|
593
|
-
hash.update("symlink:");
|
|
594
|
-
hash.update(target);
|
|
595
|
-
if (targetStat.isDirectory()) visit(target, depth + 1);
|
|
596
|
-
else if (targetStat.isFile()) {
|
|
597
|
-
hash.update(String(targetStat.mtimeMs));
|
|
598
|
-
hash.update(String(targetStat.size));
|
|
599
|
-
}
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
hash.update(rel);
|
|
603
|
-
hash.update(String(stat2.mtimeMs));
|
|
604
|
-
hash.update(String(stat2.size));
|
|
605
|
-
if (stat2.isDirectory()) {
|
|
606
|
-
visit(path, depth + 1);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
572
|
+
return resolve3(pluginRoot, normalized);
|
|
573
|
+
}
|
|
574
|
+
function normalizeBoringPluginPiPackageSource(pluginRoot, source) {
|
|
575
|
+
if (typeof source === "string") return normalizeLocalPiPackageSource(pluginRoot, source);
|
|
576
|
+
return {
|
|
577
|
+
source: normalizeLocalPiPackageSource(pluginRoot, source.source),
|
|
578
|
+
...source.extensions ? { extensions: source.extensions } : {},
|
|
579
|
+
...source.skills ? { skills: source.skills } : {},
|
|
580
|
+
...source.prompts ? { prompts: source.prompts } : {},
|
|
581
|
+
...source.themes ? { themes: source.themes } : {}
|
|
609
582
|
};
|
|
610
|
-
visit(root, 0);
|
|
611
|
-
return hash.digest("hex");
|
|
612
583
|
}
|
|
613
|
-
function
|
|
614
|
-
return
|
|
584
|
+
function normalizeBoringPluginPiPackages(plugins) {
|
|
585
|
+
return plugins.flatMap(
|
|
586
|
+
(plugin) => (plugin.pi?.packages ?? []).map(
|
|
587
|
+
(source) => normalizeBoringPluginPiPackageSource(plugin.rootDir, source)
|
|
588
|
+
)
|
|
589
|
+
);
|
|
615
590
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
591
|
+
|
|
592
|
+
// src/server/plugins/piPackages.ts
|
|
593
|
+
import {
|
|
594
|
+
compactPiPackages,
|
|
595
|
+
PI_PACKAGE_RESOURCE_FILTERS
|
|
596
|
+
} from "@hachej/boring-agent/server";
|
|
597
|
+
|
|
598
|
+
// src/server/plugins/defineServerPlugin.ts
|
|
599
|
+
function fail(pluginId, message) {
|
|
600
|
+
throw new Error(`server plugin "${pluginId}": ${message}`);
|
|
625
601
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
this.pluginDirs = options.pluginDirs;
|
|
636
|
-
this.errorRoot = options.errorRoot ?? join4(process.cwd(), ".pi", "extensions");
|
|
602
|
+
function isUrl(value) {
|
|
603
|
+
return value instanceof URL;
|
|
604
|
+
}
|
|
605
|
+
function isPathLike(value) {
|
|
606
|
+
return typeof value === "string" && value.length > 0 || isUrl(value);
|
|
607
|
+
}
|
|
608
|
+
function validateAgentTool(pluginId, tool, index) {
|
|
609
|
+
if (!tool || typeof tool !== "object") {
|
|
610
|
+
fail(pluginId, `agentTools[${index}] must be an object`);
|
|
637
611
|
}
|
|
638
|
-
|
|
639
|
-
|
|
612
|
+
const candidate = tool;
|
|
613
|
+
if (!candidate.name || typeof candidate.name !== "string") {
|
|
614
|
+
fail(pluginId, `agentTools[${index}].name must be a non-empty string`);
|
|
640
615
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
id: plugin.id,
|
|
644
|
-
boring: plugin.boring,
|
|
645
|
-
...plugin.pi ? { pi: plugin.pi } : {},
|
|
646
|
-
version: plugin.version,
|
|
647
|
-
revision: plugin.revision,
|
|
648
|
-
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {}
|
|
649
|
-
}));
|
|
616
|
+
if (typeof candidate.description !== "string") {
|
|
617
|
+
fail(pluginId, `agentTools[${index}].description must be a string`);
|
|
650
618
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (!path || !existsSync4(path)) return null;
|
|
654
|
-
return readFileSync3(path, "utf8");
|
|
619
|
+
if (!candidate.parameters || typeof candidate.parameters !== "object") {
|
|
620
|
+
fail(pluginId, `agentTools[${index}].parameters must be an object`);
|
|
655
621
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
return () => this.listeners.delete(listener);
|
|
622
|
+
if (typeof candidate.execute !== "function") {
|
|
623
|
+
fail(pluginId, `agentTools[${index}].execute must be a function`);
|
|
659
624
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
625
|
+
}
|
|
626
|
+
function validatePiPackages2(pluginId, piPackages) {
|
|
627
|
+
for (let i = 0; i < piPackages.length; i++) {
|
|
628
|
+
const source = piPackages[i];
|
|
629
|
+
if (typeof source === "string") {
|
|
630
|
+
if (source.length === 0) {
|
|
631
|
+
fail(pluginId, `piPackages[${i}] must be a non-empty string`);
|
|
632
|
+
}
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
636
|
+
fail(pluginId, `piPackages[${i}] must be a string or package source object`);
|
|
637
|
+
}
|
|
638
|
+
const candidate = source;
|
|
639
|
+
if (typeof candidate.source !== "string" || candidate.source.length === 0) {
|
|
640
|
+
fail(pluginId, `piPackages[${i}].source must be a non-empty string`);
|
|
641
|
+
}
|
|
642
|
+
for (const key of PI_PACKAGE_RESOURCE_FILTERS) {
|
|
643
|
+
const value = candidate[key];
|
|
644
|
+
if (value === void 0) continue;
|
|
645
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
646
|
+
fail(pluginId, `piPackages[${i}].${key} must be a string array when provided`);
|
|
647
|
+
}
|
|
664
648
|
}
|
|
665
|
-
this.loading = this.drainLoads().finally(() => {
|
|
666
|
-
this.loading = null;
|
|
667
|
-
});
|
|
668
|
-
return this.loading;
|
|
669
649
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
650
|
+
}
|
|
651
|
+
function validateSkills(pluginId, skills) {
|
|
652
|
+
for (let i = 0; i < skills.length; i++) {
|
|
653
|
+
const skill = skills[i];
|
|
654
|
+
if (!skill || typeof skill !== "object") {
|
|
655
|
+
fail(pluginId, `skills[${i}] must be an object`);
|
|
656
|
+
}
|
|
657
|
+
if (!skill.name || typeof skill.name !== "string") {
|
|
658
|
+
fail(pluginId, `skills[${i}].name must be a non-empty string`);
|
|
659
|
+
}
|
|
660
|
+
if (!isPathLike(skill.source)) {
|
|
661
|
+
fail(pluginId, `skills[${i}].source must be a string or URL`);
|
|
662
|
+
}
|
|
677
663
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
664
|
+
}
|
|
665
|
+
function validateProvisioning(pluginId, provisioning) {
|
|
666
|
+
if (!provisioning || typeof provisioning !== "object") {
|
|
667
|
+
fail(pluginId, "provisioning must be an object");
|
|
668
|
+
}
|
|
669
|
+
if (provisioning.templateDirs !== void 0) {
|
|
670
|
+
if (!Array.isArray(provisioning.templateDirs)) {
|
|
671
|
+
fail(pluginId, "provisioning.templateDirs must be an array when provided");
|
|
672
|
+
}
|
|
673
|
+
for (let i = 0; i < provisioning.templateDirs.length; i++) {
|
|
674
|
+
const contribution = provisioning.templateDirs[i];
|
|
675
|
+
if (!contribution || typeof contribution !== "object") {
|
|
676
|
+
fail(pluginId, `provisioning.templateDirs[${i}] must be an object`);
|
|
677
|
+
}
|
|
678
|
+
if (!contribution.id || typeof contribution.id !== "string") {
|
|
679
|
+
fail(pluginId, `provisioning.templateDirs[${i}].id must be a non-empty string`);
|
|
680
|
+
}
|
|
681
|
+
if (!isPathLike(contribution.path)) {
|
|
682
|
+
fail(pluginId, `provisioning.templateDirs[${i}].path must be a string or URL`);
|
|
683
|
+
}
|
|
684
|
+
if (contribution.target !== void 0 && typeof contribution.target !== "string") {
|
|
685
|
+
fail(pluginId, `provisioning.templateDirs[${i}].target must be a string when provided`);
|
|
697
686
|
}
|
|
698
|
-
const event = { type: "boring.plugin.unload", id, revision };
|
|
699
|
-
events.push(event);
|
|
700
|
-
this.emit(event);
|
|
701
687
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
688
|
+
}
|
|
689
|
+
if (provisioning.nodePackages !== void 0) {
|
|
690
|
+
if (!Array.isArray(provisioning.nodePackages)) {
|
|
691
|
+
fail(pluginId, "provisioning.nodePackages must be an array when provided");
|
|
692
|
+
}
|
|
693
|
+
for (let i = 0; i < provisioning.nodePackages.length; i++) {
|
|
694
|
+
const spec = provisioning.nodePackages[i];
|
|
695
|
+
if (!spec || typeof spec !== "object") {
|
|
696
|
+
fail(pluginId, `provisioning.nodePackages[${i}] must be an object`);
|
|
697
|
+
}
|
|
698
|
+
if (!spec.id || typeof spec.id !== "string") {
|
|
699
|
+
fail(pluginId, `provisioning.nodePackages[${i}].id must be a non-empty string`);
|
|
700
|
+
}
|
|
701
|
+
if (!spec.packageName || typeof spec.packageName !== "string") {
|
|
702
|
+
fail(pluginId, `provisioning.nodePackages[${i}].packageName must be a non-empty string`);
|
|
703
|
+
}
|
|
704
|
+
if (spec.packageRoot !== void 0 && !isPathLike(spec.packageRoot)) {
|
|
705
|
+
fail(pluginId, `provisioning.nodePackages[${i}].packageRoot must be a string or URL`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (provisioning.python !== void 0) {
|
|
710
|
+
if (!Array.isArray(provisioning.python)) {
|
|
711
|
+
fail(pluginId, "provisioning.python must be an array when provided");
|
|
712
|
+
}
|
|
713
|
+
for (let i = 0; i < provisioning.python.length; i++) {
|
|
714
|
+
const spec = provisioning.python[i];
|
|
715
|
+
if (!spec || typeof spec !== "object") {
|
|
716
|
+
fail(pluginId, `provisioning.python[${i}] must be an object`);
|
|
717
|
+
}
|
|
718
|
+
if (!spec.id || typeof spec.id !== "string") {
|
|
719
|
+
fail(pluginId, `provisioning.python[${i}].id must be a non-empty string`);
|
|
720
|
+
}
|
|
721
|
+
if (!isPathLike(spec.projectFile)) {
|
|
722
|
+
fail(pluginId, `provisioning.python[${i}].projectFile must be a string or URL`);
|
|
723
|
+
}
|
|
724
|
+
if (spec.extraLibs !== void 0 && (!Array.isArray(spec.extraLibs) || spec.extraLibs.some((item) => typeof item !== "string"))) {
|
|
725
|
+
fail(pluginId, `provisioning.python[${i}].extraLibs must be a string array when provided`);
|
|
726
|
+
}
|
|
727
|
+
if (spec.env !== void 0) {
|
|
728
|
+
if (!spec.env || typeof spec.env !== "object" || Array.isArray(spec.env)) {
|
|
729
|
+
fail(pluginId, `provisioning.python[${i}].env must be an object when provided`);
|
|
730
|
+
}
|
|
731
|
+
for (const [key, value] of Object.entries(spec.env)) {
|
|
732
|
+
if (!key || !isPathLike(value)) {
|
|
733
|
+
fail(pluginId, `provisioning.python[${i}].env values must be strings or URLs`);
|
|
734
|
+
}
|
|
715
735
|
}
|
|
716
|
-
const requiresRestart = computeRequiresRestart(previous, plugin);
|
|
717
|
-
const event = {
|
|
718
|
-
type: "boring.plugin.load",
|
|
719
|
-
id: plugin.id,
|
|
720
|
-
boring: plugin.boring,
|
|
721
|
-
version: plugin.version,
|
|
722
|
-
revision,
|
|
723
|
-
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {},
|
|
724
|
-
...requiresRestart.length > 0 ? { requiresRestart } : {}
|
|
725
|
-
};
|
|
726
|
-
events.push(event);
|
|
727
|
-
this.emit(event);
|
|
728
|
-
} catch (error) {
|
|
729
|
-
const revision = this.bumpRevision(plugin.id);
|
|
730
|
-
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
731
|
-
this.writeError(plugin.id, message);
|
|
732
|
-
const event = { type: "boring.plugin.error", id: plugin.id, revision, message };
|
|
733
|
-
errors.push({ id: plugin.id, revision, message });
|
|
734
|
-
events.push(event);
|
|
735
|
-
this.emit(event);
|
|
736
736
|
}
|
|
737
737
|
}
|
|
738
|
-
return { loaded: this.list(), events, errors };
|
|
739
738
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const message = `${error.code}: ${error.message}
|
|
745
|
-
|
|
746
|
-
Plugin dir: ${error.pluginDir}`;
|
|
747
|
-
const loadError = { id, revision, message };
|
|
748
|
-
errors.push(loadError);
|
|
749
|
-
this.writeError(id, message);
|
|
750
|
-
const event = { type: "boring.plugin.error", id, revision, message };
|
|
751
|
-
events.push(event);
|
|
752
|
-
this.emit(event);
|
|
753
|
-
}
|
|
739
|
+
}
|
|
740
|
+
function validateServerPlugin(plugin) {
|
|
741
|
+
if (!plugin.id || typeof plugin.id !== "string") {
|
|
742
|
+
fail(plugin.id ?? "<unknown>", "id must be a non-empty string");
|
|
754
743
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
this.revisions.set(id, next);
|
|
758
|
-
return next;
|
|
744
|
+
if (plugin.label !== void 0 && typeof plugin.label !== "string") {
|
|
745
|
+
fail(plugin.id, "label must be a string when provided");
|
|
759
746
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
747
|
+
if (plugin.systemPrompt !== void 0 && typeof plugin.systemPrompt !== "string") {
|
|
748
|
+
fail(plugin.id, "systemPrompt must be a string when provided");
|
|
749
|
+
}
|
|
750
|
+
if (plugin.piPackages !== void 0) {
|
|
751
|
+
if (!Array.isArray(plugin.piPackages)) {
|
|
752
|
+
fail(plugin.id, "piPackages must be an array when provided");
|
|
753
|
+
}
|
|
754
|
+
validatePiPackages2(plugin.id, plugin.piPackages);
|
|
755
|
+
}
|
|
756
|
+
if (plugin.extensionPaths !== void 0) {
|
|
757
|
+
if (!Array.isArray(plugin.extensionPaths)) {
|
|
758
|
+
fail(plugin.id, "extensionPaths must be an array when provided");
|
|
759
|
+
}
|
|
760
|
+
plugin.extensionPaths.forEach((path, index) => {
|
|
761
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
762
|
+
fail(plugin.id, `extensionPaths[${index}] must be a non-empty string`);
|
|
767
763
|
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
if (plugin.skills !== void 0) {
|
|
767
|
+
if (!Array.isArray(plugin.skills)) {
|
|
768
|
+
fail(plugin.id, "skills must be an array when provided");
|
|
768
769
|
}
|
|
770
|
+
validateSkills(plugin.id, plugin.skills);
|
|
769
771
|
}
|
|
770
|
-
|
|
771
|
-
if (!
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (rel.startsWith("..") || isAbsolute2(rel)) return null;
|
|
776
|
-
return path;
|
|
772
|
+
if (plugin.agentTools !== void 0) {
|
|
773
|
+
if (!Array.isArray(plugin.agentTools)) {
|
|
774
|
+
fail(plugin.id, "agentTools must be an array when provided");
|
|
775
|
+
}
|
|
776
|
+
plugin.agentTools.forEach((tool, index) => validateAgentTool(plugin.id, tool, index));
|
|
777
777
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (!path) return;
|
|
781
|
-
mkdirSync2(dirname5(path), { recursive: true });
|
|
782
|
-
writeFileSync2(path, message, "utf8");
|
|
778
|
+
if (plugin.routes !== void 0 && typeof plugin.routes !== "function") {
|
|
779
|
+
fail(plugin.id, "routes must be a Fastify plugin function when provided");
|
|
783
780
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
781
|
+
if (plugin.preservedUiStateKeys !== void 0) {
|
|
782
|
+
if (!Array.isArray(plugin.preservedUiStateKeys) || plugin.preservedUiStateKeys.some((key) => typeof key !== "string" || key.length === 0)) {
|
|
783
|
+
fail(plugin.id, "preservedUiStateKeys must be a non-empty string array when provided");
|
|
784
|
+
}
|
|
787
785
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
// src/server/agentPlugins/routes.ts
|
|
791
|
-
function collectRestartWarnings(events) {
|
|
792
|
-
const warnings = [];
|
|
793
|
-
for (const event of events) {
|
|
794
|
-
if (event.type !== "boring.plugin.load") continue;
|
|
795
|
-
const surfaces = event.requiresRestart;
|
|
796
|
-
if (!surfaces || surfaces.length === 0) continue;
|
|
797
|
-
warnings.push({
|
|
798
|
-
id: event.id,
|
|
799
|
-
surfaces: [...surfaces],
|
|
800
|
-
message: `${event.id} reloaded \u2014 front bundle is live, but server-side ${surfaces.join(" + ")} were wired at boot and still run the old code. Stop and restart the workspace process (Ctrl-C, then re-run your dev command) to pick up changes.`
|
|
801
|
-
});
|
|
786
|
+
if (plugin.provisioning !== void 0) {
|
|
787
|
+
validateProvisioning(plugin.id, plugin.provisioning);
|
|
802
788
|
}
|
|
803
|
-
return warnings;
|
|
804
789
|
}
|
|
805
|
-
async function boringPluginRoutes(app, opts) {
|
|
806
|
-
const { manager, rebuildPlugins, enableReloadRoute = true } = opts;
|
|
807
|
-
if (enableReloadRoute) {
|
|
808
|
-
app.post("/api/boring.reload", async (_request, reply) => {
|
|
809
|
-
const scan = await manager.load();
|
|
810
|
-
const rebuild = rebuildPlugins ? await rebuildPlugins() : { ok: true, diagnostics: [] };
|
|
811
|
-
const restart_warnings = collectRestartWarnings(scan.events);
|
|
812
|
-
const hasFailures = scan.errors.length > 0 || rebuild.diagnostics.length > 0;
|
|
813
|
-
if (hasFailures) {
|
|
814
|
-
return reply.status(422).send({
|
|
815
|
-
ok: false,
|
|
816
|
-
errors: scan.errors,
|
|
817
|
-
diagnostics: rebuild.diagnostics,
|
|
818
|
-
plugins: scan.loaded,
|
|
819
|
-
// Even on failure, emit warnings for plugins that DID reload
|
|
820
|
-
// — partial-failure tolerance means some loaded successfully.
|
|
821
|
-
...restart_warnings.length > 0 ? { restart_warnings } : {}
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
return reply.send({
|
|
825
|
-
ok: true,
|
|
826
|
-
plugins: scan.loaded,
|
|
827
|
-
...restart_warnings.length > 0 ? { restart_warnings } : {}
|
|
828
|
-
});
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
const listPlugins = async () => manager.list();
|
|
832
|
-
app.get("/api/v1/agent-plugins", listPlugins);
|
|
833
|
-
const getPluginError = async (request, reply) => {
|
|
834
|
-
const error = manager.getError(request.params.id);
|
|
835
|
-
if (error == null) return reply.status(404).send({ error: "not_found" });
|
|
836
|
-
return reply.type("text/plain").send(error);
|
|
837
|
-
};
|
|
838
|
-
app.get("/api/v1/agent-plugins/:id/error", getPluginError);
|
|
839
|
-
app.get("/api/v1/agent-plugins/events", async (request, reply) => {
|
|
840
|
-
reply.hijack();
|
|
841
|
-
const res = reply.raw;
|
|
842
|
-
res.statusCode = 200;
|
|
843
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
844
|
-
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
845
|
-
res.setHeader("Connection", "keep-alive");
|
|
846
|
-
res.setHeader("X-Accel-Buffering", "no");
|
|
847
|
-
res.flushHeaders?.();
|
|
848
|
-
const write = (event) => {
|
|
849
|
-
try {
|
|
850
|
-
res.write(`event: ${event.type}
|
|
851
|
-
`);
|
|
852
|
-
res.write(`data: ${JSON.stringify(event)}
|
|
853
790
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
});
|
|
791
|
+
// src/server/plugins/bootstrapServer.ts
|
|
792
|
+
function bootstrapServer(options) {
|
|
793
|
+
const excludedDefaults = new Set(options.excludeDefaults ?? []);
|
|
794
|
+
const finalPlugins = [
|
|
795
|
+
...(options.defaults ?? []).filter((p) => !excludedDefaults.has(p.id)),
|
|
796
|
+
...options.plugins ?? []
|
|
797
|
+
];
|
|
798
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
799
|
+
for (const plugin of finalPlugins) {
|
|
800
|
+
validateServerPlugin(plugin);
|
|
801
|
+
if (seenIds.has(plugin.id)) {
|
|
802
|
+
throw new Error(`plugin "${plugin.id}" registered twice`);
|
|
867
803
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
804
|
+
seenIds.add(plugin.id);
|
|
805
|
+
}
|
|
806
|
+
const agentTools = [];
|
|
807
|
+
for (const plugin of finalPlugins) {
|
|
808
|
+
for (const tool of plugin.agentTools ?? []) {
|
|
809
|
+
agentTools.push(tool);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const systemPromptAppend = finalPlugins.filter((p) => p.systemPrompt && p.systemPrompt.trim()).map((p) => p.systemPrompt.trim()).join("\n\n");
|
|
813
|
+
const piPackages = compactPiPackages(finalPlugins.flatMap((plugin) => plugin.piPackages ?? []));
|
|
814
|
+
const extensionPaths = finalPlugins.flatMap((p) => p.extensionPaths ?? []);
|
|
815
|
+
const provisioningContributions = finalPlugins.filter((p) => p.provisioning).map((p) => ({ id: p.id, provisioning: p.provisioning }));
|
|
816
|
+
const runtimePlugins = finalPlugins.map((plugin) => ({
|
|
817
|
+
id: plugin.id,
|
|
818
|
+
...plugin.skills ? { skills: plugin.skills } : {},
|
|
819
|
+
...plugin.provisioning ? { provisioning: plugin.provisioning } : {}
|
|
820
|
+
}));
|
|
821
|
+
const routeContributions = finalPlugins.filter((p) => p.routes).map((p) => ({ id: p.id, routes: p.routes }));
|
|
822
|
+
const preservedUiStateKeys = [...new Set(finalPlugins.flatMap((p) => p.preservedUiStateKeys ?? []))];
|
|
823
|
+
return {
|
|
824
|
+
registered: finalPlugins.map((p) => p.id),
|
|
825
|
+
systemPromptAppend,
|
|
826
|
+
piPackages,
|
|
827
|
+
extensionPaths,
|
|
828
|
+
agentTools,
|
|
829
|
+
runtimePlugins,
|
|
830
|
+
provisioningContributions,
|
|
831
|
+
routeContributions,
|
|
832
|
+
preservedUiStateKeys
|
|
833
|
+
};
|
|
889
834
|
}
|
|
890
835
|
|
|
891
|
-
// src/server/agentPlugins/
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
function isRemotePiPackageSource2(source) {
|
|
895
|
-
return REMOTE_PI_PACKAGE_PREFIXES2.some((prefix) => source.startsWith(prefix));
|
|
836
|
+
// src/server/agentPlugins/manager.ts
|
|
837
|
+
function skillPathForPiLoader(path) {
|
|
838
|
+
return existsSync4(join4(path, "SKILL.md")) ? dirname5(path) : path;
|
|
896
839
|
}
|
|
897
|
-
function
|
|
898
|
-
|
|
899
|
-
return source.startsWith("file:") ? source.slice("file:".length) : source;
|
|
840
|
+
function preflightErrorId(pluginDir) {
|
|
841
|
+
return `preflight-${createHash("sha256").update(pluginDir).digest("hex").slice(0, 12)}`;
|
|
900
842
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
843
|
+
function directorySignature(root) {
|
|
844
|
+
if (!root || !existsSync4(root)) return "missing";
|
|
845
|
+
const hash = createHash("sha256");
|
|
846
|
+
const visited = /* @__PURE__ */ new Set();
|
|
847
|
+
let rootReal;
|
|
848
|
+
try {
|
|
849
|
+
rootReal = realpathSync2(root);
|
|
850
|
+
} catch {
|
|
851
|
+
return "missing";
|
|
908
852
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
853
|
+
visited.add(rootReal);
|
|
854
|
+
let count = 0;
|
|
855
|
+
const visit = (dir, depth) => {
|
|
856
|
+
if (depth > 8 || count > 5e4) return;
|
|
857
|
+
const entries = readdirSync2(dir, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules").sort((a, b) => a.name.localeCompare(b.name));
|
|
858
|
+
for (const entry of entries) {
|
|
859
|
+
count++;
|
|
860
|
+
const path = join4(dir, entry.name);
|
|
861
|
+
const rel = relative2(root, path);
|
|
862
|
+
const stat2 = lstatSync(path);
|
|
863
|
+
if (stat2.isSymbolicLink()) {
|
|
864
|
+
let target;
|
|
865
|
+
try {
|
|
866
|
+
target = realpathSync2(path);
|
|
867
|
+
} catch {
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
if (visited.has(target)) {
|
|
871
|
+
hash.update(rel);
|
|
872
|
+
hash.update("symlink-cycle");
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
visited.add(target);
|
|
876
|
+
const targetStat = statSync3(target);
|
|
877
|
+
hash.update(rel);
|
|
878
|
+
hash.update("symlink:");
|
|
879
|
+
hash.update(target);
|
|
880
|
+
if (targetStat.isDirectory()) visit(target, depth + 1);
|
|
881
|
+
else if (targetStat.isFile()) {
|
|
882
|
+
hash.update(String(targetStat.mtimeMs));
|
|
883
|
+
hash.update(String(targetStat.size));
|
|
884
|
+
}
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
hash.update(rel);
|
|
888
|
+
hash.update(String(stat2.mtimeMs));
|
|
889
|
+
hash.update(String(stat2.size));
|
|
890
|
+
if (stat2.isDirectory()) {
|
|
891
|
+
visit(path, depth + 1);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
919
894
|
};
|
|
895
|
+
visit(root, 0);
|
|
896
|
+
return hash.digest("hex");
|
|
920
897
|
}
|
|
921
|
-
function
|
|
922
|
-
return
|
|
923
|
-
(plugin) => (plugin.pi?.packages ?? []).map(
|
|
924
|
-
(source) => normalizeBoringPluginPiPackageSource(plugin.rootDir, source)
|
|
925
|
-
)
|
|
926
|
-
);
|
|
898
|
+
function normalizePluginSubpath(rootDir, path) {
|
|
899
|
+
return relative2(rootDir, path).replaceAll("\\", "/");
|
|
927
900
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
import { pathToFileURL } from "url";
|
|
934
|
-
|
|
935
|
-
// src/server/plugins/piPackages.ts
|
|
936
|
-
import {
|
|
937
|
-
compactPiPackages,
|
|
938
|
-
PI_PACKAGE_RESOURCE_FILTERS
|
|
939
|
-
} from "@hachej/boring-agent/server";
|
|
940
|
-
|
|
941
|
-
// src/server/plugins/defineServerPlugin.ts
|
|
942
|
-
function fail(pluginId, message) {
|
|
943
|
-
throw new Error(`server plugin "${pluginId}": ${message}`);
|
|
901
|
+
function frontSignatureRoot(plugin) {
|
|
902
|
+
if (!plugin.frontPath) return void 0;
|
|
903
|
+
const frontRoot = join4(plugin.rootDir, "front");
|
|
904
|
+
const rel = relative2(frontRoot, plugin.frontPath);
|
|
905
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel) ? frontRoot : dirname5(plugin.frontPath);
|
|
944
906
|
}
|
|
945
|
-
function
|
|
946
|
-
return
|
|
907
|
+
function pluginSignature(plugin) {
|
|
908
|
+
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");
|
|
947
909
|
}
|
|
948
|
-
function
|
|
949
|
-
|
|
910
|
+
function computeRequiresRestart(previous, next) {
|
|
911
|
+
if (!previous) return [];
|
|
912
|
+
const prevHasServer = !!previous.serverPath;
|
|
913
|
+
const nextHasServer = !!next.serverPath;
|
|
914
|
+
if (!prevHasServer && !nextHasServer) return [];
|
|
915
|
+
if (prevHasServer !== nextHasServer) return ["routes", "agentTools"];
|
|
916
|
+
const nextSig = pluginFileSignature(next.serverPath);
|
|
917
|
+
if (previous.serverSignature === nextSig) return [];
|
|
918
|
+
return ["routes", "agentTools"];
|
|
950
919
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
920
|
+
var BoringPluginAssetManager = class {
|
|
921
|
+
pluginDirs;
|
|
922
|
+
errorRoot;
|
|
923
|
+
frontTargetResolver;
|
|
924
|
+
includeLegacyFrontUrl;
|
|
925
|
+
loaded = /* @__PURE__ */ new Map();
|
|
926
|
+
revisions = /* @__PURE__ */ new Map();
|
|
927
|
+
listeners = /* @__PURE__ */ new Set();
|
|
928
|
+
lastErrors = /* @__PURE__ */ new Map();
|
|
929
|
+
loading = null;
|
|
930
|
+
reloadQueued = false;
|
|
931
|
+
constructor(options) {
|
|
932
|
+
this.pluginDirs = options.pluginDirs;
|
|
933
|
+
this.errorRoot = options.errorRoot ?? join4(process.cwd(), ".pi", "extensions");
|
|
934
|
+
this.frontTargetResolver = options.frontTargetResolver;
|
|
935
|
+
this.includeLegacyFrontUrl = options.includeLegacyFrontUrl ?? true;
|
|
954
936
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
fail(pluginId, `agentTools[${index}].name must be a non-empty string`);
|
|
937
|
+
preflight() {
|
|
938
|
+
return preflightBoringPlugins(this.pluginDirs);
|
|
958
939
|
}
|
|
959
|
-
|
|
960
|
-
|
|
940
|
+
list() {
|
|
941
|
+
return [...this.loaded.values()].map((plugin) => this.toListEntry(plugin));
|
|
961
942
|
}
|
|
962
|
-
|
|
963
|
-
|
|
943
|
+
getError(pluginId) {
|
|
944
|
+
const path = this.errorPath(pluginId);
|
|
945
|
+
if (!path || !existsSync4(path)) return null;
|
|
946
|
+
return readFileSync3(path, "utf8");
|
|
964
947
|
}
|
|
965
|
-
|
|
966
|
-
|
|
948
|
+
getErrors() {
|
|
949
|
+
return [...this.lastErrors.values()];
|
|
967
950
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
979
|
-
fail(pluginId, `piPackages[${i}] must be a string or package source object`);
|
|
980
|
-
}
|
|
981
|
-
const candidate = source;
|
|
982
|
-
if (typeof candidate.source !== "string" || candidate.source.length === 0) {
|
|
983
|
-
fail(pluginId, `piPackages[${i}].source must be a non-empty string`);
|
|
984
|
-
}
|
|
985
|
-
for (const key of PI_PACKAGE_RESOURCE_FILTERS) {
|
|
986
|
-
const value = candidate[key];
|
|
987
|
-
if (value === void 0) continue;
|
|
988
|
-
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
989
|
-
fail(pluginId, `piPackages[${i}].${key} must be a string array when provided`);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
951
|
+
inspectLoaded() {
|
|
952
|
+
return [...this.loaded.values()].map((plugin) => ({
|
|
953
|
+
id: plugin.id,
|
|
954
|
+
version: plugin.version,
|
|
955
|
+
revision: plugin.revision,
|
|
956
|
+
rootDir: plugin.rootDir,
|
|
957
|
+
...plugin.frontPath ? { frontPath: plugin.frontPath } : {},
|
|
958
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
|
|
959
|
+
}));
|
|
992
960
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
fail(pluginId, `skills[${i}].source must be a string or URL`);
|
|
1005
|
-
}
|
|
961
|
+
inspectLoadedPiSnapshot() {
|
|
962
|
+
const plugins = [...this.loaded.values()];
|
|
963
|
+
const prompts = plugins.map((plugin) => plugin.pi?.systemPrompt?.trim()).filter((prompt) => Boolean(prompt));
|
|
964
|
+
return {
|
|
965
|
+
additionalSkillPaths: [...new Set(plugins.flatMap((plugin) => plugin.skillPaths ?? []).map(skillPathForPiLoader))],
|
|
966
|
+
packages: compactPiPackages(normalizeBoringPluginPiPackages(plugins)),
|
|
967
|
+
extensionPaths: plugins.flatMap((plugin) => plugin.extensionPaths ?? []),
|
|
968
|
+
...prompts.length > 0 ? { systemPromptAppend: `# Loaded boring-ui plugin context
|
|
969
|
+
|
|
970
|
+
${prompts.join("\n\n")}` } : {}
|
|
971
|
+
};
|
|
1006
972
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
fail(pluginId, "provisioning must be an object");
|
|
973
|
+
subscribe(listener) {
|
|
974
|
+
this.listeners.add(listener);
|
|
975
|
+
return () => this.listeners.delete(listener);
|
|
1011
976
|
}
|
|
1012
|
-
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
for (let i = 0; i < provisioning.templateDirs.length; i++) {
|
|
1017
|
-
const contribution = provisioning.templateDirs[i];
|
|
1018
|
-
if (!contribution || typeof contribution !== "object") {
|
|
1019
|
-
fail(pluginId, `provisioning.templateDirs[${i}] must be an object`);
|
|
1020
|
-
}
|
|
1021
|
-
if (!contribution.id || typeof contribution.id !== "string") {
|
|
1022
|
-
fail(pluginId, `provisioning.templateDirs[${i}].id must be a non-empty string`);
|
|
1023
|
-
}
|
|
1024
|
-
if (!isPathLike(contribution.path)) {
|
|
1025
|
-
fail(pluginId, `provisioning.templateDirs[${i}].path must be a string or URL`);
|
|
1026
|
-
}
|
|
1027
|
-
if (contribution.target !== void 0 && typeof contribution.target !== "string") {
|
|
1028
|
-
fail(pluginId, `provisioning.templateDirs[${i}].target must be a string when provided`);
|
|
1029
|
-
}
|
|
977
|
+
async load() {
|
|
978
|
+
if (this.loading) {
|
|
979
|
+
this.reloadQueued = true;
|
|
980
|
+
return this.loading;
|
|
1030
981
|
}
|
|
982
|
+
this.loading = this.drainLoads().finally(() => {
|
|
983
|
+
this.loading = null;
|
|
984
|
+
});
|
|
985
|
+
return this.loading;
|
|
1031
986
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
fail(pluginId, `provisioning.nodePackages[${i}] must be an object`);
|
|
1040
|
-
}
|
|
1041
|
-
if (!spec.id || typeof spec.id !== "string") {
|
|
1042
|
-
fail(pluginId, `provisioning.nodePackages[${i}].id must be a non-empty string`);
|
|
1043
|
-
}
|
|
1044
|
-
if (!spec.packageName || typeof spec.packageName !== "string") {
|
|
1045
|
-
fail(pluginId, `provisioning.nodePackages[${i}].packageName must be a non-empty string`);
|
|
1046
|
-
}
|
|
1047
|
-
if (spec.packageRoot !== void 0 && !isPathLike(spec.packageRoot)) {
|
|
1048
|
-
fail(pluginId, `provisioning.nodePackages[${i}].packageRoot must be a string or URL`);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
987
|
+
async drainLoads() {
|
|
988
|
+
let result;
|
|
989
|
+
do {
|
|
990
|
+
this.reloadQueued = false;
|
|
991
|
+
result = await this.doLoadOnce();
|
|
992
|
+
} while (this.reloadQueued);
|
|
993
|
+
return result;
|
|
1051
994
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
if (
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
fail(pluginId, `provisioning.python[${i}].env must be an object when provided`);
|
|
995
|
+
async doLoadOnce() {
|
|
996
|
+
this.lastErrors.clear();
|
|
997
|
+
const scan = scanBoringPlugins(this.pluginDirs);
|
|
998
|
+
const nextPlugins = scan.plugins;
|
|
999
|
+
const nextIds = new Set(nextPlugins.map((plugin) => plugin.id));
|
|
1000
|
+
const invalidPluginDirs = new Set(scan.preflight.errors.map((error) => resolve4(error.pluginDir)));
|
|
1001
|
+
const events = [];
|
|
1002
|
+
const errors = [];
|
|
1003
|
+
this.collectPreflightErrors(scan.preflight, events, errors);
|
|
1004
|
+
for (const id of [...this.loaded.keys()]) {
|
|
1005
|
+
if (nextIds.has(id)) continue;
|
|
1006
|
+
const previous = this.loaded.get(id);
|
|
1007
|
+
if (previous && invalidPluginDirs.has(resolve4(previous.rootDir))) continue;
|
|
1008
|
+
const revision = this.bumpRevision(id);
|
|
1009
|
+
this.loaded.delete(id);
|
|
1010
|
+
this.lastErrors.delete(id);
|
|
1011
|
+
if (previous) {
|
|
1012
|
+
try {
|
|
1013
|
+
clearPluginSignatureCache(previous.rootDir);
|
|
1014
|
+
} catch {
|
|
1073
1015
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1016
|
+
}
|
|
1017
|
+
const event = { type: "boring.plugin.unload", id, revision };
|
|
1018
|
+
events.push(event);
|
|
1019
|
+
this.emit(event);
|
|
1020
|
+
}
|
|
1021
|
+
for (const plugin of nextPlugins) {
|
|
1022
|
+
try {
|
|
1023
|
+
const signature = pluginSignature(plugin);
|
|
1024
|
+
const previous = this.loaded.get(plugin.id);
|
|
1025
|
+
if (previous?.signature === signature) continue;
|
|
1026
|
+
const revision = this.bumpRevision(plugin.id);
|
|
1027
|
+
const frontTarget = this.resolveFrontTarget(plugin, revision);
|
|
1028
|
+
const serverSignature = plugin.serverPath ? pluginFileSignature(plugin.serverPath) : null;
|
|
1029
|
+
const record = {
|
|
1030
|
+
...plugin,
|
|
1031
|
+
revision,
|
|
1032
|
+
signature,
|
|
1033
|
+
...frontTarget ? { frontTarget } : {},
|
|
1034
|
+
serverSignature
|
|
1035
|
+
};
|
|
1036
|
+
this.loaded.set(plugin.id, record);
|
|
1037
|
+
this.lastErrors.delete(plugin.id);
|
|
1038
|
+
this.clearError(plugin.id);
|
|
1039
|
+
try {
|
|
1040
|
+
writePluginSignatureCache(plugin.rootDir, { serverSignature });
|
|
1041
|
+
} catch {
|
|
1078
1042
|
}
|
|
1043
|
+
const requiresRestart = computeRequiresRestart(previous, plugin);
|
|
1044
|
+
const event = {
|
|
1045
|
+
type: "boring.plugin.load",
|
|
1046
|
+
id: plugin.id,
|
|
1047
|
+
boring: plugin.boring,
|
|
1048
|
+
version: plugin.version,
|
|
1049
|
+
revision,
|
|
1050
|
+
...this.frontUrlPayload(plugin.frontUrl),
|
|
1051
|
+
...frontTarget ? { frontTarget } : {},
|
|
1052
|
+
...requiresRestart.length > 0 ? { requiresRestart } : {}
|
|
1053
|
+
};
|
|
1054
|
+
events.push(event);
|
|
1055
|
+
this.emit(event);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
const revision = this.bumpRevision(plugin.id);
|
|
1058
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1059
|
+
this.writeError(plugin.id, message);
|
|
1060
|
+
const event = { type: "boring.plugin.error", id: plugin.id, revision, message };
|
|
1061
|
+
const loadError = { id: plugin.id, revision, message };
|
|
1062
|
+
this.lastErrors.set(plugin.id, loadError);
|
|
1063
|
+
errors.push(loadError);
|
|
1064
|
+
events.push(event);
|
|
1065
|
+
this.emit(event);
|
|
1079
1066
|
}
|
|
1080
1067
|
}
|
|
1068
|
+
return { loaded: this.list(), events, errors };
|
|
1081
1069
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1070
|
+
collectPreflightErrors(preflight, events, errors) {
|
|
1071
|
+
for (const error of preflight.errors) {
|
|
1072
|
+
const id = error.pluginId ?? preflightErrorId(error.pluginDir);
|
|
1073
|
+
const revision = this.bumpRevision(id);
|
|
1074
|
+
const message = `${error.code}: ${error.message}
|
|
1075
|
+
|
|
1076
|
+
Plugin dir: ${error.pluginDir}`;
|
|
1077
|
+
const loadError = { id, revision, message };
|
|
1078
|
+
this.lastErrors.set(id, loadError);
|
|
1079
|
+
errors.push(loadError);
|
|
1080
|
+
this.writeError(id, message);
|
|
1081
|
+
const event = { type: "boring.plugin.error", id, revision, message };
|
|
1082
|
+
events.push(event);
|
|
1083
|
+
this.emit(event);
|
|
1084
|
+
}
|
|
1086
1085
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1086
|
+
bumpRevision(id) {
|
|
1087
|
+
const next = (this.revisions.get(id) ?? 0) + 1;
|
|
1088
|
+
this.revisions.set(id, next);
|
|
1089
|
+
return next;
|
|
1089
1090
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1091
|
+
toListEntry(plugin) {
|
|
1092
|
+
return {
|
|
1093
|
+
id: plugin.id,
|
|
1094
|
+
boring: plugin.boring,
|
|
1095
|
+
...plugin.pi ? { pi: plugin.pi } : {},
|
|
1096
|
+
version: plugin.version,
|
|
1097
|
+
revision: plugin.revision,
|
|
1098
|
+
...this.frontUrlPayload(plugin.frontUrl),
|
|
1099
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {}
|
|
1100
|
+
};
|
|
1092
1101
|
}
|
|
1093
|
-
|
|
1094
|
-
if (!
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
validatePiPackages2(plugin.id, plugin.piPackages);
|
|
1102
|
+
frontUrlPayload(frontUrl) {
|
|
1103
|
+
if (!this.includeLegacyFrontUrl || !frontUrl) return {};
|
|
1104
|
+
return { frontUrl };
|
|
1098
1105
|
}
|
|
1099
|
-
|
|
1100
|
-
if (!
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
fail(plugin.id, `extensionPaths[${index}] must be a non-empty string`);
|
|
1106
|
-
}
|
|
1106
|
+
resolveFrontTarget(plugin, revision) {
|
|
1107
|
+
if (!plugin.frontPath || !this.frontTargetResolver) return void 0;
|
|
1108
|
+
const frontEntrySubpath = typeof plugin.boring.front === "string" ? plugin.boring.front.replace(/^\.\//, "") : normalizePluginSubpath(plugin.rootDir, plugin.frontPath);
|
|
1109
|
+
const frontTarget = this.frontTargetResolver(plugin, {
|
|
1110
|
+
revision,
|
|
1111
|
+
frontEntrySubpath
|
|
1107
1112
|
});
|
|
1113
|
+
if (!frontTarget) return void 0;
|
|
1114
|
+
return { ...frontTarget, revision };
|
|
1108
1115
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
fail(plugin.id, "agentTools must be an array when provided");
|
|
1116
|
+
emit(event) {
|
|
1117
|
+
for (const listener of [...this.listeners]) {
|
|
1118
|
+
try {
|
|
1119
|
+
listener(event);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1122
|
+
console.error(`[BoringPluginAssetManager] listener threw on ${event.type} for ${event.id}: ${message}`);
|
|
1123
|
+
}
|
|
1118
1124
|
}
|
|
1119
|
-
plugin.agentTools.forEach((tool, index) => validateAgentTool(plugin.id, tool, index));
|
|
1120
1125
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1126
|
+
errorPath(pluginId) {
|
|
1127
|
+
if (!isValidBoringPluginId(pluginId)) return null;
|
|
1128
|
+
const root = resolve4(this.errorRoot);
|
|
1129
|
+
const path = resolve4(root, pluginId, ".error");
|
|
1130
|
+
const rel = relative2(root, path);
|
|
1131
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) return null;
|
|
1132
|
+
return path;
|
|
1123
1133
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1134
|
+
writeError(pluginId, message) {
|
|
1135
|
+
const path = this.errorPath(pluginId);
|
|
1136
|
+
if (!path) return;
|
|
1137
|
+
mkdirSync2(dirname5(path), { recursive: true });
|
|
1138
|
+
writeFileSync2(path, message, "utf8");
|
|
1128
1139
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1140
|
+
clearError(pluginId) {
|
|
1141
|
+
const path = this.errorPath(pluginId);
|
|
1142
|
+
if (path && existsSync4(path)) rmSync2(path, { force: true });
|
|
1131
1143
|
}
|
|
1132
|
-
}
|
|
1144
|
+
};
|
|
1133
1145
|
|
|
1134
|
-
// src/server/
|
|
1135
|
-
function
|
|
1136
|
-
const
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
seenIds.add(plugin.id);
|
|
1146
|
+
// src/server/agentPlugins/routes.ts
|
|
1147
|
+
function collectRestartWarnings(events) {
|
|
1148
|
+
const warnings = [];
|
|
1149
|
+
for (const event of events) {
|
|
1150
|
+
if (event.type !== "boring.plugin.load") continue;
|
|
1151
|
+
const surfaces = event.requiresRestart;
|
|
1152
|
+
if (!surfaces || surfaces.length === 0) continue;
|
|
1153
|
+
warnings.push({
|
|
1154
|
+
id: event.id,
|
|
1155
|
+
surfaces: [...surfaces],
|
|
1156
|
+
message: `${event.id} reloaded \u2014 front bundle is live, but server-side ${surfaces.join(" + ")} were wired at boot and still run the old code. Stop and restart the workspace process (Ctrl-C, then re-run your dev command) to pick up changes.`
|
|
1157
|
+
});
|
|
1148
1158
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1159
|
+
return warnings;
|
|
1160
|
+
}
|
|
1161
|
+
async function boringPluginRoutes(app, opts) {
|
|
1162
|
+
const { manager, rebuildPlugins, enableReloadRoute = true } = opts;
|
|
1163
|
+
if (enableReloadRoute) {
|
|
1164
|
+
app.post("/api/boring.reload", async (_request, reply) => {
|
|
1165
|
+
const scan = await manager.load();
|
|
1166
|
+
const rebuild = rebuildPlugins ? await rebuildPlugins() : { ok: true, diagnostics: [] };
|
|
1167
|
+
const restart_warnings = collectRestartWarnings(scan.events);
|
|
1168
|
+
const hasFailures = scan.errors.length > 0 || rebuild.diagnostics.length > 0;
|
|
1169
|
+
if (hasFailures) {
|
|
1170
|
+
return reply.status(422).send({
|
|
1171
|
+
ok: false,
|
|
1172
|
+
errors: scan.errors,
|
|
1173
|
+
diagnostics: rebuild.diagnostics,
|
|
1174
|
+
plugins: scan.loaded,
|
|
1175
|
+
// Even on failure, emit warnings for plugins that DID reload
|
|
1176
|
+
// — partial-failure tolerance means some loaded successfully.
|
|
1177
|
+
...restart_warnings.length > 0 ? { restart_warnings } : {}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
return reply.send({
|
|
1181
|
+
ok: true,
|
|
1182
|
+
plugins: scan.loaded,
|
|
1183
|
+
...restart_warnings.length > 0 ? { restart_warnings } : {}
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1154
1186
|
}
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
...plugin.skills ? { skills: plugin.skills } : {},
|
|
1162
|
-
...plugin.provisioning ? { provisioning: plugin.provisioning } : {}
|
|
1163
|
-
}));
|
|
1164
|
-
const routeContributions = finalPlugins.filter((p) => p.routes).map((p) => ({ id: p.id, routes: p.routes }));
|
|
1165
|
-
const preservedUiStateKeys = [...new Set(finalPlugins.flatMap((p) => p.preservedUiStateKeys ?? []))];
|
|
1166
|
-
return {
|
|
1167
|
-
registered: finalPlugins.map((p) => p.id),
|
|
1168
|
-
systemPromptAppend,
|
|
1169
|
-
piPackages,
|
|
1170
|
-
extensionPaths,
|
|
1171
|
-
agentTools,
|
|
1172
|
-
runtimePlugins,
|
|
1173
|
-
provisioningContributions,
|
|
1174
|
-
routeContributions,
|
|
1175
|
-
preservedUiStateKeys
|
|
1187
|
+
const listPlugins = async () => manager.list();
|
|
1188
|
+
app.get("/api/v1/agent-plugins", listPlugins);
|
|
1189
|
+
const getPluginError = async (request, reply) => {
|
|
1190
|
+
const error = manager.getError(request.params.id);
|
|
1191
|
+
if (error == null) return reply.status(404).send({ error: "not_found" });
|
|
1192
|
+
return reply.type("text/plain").send(error);
|
|
1176
1193
|
};
|
|
1194
|
+
app.get("/api/v1/agent-plugins/:id/error", getPluginError);
|
|
1195
|
+
app.get("/api/v1/agent-plugins/events", async (request, reply) => {
|
|
1196
|
+
reply.hijack();
|
|
1197
|
+
const res = reply.raw;
|
|
1198
|
+
res.statusCode = 200;
|
|
1199
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
1200
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
1201
|
+
res.setHeader("Connection", "keep-alive");
|
|
1202
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
1203
|
+
res.flushHeaders?.();
|
|
1204
|
+
const write = (eventName, payload) => {
|
|
1205
|
+
try {
|
|
1206
|
+
res.write(`event: ${eventName}
|
|
1207
|
+
`);
|
|
1208
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
1209
|
+
|
|
1210
|
+
`);
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
const liveQueue = [];
|
|
1215
|
+
let replaying = true;
|
|
1216
|
+
const unsubscribe = manager.subscribe((event) => {
|
|
1217
|
+
const payload = { ...event, replay: false };
|
|
1218
|
+
if (replaying) {
|
|
1219
|
+
liveQueue.push({ eventName: event.type, payload });
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
write(event.type, payload);
|
|
1223
|
+
});
|
|
1224
|
+
for (const plugin of manager.list()) {
|
|
1225
|
+
write("boring.plugin.load", {
|
|
1226
|
+
type: "boring.plugin.load",
|
|
1227
|
+
id: plugin.id,
|
|
1228
|
+
boring: plugin.boring,
|
|
1229
|
+
version: plugin.version,
|
|
1230
|
+
revision: plugin.revision,
|
|
1231
|
+
...plugin.frontUrl ? { frontUrl: plugin.frontUrl } : {},
|
|
1232
|
+
...plugin.frontTarget ? { frontTarget: plugin.frontTarget } : {},
|
|
1233
|
+
replay: true
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
write("boring.plugin.replay-complete", {
|
|
1237
|
+
type: "boring.plugin.replay-complete",
|
|
1238
|
+
replay: true
|
|
1239
|
+
});
|
|
1240
|
+
replaying = false;
|
|
1241
|
+
for (const event of liveQueue) write(event.eventName, event.payload);
|
|
1242
|
+
const heartbeat = setInterval(() => {
|
|
1243
|
+
try {
|
|
1244
|
+
res.write(": heartbeat\n\n");
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
}, 25e3);
|
|
1248
|
+
request.raw.on("close", () => {
|
|
1249
|
+
clearInterval(heartbeat);
|
|
1250
|
+
unsubscribe();
|
|
1251
|
+
});
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/server/agentPlugins/aggregatePluginPrompts.ts
|
|
1256
|
+
function aggregatePluginPrompts(manager) {
|
|
1257
|
+
const prompts = manager.list().map((plugin) => plugin.pi?.systemPrompt?.trim()).filter((prompt) => Boolean(prompt));
|
|
1258
|
+
if (prompts.length === 0) return void 0;
|
|
1259
|
+
return `# Loaded boring-ui plugin context
|
|
1260
|
+
|
|
1261
|
+
${prompts.join("\n\n")}`;
|
|
1177
1262
|
}
|
|
1178
1263
|
|
|
1179
1264
|
// src/app/server/pluginEntryResolver.ts
|
|
1265
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1266
|
+
import { join as join5, resolve as resolve5 } from "path";
|
|
1267
|
+
import { createRequire as createRequire2 } from "module";
|
|
1268
|
+
import { pathToFileURL } from "url";
|
|
1180
1269
|
function readPluginPackageJson(dir) {
|
|
1181
1270
|
const pkgPath = resolve5(dir, "package.json");
|
|
1182
1271
|
if (!existsSync5(pkgPath)) return null;
|
|
@@ -1832,9 +1921,10 @@ data: ${JSON.stringify({ v: UI_BRIDGE_PROTOCOL_VERSION })}
|
|
|
1832
1921
|
var __dirname = dirname7(fileURLToPath(import.meta.url));
|
|
1833
1922
|
var require4 = createRequire4(import.meta.url);
|
|
1834
1923
|
function boringPiRootVisibleToAgentTools(workspaceRoot, resolvedMode, provisioned) {
|
|
1924
|
+
void workspaceRoot;
|
|
1925
|
+
void resolvedMode;
|
|
1835
1926
|
if (!provisioned) return void 0;
|
|
1836
|
-
|
|
1837
|
-
return join7(workspaceRoot, "node_modules", "@hachej", "boring-pi");
|
|
1927
|
+
return "/workspace/.boring-agent/node/node_modules/@hachej/boring-pi";
|
|
1838
1928
|
}
|
|
1839
1929
|
function resolveWorkspacePackageRoot() {
|
|
1840
1930
|
const candidates = [
|
|
@@ -1949,7 +2039,7 @@ function createBoringUiCliPackageProvisioningContribution() {
|
|
|
1949
2039
|
"boring-ui-cli-package",
|
|
1950
2040
|
"boring-ui-cli",
|
|
1951
2041
|
"@hachej/boring-ui-cli",
|
|
1952
|
-
readPackageVersion(packageRoot) ?? readPackageVersion(resolveWorkspacePackageRoot()),
|
|
2042
|
+
packageRoot === join7(resolveWorkspacePackageRoot(), "..", "cli") ? void 0 : readPackageVersion(packageRoot) ?? readPackageVersion(resolveWorkspacePackageRoot()),
|
|
1953
2043
|
["boring-ui"]
|
|
1954
2044
|
);
|
|
1955
2045
|
}
|
|
@@ -2026,7 +2116,7 @@ async function provisionWorkspaceAgentServer(opts) {
|
|
|
2026
2116
|
force: opts.force
|
|
2027
2117
|
});
|
|
2028
2118
|
}
|
|
2029
|
-
function collectBoringPluginDirs(workspaceRoot, pluginCollection) {
|
|
2119
|
+
function collectBoringPluginDirs(workspaceRoot, pluginCollection, additionalPluginDirs = []) {
|
|
2030
2120
|
const extensionPaths = pluginCollection.agentOptions.pi?.extensionPaths ?? [];
|
|
2031
2121
|
const pluginRoots = extensionPaths.flatMap((path) => {
|
|
2032
2122
|
try {
|
|
@@ -2035,10 +2125,11 @@ function collectBoringPluginDirs(workspaceRoot, pluginCollection) {
|
|
|
2035
2125
|
return [];
|
|
2036
2126
|
}
|
|
2037
2127
|
});
|
|
2038
|
-
return [
|
|
2128
|
+
return [.../* @__PURE__ */ new Set([
|
|
2039
2129
|
join7(workspaceRoot, ".pi", "extensions"),
|
|
2040
|
-
...pluginRoots
|
|
2041
|
-
|
|
2130
|
+
...pluginRoots,
|
|
2131
|
+
...additionalPluginDirs
|
|
2132
|
+
])];
|
|
2042
2133
|
}
|
|
2043
2134
|
function mergeRuntimeProvisioningInputs(plugins) {
|
|
2044
2135
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -2064,7 +2155,7 @@ function skillNameFromResolvedPath(path) {
|
|
|
2064
2155
|
if (leaf.toLowerCase() !== "skill.md") return leaf;
|
|
2065
2156
|
return path.split(/[\\/]/).filter(Boolean).at(-2) ?? "skill";
|
|
2066
2157
|
}
|
|
2067
|
-
function
|
|
2158
|
+
function skillPathForPiLoader2(path) {
|
|
2068
2159
|
return existsSync7(join7(path, "SKILL.md")) ? dirname7(path) : path;
|
|
2069
2160
|
}
|
|
2070
2161
|
function uniqueStrings(values) {
|
|
@@ -2095,7 +2186,7 @@ function readWorkspacePluginPackagePiSnapshot(pluginDirs) {
|
|
|
2095
2186
|
const systemPromptAppend = aggregatePluginSystemPromptsFromScan(scan);
|
|
2096
2187
|
return {
|
|
2097
2188
|
additionalSkillPaths: uniqueStrings(
|
|
2098
|
-
scan.plugins.flatMap((plugin) => plugin.skillPaths ?? []).map(
|
|
2189
|
+
scan.plugins.flatMap((plugin) => plugin.skillPaths ?? []).map(skillPathForPiLoader2)
|
|
2099
2190
|
),
|
|
2100
2191
|
packages: compactPiPackages(normalizeBoringPluginPiPackages(scan.plugins)),
|
|
2101
2192
|
extensionPaths: scan.plugins.flatMap((plugin) => plugin.extensionPaths ?? []),
|
|
@@ -2150,7 +2241,7 @@ async function createWorkspaceAgentServer(opts = {}) {
|
|
|
2150
2241
|
];
|
|
2151
2242
|
const baseStaticPiExtensionPaths = pluginCollection.agentOptions.pi?.extensionPaths ?? [];
|
|
2152
2243
|
const boringPluginDirs = [
|
|
2153
|
-
...collectBoringPluginDirs(workspaceRoot, pluginCollection),
|
|
2244
|
+
...collectBoringPluginDirs(workspaceRoot, pluginCollection, opts.additionalBoringPluginDirs),
|
|
2154
2245
|
...defaultPluginPackagePaths
|
|
2155
2246
|
];
|
|
2156
2247
|
const staticPluginPackagePiSnapshot = pluginHotReload ? emptyPackageJsonPiSnapshot() : readWorkspacePluginPackagePiSnapshot(boringPluginDirs);
|
|
@@ -2169,7 +2260,9 @@ async function createWorkspaceAgentServer(opts = {}) {
|
|
|
2169
2260
|
const getHotReloadablePiResources = pluginHotReload ? () => readWorkspacePluginPackagePiSnapshot(boringPluginDirs) : void 0;
|
|
2170
2261
|
const boringAssetManager = new BoringPluginAssetManager({
|
|
2171
2262
|
pluginDirs: boringPluginDirs,
|
|
2172
|
-
errorRoot: join7(workspaceRoot, ".pi", "extensions")
|
|
2263
|
+
errorRoot: join7(workspaceRoot, ".pi", "extensions"),
|
|
2264
|
+
frontTargetResolver: opts.boringPluginFrontTargetResolver,
|
|
2265
|
+
includeLegacyFrontUrl: opts.boringPluginIncludeLegacyFrontUrl
|
|
2173
2266
|
});
|
|
2174
2267
|
const buildRuntimeProvisioningInputs = () => mergeRuntimeProvisioningInputs([
|
|
2175
2268
|
...pluginCollection.runtimePlugins,
|
|
@@ -2185,11 +2278,18 @@ async function createWorkspaceAgentServer(opts = {}) {
|
|
|
2185
2278
|
sessionId: opts.sessionId ?? "default"
|
|
2186
2279
|
});
|
|
2187
2280
|
if (!adapter) return currentRuntimeProvisioning;
|
|
2188
|
-
|
|
2281
|
+
const provisioned = await provisionWorkspaceRuntime({
|
|
2189
2282
|
plugins: buildRuntimeProvisioningInputs(),
|
|
2190
2283
|
adapter,
|
|
2191
2284
|
runtimeLayout
|
|
2192
2285
|
});
|
|
2286
|
+
currentRuntimeProvisioning = provisioned ? {
|
|
2287
|
+
...provisioned,
|
|
2288
|
+
env: {
|
|
2289
|
+
...provisioned.env,
|
|
2290
|
+
BORING_AGENT_WORKSPACE_LOCAL_PLUGIN_ROOTS: workspaceFsCapability === "strong" ? "1" : "0"
|
|
2291
|
+
}
|
|
2292
|
+
} : currentRuntimeProvisioning;
|
|
2193
2293
|
return currentRuntimeProvisioning;
|
|
2194
2294
|
};
|
|
2195
2295
|
await runRuntimeProvisioning();
|
|
@@ -2274,6 +2374,7 @@ async function createWorkspaceAgentServer(opts = {}) {
|
|
|
2274
2374
|
}
|
|
2275
2375
|
;
|
|
2276
2376
|
app.__boringRebuildPlugins = rebuildPlugins;
|
|
2377
|
+
app.__boringAssetManager = boringAssetManager;
|
|
2277
2378
|
return app;
|
|
2278
2379
|
}
|
|
2279
2380
|
export {
|