@blackbelt-technology/pi-agent-dashboard 0.4.0 → 0.4.2
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/AGENTS.md +104 -35
- package/README.md +390 -494
- package/docs/architecture.md +423 -20
- package/package.json +11 -8
- package/packages/extension/package.json +11 -4
- package/packages/extension/src/__tests__/ask-user-schema-discriminator.test.ts +141 -0
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +91 -15
- package/packages/extension/src/__tests__/bridge-entry-id-pi-070.test.ts +174 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +30 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +64 -76
- package/packages/extension/src/__tests__/multiselect-dashboard-routing.test.ts +203 -0
- package/packages/extension/src/__tests__/multiselect-list.test.ts +137 -0
- package/packages/extension/src/__tests__/multiselect-polyfill.test.ts +92 -0
- package/packages/extension/src/__tests__/no-session-replacement-calls.test.ts +99 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +81 -0
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +37 -0
- package/packages/extension/src/__tests__/ui-decorators.test.ts +309 -0
- package/packages/extension/src/__tests__/ui-modules.test.ts +293 -0
- package/packages/extension/src/ask-user-tool.ts +170 -61
- package/packages/extension/src/bridge.ts +199 -19
- package/packages/extension/src/multiselect-decode.ts +40 -0
- package/packages/extension/src/multiselect-list.ts +146 -0
- package/packages/extension/src/multiselect-polyfill.ts +73 -0
- package/packages/extension/src/server-launcher.ts +15 -3
- package/packages/extension/src/ui-modules.ts +272 -0
- package/packages/server/package.json +11 -5
- package/packages/server/src/__tests__/auto-attach.test.ts +61 -8
- package/packages/server/src/__tests__/browse-endpoint.test.ts +295 -19
- package/packages/server/src/__tests__/cli-bootstrap.test.ts +36 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +163 -0
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +315 -0
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +303 -0
- package/packages/server/src/__tests__/directory-service.test.ts +174 -0
- package/packages/server/src/__tests__/fixtures/fork-jsonl-roundtrip.jsonl +8 -0
- package/packages/server/src/__tests__/fork-jsonl-roundtrip.test.ts +49 -0
- package/packages/server/src/__tests__/installed-package-enricher.test.ts +225 -0
- package/packages/server/src/__tests__/package-manager-wrapper-move.test.ts +414 -0
- package/packages/server/src/__tests__/package-routes.test.ts +136 -3
- package/packages/server/src/__tests__/package-source-helpers.test.ts +101 -0
- package/packages/server/src/__tests__/pending-attach-registry.test.ts +123 -0
- package/packages/server/src/__tests__/pending-resume-intent-registry.test.ts +138 -0
- package/packages/server/src/__tests__/pi-core-checker.test.ts +73 -30
- package/packages/server/src/__tests__/pi-gateway-consume-pending-attach.test.ts +112 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +72 -0
- package/packages/server/src/__tests__/post-install-openspec-refresh.test.ts +180 -0
- package/packages/server/src/__tests__/post-install-rescan.test.ts +134 -0
- package/packages/server/src/__tests__/proposal-attach-naming.test.ts +79 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +34 -6
- package/packages/server/src/__tests__/session-action-handler-spawn-with-attach.test.ts +108 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +55 -0
- package/packages/server/src/__tests__/session-order-reboot.test.ts +242 -0
- package/packages/server/src/__tests__/session-scanner.test.ts +44 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +40 -0
- package/packages/server/src/__tests__/translate-path-source.test.ts +77 -0
- package/packages/server/src/__tests__/ui-decorators-replay.test.ts +209 -0
- package/packages/server/src/__tests__/ui-modules-replay.test.ts +221 -0
- package/packages/server/src/browse.ts +118 -13
- package/packages/server/src/browser-gateway.ts +19 -0
- package/packages/server/src/browser-handlers/__tests__/session-meta-handler.test.ts +183 -0
- package/packages/server/src/browser-handlers/directory-handler.ts +7 -1
- package/packages/server/src/browser-handlers/handler-context.ts +15 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +29 -3
- package/packages/server/src/browser-handlers/session-meta-handler.ts +46 -12
- package/packages/server/src/browser-handlers/subscription-handler.ts +46 -1
- package/packages/server/src/cli.ts +61 -15
- package/packages/server/src/directory-service.ts +156 -15
- package/packages/server/src/event-wiring.ts +111 -10
- package/packages/server/src/installed-package-enricher.ts +143 -0
- package/packages/server/src/package-manager-wrapper.ts +305 -8
- package/packages/server/src/package-source-helpers.ts +104 -0
- package/packages/server/src/pending-attach-registry.ts +112 -0
- package/packages/server/src/pending-resume-intent-registry.ts +107 -0
- package/packages/server/src/pi-core-checker.ts +9 -14
- package/packages/server/src/pi-gateway.ts +14 -0
- package/packages/server/src/pi-version-skew.ts +12 -1
- package/packages/server/src/proposal-attach-naming.ts +47 -0
- package/packages/server/src/restart-helper.ts +13 -2
- package/packages/server/src/routes/file-routes.ts +29 -3
- package/packages/server/src/routes/package-routes.ts +72 -3
- package/packages/server/src/routes/plugin-config-routes.ts +129 -0
- package/packages/server/src/routes/system-routes.ts +2 -0
- package/packages/server/src/server.ts +339 -10
- package/packages/server/src/session-api.ts +30 -5
- package/packages/server/src/session-order-manager.ts +22 -0
- package/packages/server/src/session-scanner.ts +10 -1
- package/packages/shared/package.json +9 -2
- package/packages/shared/src/__tests__/browser-protocol-types.test.ts +59 -0
- package/packages/shared/src/__tests__/config-plugins.test.ts +68 -0
- package/packages/shared/src/__tests__/extension-ui-module-shape.test.ts +265 -0
- package/packages/shared/src/__tests__/no-hardcoded-node-modules-paths.test.ts +176 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +146 -0
- package/packages/shared/src/__tests__/no-raw-openspec-status-in-skills.test.ts +81 -0
- package/packages/shared/src/__tests__/node-spawn.test.ts +210 -0
- package/packages/shared/src/__tests__/openspec-design-evidence.test.ts +288 -0
- package/packages/shared/src/__tests__/openspec-effective-status-script.test.ts +174 -0
- package/packages/shared/src/__tests__/openspec-poller-design-override.test.ts +225 -0
- package/packages/shared/src/__tests__/openspec-poller-specs-override.test.ts +284 -0
- package/packages/shared/src/__tests__/openspec-specs-evidence.test.ts +144 -0
- package/packages/shared/src/__tests__/platform/is-appimage-self-hit.test.ts +164 -0
- package/packages/shared/src/__tests__/plugin-bridge-register-extended.test.ts +72 -0
- package/packages/shared/src/__tests__/plugin-bridge-register.test.ts +113 -0
- package/packages/shared/src/__tests__/plugin-config-update-protocol.test.ts +41 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +5 -1
- package/packages/shared/src/__tests__/resolve-tool-cli.test.ts +105 -0
- package/packages/shared/src/__tests__/spawn-session-attach-proposal.test.ts +47 -0
- package/packages/shared/src/__tests__/state-replay-entry-id.test.ts +69 -0
- package/packages/shared/src/__tests__/tool-registry-strategies-appimage.test.ts +118 -0
- package/packages/shared/src/browser-protocol.ts +110 -4
- package/packages/shared/src/config.ts +45 -0
- package/packages/shared/src/dashboard-plugin/index.ts +11 -0
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +58 -0
- package/packages/shared/src/dashboard-plugin/plugin-status.ts +26 -0
- package/packages/shared/src/dashboard-plugin/slot-props.ts +92 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +151 -0
- package/packages/shared/src/openspec-activity-detector.ts +18 -22
- package/packages/shared/src/openspec-design-evidence.ts +109 -0
- package/packages/shared/src/openspec-poller.ts +117 -3
- package/packages/shared/src/openspec-specs-evidence.ts +79 -0
- package/packages/shared/src/platform/binary-lookup.ts +96 -1
- package/packages/shared/src/platform/index.ts +1 -0
- package/packages/shared/src/platform/node-spawn.ts +154 -0
- package/packages/shared/src/plugin-bridge-register.ts +139 -0
- package/packages/shared/src/protocol.ts +79 -2
- package/packages/shared/src/recommended-extensions.ts +7 -1
- package/packages/shared/src/rest-api.ts +68 -3
- package/packages/shared/src/state-replay.ts +20 -1
- package/packages/shared/src/tool-registry/definitions.ts +92 -0
- package/packages/shared/src/tool-registry/strategies.ts +17 -3
- package/packages/shared/src/types.ts +160 -0
|
@@ -80,6 +80,12 @@ export interface KnownServer {
|
|
|
80
80
|
addedAt: string; // ISO timestamp
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Plugin-specific config namespace.
|
|
85
|
+
* Lives at ~/.pi/dashboard/config.json#plugins.<id>.*
|
|
86
|
+
*/
|
|
87
|
+
export type PluginsConfig = Record<string, Record<string, unknown>>;
|
|
88
|
+
|
|
83
89
|
export interface DashboardConfig {
|
|
84
90
|
port: number;
|
|
85
91
|
piPort: number;
|
|
@@ -107,6 +113,13 @@ export interface DashboardConfig {
|
|
|
107
113
|
electronMode: boolean;
|
|
108
114
|
/** Persisted list of known remote servers */
|
|
109
115
|
knownServers: KnownServer[];
|
|
116
|
+
/**
|
|
117
|
+
* Per-plugin config namespaces. Reserved top-level key.
|
|
118
|
+
* Each plugin's config lives at plugins.<id>.*
|
|
119
|
+
* Plugin-shaped legacy top-level keys (e.g. openspec.*) stay at top-level
|
|
120
|
+
* until each extract-*-as-plugin change migrates them.
|
|
121
|
+
*/
|
|
122
|
+
plugins: PluginsConfig;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
export interface CorsConfig {
|
|
@@ -117,6 +130,7 @@ export interface CorsConfig {
|
|
|
117
130
|
const VALID_SPAWN_STRATEGIES: SpawnStrategy[] = ["tmux", "headless"];
|
|
118
131
|
|
|
119
132
|
const DEFAULTS: DashboardConfig = {
|
|
133
|
+
plugins: {},
|
|
120
134
|
port: 8000,
|
|
121
135
|
piPort: 9999,
|
|
122
136
|
autoStart: true,
|
|
@@ -237,6 +251,36 @@ function parseMemoryLimits(raw: any): MemoryLimitsConfig {
|
|
|
237
251
|
};
|
|
238
252
|
}
|
|
239
253
|
|
|
254
|
+
function parsePluginsConfig(raw: unknown): PluginsConfig {
|
|
255
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
256
|
+
const result: PluginsConfig = {};
|
|
257
|
+
for (const [id, val] of Object.entries(raw as Record<string, unknown>)) {
|
|
258
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
259
|
+
result[id] = val as Record<string, unknown>;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the plugins config block from a loaded DashboardConfig.
|
|
267
|
+
* Provides typed access to plugins.<id>.* namespaces.
|
|
268
|
+
*/
|
|
269
|
+
export function getPluginsConfig(config: DashboardConfig): PluginsConfig {
|
|
270
|
+
return config.plugins ?? {};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get a single plugin's config from a loaded DashboardConfig.
|
|
275
|
+
* Returns {} if the plugin has no stored config.
|
|
276
|
+
*/
|
|
277
|
+
export function getPluginConfig(
|
|
278
|
+
config: DashboardConfig,
|
|
279
|
+
pluginId: string,
|
|
280
|
+
): Record<string, unknown> {
|
|
281
|
+
return config.plugins?.[pluginId] ?? {};
|
|
282
|
+
}
|
|
283
|
+
|
|
240
284
|
function parseKnownServers(raw: any): KnownServer[] {
|
|
241
285
|
if (!Array.isArray(raw)) return [];
|
|
242
286
|
return raw
|
|
@@ -299,6 +343,7 @@ export function loadConfig(): DashboardConfig {
|
|
|
299
343
|
...(typeof parsed.lastServer === "string" ? { lastServer: parsed.lastServer } : {}),
|
|
300
344
|
electronMode: parsed.electronMode === true,
|
|
301
345
|
knownServers: parseKnownServers(parsed.knownServers),
|
|
346
|
+
plugins: parsePluginsConfig(parsed.plugins),
|
|
302
347
|
};
|
|
303
348
|
|
|
304
349
|
// Compute resolvedTrustedNetworks: merge trustedNetworks + auth.bypassHosts
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel export for dashboard plugin system shared types.
|
|
3
|
+
* Import from:
|
|
4
|
+
* @blackbelt-technology/pi-dashboard-shared/dashboard-plugin/index.js
|
|
5
|
+
* @blackbelt-technology/pi-dashboard-shared/dashboard-plugin/slot-types.js
|
|
6
|
+
* etc.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./slot-types.js";
|
|
9
|
+
export * from "./manifest-types.js";
|
|
10
|
+
export * from "./slot-props.js";
|
|
11
|
+
export * from "./plugin-status.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SlotId, SettingsTab } from "./slot-types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A single slot claim in a plugin manifest.
|
|
5
|
+
*/
|
|
6
|
+
export interface PluginClaim {
|
|
7
|
+
/** The slot this claim targets. */
|
|
8
|
+
slot: SlotId;
|
|
9
|
+
/** Exported component name from the plugin's client entry (for React slots). */
|
|
10
|
+
component?: string;
|
|
11
|
+
/** Route command for "command-route" slot (e.g. "/specs"). */
|
|
12
|
+
command?: string;
|
|
13
|
+
/** Trigger id for "anchored-popover" slot. */
|
|
14
|
+
trigger?: string;
|
|
15
|
+
/** toolName for "tool-renderer" slot. */
|
|
16
|
+
toolName?: string;
|
|
17
|
+
/**
|
|
18
|
+
* For "settings-section" slot: which SettingsPanel tab to render in.
|
|
19
|
+
* Defaults to "general" if omitted.
|
|
20
|
+
*/
|
|
21
|
+
tab?: SettingsTab;
|
|
22
|
+
/** Slot-specific extra config. */
|
|
23
|
+
config?: Record<string, unknown>;
|
|
24
|
+
/** Optional exported predicate function name for filtering contributions. */
|
|
25
|
+
predicate?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The pi-dashboard-plugin manifest.
|
|
30
|
+
* Declared as the `pi-dashboard-plugin` field in a package.json,
|
|
31
|
+
* or as a top-level `dashboard-plugin.json` adjacent to package.json.
|
|
32
|
+
*/
|
|
33
|
+
export interface PluginManifest {
|
|
34
|
+
/** Globally unique kebab-case plugin id. */
|
|
35
|
+
id: string;
|
|
36
|
+
/** Human-readable display name. */
|
|
37
|
+
displayName: string;
|
|
38
|
+
/**
|
|
39
|
+
* Lower number = rendered earlier in multi-contribution slots.
|
|
40
|
+
* Default 1000. First-party plugins use 100.
|
|
41
|
+
*/
|
|
42
|
+
priority?: number;
|
|
43
|
+
/** Relative path to the bundled client entry (from package root). */
|
|
44
|
+
client?: string;
|
|
45
|
+
/** Optional relative path to the server entry. */
|
|
46
|
+
server?: string;
|
|
47
|
+
/** Optional relative path to a pi-extension/bridge entry. */
|
|
48
|
+
bridge?: string;
|
|
49
|
+
/** Optional relative path to a JSON Schema 7 file for plugin config validation. */
|
|
50
|
+
configSchema?: string;
|
|
51
|
+
/** Slot claims. */
|
|
52
|
+
claims: PluginClaim[];
|
|
53
|
+
/**
|
|
54
|
+
* When true, the plugin is a test fixture and SHALL be excluded from
|
|
55
|
+
* production bundles (NODE_ENV=production).
|
|
56
|
+
*/
|
|
57
|
+
fixture?: boolean;
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin status types used in /api/health.plugins[] and WebSocket broadcasts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Status of a single discovered plugin, reported by /api/health. */
|
|
6
|
+
export interface PluginStatus {
|
|
7
|
+
id: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
loaded: boolean;
|
|
10
|
+
/** Error message if the plugin failed to load or has a conflict. */
|
|
11
|
+
error?: string;
|
|
12
|
+
/** Number of slot claims declared in the plugin's manifest. */
|
|
13
|
+
claims: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** WebSocket broadcast sent to all browsers when a plugin's config changes. */
|
|
17
|
+
export interface PluginConfigUpdate {
|
|
18
|
+
type: "plugin_config_update";
|
|
19
|
+
/** Plugin id that was updated. */
|
|
20
|
+
id: string;
|
|
21
|
+
/**
|
|
22
|
+
* Only this plugin's namespace config (plugins.<id>.*).
|
|
23
|
+
* Never contains other plugins' config.
|
|
24
|
+
*/
|
|
25
|
+
config: unknown;
|
|
26
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed prop contracts for each slot id.
|
|
3
|
+
*
|
|
4
|
+
* Every slot consumer passes exactly the props defined here to each contribution
|
|
5
|
+
* component. Plugins receive only the props for the slot they claim.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: PluginContext is imported as a type-only forward reference so this
|
|
8
|
+
* shared package doesn't depend on the runtime package. The runtime package
|
|
9
|
+
* will re-export this map with the concrete PluginContext type filled in.
|
|
10
|
+
*/
|
|
11
|
+
import type { DashboardSession } from "../types.js";
|
|
12
|
+
import type { SlotId } from "./slot-types.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Opaque marker type for PluginContext.
|
|
16
|
+
* The concrete type is defined in @blackbelt-technology/dashboard-plugin-runtime/context.
|
|
17
|
+
* Using `unknown` here keeps this shared types-only package free of runtime deps.
|
|
18
|
+
*/
|
|
19
|
+
export type AnyPluginContext = unknown;
|
|
20
|
+
|
|
21
|
+
/** Folder descriptor passed to sidebar-folder-section slot. */
|
|
22
|
+
export interface FolderDescriptor {
|
|
23
|
+
cwd: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Map of slot id → props type for that slot's contributions. */
|
|
28
|
+
export interface SlotPropsMap {
|
|
29
|
+
"sidebar-folder-section": {
|
|
30
|
+
folder: FolderDescriptor;
|
|
31
|
+
pluginContext: AnyPluginContext;
|
|
32
|
+
};
|
|
33
|
+
"session-card-badge": {
|
|
34
|
+
session: DashboardSession;
|
|
35
|
+
pluginContext: AnyPluginContext;
|
|
36
|
+
};
|
|
37
|
+
"session-card-action-bar": {
|
|
38
|
+
session: DashboardSession;
|
|
39
|
+
pluginContext: AnyPluginContext;
|
|
40
|
+
};
|
|
41
|
+
"content-view": {
|
|
42
|
+
session: DashboardSession;
|
|
43
|
+
routeParams: Record<string, string>;
|
|
44
|
+
onClose: () => void;
|
|
45
|
+
pluginContext: AnyPluginContext;
|
|
46
|
+
};
|
|
47
|
+
"content-header-sticky": {
|
|
48
|
+
session: DashboardSession;
|
|
49
|
+
pluginContext: AnyPluginContext;
|
|
50
|
+
};
|
|
51
|
+
"content-inline-footer": {
|
|
52
|
+
session: DashboardSession;
|
|
53
|
+
pluginContext: AnyPluginContext;
|
|
54
|
+
};
|
|
55
|
+
"anchored-popover": {
|
|
56
|
+
anchorEl: HTMLElement;
|
|
57
|
+
onDismiss: () => void;
|
|
58
|
+
pluginContext: AnyPluginContext;
|
|
59
|
+
};
|
|
60
|
+
"command-route": {
|
|
61
|
+
session: DashboardSession;
|
|
62
|
+
routeParams: Record<string, string>;
|
|
63
|
+
onClose: () => void;
|
|
64
|
+
pluginContext: AnyPluginContext;
|
|
65
|
+
};
|
|
66
|
+
"settings-section": {
|
|
67
|
+
pluginContext: AnyPluginContext;
|
|
68
|
+
};
|
|
69
|
+
"tool-renderer": {
|
|
70
|
+
toolName: string;
|
|
71
|
+
toolInput: Record<string, unknown>;
|
|
72
|
+
sessionId: string;
|
|
73
|
+
pluginContext: AnyPluginContext;
|
|
74
|
+
};
|
|
75
|
+
// Descriptor-only slots don't have React props (consumed by extension-ui-system)
|
|
76
|
+
"management-modal": Record<string, unknown>;
|
|
77
|
+
"footer-segment": Record<string, unknown>;
|
|
78
|
+
"agent-metric": Record<string, unknown>;
|
|
79
|
+
"breadcrumb": Record<string, unknown>;
|
|
80
|
+
"gate": Record<string, unknown>;
|
|
81
|
+
"toast": Record<string, unknown>;
|
|
82
|
+
"rjsf-form": Record<string, unknown>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Get the props type for a specific slot id. */
|
|
86
|
+
export type SlotProps<S extends SlotId> = SlotPropsMap[S];
|
|
87
|
+
|
|
88
|
+
// Type-level test: assert SlotPropsMap covers every SlotId.
|
|
89
|
+
// This will produce a TS error if any SlotId is not in SlotPropsMap.
|
|
90
|
+
type _AssertAllSlotsCovered = {
|
|
91
|
+
[K in SlotId]: SlotPropsMap[K];
|
|
92
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frozen slot taxonomy for the dashboard plugin system.
|
|
3
|
+
* These ids and their payload contracts are versioned via
|
|
4
|
+
* @blackbelt-technology/pi-dashboard-shared.
|
|
5
|
+
*
|
|
6
|
+
* Adding a slot: minor (non-breaking).
|
|
7
|
+
* Removing or renaming a slot: major (breaking).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** All valid slot ids (frozen for v0.x). */
|
|
11
|
+
export type SlotId =
|
|
12
|
+
// React-only slots
|
|
13
|
+
| "sidebar-folder-section"
|
|
14
|
+
| "session-card-action-bar"
|
|
15
|
+
| "content-inline-footer"
|
|
16
|
+
| "anchored-popover"
|
|
17
|
+
| "command-route"
|
|
18
|
+
| "tool-renderer"
|
|
19
|
+
// React-or-descriptor slots
|
|
20
|
+
| "session-card-badge"
|
|
21
|
+
| "content-view"
|
|
22
|
+
| "content-header-sticky"
|
|
23
|
+
| "settings-section"
|
|
24
|
+
// Descriptor-only slots (from extension-ui-system)
|
|
25
|
+
| "management-modal"
|
|
26
|
+
| "footer-segment"
|
|
27
|
+
| "agent-metric"
|
|
28
|
+
| "breadcrumb"
|
|
29
|
+
| "gate"
|
|
30
|
+
| "toast"
|
|
31
|
+
| "rjsf-form";
|
|
32
|
+
|
|
33
|
+
/** How many contributions a slot allows. */
|
|
34
|
+
export type Multiplicity = "one" | "many" | "one-active";
|
|
35
|
+
|
|
36
|
+
/** Which payload types the slot accepts. */
|
|
37
|
+
export type PayloadTier = "react-only" | "descriptor-only" | "react-or-descriptor";
|
|
38
|
+
|
|
39
|
+
export interface SlotDefinition {
|
|
40
|
+
multiplicity: Multiplicity;
|
|
41
|
+
payloadTier: PayloadTier;
|
|
42
|
+
description: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Frozen slot definitions map for v0.x. */
|
|
46
|
+
export const SLOT_DEFINITIONS: Record<SlotId, SlotDefinition> = {
|
|
47
|
+
"sidebar-folder-section": {
|
|
48
|
+
multiplicity: "many",
|
|
49
|
+
payloadTier: "react-only",
|
|
50
|
+
description: "Collapsible block above session list per workspace folder",
|
|
51
|
+
},
|
|
52
|
+
"session-card-badge": {
|
|
53
|
+
multiplicity: "many",
|
|
54
|
+
payloadTier: "react-or-descriptor",
|
|
55
|
+
description: "Compact info chip on a session card",
|
|
56
|
+
},
|
|
57
|
+
"session-card-action-bar": {
|
|
58
|
+
multiplicity: "many",
|
|
59
|
+
payloadTier: "react-only",
|
|
60
|
+
description: "Action buttons on a session card",
|
|
61
|
+
},
|
|
62
|
+
"content-view": {
|
|
63
|
+
multiplicity: "one-active",
|
|
64
|
+
payloadTier: "react-or-descriptor",
|
|
65
|
+
description: "Full-screen content area view for a session",
|
|
66
|
+
},
|
|
67
|
+
"content-header-sticky": {
|
|
68
|
+
multiplicity: "many",
|
|
69
|
+
payloadTier: "react-or-descriptor",
|
|
70
|
+
description: "Sticky header above the content view",
|
|
71
|
+
},
|
|
72
|
+
"content-inline-footer": {
|
|
73
|
+
multiplicity: "many",
|
|
74
|
+
payloadTier: "react-only",
|
|
75
|
+
description: "Inline footer below the content view (React-only)",
|
|
76
|
+
},
|
|
77
|
+
"anchored-popover": {
|
|
78
|
+
multiplicity: "one",
|
|
79
|
+
payloadTier: "react-only",
|
|
80
|
+
description: "Popover anchored to a UI trigger element",
|
|
81
|
+
},
|
|
82
|
+
"command-route": {
|
|
83
|
+
multiplicity: "many",
|
|
84
|
+
payloadTier: "react-only",
|
|
85
|
+
description: "Maps a slash command or URL route to a content view",
|
|
86
|
+
},
|
|
87
|
+
"settings-section": {
|
|
88
|
+
multiplicity: "many",
|
|
89
|
+
payloadTier: "react-or-descriptor",
|
|
90
|
+
description: "A section in the Settings page",
|
|
91
|
+
},
|
|
92
|
+
"tool-renderer": {
|
|
93
|
+
multiplicity: "many",
|
|
94
|
+
payloadTier: "react-only",
|
|
95
|
+
description: "Custom React renderer for a specific tool call by toolName",
|
|
96
|
+
},
|
|
97
|
+
// Descriptor-only (extension-ui-system)
|
|
98
|
+
"management-modal": {
|
|
99
|
+
multiplicity: "many",
|
|
100
|
+
payloadTier: "descriptor-only",
|
|
101
|
+
description: "Full-screen management modal (extension-ui-system)",
|
|
102
|
+
},
|
|
103
|
+
"footer-segment": {
|
|
104
|
+
multiplicity: "many",
|
|
105
|
+
payloadTier: "descriptor-only",
|
|
106
|
+
description: "Segment in the session footer bar (extension-ui-system)",
|
|
107
|
+
},
|
|
108
|
+
"agent-metric": {
|
|
109
|
+
multiplicity: "one",
|
|
110
|
+
payloadTier: "descriptor-only",
|
|
111
|
+
description: "Metric chip on an agent card (extension-ui-system)",
|
|
112
|
+
},
|
|
113
|
+
"breadcrumb": {
|
|
114
|
+
multiplicity: "many",
|
|
115
|
+
payloadTier: "descriptor-only",
|
|
116
|
+
description: "Breadcrumb item in the content header (extension-ui-system)",
|
|
117
|
+
},
|
|
118
|
+
"gate": {
|
|
119
|
+
multiplicity: "many",
|
|
120
|
+
payloadTier: "descriptor-only",
|
|
121
|
+
description: "Flow gate/checkpoint UI (extension-ui-system)",
|
|
122
|
+
},
|
|
123
|
+
"toast": {
|
|
124
|
+
multiplicity: "many",
|
|
125
|
+
payloadTier: "descriptor-only",
|
|
126
|
+
description: "Transient notification toast (extension-ui-system)",
|
|
127
|
+
},
|
|
128
|
+
"rjsf-form": {
|
|
129
|
+
multiplicity: "many",
|
|
130
|
+
payloadTier: "descriptor-only",
|
|
131
|
+
description: "JSON-Schema-driven form (extension-ui-system Phase 4)",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** Valid settings tab ids in SettingsPanel. */
|
|
136
|
+
export type SettingsTab =
|
|
137
|
+
| "general"
|
|
138
|
+
| "servers"
|
|
139
|
+
| "packages"
|
|
140
|
+
| "providers"
|
|
141
|
+
| "security"
|
|
142
|
+
| "advanced";
|
|
143
|
+
|
|
144
|
+
export const VALID_SETTINGS_TABS: SettingsTab[] = [
|
|
145
|
+
"general",
|
|
146
|
+
"servers",
|
|
147
|
+
"packages",
|
|
148
|
+
"providers",
|
|
149
|
+
"security",
|
|
150
|
+
"advanced",
|
|
151
|
+
];
|
|
@@ -83,29 +83,25 @@ export function detectOpenSpecActivity(
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
if (tool === "bash") {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return { changeName: newChangeMatch[1], isActive: true };
|
|
86
|
+
const command = args.command as string | undefined;
|
|
87
|
+
if (!command || !command.includes("openspec")) return null;
|
|
88
|
+
|
|
89
|
+
// Try each CLI regex in order; first match wins.
|
|
90
|
+
const match =
|
|
91
|
+
command.match(CLI_CHANGE_FLAG_RE) ??
|
|
92
|
+
command.match(CLI_ARCHIVE_RE) ??
|
|
93
|
+
command.match(CLI_NEW_CHANGE_RE);
|
|
94
|
+
if (!match) return null;
|
|
95
|
+
|
|
96
|
+
const name = match[1];
|
|
97
|
+
// Reject flag-shaped tokens (e.g. `--help`, `-h`). The CLI regex capture
|
|
98
|
+
// groups use `[^\s"']+` which would otherwise treat `--help` as a change
|
|
99
|
+
// name and trigger downstream auto-attach + auto-rename.
|
|
100
|
+
// See change: fix-openspec-flag-rename-bug.
|
|
101
|
+
if (name.startsWith("-")) return null;
|
|
102
|
+
|
|
103
|
+
return { changeName: name, isActive: true };
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
106
|
return null;
|
|
111
107
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-evidence override for the OpenSpec `design` artifact.
|
|
3
|
+
*
|
|
4
|
+
* The upstream `spec-driven` schema requires `design.md` as a hard,
|
|
5
|
+
* single-file dependency of `tasks`. Two real-world workflows fight that:
|
|
6
|
+
*
|
|
7
|
+
* • **Split design** — users put design content in `design-rendering.md`
|
|
8
|
+
* + `design-state.md`. The CLI doesn't see them; status reports
|
|
9
|
+
* `design: ready` forever; dashboard shows `[Continue] [FF]` instead of
|
|
10
|
+
* `[Apply]`.
|
|
11
|
+
* • **No-design changes** — trivial fixes that don't need a design doc.
|
|
12
|
+
* User writes `tasks.md`, starts implementing; CLI still says
|
|
13
|
+
* `design: ready`; dashboard buttons are wrong.
|
|
14
|
+
*
|
|
15
|
+
* This module computes a boolean "is design satisfied locally?" from
|
|
16
|
+
* file-system evidence the CLI ignores. It is consumed by:
|
|
17
|
+
*
|
|
18
|
+
* 1. `buildOpenSpecData` in `openspec-poller.ts` — promotes
|
|
19
|
+
* `artifacts[design].status` from "ready" to "done" when the rules fire.
|
|
20
|
+
* Promote-only; design-only; never demotes; never touches other artifacts.
|
|
21
|
+
*
|
|
22
|
+
* 2. `.pi/skills/openspec-shared/scripts/effective-status.sh` — the
|
|
23
|
+
* OpenSpec workflow skills invoke this wrapper instead of
|
|
24
|
+
* `openspec status --json` so skill-driven prompts and dashboard buttons
|
|
25
|
+
* cannot disagree.
|
|
26
|
+
*
|
|
27
|
+
* Three rules, evaluated in order with short-circuit:
|
|
28
|
+
*
|
|
29
|
+
* R1 any file matching ^design.*\.md$ exists in the change folder
|
|
30
|
+
* R2 a design/ subdirectory exists with at least one *.md inside
|
|
31
|
+
* R3 tasks.md exists AND contains at least one Markdown checkbox
|
|
32
|
+
* (^\s*-\s+\[[ xX]\]\s)
|
|
33
|
+
*
|
|
34
|
+
* R3 is heuristic but defensible: a user who wrote actionable tasks has
|
|
35
|
+
* already made the design decisions. The schema's hard dependency is
|
|
36
|
+
* paperwork we don't believe in for trivial changes.
|
|
37
|
+
*
|
|
38
|
+
* See change: fix-openspec-design-detection.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
42
|
+
import path from "node:path";
|
|
43
|
+
|
|
44
|
+
/** Probe surface — kept tiny so unit tests can pass an in-memory stub. */
|
|
45
|
+
export interface DesignEvidenceProbe {
|
|
46
|
+
/** R1: any file matching `^design.*\.md$` in `changeDir`. */
|
|
47
|
+
hasDesignFile(changeDir: string): boolean;
|
|
48
|
+
/** R2: `<changeDir>/design/` exists and contains at least one `*.md`. */
|
|
49
|
+
hasDesignDirWithMd(changeDir: string): boolean;
|
|
50
|
+
/** R3: `<changeDir>/tasks.md` contains at least one Markdown checkbox. */
|
|
51
|
+
tasksHasCheckboxes(changeDir: string): boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Pure rule evaluator. R1 → R2 → R3, short-circuits on first match. */
|
|
55
|
+
export function evaluateLocalDesignSatisfaction(
|
|
56
|
+
changeDir: string,
|
|
57
|
+
probe: DesignEvidenceProbe,
|
|
58
|
+
): boolean {
|
|
59
|
+
if (probe.hasDesignFile(changeDir)) return true;
|
|
60
|
+
if (probe.hasDesignDirWithMd(changeDir)) return true;
|
|
61
|
+
if (probe.tasksHasCheckboxes(changeDir)) return true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const DESIGN_FILE_RE = /^design.*\.md$/;
|
|
66
|
+
const CHECKBOX_RE = /^\s*-\s+\[[ xX]\]\s/m;
|
|
67
|
+
|
|
68
|
+
/** Production probe — backed by the real filesystem. Sync, defensive. */
|
|
69
|
+
export function createFsDesignEvidenceProbe(): DesignEvidenceProbe {
|
|
70
|
+
return {
|
|
71
|
+
hasDesignFile(changeDir) {
|
|
72
|
+
try {
|
|
73
|
+
const entries = readdirSync(changeDir, { withFileTypes: true });
|
|
74
|
+
for (const e of entries) {
|
|
75
|
+
if (e.isFile() && DESIGN_FILE_RE.test(e.name)) return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
hasDesignDirWithMd(changeDir) {
|
|
84
|
+
const dir = path.join(changeDir, "design");
|
|
85
|
+
try {
|
|
86
|
+
const st = statSync(dir);
|
|
87
|
+
if (!st.isDirectory()) return false;
|
|
88
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
89
|
+
for (const e of entries) {
|
|
90
|
+
if (e.isFile() && e.name.endsWith(".md")) return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
tasksHasCheckboxes(changeDir) {
|
|
99
|
+
const tasks = path.join(changeDir, "tasks.md");
|
|
100
|
+
if (!existsSync(tasks)) return false;
|
|
101
|
+
try {
|
|
102
|
+
const text = readFileSync(tasks, "utf8");
|
|
103
|
+
return CHECKBOX_RE.test(text);
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|