@hachej/boring-workspace 0.1.22 → 0.1.24
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-nBvBA8m7.js → FileTree-DjPzfDMq.js} +114 -104
- package/dist/{MarkdownEditor-CeigBQee.js → MarkdownEditor-BbSy0bLV.js} +9 -9
- package/dist/{WorkspaceLoadingState-DuoIpOxl.js → WorkspaceLoadingState-fccm3AQg.js} +238 -218
- package/dist/WorkspaceProvider-BW4wzbpR.js +6264 -0
- package/dist/app-front.d.ts +114 -2
- package/dist/app-front.js +744 -323
- package/dist/app-server.d.ts +3 -3
- package/dist/app-server.js +85 -21
- package/dist/{createInMemoryBridge-CYNW1h_o.d.ts → createInMemoryBridge-DLckqafe.d.ts} +1 -1
- 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 +11 -4
- package/dist/server.js +37 -18
- 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 +151 -42
- 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-DihXY2wk.js +0 -5971
package/dist/app-server.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { PiPackageSource, PiExtensionFactory, CreateAgentAppOptions, ProvisionWorkspaceRuntimeOptions } from '@hachej/boring-agent/server';
|
|
2
2
|
export { PiPackageSource as WorkspacePiPackageSource } from '@hachej/boring-agent/server';
|
|
3
3
|
import { FastifyInstance } from 'fastify';
|
|
4
|
-
import { W as WorkspaceServerPlugin, S as ServerBootstrapOptions, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, c as createInMemoryBridge } from './createInMemoryBridge-
|
|
5
|
-
export { d as ServerWorkspaceRuntimeProvisioningInput } from './createInMemoryBridge-
|
|
6
|
-
import './ui-bridge-
|
|
4
|
+
import { W as WorkspaceServerPlugin, S as ServerBootstrapOptions, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, c as createInMemoryBridge } from './createInMemoryBridge-DLckqafe.js';
|
|
5
|
+
export { d as ServerWorkspaceRuntimeProvisioningInput } from './createInMemoryBridge-DLckqafe.js';
|
|
6
|
+
import './ui-bridge-Bdgl2hR8.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Directory-source entry: `{ dir, options?, hotReload? }`. Resolved via
|
package/dist/app-server.js
CHANGED
|
@@ -162,8 +162,8 @@ function validateBoringField(issues, boring) {
|
|
|
162
162
|
));
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
if (boring.id !== void 0) {
|
|
166
|
-
issues.push(issue("
|
|
165
|
+
if (boring.id !== void 0 && (typeof boring.id !== "string" || !isValidBoringPluginId(boring.id))) {
|
|
166
|
+
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"));
|
|
167
167
|
}
|
|
168
168
|
const front = boring.front;
|
|
169
169
|
if (front !== void 0 && (typeof front !== "string" || !isSafePluginRelativePath(front))) {
|
|
@@ -177,6 +177,7 @@ function validateBoringField(issues, boring) {
|
|
|
177
177
|
issues.push(issue("INVALID_FIELD", "boring.label", "boring.label must be a string when provided"));
|
|
178
178
|
}
|
|
179
179
|
return {
|
|
180
|
+
...typeof boring.id === "string" ? { id: boring.id } : {},
|
|
180
181
|
...typeof boring.front === "string" ? { front: boring.front } : {},
|
|
181
182
|
...typeof boring.server === "string" || boring.server === false ? { server: boring.server } : {},
|
|
182
183
|
...typeof boring.label === "string" ? { label: boring.label } : {}
|
|
@@ -343,6 +344,8 @@ function resolveSafePluginEntryPath({
|
|
|
343
344
|
|
|
344
345
|
// src/server/agentPlugins/scan.ts
|
|
345
346
|
function pluginIdFromPackageJson(pkg, rootDir) {
|
|
347
|
+
const explicitId = typeof pkg.boring?.id === "string" && pkg.boring.id.trim() ? pkg.boring.id.trim() : void 0;
|
|
348
|
+
if (explicitId) return explicitId;
|
|
346
349
|
const name = typeof pkg.name === "string" && pkg.name.trim() ? pkg.name.trim() : void 0;
|
|
347
350
|
return (name ?? rootDir.split(/[\\/]/).at(-1) ?? "plugin").replace(/^@/, "").replaceAll("/", "-");
|
|
348
351
|
}
|
|
@@ -526,8 +529,8 @@ import { dirname as dirname4, join as join3 } from "path";
|
|
|
526
529
|
var PLUGIN_SIGNATURE_CACHE_FILE = ".boring-signature.json";
|
|
527
530
|
function pluginFileSignature(path) {
|
|
528
531
|
if (!path || !existsSync3(path)) return "missing";
|
|
529
|
-
const
|
|
530
|
-
return `${
|
|
532
|
+
const stat2 = statSync2(path);
|
|
533
|
+
return `${stat2.mtimeMs}:${stat2.size}`;
|
|
531
534
|
}
|
|
532
535
|
function cachePath(pluginRootDir) {
|
|
533
536
|
return join3(pluginRootDir, PLUGIN_SIGNATURE_CACHE_FILE);
|
|
@@ -571,8 +574,8 @@ function directorySignature(root) {
|
|
|
571
574
|
count++;
|
|
572
575
|
const path = join4(dir, entry.name);
|
|
573
576
|
const rel = relative2(root, path);
|
|
574
|
-
const
|
|
575
|
-
if (
|
|
577
|
+
const stat2 = lstatSync(path);
|
|
578
|
+
if (stat2.isSymbolicLink()) {
|
|
576
579
|
let target;
|
|
577
580
|
try {
|
|
578
581
|
target = realpathSync2(path);
|
|
@@ -597,9 +600,9 @@ function directorySignature(root) {
|
|
|
597
600
|
continue;
|
|
598
601
|
}
|
|
599
602
|
hash.update(rel);
|
|
600
|
-
hash.update(String(
|
|
601
|
-
hash.update(String(
|
|
602
|
-
if (
|
|
603
|
+
hash.update(String(stat2.mtimeMs));
|
|
604
|
+
hash.update(String(stat2.size));
|
|
605
|
+
if (stat2.isDirectory()) {
|
|
603
606
|
visit(path, depth + 1);
|
|
604
607
|
}
|
|
605
608
|
}
|
|
@@ -1393,7 +1396,7 @@ function createInMemoryBridge() {
|
|
|
1393
1396
|
}
|
|
1394
1397
|
|
|
1395
1398
|
// src/server/ui-control/tools/uiTools.ts
|
|
1396
|
-
import {
|
|
1399
|
+
import { stat } from "fs/promises";
|
|
1397
1400
|
import { resolve as resolve6, isAbsolute as isAbsolute4, relative as relative3, win32 } from "path";
|
|
1398
1401
|
function makeError(message) {
|
|
1399
1402
|
return {
|
|
@@ -1431,7 +1434,7 @@ function validatePathSyntax(relPath, workspaceRoot) {
|
|
|
1431
1434
|
}
|
|
1432
1435
|
return { ok: true };
|
|
1433
1436
|
}
|
|
1434
|
-
async function
|
|
1437
|
+
async function validateExistingPath(workspaceRoot, relPath) {
|
|
1435
1438
|
const syntax = validatePathSyntax(relPath, workspaceRoot);
|
|
1436
1439
|
if (!syntax.ok) return syntax;
|
|
1437
1440
|
const resolved = resolve6(workspaceRoot, relPath);
|
|
@@ -1443,8 +1446,8 @@ async function validatePath(workspaceRoot, relPath) {
|
|
|
1443
1446
|
};
|
|
1444
1447
|
}
|
|
1445
1448
|
try {
|
|
1446
|
-
await
|
|
1447
|
-
return { ok: true };
|
|
1449
|
+
const fileStat = await stat(resolved);
|
|
1450
|
+
return { ok: true, kind: fileStat.isDirectory() ? "dir" : "file" };
|
|
1448
1451
|
} catch {
|
|
1449
1452
|
return {
|
|
1450
1453
|
ok: false,
|
|
@@ -1455,6 +1458,7 @@ async function validatePath(workspaceRoot, relPath) {
|
|
|
1455
1458
|
function createGetUiStateTool(uiBridge) {
|
|
1456
1459
|
return {
|
|
1457
1460
|
name: "get_ui_state",
|
|
1461
|
+
readinessRequirements: ["ui-bridge"],
|
|
1458
1462
|
description: [
|
|
1459
1463
|
"Read the current workspace UI state. Returns a JSON object with:",
|
|
1460
1464
|
"- workbenchOpen (boolean): is the right-side workbench pane visible?",
|
|
@@ -1508,12 +1512,13 @@ function isVerified(kind, params, state) {
|
|
|
1508
1512
|
return true;
|
|
1509
1513
|
}
|
|
1510
1514
|
function createExecUiTool(uiBridge, opts = {}) {
|
|
1511
|
-
const { workspaceRoot } = opts;
|
|
1515
|
+
const { workspaceRoot, resolvePathKind } = opts;
|
|
1512
1516
|
const verifyDelayMs = opts.verifyDelayMs ?? 200;
|
|
1513
1517
|
const verifyRetries = opts.verifyRetries ?? 2;
|
|
1514
1518
|
const verifyIntervalMs = opts.verifyIntervalMs ?? 200;
|
|
1515
1519
|
return {
|
|
1516
1520
|
name: "exec_ui",
|
|
1521
|
+
readinessRequirements: ["ui-bridge"],
|
|
1517
1522
|
description: [
|
|
1518
1523
|
"Execute a UI command in the workspace. Use this to open files, panels,",
|
|
1519
1524
|
"navigate to lines, or show notifications.",
|
|
@@ -1547,6 +1552,8 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
1547
1552
|
" returned \u2014 don't give up and don't switch to the read",
|
|
1548
1553
|
" tool. Repeat until openFile succeeds or no candidate",
|
|
1549
1554
|
" is found.",
|
|
1555
|
+
" If the path is a folder, openFile reveals/selects it in",
|
|
1556
|
+
" the file tree instead of opening an editor tab.",
|
|
1550
1557
|
" Example: {kind:'openFile', params:{path:'README.md'}}",
|
|
1551
1558
|
"",
|
|
1552
1559
|
" openPanel params: { id: string, component: string,",
|
|
@@ -1630,6 +1637,7 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
1630
1637
|
return makeError("openSurface: meta must be an object when provided");
|
|
1631
1638
|
}
|
|
1632
1639
|
}
|
|
1640
|
+
let effectiveKind = kind;
|
|
1633
1641
|
if (PATH_BEARING_KINDS.has(kind)) {
|
|
1634
1642
|
const relPath = getPathParam(kind, cmdParams);
|
|
1635
1643
|
if (!relPath) {
|
|
@@ -1640,14 +1648,25 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
1640
1648
|
const syntax = validatePathSyntax(relPath, workspaceRoot);
|
|
1641
1649
|
if (!syntax.ok) return makeError(syntax.reason);
|
|
1642
1650
|
if (workspaceRoot) {
|
|
1643
|
-
const check = await
|
|
1651
|
+
const check = await validateExistingPath(workspaceRoot, relPath);
|
|
1644
1652
|
if (!check.ok) {
|
|
1645
1653
|
return makeError(check.reason);
|
|
1646
1654
|
}
|
|
1655
|
+
if (kind === "openFile" && check.kind === "dir") {
|
|
1656
|
+
effectiveKind = "expandToFile";
|
|
1657
|
+
}
|
|
1658
|
+
} else if (resolvePathKind) {
|
|
1659
|
+
const pathKind = await resolvePathKind(relPath);
|
|
1660
|
+
if (!pathKind) {
|
|
1661
|
+
return makeError(`file not found at "${relPath}". Try find or grep to locate the file before retrying openFile.`);
|
|
1662
|
+
}
|
|
1663
|
+
if (kind === "openFile" && pathKind === "dir") {
|
|
1664
|
+
effectiveKind = "expandToFile";
|
|
1665
|
+
}
|
|
1647
1666
|
}
|
|
1648
1667
|
}
|
|
1649
1668
|
try {
|
|
1650
|
-
const command = { kind, params: cmdParams };
|
|
1669
|
+
const command = { kind: effectiveKind, params: cmdParams };
|
|
1651
1670
|
const result = await uiBridge.postCommand(command);
|
|
1652
1671
|
if (result.status === "error") {
|
|
1653
1672
|
return {
|
|
@@ -1656,11 +1675,11 @@ function createExecUiTool(uiBridge, opts = {}) {
|
|
|
1656
1675
|
details: result
|
|
1657
1676
|
};
|
|
1658
1677
|
}
|
|
1659
|
-
if (verifyDelayMs > 0 && VERIFIABLE_KINDS.has(
|
|
1678
|
+
if (verifyDelayMs > 0 && VERIFIABLE_KINDS.has(effectiveKind)) {
|
|
1660
1679
|
await new Promise((r) => setTimeout(r, verifyDelayMs));
|
|
1661
1680
|
let uiState = await uiBridge.getState();
|
|
1662
1681
|
for (let i = 0; i < verifyRetries; i++) {
|
|
1663
|
-
if (isVerified(
|
|
1682
|
+
if (isVerified(effectiveKind, cmdParams, uiState)) break;
|
|
1664
1683
|
await new Promise((r) => setTimeout(r, verifyIntervalMs));
|
|
1665
1684
|
uiState = await uiBridge.getState();
|
|
1666
1685
|
}
|
|
@@ -1831,6 +1850,15 @@ function resolveWorkspacePackageRoot() {
|
|
|
1831
1850
|
}
|
|
1832
1851
|
return join7(__dirname, "../../..");
|
|
1833
1852
|
}
|
|
1853
|
+
function readPackageVersion(packageRoot) {
|
|
1854
|
+
if (!packageRoot) return void 0;
|
|
1855
|
+
try {
|
|
1856
|
+
const pkg = JSON.parse(readFileSync6(join7(packageRoot, "package.json"), "utf8"));
|
|
1857
|
+
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : void 0;
|
|
1858
|
+
} catch {
|
|
1859
|
+
return void 0;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1834
1862
|
function nodePackageContribution(contributionId, nodePackageId, packageName, packageRoot) {
|
|
1835
1863
|
if (!packageRoot || !existsSync7(join7(packageRoot, "package.json"))) return null;
|
|
1836
1864
|
return {
|
|
@@ -1840,6 +1868,24 @@ function nodePackageContribution(contributionId, nodePackageId, packageName, pac
|
|
|
1840
1868
|
}
|
|
1841
1869
|
};
|
|
1842
1870
|
}
|
|
1871
|
+
function publishedNodePackageContribution(contributionId, nodePackageId, packageName, version, expectedBins) {
|
|
1872
|
+
return {
|
|
1873
|
+
id: contributionId,
|
|
1874
|
+
provisioning: {
|
|
1875
|
+
nodePackages: [
|
|
1876
|
+
{
|
|
1877
|
+
id: nodePackageId,
|
|
1878
|
+
packageName,
|
|
1879
|
+
...version ? { version } : {},
|
|
1880
|
+
...expectedBins ? { expectedBins } : {}
|
|
1881
|
+
}
|
|
1882
|
+
]
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function useLocalPackageProvisioning() {
|
|
1887
|
+
return process.env.BORING_USE_LOCAL_PACKAGES === "1";
|
|
1888
|
+
}
|
|
1843
1889
|
function createWorkspacePackageProvisioningContribution() {
|
|
1844
1890
|
return nodePackageContribution(
|
|
1845
1891
|
"boring-workspace-package",
|
|
@@ -1890,11 +1936,21 @@ function resolveBoringUiCliPackageRoot() {
|
|
|
1890
1936
|
}
|
|
1891
1937
|
}
|
|
1892
1938
|
function createBoringUiCliPackageProvisioningContribution() {
|
|
1893
|
-
|
|
1939
|
+
const packageRoot = resolveBoringUiCliPackageRoot();
|
|
1940
|
+
if (useLocalPackageProvisioning()) {
|
|
1941
|
+
return nodePackageContribution(
|
|
1942
|
+
"boring-ui-cli-package",
|
|
1943
|
+
"boring-ui-cli",
|
|
1944
|
+
"@hachej/boring-ui-cli",
|
|
1945
|
+
packageRoot
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
return publishedNodePackageContribution(
|
|
1894
1949
|
"boring-ui-cli-package",
|
|
1895
1950
|
"boring-ui-cli",
|
|
1896
1951
|
"@hachej/boring-ui-cli",
|
|
1897
|
-
|
|
1952
|
+
readPackageVersion(packageRoot) ?? readPackageVersion(resolveWorkspacePackageRoot()),
|
|
1953
|
+
["boring-ui"]
|
|
1898
1954
|
);
|
|
1899
1955
|
}
|
|
1900
1956
|
function createBoringPiPackageSource(workspaceRoot) {
|
|
@@ -2008,6 +2064,12 @@ function skillNameFromResolvedPath(path) {
|
|
|
2008
2064
|
if (leaf.toLowerCase() !== "skill.md") return leaf;
|
|
2009
2065
|
return path.split(/[\\/]/).filter(Boolean).at(-2) ?? "skill";
|
|
2010
2066
|
}
|
|
2067
|
+
function skillPathForPiLoader(path) {
|
|
2068
|
+
return existsSync7(join7(path, "SKILL.md")) ? dirname7(path) : path;
|
|
2069
|
+
}
|
|
2070
|
+
function uniqueStrings(values) {
|
|
2071
|
+
return [...new Set(values)];
|
|
2072
|
+
}
|
|
2011
2073
|
function readWorkspacePluginPackageRuntimePlugins(pluginDirs) {
|
|
2012
2074
|
const scan = scanBoringPlugins(pluginDirs);
|
|
2013
2075
|
return scan.plugins.map((plugin) => ({
|
|
@@ -2032,7 +2094,9 @@ function readWorkspacePluginPackagePiSnapshot(pluginDirs) {
|
|
|
2032
2094
|
const scan = scanBoringPlugins(pluginDirs);
|
|
2033
2095
|
const systemPromptAppend = aggregatePluginSystemPromptsFromScan(scan);
|
|
2034
2096
|
return {
|
|
2035
|
-
additionalSkillPaths:
|
|
2097
|
+
additionalSkillPaths: uniqueStrings(
|
|
2098
|
+
scan.plugins.flatMap((plugin) => plugin.skillPaths ?? []).map(skillPathForPiLoader)
|
|
2099
|
+
),
|
|
2036
2100
|
packages: compactPiPackages(normalizeBoringPluginPiPackages(scan.plugins)),
|
|
2037
2101
|
extensionPaths: scan.plugins.flatMap((plugin) => plugin.extensionPaths ?? []),
|
|
2038
2102
|
...systemPromptAppend ? { systemPromptAppend } : {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PiPackageSource, PluginSkillSource, ProvisionWorkspaceRuntimeOptions } from '@hachej/boring-agent/server';
|
|
2
2
|
import { FastifyPluginAsync } from 'fastify';
|
|
3
|
-
import { A as AgentTool, U as UiBridge } from './ui-bridge-
|
|
3
|
+
import { A as AgentTool, U as UiBridge } from './ui-bridge-Bdgl2hR8.js';
|
|
4
4
|
|
|
5
5
|
type WorkspaceRuntimeProvisioning = NonNullable<ProvisionWorkspaceRuntimeOptions["plugins"][number]["provisioning"]>;
|
|
6
6
|
interface WorkspaceServerPlugin {
|
package/dist/events.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* - `boring`: workspace/UI package discovery (front/server entrypoints and labels)
|
|
9
9
|
*/
|
|
10
10
|
interface BoringPackageBoringField {
|
|
11
|
+
/** Optional stable plugin id. Defaults to package.json#name normalized for package discovery. */
|
|
12
|
+
id?: string;
|
|
11
13
|
/** Browser entry that default-exports a BoringFrontFactory. */
|
|
12
14
|
front?: string;
|
|
13
15
|
/** Workspace/UI support server entry. Set false to disable convention lookup. */
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import { P as PaneProps, a as PanelConfig, S as SurfaceOpenRequest, c as SurfacePanelResolution } from './surface-
|
|
3
|
-
export { W as WORKSPACE_OPEN_PATH_SURFACE_KIND } from './surface-
|
|
4
|
-
export { B as BoringPackageBoringField, a as BoringPackagePiField, b as BoringPackagePiSource, c as BoringPackagePiSourceObject, d as BoringPluginManifestErrorCode, e as BoringPluginManifestIssue, f as BoringPluginManifestValidationResult, g as BoringPluginPackageJson, i as isSafePluginRelativePath, h as isValidBoringPluginId, v as validateBoringPluginManifest } from './manifest-
|
|
2
|
+
import { P as PaneProps, a as PanelConfig, S as SurfaceOpenRequest, c as SurfacePanelResolution } from './surface-CEEkd81D.js';
|
|
3
|
+
export { W as WORKSPACE_OPEN_PATH_SURFACE_KIND } from './surface-CEEkd81D.js';
|
|
4
|
+
export { B as BoringPackageBoringField, a as BoringPackagePiField, b as BoringPackagePiSource, c as BoringPackagePiSourceObject, d as BoringPluginManifestErrorCode, e as BoringPluginManifestIssue, f as BoringPluginManifestValidationResult, g as BoringPluginPackageJson, i as isSafePluginRelativePath, h as isValidBoringPluginId, v as validateBoringPluginManifest } from './manifest-C2vVgH_e.js';
|
|
5
5
|
import 'dockview-react';
|
|
6
6
|
|
|
7
7
|
type CatalogBadge = {
|
|
@@ -69,6 +69,10 @@ interface LeftTabParams {
|
|
|
69
69
|
searchQuery?: string;
|
|
70
70
|
bridge?: unknown;
|
|
71
71
|
chromeless?: boolean;
|
|
72
|
+
revealFileTreeRequest?: {
|
|
73
|
+
path: string;
|
|
74
|
+
seq: number;
|
|
75
|
+
} | null;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
interface BoringFrontPanelRegistration<T = unknown> {
|
|
@@ -85,6 +89,7 @@ interface BoringFrontPanelRegistration<T = unknown> {
|
|
|
85
89
|
essential?: boolean;
|
|
86
90
|
lazy?: boolean;
|
|
87
91
|
chromeless?: boolean;
|
|
92
|
+
supportsFullPage?: boolean;
|
|
88
93
|
source?: string;
|
|
89
94
|
}
|
|
90
95
|
interface BoringFrontPanelCommandRegistration {
|
package/dist/plugin.js
CHANGED
|
@@ -183,8 +183,8 @@ function validateBoringField(issues, boring) {
|
|
|
183
183
|
));
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
if (boring.id !== void 0) {
|
|
187
|
-
issues.push(issue("
|
|
186
|
+
if (boring.id !== void 0 && (typeof boring.id !== "string" || !isValidBoringPluginId(boring.id))) {
|
|
187
|
+
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"));
|
|
188
188
|
}
|
|
189
189
|
const front = boring.front;
|
|
190
190
|
if (front !== void 0 && (typeof front !== "string" || !isSafePluginRelativePath(front))) {
|
|
@@ -198,6 +198,7 @@ function validateBoringField(issues, boring) {
|
|
|
198
198
|
issues.push(issue("INVALID_FIELD", "boring.label", "boring.label must be a string when provided"));
|
|
199
199
|
}
|
|
200
200
|
return {
|
|
201
|
+
...typeof boring.id === "string" ? { id: boring.id } : {},
|
|
201
202
|
...typeof boring.front === "string" ? { front: boring.front } : {},
|
|
202
203
|
...typeof boring.server === "string" || boring.server === false ? { server: boring.server } : {},
|
|
203
204
|
...typeof boring.label === "string" ? { label: boring.label } : {}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { S as ServerBootstrapOptions, e as ServerBootstrapResult, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, W as WorkspaceServerPlugin, f as bootstrapServer, c as createInMemoryBridge, g as defineServerPlugin, v as validateServerPlugin } from './createInMemoryBridge-
|
|
1
|
+
export { S as ServerBootstrapOptions, e as ServerBootstrapResult, a as WorkspaceProvisioningContribution, b as WorkspaceRouteContribution, W as WorkspaceServerPlugin, f as bootstrapServer, c as createInMemoryBridge, g as defineServerPlugin, v as validateServerPlugin } from './createInMemoryBridge-DLckqafe.js';
|
|
2
2
|
import { FastifyRequest, FastifyInstance } from 'fastify';
|
|
3
|
-
import { U as UiBridge, A as AgentTool } from './ui-bridge-
|
|
4
|
-
export { C as CommandResult, a as UiCommand, b as UiState } from './ui-bridge-
|
|
5
|
-
import { B as BoringPackageBoringField, a as BoringPackagePiField } from './manifest-
|
|
3
|
+
import { U as UiBridge, A as AgentTool } from './ui-bridge-Bdgl2hR8.js';
|
|
4
|
+
export { C as CommandResult, a as UiCommand, b as UiState } from './ui-bridge-Bdgl2hR8.js';
|
|
5
|
+
import { B as BoringPackageBoringField, a as BoringPackagePiField } from './manifest-C2vVgH_e.js';
|
|
6
6
|
export { PiPackageSource as WorkspacePiPackageSource } from '@hachej/boring-agent/server';
|
|
7
7
|
|
|
8
8
|
interface UiRoutesOptions {
|
|
@@ -29,6 +29,13 @@ interface ExecUiToolOptions {
|
|
|
29
29
|
* but existence is left to the frontend/remote filesystem.
|
|
30
30
|
*/
|
|
31
31
|
workspaceRoot?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Optional workspace-backed stat hook for modes where the host path is not
|
|
34
|
+
* directly stat-able (for example remote sandboxes). When provided, path-
|
|
35
|
+
* bearing UI commands can still reject missing paths and convert folders to
|
|
36
|
+
* file-tree reveal commands.
|
|
37
|
+
*/
|
|
38
|
+
resolvePathKind?: (relPath: string) => Promise<"file" | "dir" | null>;
|
|
32
39
|
/**
|
|
33
40
|
* After dispatching a state-changing command (openFile, openPanel,
|
|
34
41
|
* openSurface, closePanel), wait this many ms before the first state
|
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
|
}
|
|
@@ -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);
|
|
@@ -1245,8 +1264,8 @@ function directorySignature(root) {
|
|
|
1245
1264
|
count++;
|
|
1246
1265
|
const path = join4(dir, entry.name);
|
|
1247
1266
|
const rel = relative3(root, path);
|
|
1248
|
-
const
|
|
1249
|
-
if (
|
|
1267
|
+
const stat2 = lstatSync(path);
|
|
1268
|
+
if (stat2.isSymbolicLink()) {
|
|
1250
1269
|
let target;
|
|
1251
1270
|
try {
|
|
1252
1271
|
target = realpathSync2(path);
|
|
@@ -1271,9 +1290,9 @@ function directorySignature(root) {
|
|
|
1271
1290
|
continue;
|
|
1272
1291
|
}
|
|
1273
1292
|
hash.update(rel);
|
|
1274
|
-
hash.update(String(
|
|
1275
|
-
hash.update(String(
|
|
1276
|
-
if (
|
|
1293
|
+
hash.update(String(stat2.mtimeMs));
|
|
1294
|
+
hash.update(String(stat2.size));
|
|
1295
|
+
if (stat2.isDirectory()) {
|
|
1277
1296
|
visit(path, depth + 1);
|
|
1278
1297
|
}
|
|
1279
1298
|
}
|
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