@blackbelt-technology/pi-agent-dashboard 0.5.1 → 0.5.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.
Files changed (129) hide show
  1. package/AGENTS.md +26 -5
  2. package/README.md +30 -0
  3. package/docs/architecture.md +129 -1
  4. package/package.json +6 -6
  5. package/packages/extension/package.json +2 -2
  6. package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +362 -0
  7. package/packages/extension/src/__tests__/command-handler.test.ts +10 -8
  8. package/packages/extension/src/__tests__/extension-slash-command-detection.test.ts +107 -0
  9. package/packages/extension/src/__tests__/prompt-expander.test.ts +110 -1
  10. package/packages/extension/src/__tests__/server-launcher-launch.test.ts +78 -0
  11. package/packages/extension/src/bridge-context.ts +67 -3
  12. package/packages/extension/src/bridge.ts +20 -8
  13. package/packages/extension/src/command-handler.ts +36 -13
  14. package/packages/extension/src/prompt-expander.ts +74 -63
  15. package/packages/extension/src/server-launcher.ts +31 -70
  16. package/packages/extension/src/slash-dispatch.ts +123 -0
  17. package/packages/server/bin/pi-dashboard.mjs +84 -0
  18. package/packages/server/package.json +6 -5
  19. package/packages/server/scripts/fix-pty-permissions.cjs +52 -0
  20. package/packages/server/src/__tests__/cli-parse.test.ts +12 -18
  21. package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +187 -0
  22. package/packages/server/src/__tests__/directory-service.test.ts +1 -1
  23. package/packages/server/src/__tests__/dispatch-extension-command-router.test.ts +178 -0
  24. package/packages/server/src/__tests__/e2e/model-proxy-google-flash.test.ts +184 -0
  25. package/packages/server/src/__tests__/headless-pid-registry.test.ts +233 -0
  26. package/packages/server/src/__tests__/keeper-manager.test.ts +298 -0
  27. package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +149 -0
  28. package/packages/server/src/__tests__/model-proxy-api-key-routes.test.ts +277 -0
  29. package/packages/server/src/__tests__/model-proxy-auth-gate.test.ts +263 -0
  30. package/packages/server/src/__tests__/model-proxy-multi-user.test.ts +169 -0
  31. package/packages/server/src/__tests__/model-proxy-routes.test.ts +286 -0
  32. package/packages/server/src/__tests__/model-proxy-second-port.test.ts +116 -0
  33. package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +64 -8
  34. package/packages/server/src/__tests__/openspec-group-broadcast.test.ts +97 -0
  35. package/packages/server/src/__tests__/openspec-group-join.test.ts +80 -0
  36. package/packages/server/src/__tests__/openspec-group-routes.test.ts +370 -0
  37. package/packages/server/src/__tests__/openspec-group-store.test.ts +496 -0
  38. package/packages/server/src/__tests__/pi-ai-shape.test.ts +147 -0
  39. package/packages/server/src/__tests__/pi-dashboard-bin-wrapper.test.ts +84 -0
  40. package/packages/server/src/__tests__/process-manager-keeper-spawn.test.ts +206 -0
  41. package/packages/server/src/__tests__/provider-routes-recursion-guard.test.ts +131 -0
  42. package/packages/server/src/__tests__/recommended-routes.test.ts +2 -2
  43. package/packages/server/src/__tests__/tunnel-watchdog.test.ts +139 -0
  44. package/packages/server/src/auth-plugin.ts +3 -0
  45. package/packages/server/src/bootstrap-state.ts +10 -0
  46. package/packages/server/src/browser-gateway.ts +15 -7
  47. package/packages/server/src/browser-handlers/session-action-handler.ts +30 -4
  48. package/packages/server/src/cli.ts +61 -81
  49. package/packages/server/src/config-api.ts +14 -2
  50. package/packages/server/src/directory-service.ts +106 -4
  51. package/packages/server/src/event-wiring.ts +31 -1
  52. package/packages/server/src/headless-pid-registry.ts +299 -41
  53. package/packages/server/src/legacy-pi-cleanup.ts +151 -0
  54. package/packages/server/src/model-proxy/__tests__/api-key-store.test.ts +142 -0
  55. package/packages/server/src/model-proxy/__tests__/auth-json-contention.test.ts +98 -0
  56. package/packages/server/src/model-proxy/__tests__/concurrency.test.ts +107 -0
  57. package/packages/server/src/model-proxy/__tests__/failed-auth-backoff.test.ts +46 -0
  58. package/packages/server/src/model-proxy/__tests__/recursion-guard.test.ts +61 -0
  59. package/packages/server/src/model-proxy/__tests__/streamer.test.ts +139 -0
  60. package/packages/server/src/model-proxy/api-key-store.ts +87 -0
  61. package/packages/server/src/model-proxy/auth-gate.ts +116 -0
  62. package/packages/server/src/model-proxy/concurrency.ts +76 -0
  63. package/packages/server/src/model-proxy/convert/UPSTREAM.md +13 -0
  64. package/packages/server/src/model-proxy/convert/__tests__/anthropic-in.test.ts +137 -0
  65. package/packages/server/src/model-proxy/convert/__tests__/anthropic-out.test.ts +183 -0
  66. package/packages/server/src/model-proxy/convert/__tests__/openai-in.test.ts +134 -0
  67. package/packages/server/src/model-proxy/convert/__tests__/openai-out.test.ts +166 -0
  68. package/packages/server/src/model-proxy/convert/anthropic-in.ts +129 -0
  69. package/packages/server/src/model-proxy/convert/anthropic-out.ts +173 -0
  70. package/packages/server/src/model-proxy/convert/index.ts +8 -0
  71. package/packages/server/src/model-proxy/convert/openai-in.ts +119 -0
  72. package/packages/server/src/model-proxy/convert/openai-out.ts +151 -0
  73. package/packages/server/src/model-proxy/convert/types.ts +70 -0
  74. package/packages/server/src/model-proxy/failed-auth-backoff.ts +45 -0
  75. package/packages/server/src/model-proxy/internal-auth-storage.ts +146 -0
  76. package/packages/server/src/model-proxy/internal-registry.ts +157 -0
  77. package/packages/server/src/model-proxy/recursion-guard.ts +72 -0
  78. package/packages/server/src/model-proxy/registry-singleton.ts +109 -0
  79. package/packages/server/src/model-proxy/request-log.ts +53 -0
  80. package/packages/server/src/model-proxy/streamer.ts +59 -0
  81. package/packages/server/src/openspec-group-store.ts +490 -0
  82. package/packages/server/src/process-manager.ts +128 -0
  83. package/packages/server/src/provider-auth-storage.ts +29 -47
  84. package/packages/server/src/restart-helper.ts +17 -16
  85. package/packages/server/src/routes/bootstrap-routes.ts +37 -0
  86. package/packages/server/src/routes/jj-routes.ts +3 -0
  87. package/packages/server/src/routes/model-proxy-api-key-routes.ts +168 -0
  88. package/packages/server/src/routes/model-proxy-refresh-routes.ts +24 -0
  89. package/packages/server/src/routes/model-proxy-routes.ts +330 -0
  90. package/packages/server/src/routes/openspec-group-routes.ts +231 -0
  91. package/packages/server/src/routes/provider-auth-routes.ts +3 -0
  92. package/packages/server/src/routes/provider-routes.ts +24 -1
  93. package/packages/server/src/routes/system-routes.ts +44 -2
  94. package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi-shim.sh +9 -0
  95. package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi.cjs +50 -0
  96. package/packages/server/src/rpc-keeper/__tests__/keeper.test.ts +371 -0
  97. package/packages/server/src/rpc-keeper/dispatch-router.ts +85 -0
  98. package/packages/server/src/rpc-keeper/keeper-manager.ts +364 -0
  99. package/packages/server/src/rpc-keeper/keeper.cjs +313 -0
  100. package/packages/server/src/server.ts +178 -2
  101. package/packages/server/src/session-api.ts +9 -1
  102. package/packages/server/src/tunnel-watchdog.ts +230 -0
  103. package/packages/server/src/tunnel.ts +5 -1
  104. package/packages/shared/package.json +1 -1
  105. package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +228 -0
  106. package/packages/shared/src/__tests__/config-openspec.test.ts +74 -0
  107. package/packages/shared/src/__tests__/model-proxy-config.test.ts +146 -0
  108. package/packages/shared/src/__tests__/no-raw-node-import.test.ts +7 -5
  109. package/packages/shared/src/__tests__/node-spawn.test.ts +51 -0
  110. package/packages/shared/src/__tests__/openspec-groups-types.test.ts +135 -0
  111. package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +96 -0
  112. package/packages/shared/src/__tests__/recommended-extensions.test.ts +11 -3
  113. package/packages/shared/src/__tests__/server-launcher.test.ts +227 -0
  114. package/packages/shared/src/bootstrap-install.ts +1 -1
  115. package/packages/shared/src/browser-protocol.ts +27 -0
  116. package/packages/shared/src/config.ts +172 -2
  117. package/packages/shared/src/dashboard-plugin/manifest-types.ts +16 -1
  118. package/packages/shared/src/dashboard-plugin/slot-props.ts +8 -0
  119. package/packages/shared/src/dashboard-plugin/slot-types.ts +57 -0
  120. package/packages/shared/src/platform/binary-lookup.ts +204 -0
  121. package/packages/shared/src/platform/node-spawn.ts +42 -5
  122. package/packages/shared/src/protocol.ts +19 -1
  123. package/packages/shared/src/recommended-extensions.ts +18 -0
  124. package/packages/shared/src/rest-api.ts +219 -1
  125. package/packages/shared/src/server-launcher.ts +277 -0
  126. package/packages/shared/src/tool-registry/__tests__/pi-ai-registration.test.ts +124 -0
  127. package/packages/shared/src/types.ts +55 -0
  128. package/packages/shared/src/__tests__/resolve-jiti.test.ts +0 -184
  129. package/packages/shared/src/resolve-jiti.ts +0 -155
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Singleton accessor for the server-resident model registry.
3
+ *
4
+ * Lazy initialization: on first call, resolves pi-ai via ToolRegistry,
5
+ * constructs InternalAuthStorage + InternalRegistry, and caches the instance.
6
+ *
7
+ * See change: add-dashboard-model-proxy, design §1.
8
+ */
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import { getDefaultRegistry, ModuleResolutionError } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
14
+ import { InternalRegistry, type PiAiModule, type CustomProviderEntry, type CustomModelEntry } from "./internal-registry.js";
15
+ import { InternalAuthStorage, type PiAiOAuthModule } from "./internal-auth-storage.js";
16
+ import { readAuthJson } from "../provider-auth-storage.js";
17
+
18
+ let cachedRegistry: InternalRegistry | null = null;
19
+ let cachedPiAi: PiAiModule | null = null;
20
+ let lastError: string | null = null;
21
+
22
+ // ── Disk readers ──────────────────────────────────────────────────────────────
23
+
24
+ const PROVIDERS_PATH = join(homedir(), ".pi", "agent", "providers.json");
25
+ const MODELS_PATH = join(homedir(), ".pi", "agent", "models.json");
26
+
27
+ function readProviders(): Record<string, CustomProviderEntry> {
28
+ if (!existsSync(PROVIDERS_PATH)) return {};
29
+ try {
30
+ const raw = JSON.parse(readFileSync(PROVIDERS_PATH, "utf-8"));
31
+ return raw.providers ?? {};
32
+ } catch {
33
+ return {};
34
+ }
35
+ }
36
+
37
+ function readModels(): CustomModelEntry[] {
38
+ if (!existsSync(MODELS_PATH)) return [];
39
+ try {
40
+ const raw = JSON.parse(readFileSync(MODELS_PATH, "utf-8"));
41
+ if (Array.isArray(raw)) return raw;
42
+ if (raw.models && Array.isArray(raw.models)) return raw.models;
43
+ return [];
44
+ } catch {
45
+ return [];
46
+ }
47
+ }
48
+
49
+ // ── Public API ────────────────────────────────────────────────────────────────
50
+
51
+ export async function getModelRegistry(): Promise<InternalRegistry> {
52
+ if (cachedRegistry) return cachedRegistry;
53
+
54
+ try {
55
+ const { resolution, module: piAi } = await getDefaultRegistry().resolveModule<PiAiModule>("pi-ai");
56
+
57
+ // Resolve oauth subpath
58
+ let oauthModule: PiAiOAuthModule | null = null;
59
+ if (resolution.path) {
60
+ const oauthPath = resolution.path.replace(/\/dist\/index\.js$/, "/dist/oauth.js");
61
+ try {
62
+ oauthModule = (await import(pathToFileURL(oauthPath).href)) as PiAiOAuthModule;
63
+ } catch {
64
+ // OAuth subpath may not exist; non-fatal
65
+ }
66
+ }
67
+
68
+ const authStorage = new InternalAuthStorage(oauthModule);
69
+ cachedPiAi = piAi;
70
+ cachedRegistry = new InternalRegistry(piAi, authStorage, {
71
+ readProviders,
72
+ readModels,
73
+ readAuth: readAuthJson,
74
+ });
75
+ lastError = null;
76
+ return cachedRegistry;
77
+ } catch (err) {
78
+ const msg = err instanceof ModuleResolutionError
79
+ ? err.message
80
+ : (err as Error).message;
81
+ lastError = msg;
82
+ throw err;
83
+ }
84
+ }
85
+
86
+ export async function refreshModelRegistry(): Promise<void> {
87
+ if (!cachedRegistry) return;
88
+ await cachedRegistry.refresh();
89
+ }
90
+
91
+ export function disposeModelRegistry(): void {
92
+ cachedRegistry = null;
93
+ cachedPiAi = null;
94
+ lastError = null;
95
+ }
96
+
97
+ /**
98
+ * Returns pi-ai's streamSimple after registry is initialized.
99
+ * Throws if registry has not been initialized.
100
+ */
101
+ export function getStreamSimpleFn(): PiAiModule["streamSimple"] | null {
102
+ return cachedPiAi?.streamSimple ?? null;
103
+ }
104
+
105
+ export function getModelProxyStatus(): { status: "ready" | "degraded"; reason?: string } {
106
+ if (cachedRegistry) return { status: "ready" };
107
+ if (lastError) return { status: "degraded", reason: lastError };
108
+ return { status: "degraded", reason: "Model registry not yet initialized" };
109
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Optional JSONL request log for the model proxy.
3
+ *
4
+ * Appends one line per completed request to ~/.pi/dashboard/model-proxy.jsonl.
5
+ * Never logs request/response body or API keys — only metadata.
6
+ * Daily rotation when file exceeds 50MB.
7
+ *
8
+ * See change: add-dashboard-model-proxy.
9
+ */
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import os from "node:os";
13
+
14
+ const LOG_DIR = path.join(os.homedir(), ".pi", "dashboard");
15
+ const LOG_FILE = path.join(LOG_DIR, "model-proxy.jsonl");
16
+ const MAX_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
17
+
18
+ export interface RequestLogEntry {
19
+ ts: string;
20
+ requestId: string;
21
+ apiKeyId?: string;
22
+ model: string;
23
+ format: "openai" | "anthropic";
24
+ status: number;
25
+ durationMs: number;
26
+ inputTokens?: number;
27
+ outputTokens?: number;
28
+ error?: string;
29
+ }
30
+
31
+ function rotateIfNeeded(): void {
32
+ try {
33
+ const stat = fs.statSync(LOG_FILE);
34
+ if (stat.size >= MAX_SIZE_BYTES) {
35
+ const date = new Date().toISOString().split("T")[0];
36
+ const rotatedPath = `${LOG_FILE}.${date}`;
37
+ fs.renameSync(LOG_FILE, rotatedPath);
38
+ }
39
+ } catch {
40
+ // File doesn't exist or stat failed — no rotation needed
41
+ }
42
+ }
43
+
44
+ export function logRequest(entry: RequestLogEntry): void {
45
+ try {
46
+ fs.mkdirSync(LOG_DIR, { recursive: true });
47
+ rotateIfNeeded();
48
+ const line = JSON.stringify(entry) + "\n";
49
+ fs.appendFileSync(LOG_FILE, line, "utf-8");
50
+ } catch {
51
+ // Best-effort logging — never crash the server
52
+ }
53
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Wrapper around pi-ai's streamSimple for model proxy route handlers.
3
+ *
4
+ * Resolves model credentials from the InternalRegistry, then delegates
5
+ * to pi-ai's streamSimple. Returns an AsyncIterable of pi-ai StreamEvents.
6
+ *
7
+ * See change: add-dashboard-model-proxy, task 6.1.
8
+ */
9
+ import { getModelRegistry } from "./registry-singleton.js";
10
+ import type { PiAiModule } from "./internal-registry.js";
11
+
12
+ export interface StreamCompletionOpts {
13
+ model: any;
14
+ messages: any[];
15
+ system?: string;
16
+ tools?: any[];
17
+ maxTokens?: number;
18
+ temperature?: number;
19
+ signal?: AbortSignal;
20
+ }
21
+
22
+ export interface RegistryLike {
23
+ getApiKeyAndHeaders(model: any): Promise<{ apiKey: string; headers: Record<string, string> }>;
24
+ }
25
+
26
+ /**
27
+ * Stream a completion from the upstream provider via pi-ai's streamSimple.
28
+ *
29
+ * Resolves API key + headers from the registry, then calls streamSimple.
30
+ * The returned iterable yields pi-ai's AssistantMessageEvent objects.
31
+ *
32
+ * @param opts - stream options
33
+ * @param piAiStreamSimple - pi-ai's streamSimple function
34
+ * @param registryOverride - optional registry for testing (defaults to getModelRegistry())
35
+ */
36
+ export async function streamCompletion(
37
+ opts: StreamCompletionOpts,
38
+ piAiStreamSimple: PiAiModule["streamSimple"],
39
+ registryOverride?: RegistryLike,
40
+ ): Promise<AsyncIterable<any>> {
41
+ const registry = registryOverride ?? await getModelRegistry();
42
+ const { apiKey, headers } = await registry.getApiKeyAndHeaders(opts.model);
43
+
44
+ const context: any = {
45
+ messages: opts.messages,
46
+ ...(opts.system !== undefined ? { systemPrompt: opts.system } : {}),
47
+ ...(opts.tools ? { tools: opts.tools } : {}),
48
+ };
49
+
50
+ const options: any = {
51
+ apiKey,
52
+ headers,
53
+ ...(opts.maxTokens != null ? { maxTokens: opts.maxTokens } : {}),
54
+ ...(opts.temperature != null ? { temperature: opts.temperature } : {}),
55
+ ...(opts.signal ? { signal: opts.signal } : {}),
56
+ };
57
+
58
+ return piAiStreamSimple(opts.model, context, options);
59
+ }