@blackbelt-technology/pi-agent-dashboard 0.4.6 → 0.5.1
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 +339 -190
- package/README.md +50 -7
- package/docs/architecture.md +238 -23
- package/package.json +23 -13
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +1 -1
- package/packages/extension/src/__tests__/build-provider-catalogue.test.ts +176 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +68 -0
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +1 -1
- package/packages/extension/src/__tests__/markdown-image-inliner.test.ts +355 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +1 -1
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +68 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +45 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +74 -0
- package/packages/extension/src/__tests__/retry-tracker.test.ts +147 -0
- package/packages/extension/src/__tests__/server-launcher.test.ts +24 -1
- package/packages/extension/src/__tests__/session-sync.test.ts +72 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +105 -0
- package/packages/extension/src/ask-user-tool.ts +1 -1
- package/packages/extension/src/bridge-context.ts +1 -1
- package/packages/extension/src/bridge.ts +169 -4
- package/packages/extension/src/command-handler.ts +65 -2
- package/packages/extension/src/flow-event-wiring.ts +1 -1
- package/packages/extension/src/markdown-image-inliner.ts +268 -0
- package/packages/extension/src/multiselect-list.ts +1 -1
- package/packages/extension/src/pi-env.d.ts +16 -9
- package/packages/extension/src/prompt-expander.ts +50 -2
- package/packages/extension/src/provider-register.ts +132 -8
- package/packages/extension/src/retry-tracker.ts +123 -0
- package/packages/extension/src/server-launcher.ts +18 -1
- package/packages/extension/src/session-sync.ts +15 -1
- package/packages/extension/src/usage-limit-orderer.ts +76 -0
- package/packages/server/package.json +6 -6
- package/packages/server/src/__tests__/auto-attach-slug-defense.test.ts +104 -0
- package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +263 -0
- package/packages/server/src/__tests__/browser-gateway-snapshot-on-connect.test.ts +143 -0
- package/packages/server/src/__tests__/build-auth-status.test.ts +190 -0
- package/packages/server/src/__tests__/changelog-fs.test.ts +171 -0
- package/packages/server/src/__tests__/changelog-parser.test.ts +220 -0
- package/packages/server/src/__tests__/changelog-remote.test.ts +193 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +22 -4
- package/packages/server/src/__tests__/cold-boot-openspec-broadcast.test.ts +161 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service.test.ts +1 -1
- package/packages/server/src/__tests__/doctor-route.test.ts +132 -0
- package/packages/server/src/__tests__/event-wiring-providers-list.test.ts +154 -0
- package/packages/server/src/__tests__/fixtures/pi-changelog-slice.md +180 -0
- package/packages/server/src/__tests__/fork-empty-session-preflight.test.ts +268 -0
- package/packages/server/src/__tests__/has-openspec-dir.test.ts +64 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +83 -0
- package/packages/server/src/__tests__/health-shape.test.ts +43 -0
- package/packages/server/src/__tests__/idle-timer-respects-terminals.test.ts +115 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +1 -1
- package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +92 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +4 -4
- package/packages/server/src/__tests__/package-routes.test.ts +1 -1
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +48 -24
- package/packages/server/src/__tests__/pi-changelog-integration.test.ts +165 -0
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +409 -0
- package/packages/server/src/__tests__/pi-core-checker.test.ts +155 -13
- package/packages/server/src/__tests__/pi-core-updater-managed-path.test.ts +236 -0
- package/packages/server/src/__tests__/pi-core-updater.test.ts +1 -1
- package/packages/server/src/__tests__/pi-dev-version-check.test.ts +184 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +4 -4
- package/packages/server/src/__tests__/process-manager-codes.test.ts +80 -0
- package/packages/server/src/__tests__/process-manager-managed-path.test.ts +73 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +12 -4
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +42 -11
- package/packages/server/src/__tests__/provider-catalogue-cache.test.ts +44 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +1 -1
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +17 -2
- package/packages/server/src/__tests__/session-action-handler-spawn.test.ts +150 -0
- package/packages/server/src/__tests__/session-discovery-skill-firstmessage.test.ts +95 -0
- package/packages/server/src/__tests__/spawn-correlation-token-integration.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-failure-log.test.ts +118 -0
- package/packages/server/src/__tests__/spawn-preflight.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-register-watchdog.test.ts +250 -0
- package/packages/server/src/__tests__/spawn-token.test.ts +57 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +98 -6
- package/packages/server/src/__tests__/system-routes-reextract.test.ts +91 -0
- package/packages/server/src/__tests__/system-routes-spawn-failures.test.ts +84 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +45 -0
- package/packages/server/src/bootstrap-install-from-list.ts +232 -0
- package/packages/server/src/bootstrap-state.ts +18 -0
- package/packages/server/src/browser-gateway.ts +70 -24
- package/packages/server/src/browser-handlers/directory-handler.ts +4 -0
- package/packages/server/src/browser-handlers/handler-context.ts +9 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +159 -18
- package/packages/server/src/browser-handlers/subscription-handler.ts +50 -3
- package/packages/server/src/changelog-fs.ts +167 -0
- package/packages/server/src/changelog-parser.ts +321 -0
- package/packages/server/src/changelog-remote.ts +134 -0
- package/packages/server/src/cli.ts +23 -2
- package/packages/server/src/directory-service.ts +31 -0
- package/packages/server/src/event-wiring.ts +105 -5
- package/packages/server/src/headless-pid-registry.ts +54 -5
- package/packages/server/src/home-lock.d.ts +124 -0
- package/packages/server/src/home-lock.js +330 -0
- package/packages/server/src/home-lock.js.map +1 -0
- package/packages/server/src/idle-timer.ts +15 -1
- package/packages/server/src/pending-client-correlations.ts +73 -0
- package/packages/server/src/pending-fork-registry.ts +24 -12
- package/packages/server/src/pi-core-checker.ts +77 -17
- package/packages/server/src/pi-core-updater.ts +81 -15
- package/packages/server/src/pi-dev-version-check.ts +145 -0
- package/packages/server/src/pi-gateway.ts +10 -0
- package/packages/server/src/pi-version-skew.ts +12 -4
- package/packages/server/src/process-manager.ts +115 -21
- package/packages/server/src/provider-auth-handlers.ts +9 -0
- package/packages/server/src/provider-auth-storage.ts +83 -51
- package/packages/server/src/provider-catalogue-cache.ts +47 -0
- package/packages/server/src/routes/doctor-routes.ts +140 -0
- package/packages/server/src/routes/pi-changelog-routes.ts +194 -0
- package/packages/server/src/routes/pi-core-routes.ts +1 -1
- package/packages/server/src/routes/provider-auth-routes.ts +14 -1
- package/packages/server/src/routes/provider-routes.ts +4 -4
- package/packages/server/src/routes/system-routes.ts +38 -1
- package/packages/server/src/server.ts +85 -66
- package/packages/server/src/session-api.ts +54 -3
- package/packages/server/src/session-bootstrap.ts +27 -12
- package/packages/server/src/session-discovery.ts +11 -4
- package/packages/server/src/session-file-reader.ts +1 -1
- package/packages/server/src/session-scanner.ts +4 -2
- package/packages/server/src/spawn-failure-log.ts +130 -0
- package/packages/server/src/spawn-preflight.ts +82 -0
- package/packages/server/src/spawn-register-watchdog.ts +291 -0
- package/packages/server/src/spawn-token.ts +20 -0
- package/packages/server/src/terminal-manager.ts +12 -1
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +25 -17
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +6 -5
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +1 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +2 -1
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +6 -3
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +1 -1
- package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +72 -0
- package/packages/shared/src/__tests__/browser-protocol-types.test.ts +47 -1
- package/packages/shared/src/__tests__/changelog-types.test.ts +78 -0
- package/packages/shared/src/__tests__/config.test.ts +48 -0
- package/packages/shared/src/__tests__/dashboard-starter.test.ts +40 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +24 -0
- package/packages/shared/src/__tests__/doctor-core.test.ts +134 -0
- package/packages/shared/src/__tests__/doctor-fault-tolerance.test.ts +218 -0
- package/packages/shared/src/__tests__/doctor-format.test.ts +121 -0
- package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +68 -0
- package/packages/shared/src/__tests__/install-managed-node.test.ts +192 -0
- package/packages/shared/src/__tests__/installable-list.test.ts +130 -0
- package/packages/shared/src/__tests__/managed-node-path.test.ts +122 -0
- package/packages/shared/src/__tests__/managed-runtime-strategy.test.ts +74 -0
- package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +52 -0
- package/packages/shared/src/__tests__/no-raw-openspec-status-in-skills.test.ts +6 -1
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +56 -20
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +140 -9
- package/packages/shared/src/__tests__/skill-block-parser.test.ts +153 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +1 -1
- package/packages/shared/src/bootstrap-install.ts +197 -3
- package/packages/shared/src/browser-protocol.ts +155 -1
- package/packages/shared/src/changelog-types.ts +111 -0
- package/packages/shared/src/config.ts +15 -0
- package/packages/shared/src/dashboard-starter.ts +33 -0
- package/packages/shared/src/doctor-core.ts +821 -0
- package/packages/shared/src/index.ts +9 -0
- package/packages/shared/src/installable-list.ts +152 -0
- package/packages/shared/src/launch-source-flag.ts +14 -0
- package/packages/shared/src/launch-source-types.ts +18 -0
- package/packages/shared/src/openspec-activity-detector.ts +25 -7
- package/packages/shared/src/platform/detached-spawn.ts +13 -2
- package/packages/shared/src/platform/managed-node-path.ts +77 -0
- package/packages/shared/src/platform/node-spawn.ts +29 -21
- package/packages/shared/src/protocol.ts +54 -2
- package/packages/shared/src/resolve-jiti.ts +62 -9
- package/packages/shared/src/rest-api.ts +4 -0
- package/packages/shared/src/skill-block-parser.ts +115 -0
- package/packages/shared/src/tool-registry/__tests__/managed-runtime-strategy.test.ts +166 -0
- package/packages/shared/src/tool-registry/definitions.ts +33 -8
- package/packages/shared/src/tool-registry/strategies.ts +42 -0
- package/packages/shared/src/types.ts +64 -0
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
// Ambient declarations for pi runtime packages.
|
|
2
|
-
// The actual types are provided by whichever host
|
|
2
|
+
// The actual types are provided by whichever host loads this extension.
|
|
3
3
|
// tsconfig paths handles resolution when one of the packages is installed;
|
|
4
4
|
// these declarations serve as fallback when neither is available (e.g. CI, dev without pi).
|
|
5
|
-
declare module "@
|
|
6
|
-
export type ExtensionAPI = import("@oh-my-pi/pi-coding-agent").ExtensionAPI;
|
|
7
|
-
}
|
|
8
|
-
declare module "@mariozechner/pi-ai" {
|
|
9
|
-
export function StringEnum<T extends readonly string[]>(values: T, schema?: Record<string, unknown>): any;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
declare module "@oh-my-pi/pi-coding-agent" {
|
|
5
|
+
declare module "@earendil-works/pi-coding-agent" {
|
|
13
6
|
export interface ModelRegistry {
|
|
14
7
|
getAvailable(): Array<{ provider: string; id: string }>;
|
|
15
8
|
refresh(): void;
|
|
@@ -35,3 +28,17 @@ declare module "@oh-my-pi/pi-coding-agent" {
|
|
|
35
28
|
events: EventBus;
|
|
36
29
|
}
|
|
37
30
|
}
|
|
31
|
+
|
|
32
|
+
// Legacy fork — re-exports the same ExtensionAPI shape so existing installs still type-check.
|
|
33
|
+
declare module "@mariozechner/pi-coding-agent" {
|
|
34
|
+
export type ExtensionAPI = import("@earendil-works/pi-coding-agent").ExtensionAPI;
|
|
35
|
+
export type ModelRegistry = import("@earendil-works/pi-coding-agent").ModelRegistry;
|
|
36
|
+
export type EventBus = import("@earendil-works/pi-coding-agent").EventBus;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare module "@earendil-works/pi-ai" {
|
|
40
|
+
export function StringEnum<T extends readonly string[]>(values: T, schema?: Record<string, unknown>): any;
|
|
41
|
+
}
|
|
42
|
+
declare module "@mariozechner/pi-ai" {
|
|
43
|
+
export function StringEnum<T extends readonly string[]>(values: T, schema?: Record<string, unknown>): any;
|
|
44
|
+
}
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* by reading template/skill files directly and expanding them.
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
-
import { join, resolve } from "node:path";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
10
|
import { readdirSync, statSync } from "node:fs";
|
|
11
|
+
import { buildSkillBlock } from "@blackbelt-technology/pi-dashboard-shared/skill-block-parser.js";
|
|
11
12
|
|
|
12
13
|
/** Scan directories for .md prompt template files */
|
|
13
14
|
function findPromptTemplates(cwd: string): Map<string, string> {
|
|
@@ -97,7 +98,26 @@ export function expandPromptTemplateFromDisk(text: string, cwd: string, pi?: any
|
|
|
97
98
|
|
|
98
99
|
try {
|
|
99
100
|
const content = readTemplate(filePath);
|
|
100
|
-
|
|
101
|
+
|
|
102
|
+
// Skill detection: either the local-scan key starts with `skill:` or the
|
|
103
|
+
// pi.getCommands() fallback resolved a command whose `source === "skill"`
|
|
104
|
+
// (we re-check below). Skill expansions wrap in pi's `<skill>` envelope so
|
|
105
|
+
// the dashboard ingress path is byte-identical to pi's own _expandSkillCommand,
|
|
106
|
+
// which lets the client and server recover the slash-command form.
|
|
107
|
+
// See change: render-skill-invocations-collapsibly.
|
|
108
|
+
const isSkill = isSkillResolution(templateName, filePath, pi);
|
|
109
|
+
if (isSkill) {
|
|
110
|
+
const bareName = templateName.replace(/^skill:/, "");
|
|
111
|
+
return buildSkillBlock({
|
|
112
|
+
name: bareName,
|
|
113
|
+
filePath,
|
|
114
|
+
baseDir: dirname(filePath),
|
|
115
|
+
body: content,
|
|
116
|
+
userArgs: argsString || undefined,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Plain prompt templates: append args after a blank line, no wrapper.
|
|
101
121
|
if (argsString) {
|
|
102
122
|
return `${content}\n\n${argsString}`;
|
|
103
123
|
}
|
|
@@ -106,3 +126,31 @@ export function expandPromptTemplateFromDisk(text: string, cwd: string, pi?: any
|
|
|
106
126
|
return text;
|
|
107
127
|
}
|
|
108
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Detect whether the resolved `filePath` came from a skill source.
|
|
132
|
+
*
|
|
133
|
+
* The local-scan key tells us directly when it starts with `skill:`. For the
|
|
134
|
+
* pi.getCommands() fallback we re-query and check `source === "skill"` against
|
|
135
|
+
* the same templateName.
|
|
136
|
+
*/
|
|
137
|
+
function isSkillResolution(
|
|
138
|
+
templateName: string,
|
|
139
|
+
filePath: string,
|
|
140
|
+
pi: any | undefined,
|
|
141
|
+
): boolean {
|
|
142
|
+
if (templateName.startsWith("skill:")) return true;
|
|
143
|
+
// The colon-alias path (e.g. /opsx:continue) maps to a hyphen filename and is
|
|
144
|
+
// a prompt template, not a skill. Skills always use the `skill:` prefix in
|
|
145
|
+
// both the local scan and pi.getCommands().
|
|
146
|
+
if (!pi?.getCommands) return false;
|
|
147
|
+
try {
|
|
148
|
+
const commands = pi.getCommands();
|
|
149
|
+
const match = commands.find(
|
|
150
|
+
(c: any) => c.name === templateName && c.source === "skill" && c.path === filePath,
|
|
151
|
+
);
|
|
152
|
+
return !!match;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* flow:resolve-model / flow:get-available-models
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { ExtensionAPI } from "@
|
|
14
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { existsSync, readFileSync } from "node:fs";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { join } from "node:path";
|
|
18
|
+
import type { ProviderInfo } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
18
19
|
|
|
19
20
|
// -- Types ----------------------------------------------------------------
|
|
20
21
|
|
|
@@ -288,6 +289,122 @@ export function getSessionInfo(): { provider: string; modelId: string } {
|
|
|
288
289
|
return { provider: currentSessionProvider, modelId: currentSessionModelId };
|
|
289
290
|
}
|
|
290
291
|
|
|
292
|
+
// -- Provider catalogue (for dashboard /api/provider-auth/status) -------
|
|
293
|
+
//
|
|
294
|
+
// Pure derivation: given a captured `ModelRegistry` and the pi-ai
|
|
295
|
+
// helpers (`findEnvKeys`, `getEnvApiKey`), build a flat ProviderInfo[]
|
|
296
|
+
// covering every OAuth provider plus every distinct provider id from
|
|
297
|
+
// `getAll()`. The bridge pushes this to the server alongside
|
|
298
|
+
// `models_list`. See change: replace-hardcoded-provider-lists.
|
|
299
|
+
|
|
300
|
+
type PiAiHelpers = {
|
|
301
|
+
findEnvKeys?: (id: string) => string[] | undefined;
|
|
302
|
+
getEnvApiKey?: (id: string) => string | undefined;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export function _buildProviderCatalogue(
|
|
306
|
+
modelRegistry: any,
|
|
307
|
+
piAi: PiAiHelpers,
|
|
308
|
+
customIds: ReadonlySet<string> = new Set(),
|
|
309
|
+
): ProviderInfo[] {
|
|
310
|
+
if (!modelRegistry) return [];
|
|
311
|
+
const oauthIds = new Set<string>(
|
|
312
|
+
(modelRegistry.authStorage?.getOAuthProviders?.() ?? []).map((p: any) => p.id),
|
|
313
|
+
);
|
|
314
|
+
// The catalogue is the complete picture of what pi knows about —
|
|
315
|
+
// built-in providers, OAuth providers, AND custom providers registered
|
|
316
|
+
// by the dashboard via pi.registerProvider() from ~/.pi/agent/providers.json.
|
|
317
|
+
// Custom providers carry `custom: true` so consumers can decide what
|
|
318
|
+
// to surface where (e.g. the auth UI suppresses their API-key rows
|
|
319
|
+
// because they're managed by the LLM Providers settings section).
|
|
320
|
+
// Filtering decisions belong to consumers, not to this function.
|
|
321
|
+
// See change: replace-hardcoded-provider-lists.
|
|
322
|
+
const allIds = new Set<string>(oauthIds);
|
|
323
|
+
for (const m of (modelRegistry.getAll?.() ?? []) as Array<{ provider?: string }>) {
|
|
324
|
+
if (m.provider) allIds.add(m.provider);
|
|
325
|
+
}
|
|
326
|
+
return [...allIds].map((id) => {
|
|
327
|
+
let displayName = id;
|
|
328
|
+
try {
|
|
329
|
+
displayName = modelRegistry.getProviderDisplayName?.(id) ?? id;
|
|
330
|
+
} catch { /* fallback to id */ }
|
|
331
|
+
let configured = false;
|
|
332
|
+
let source: ProviderInfo["source"];
|
|
333
|
+
try {
|
|
334
|
+
const status = modelRegistry.authStorage?.getAuthStatus?.(id);
|
|
335
|
+
if (status) {
|
|
336
|
+
configured = !!status.configured;
|
|
337
|
+
source = status.source;
|
|
338
|
+
}
|
|
339
|
+
} catch { /* ignore */ }
|
|
340
|
+
let expires: number | undefined;
|
|
341
|
+
try {
|
|
342
|
+
const cred = modelRegistry.authStorage?.get?.(id);
|
|
343
|
+
if (cred?.type === "oauth" && typeof cred.expires === "number") {
|
|
344
|
+
expires = cred.expires;
|
|
345
|
+
}
|
|
346
|
+
} catch { /* ignore */ }
|
|
347
|
+
let envVar: string | undefined;
|
|
348
|
+
let ambient: boolean | undefined;
|
|
349
|
+
try {
|
|
350
|
+
const keys = piAi.findEnvKeys?.(id);
|
|
351
|
+
if (keys && keys.length > 0) envVar = keys[0];
|
|
352
|
+
if (piAi.getEnvApiKey?.(id) === "<authenticated>") ambient = true;
|
|
353
|
+
} catch { /* ignore */ }
|
|
354
|
+
return {
|
|
355
|
+
id,
|
|
356
|
+
displayName,
|
|
357
|
+
hasOAuth: oauthIds.has(id),
|
|
358
|
+
configured,
|
|
359
|
+
source,
|
|
360
|
+
envVar,
|
|
361
|
+
ambient,
|
|
362
|
+
expires,
|
|
363
|
+
custom: customIds.has(id) || undefined,
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Lazy-cached pi-ai module (in scope inside pi's process).
|
|
369
|
+
let _piAiModule: PiAiHelpers | null = null;
|
|
370
|
+
let _piAiLoadAttempted = false;
|
|
371
|
+
async function loadPiAi(): Promise<PiAiHelpers> {
|
|
372
|
+
if (_piAiModule) return _piAiModule;
|
|
373
|
+
if (_piAiLoadAttempted) return {};
|
|
374
|
+
_piAiLoadAttempted = true;
|
|
375
|
+
try {
|
|
376
|
+
const mod: any = await import("@earendil-works/pi-ai");
|
|
377
|
+
_piAiModule = { findEnvKeys: mod.findEnvKeys, getEnvApiKey: mod.getEnvApiKey };
|
|
378
|
+
return _piAiModule;
|
|
379
|
+
} catch {
|
|
380
|
+
return {};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Eagerly kick off pi-ai load at module import time so env-var hints
|
|
385
|
+
// are populated by the time the first session_register fires. Failure
|
|
386
|
+
// is silent; `buildProviderCatalogue` falls back to {} which still
|
|
387
|
+
// produces a valid catalogue minus envVar/ambient hints.
|
|
388
|
+
void loadPiAi();
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Public wrapper: returns the current provider catalogue, or [] when
|
|
392
|
+
* the model registry has not been captured yet. Marks providers the
|
|
393
|
+
* bridge itself registered (from `~/.pi/agent/providers.json` via
|
|
394
|
+
* `pi.registerProvider()`) with `custom: true` so consumers can
|
|
395
|
+
* suppress their API-key auth rows (those are managed by the LLM
|
|
396
|
+
* Providers settings section). The catalogue itself is complete —
|
|
397
|
+
* including custom providers — so other consumers (e.g. diagnostics)
|
|
398
|
+
* see the full picture.
|
|
399
|
+
*/
|
|
400
|
+
export function buildProviderCatalogue(): ProviderInfo[] {
|
|
401
|
+
const mr = getModelRegistry();
|
|
402
|
+
if (!mr) return [];
|
|
403
|
+
const piAi = _piAiModule ?? {};
|
|
404
|
+
const customIds = new Set<string>(lastRegistered.keys());
|
|
405
|
+
return _buildProviderCatalogue(mr, piAi, customIds);
|
|
406
|
+
}
|
|
407
|
+
|
|
291
408
|
export function getModelDisplayName(modelId: string): string {
|
|
292
409
|
if (piRef) {
|
|
293
410
|
const data: any = {};
|
|
@@ -319,6 +436,20 @@ function getModelRegistry(): any {
|
|
|
319
436
|
// -- Provider registration (with auto-discovery) --------------------------
|
|
320
437
|
|
|
321
438
|
async function registerEntry(pi: ExtensionAPI, name: string, entry: ProviderEntry): Promise<number> {
|
|
439
|
+
// Record snapshot SYNCHRONOUSLY before awaiting discovery so the very
|
|
440
|
+
// first providers_list push (typically fired from `session_start`
|
|
441
|
+
// shortly after `activate()` kicked off async registerEntry calls) carries
|
|
442
|
+
// the correct `custom: true` flags. Otherwise a slow / unreachable
|
|
443
|
+
// /v1/models endpoint causes custom providers from
|
|
444
|
+
// `~/.pi/agent/providers.json` to leak into Settings → Provider
|
|
445
|
+
// Authentication → API Keys until the discovery probe resolves.
|
|
446
|
+
// See change: fix-custom-provider-flag-race.
|
|
447
|
+
lastRegistered.set(name, {
|
|
448
|
+
baseUrl: entry.baseUrl,
|
|
449
|
+
apiKey: entry.apiKey,
|
|
450
|
+
api: entry.api ?? "openai-completions",
|
|
451
|
+
});
|
|
452
|
+
|
|
322
453
|
const discovered = await discoverModels(entry.baseUrl, entry.apiKey);
|
|
323
454
|
|
|
324
455
|
// Metadata (contextWindow, maxTokens, reasoning, cost, input) is resolved
|
|
@@ -347,13 +478,6 @@ async function registerEntry(pi: ExtensionAPI, name: string, entry: ProviderEntr
|
|
|
347
478
|
models,
|
|
348
479
|
});
|
|
349
480
|
|
|
350
|
-
// Record snapshot so reloadProviders can detect subsequent changes.
|
|
351
|
-
lastRegistered.set(name, {
|
|
352
|
-
baseUrl: entry.baseUrl,
|
|
353
|
-
apiKey: entry.apiKey,
|
|
354
|
-
api: entry.api ?? "openai-completions",
|
|
355
|
-
});
|
|
356
|
-
|
|
357
481
|
// Notify bridge directly (same package — no cross-package event needed)
|
|
358
482
|
onProvidersChanged?.();
|
|
359
483
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RetryTracker — synthesizes `auto_retry_start` / `auto_retry_end` events from
|
|
3
|
+
* observed pi events.
|
|
4
|
+
*
|
|
5
|
+
* Background: pi's ExtensionAPI does NOT expose `auto_retry_*` events to
|
|
6
|
+
* extensions (verified against pi 0.70/0.73 — see
|
|
7
|
+
* https://github.com/badlogic/pi-mono/discussions/2073). They fire only via
|
|
8
|
+
* `AgentSession._emit → _eventListeners` which only the embedded SDK can
|
|
9
|
+
* subscribe to.
|
|
10
|
+
*
|
|
11
|
+
* Workaround: pi-coding-agent's `_handleRetryableError` fires `message_end`
|
|
12
|
+
* for the failed assistant message BEFORE entering its retry sleep. The
|
|
13
|
+
* bridge sees that `message_end` via `pi.on("message_end")`. By matching
|
|
14
|
+
* the same regex pi-coding-agent uses internally, we can detect that a
|
|
15
|
+
* retry is about to happen and emit our own `auto_retry_start` to the
|
|
16
|
+
* dashboard. When the next non-error `message_end` or `agent_end` arrives,
|
|
17
|
+
* we emit `auto_retry_end`.
|
|
18
|
+
*
|
|
19
|
+
* `delayMs` and `maxAttempts` are unknowable from observed events (pi's
|
|
20
|
+
* settings are not exposed); we send sentinel `-1` for both. The
|
|
21
|
+
* RetryBanner renders an indeterminate "retrying…" UI in that case.
|
|
22
|
+
*
|
|
23
|
+
* See change: fix-provider-retry-infinite-loop.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Regex copied verbatim from pi-coding-agent `agent-session.js`
|
|
28
|
+
* `_isRetryableError`. If pi adds new retryable categories, this regex
|
|
29
|
+
* goes stale — but the failure mode is "tracker silently misses some
|
|
30
|
+
* retries", never "tracker breaks". Sync at major pi version bumps.
|
|
31
|
+
*/
|
|
32
|
+
export const RETRYABLE_PATTERN =
|
|
33
|
+
/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i;
|
|
34
|
+
|
|
35
|
+
export interface SyntheticRetryEvent {
|
|
36
|
+
eventType: "auto_retry_start" | "auto_retry_end";
|
|
37
|
+
data: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Minimal shape we pluck from a `message_end` event. */
|
|
41
|
+
export interface ObservedAssistantMessage {
|
|
42
|
+
role?: string;
|
|
43
|
+
stopReason?: string;
|
|
44
|
+
errorMessage?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class RetryTracker {
|
|
48
|
+
/** sessionId → 1-based attempt counter for the current retry chain. */
|
|
49
|
+
private attempt = new Map<string, number>();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Process a `message_end` event. Returns a synthetic event the bridge
|
|
53
|
+
* should ALSO forward (after the original message_end), or null.
|
|
54
|
+
*/
|
|
55
|
+
observeMessageEnd(
|
|
56
|
+
sessionId: string,
|
|
57
|
+
message: ObservedAssistantMessage | undefined | null,
|
|
58
|
+
): SyntheticRetryEvent | null {
|
|
59
|
+
if (!message || message.role !== "assistant") return null;
|
|
60
|
+
|
|
61
|
+
if (message.stopReason === "error") {
|
|
62
|
+
const err = typeof message.errorMessage === "string" ? message.errorMessage : "";
|
|
63
|
+
if (!err || !RETRYABLE_PATTERN.test(err)) return null;
|
|
64
|
+
const next = (this.attempt.get(sessionId) ?? 0) + 1;
|
|
65
|
+
this.attempt.set(sessionId, next);
|
|
66
|
+
return {
|
|
67
|
+
eventType: "auto_retry_start",
|
|
68
|
+
data: { attempt: next, maxAttempts: -1, delayMs: -1, errorMessage: err },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Non-error assistant message — clears any in-flight retry chain.
|
|
73
|
+
if (this.attempt.has(sessionId)) {
|
|
74
|
+
const last = this.attempt.get(sessionId) ?? 0;
|
|
75
|
+
this.attempt.delete(sessionId);
|
|
76
|
+
return { eventType: "auto_retry_end", data: { success: true, attempt: last } };
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Process an `agent_end` event. Returns a synthetic event the bridge
|
|
83
|
+
* should forward BEFORE the original agent_end, or null.
|
|
84
|
+
*
|
|
85
|
+
* Always clears any in-flight retry tracking (terminal turn boundary).
|
|
86
|
+
*/
|
|
87
|
+
observeAgentEnd(
|
|
88
|
+
sessionId: string,
|
|
89
|
+
agentEndData: { messages?: unknown } | undefined | null,
|
|
90
|
+
): SyntheticRetryEvent | null {
|
|
91
|
+
const wasRetrying = this.attempt.has(sessionId);
|
|
92
|
+
const last = this.attempt.get(sessionId) ?? -1;
|
|
93
|
+
this.attempt.delete(sessionId);
|
|
94
|
+
if (!wasRetrying) return null;
|
|
95
|
+
|
|
96
|
+
// Inspect terminal message for error context.
|
|
97
|
+
const messages = agentEndData?.messages;
|
|
98
|
+
const lastMsg =
|
|
99
|
+
Array.isArray(messages) && messages.length > 0
|
|
100
|
+
? (messages[messages.length - 1] as ObservedAssistantMessage)
|
|
101
|
+
: undefined;
|
|
102
|
+
if (lastMsg?.stopReason === "error" && typeof lastMsg.errorMessage === "string") {
|
|
103
|
+
return {
|
|
104
|
+
eventType: "auto_retry_end",
|
|
105
|
+
data: { success: false, attempt: last, finalError: lastMsg.errorMessage },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { eventType: "auto_retry_end", data: { success: true, attempt: last } };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Notify the tracker of a user abort. Clears in-flight tracking so a
|
|
113
|
+
* subsequent agent_end does not double-emit auto_retry_end.
|
|
114
|
+
*/
|
|
115
|
+
noteAbort(sessionId: string): void {
|
|
116
|
+
this.attempt.delete(sessionId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Test-only / bridge-coordination: is a retry currently in flight? */
|
|
120
|
+
isRetrying(sessionId: string): boolean {
|
|
121
|
+
return this.attempt.has(sessionId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -45,6 +45,23 @@ export function resolveServerCliPath(): string {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Build the environment object passed to the spawned server process.
|
|
50
|
+
* Always stamps DASHBOARD_STARTER=Bridge so the server knows it was
|
|
51
|
+
* launched by the pi bridge extension.
|
|
52
|
+
*/
|
|
53
|
+
export function buildSpawnEnv(
|
|
54
|
+
baseEnv: NodeJS.ProcessEnv = process.env,
|
|
55
|
+
): Record<string, string> {
|
|
56
|
+
// Spread process.env (may contain undefined values); filter them out.
|
|
57
|
+
const out: Record<string, string> = {};
|
|
58
|
+
for (const [k, v] of Object.entries(baseEnv)) {
|
|
59
|
+
if (v !== undefined) out[k] = v;
|
|
60
|
+
}
|
|
61
|
+
out["DASHBOARD_STARTER"] = "Bridge";
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
/**
|
|
49
66
|
* Build the spawn arguments from config.
|
|
50
67
|
*/
|
|
@@ -94,7 +111,7 @@ export async function launchServer(config: DashboardConfig): Promise<LaunchResul
|
|
|
94
111
|
const r = await spawnDetached({
|
|
95
112
|
cmd: process.execPath,
|
|
96
113
|
args: ["--import", loader, entry, ...args], // ban:raw-node-import-ok: entry gated by shouldUrlWrapEntry
|
|
97
|
-
env: { ...process.env },
|
|
114
|
+
env: { ...process.env, DASHBOARD_STARTER: "Bridge" },
|
|
98
115
|
logFd,
|
|
99
116
|
});
|
|
100
117
|
|
|
@@ -8,6 +8,7 @@ import { detectSessionSource } from "./source-detector.js";
|
|
|
8
8
|
import { replayEntriesAsEvents } from "@blackbelt-technology/pi-dashboard-shared/state-replay.js";
|
|
9
9
|
import { gatherGitInfo, gatherJjInfo } from "./vcs-info.js";
|
|
10
10
|
import type { FlowInfo } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
11
|
+
import { buildProviderCatalogue } from "./provider-register.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Send full state sync to the server (session_register, commands, flows, models).
|
|
@@ -38,7 +39,15 @@ export function sendStateSync(
|
|
|
38
39
|
// dashboard restart) is a "reattach". Server applies the configured
|
|
39
40
|
// `reattachPlacement` policy on "reattach".
|
|
40
41
|
// See change: reattach-move-to-front.
|
|
41
|
-
const
|
|
42
|
+
const isFirstRegister = !bc.hasRegisteredOnce;
|
|
43
|
+
const registerReason: "spawn" | "reattach" = isFirstRegister ? "spawn" : "reattach";
|
|
44
|
+
|
|
45
|
+
// Include the spawn correlation token (server-minted UUID injected via
|
|
46
|
+
// env var at spawn time) ONLY on the first register. Subsequent
|
|
47
|
+
// registers (reattach after dashboard restart, in-process Ctrl+F fork)
|
|
48
|
+
// omit it because the sessionId is already known to the server.
|
|
49
|
+
// See change: spawn-correlation-token (Decision 3).
|
|
50
|
+
const spawnToken = isFirstRegister ? process.env.PI_DASHBOARD_SPAWN_TOKEN : undefined;
|
|
42
51
|
|
|
43
52
|
bc.connection.send({
|
|
44
53
|
type: "session_register",
|
|
@@ -54,6 +63,7 @@ export function sendStateSync(
|
|
|
54
63
|
eventCount,
|
|
55
64
|
pid: process.pid,
|
|
56
65
|
registerReason,
|
|
66
|
+
...(spawnToken ? { spawnToken } : {}),
|
|
57
67
|
});
|
|
58
68
|
|
|
59
69
|
bc.hasRegisteredOnce = true;
|
|
@@ -72,6 +82,8 @@ export function sendStateSync(
|
|
|
72
82
|
id: m.id,
|
|
73
83
|
}));
|
|
74
84
|
bc.connection.send({ type: "models_list", sessionId: bc.sessionId, models });
|
|
85
|
+
// See change: replace-hardcoded-provider-lists.
|
|
86
|
+
bc.connection.send({ type: "providers_list", sessionId: bc.sessionId, providers: buildProviderCatalogue() });
|
|
75
87
|
} catch { /* ignore */ }
|
|
76
88
|
}
|
|
77
89
|
}
|
|
@@ -164,6 +176,8 @@ export function handleSessionChange(
|
|
|
164
176
|
id: m.id,
|
|
165
177
|
}));
|
|
166
178
|
bc.connection.send({ type: "models_list", sessionId: bc.sessionId, models });
|
|
179
|
+
// See change: replace-hardcoded-provider-lists.
|
|
180
|
+
bc.connection.send({ type: "providers_list", sessionId: bc.sessionId, providers: buildProviderCatalogue() });
|
|
167
181
|
} catch { /* ignore */ }
|
|
168
182
|
}
|
|
169
183
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage-limit event orderer.
|
|
3
|
+
*
|
|
4
|
+
* Tracks per-session whether an `auto_retry_start` was forwarded without a
|
|
5
|
+
* matching `auto_retry_end`, and — when an `agent_end` arrives whose terminal
|
|
6
|
+
* assistant message has a usage-limit / quota errorMessage — synthesizes an
|
|
7
|
+
* `auto_retry_end { success: false }` to emit BEFORE the `agent_end`.
|
|
8
|
+
*
|
|
9
|
+
* Pure logic (no I/O). The bridge wires this into its forwarding pipeline.
|
|
10
|
+
*
|
|
11
|
+
* See change: fix-provider-retry-infinite-loop.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const USAGE_LIMIT_PATTERN =
|
|
15
|
+
/usage[_ ]limit[_ ]reached|usage_not_included|quota[_ ]exceeded|monthly limit|hourly limit|reset after \d+[hms]/i;
|
|
16
|
+
|
|
17
|
+
export interface SyntheticEventEnvelope {
|
|
18
|
+
eventType: "auto_retry_end";
|
|
19
|
+
data: { success: false; attempt: -1; finalError: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class UsageLimitOrderer {
|
|
23
|
+
/** sessionId → true while a retry is in flight (no auto_retry_end seen yet). */
|
|
24
|
+
private pending = new Set<string>();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Notify the orderer of an outbound `auto_retry_start` for sessionId.
|
|
28
|
+
*/
|
|
29
|
+
noteRetryStart(sessionId: string): void {
|
|
30
|
+
this.pending.add(sessionId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Notify the orderer of an outbound `auto_retry_end` for sessionId.
|
|
35
|
+
* Subsequent `agent_end` events will not synthesize unless a new retry
|
|
36
|
+
* has been started.
|
|
37
|
+
*/
|
|
38
|
+
noteRetryEnd(sessionId: string): void {
|
|
39
|
+
this.pending.delete(sessionId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Inspect an `agent_end` payload. If the terminal message has a
|
|
44
|
+
* usage-limit error AND we have an unmatched retry-start for this session,
|
|
45
|
+
* return the synthetic event the bridge should forward BEFORE the agent_end.
|
|
46
|
+
*
|
|
47
|
+
* Returns null when no synthesis is needed. Always clears the pending flag
|
|
48
|
+
* after a terminal agent_end (errored or not) so we don't double-synthesize.
|
|
49
|
+
*/
|
|
50
|
+
maybeSynthesize(
|
|
51
|
+
sessionId: string,
|
|
52
|
+
agentEndData: Record<string, unknown> | undefined,
|
|
53
|
+
): SyntheticEventEnvelope | null {
|
|
54
|
+
const wasPending = this.pending.has(sessionId);
|
|
55
|
+
// Always clear on agent_end: a terminal turn ends any retry tracking.
|
|
56
|
+
this.pending.delete(sessionId);
|
|
57
|
+
|
|
58
|
+
if (!wasPending || !agentEndData) return null;
|
|
59
|
+
const messages = agentEndData.messages;
|
|
60
|
+
if (!Array.isArray(messages) || messages.length === 0) return null;
|
|
61
|
+
const last = messages[messages.length - 1] as Record<string, unknown> | undefined;
|
|
62
|
+
if (!last || last.stopReason !== "error") return null;
|
|
63
|
+
const errorMessage = typeof last.errorMessage === "string" ? last.errorMessage : "";
|
|
64
|
+
if (!errorMessage || !USAGE_LIMIT_PATTERN.test(errorMessage)) return null;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
eventType: "auto_retry_end",
|
|
68
|
+
data: { success: false, attempt: -1, finalError: errorMessage },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Test-only: inspect pending state. */
|
|
73
|
+
hasPending(sessionId: string): boolean {
|
|
74
|
+
return this.pending.has(sessionId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blackbelt-technology/pi-dashboard-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Dashboard server for monitoring and interacting with pi agent sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"node": ">=22.18.0"
|
|
16
16
|
},
|
|
17
17
|
"piCompatibility": {
|
|
18
|
-
"minimum": "0.
|
|
19
|
-
"recommended": "0.
|
|
18
|
+
"minimum": "0.74.0",
|
|
19
|
+
"recommended": "0.74.0",
|
|
20
20
|
"maximum": null
|
|
21
21
|
},
|
|
22
22
|
"main": "src/cli.ts",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"postinstall": "node scripts/fix-pty-permissions.cjs"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@blackbelt-technology/dashboard-plugin-runtime": "^0.
|
|
35
|
-
"@blackbelt-technology/pi-dashboard-extension": "^0.
|
|
36
|
-
"@blackbelt-technology/pi-dashboard-shared": "^0.
|
|
34
|
+
"@blackbelt-technology/dashboard-plugin-runtime": "^0.5.1",
|
|
35
|
+
"@blackbelt-technology/pi-dashboard-extension": "^0.5.1",
|
|
36
|
+
"@blackbelt-technology/pi-dashboard-shared": "^0.5.1",
|
|
37
37
|
"@fastify/compress": "^8.3.1",
|
|
38
38
|
"@fastify/cookie": "^11.0.2",
|
|
39
39
|
"@fastify/cors": "^11.0.0",
|