@blackbelt-technology/pi-agent-dashboard 0.5.0 → 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.
- package/AGENTS.md +26 -5
- package/README.md +49 -7
- package/docs/architecture.md +129 -1
- package/package.json +15 -15
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +1 -1
- package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +362 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +78 -8
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +1 -1
- package/packages/extension/src/__tests__/extension-slash-command-detection.test.ts +107 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +1 -1
- package/packages/extension/src/__tests__/prompt-expander.test.ts +110 -1
- 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-launch.test.ts +78 -0
- 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 +68 -4
- package/packages/extension/src/bridge.ts +79 -11
- package/packages/extension/src/command-handler.ts +95 -15
- package/packages/extension/src/flow-event-wiring.ts +1 -1
- 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 +74 -63
- package/packages/extension/src/provider-register.ts +16 -9
- package/packages/extension/src/retry-tracker.ts +123 -0
- package/packages/extension/src/server-launcher.ts +31 -70
- package/packages/extension/src/session-sync.ts +10 -1
- package/packages/extension/src/slash-dispatch.ts +123 -0
- package/packages/extension/src/usage-limit-orderer.ts +76 -0
- package/packages/server/bin/pi-dashboard.mjs +84 -0
- package/packages/server/package.json +8 -7
- package/packages/server/scripts/fix-pty-permissions.cjs +52 -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 +16 -4
- package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +187 -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 +2 -2
- package/packages/server/src/__tests__/dispatch-extension-command-router.test.ts +178 -0
- package/packages/server/src/__tests__/e2e/model-proxy-google-flash.test.ts +184 -0
- package/packages/server/src/__tests__/event-wiring-providers-list.test.ts +68 -1
- 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__/headless-pid-registry.test.ts +316 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +1 -1
- package/packages/server/src/__tests__/keeper-manager.test.ts +298 -0
- package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +149 -0
- package/packages/server/src/__tests__/model-proxy-api-key-routes.test.ts +277 -0
- package/packages/server/src/__tests__/model-proxy-auth-gate.test.ts +263 -0
- package/packages/server/src/__tests__/model-proxy-multi-user.test.ts +169 -0
- package/packages/server/src/__tests__/model-proxy-routes.test.ts +286 -0
- package/packages/server/src/__tests__/model-proxy-second-port.test.ts +116 -0
- package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +64 -8
- package/packages/server/src/__tests__/openspec-group-broadcast.test.ts +97 -0
- package/packages/server/src/__tests__/openspec-group-join.test.ts +80 -0
- package/packages/server/src/__tests__/openspec-group-routes.test.ts +370 -0
- package/packages/server/src/__tests__/openspec-group-store.test.ts +496 -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-ai-shape.test.ts +147 -0
- 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 +62 -3
- package/packages/server/src/__tests__/pi-core-updater.test.ts +1 -1
- package/packages/server/src/__tests__/pi-dashboard-bin-wrapper.test.ts +84 -0
- 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-keeper-spawn.test.ts +206 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +12 -4
- package/packages/server/src/__tests__/provider-catalogue-cache.test.ts +13 -23
- package/packages/server/src/__tests__/provider-routes-recursion-guard.test.ts +131 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +3 -3
- package/packages/server/src/__tests__/spawn-correlation-token-integration.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-register-watchdog.test.ts +84 -0
- package/packages/server/src/__tests__/spawn-token.test.ts +57 -0
- package/packages/server/src/__tests__/tunnel-watchdog.test.ts +139 -0
- package/packages/server/src/auth-plugin.ts +3 -0
- package/packages/server/src/bootstrap-state.ts +10 -0
- package/packages/server/src/browser-gateway.ts +27 -10
- package/packages/server/src/browser-handlers/handler-context.ts +9 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +128 -19
- 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 +62 -82
- package/packages/server/src/config-api.ts +14 -2
- package/packages/server/src/directory-service.ts +106 -4
- package/packages/server/src/event-wiring.ts +90 -6
- package/packages/server/src/headless-pid-registry.ts +344 -37
- package/packages/server/src/legacy-pi-cleanup.ts +151 -0
- package/packages/server/src/model-proxy/__tests__/api-key-store.test.ts +142 -0
- package/packages/server/src/model-proxy/__tests__/auth-json-contention.test.ts +98 -0
- package/packages/server/src/model-proxy/__tests__/concurrency.test.ts +107 -0
- package/packages/server/src/model-proxy/__tests__/failed-auth-backoff.test.ts +46 -0
- package/packages/server/src/model-proxy/__tests__/recursion-guard.test.ts +61 -0
- package/packages/server/src/model-proxy/__tests__/streamer.test.ts +139 -0
- package/packages/server/src/model-proxy/api-key-store.ts +87 -0
- package/packages/server/src/model-proxy/auth-gate.ts +116 -0
- package/packages/server/src/model-proxy/concurrency.ts +76 -0
- package/packages/server/src/model-proxy/convert/UPSTREAM.md +13 -0
- package/packages/server/src/model-proxy/convert/__tests__/anthropic-in.test.ts +137 -0
- package/packages/server/src/model-proxy/convert/__tests__/anthropic-out.test.ts +183 -0
- package/packages/server/src/model-proxy/convert/__tests__/openai-in.test.ts +134 -0
- package/packages/server/src/model-proxy/convert/__tests__/openai-out.test.ts +166 -0
- package/packages/server/src/model-proxy/convert/anthropic-in.ts +129 -0
- package/packages/server/src/model-proxy/convert/anthropic-out.ts +173 -0
- package/packages/server/src/model-proxy/convert/index.ts +8 -0
- package/packages/server/src/model-proxy/convert/openai-in.ts +119 -0
- package/packages/server/src/model-proxy/convert/openai-out.ts +151 -0
- package/packages/server/src/model-proxy/convert/types.ts +70 -0
- package/packages/server/src/model-proxy/failed-auth-backoff.ts +45 -0
- package/packages/server/src/model-proxy/internal-auth-storage.ts +146 -0
- package/packages/server/src/model-proxy/internal-registry.ts +157 -0
- package/packages/server/src/model-proxy/recursion-guard.ts +72 -0
- package/packages/server/src/model-proxy/registry-singleton.ts +109 -0
- package/packages/server/src/model-proxy/request-log.ts +53 -0
- package/packages/server/src/model-proxy/streamer.ts +59 -0
- package/packages/server/src/openspec-group-store.ts +490 -0
- 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 +16 -6
- package/packages/server/src/pi-dev-version-check.ts +145 -0
- package/packages/server/src/pi-gateway.ts +4 -0
- package/packages/server/src/pi-version-skew.ts +12 -4
- package/packages/server/src/process-manager.ts +182 -11
- package/packages/server/src/provider-auth-storage.ts +29 -47
- package/packages/server/src/provider-catalogue-cache.ts +24 -18
- package/packages/server/src/restart-helper.ts +17 -16
- package/packages/server/src/routes/bootstrap-routes.ts +37 -0
- package/packages/server/src/routes/jj-routes.ts +3 -0
- package/packages/server/src/routes/model-proxy-api-key-routes.ts +168 -0
- package/packages/server/src/routes/model-proxy-refresh-routes.ts +24 -0
- package/packages/server/src/routes/model-proxy-routes.ts +330 -0
- package/packages/server/src/routes/openspec-group-routes.ts +231 -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 +8 -1
- package/packages/server/src/routes/provider-routes.ts +28 -5
- package/packages/server/src/routes/system-routes.ts +44 -2
- package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi-shim.sh +9 -0
- package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi.cjs +50 -0
- package/packages/server/src/rpc-keeper/__tests__/keeper.test.ts +371 -0
- package/packages/server/src/rpc-keeper/dispatch-router.ts +85 -0
- package/packages/server/src/rpc-keeper/keeper-manager.ts +364 -0
- package/packages/server/src/rpc-keeper/keeper.cjs +313 -0
- package/packages/server/src/server.ts +254 -60
- package/packages/server/src/session-api.ts +63 -4
- package/packages/server/src/session-discovery.ts +1 -1
- package/packages/server/src/session-file-reader.ts +1 -1
- package/packages/server/src/spawn-register-watchdog.ts +62 -7
- package/packages/server/src/spawn-token.ts +20 -0
- package/packages/server/src/tunnel-watchdog.ts +230 -0
- package/packages/server/src/tunnel.ts +5 -1
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +228 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +24 -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 +5 -3
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +1 -1
- package/packages/shared/src/__tests__/changelog-types.test.ts +78 -0
- package/packages/shared/src/__tests__/config-openspec.test.ts +74 -0
- package/packages/shared/src/__tests__/model-proxy-config.test.ts +146 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +7 -5
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +56 -20
- package/packages/shared/src/__tests__/node-spawn.test.ts +51 -0
- package/packages/shared/src/__tests__/openspec-groups-types.test.ts +135 -0
- package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +96 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +11 -3
- package/packages/shared/src/__tests__/server-launcher.test.ts +227 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +1 -1
- package/packages/shared/src/bootstrap-install.ts +1 -1
- package/packages/shared/src/browser-protocol.ts +70 -0
- package/packages/shared/src/changelog-types.ts +111 -0
- package/packages/shared/src/config.ts +172 -2
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +16 -1
- package/packages/shared/src/dashboard-plugin/slot-props.ts +8 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +57 -0
- package/packages/shared/src/platform/binary-lookup.ts +204 -0
- package/packages/shared/src/platform/node-spawn.ts +71 -26
- package/packages/shared/src/protocol.ts +27 -1
- package/packages/shared/src/recommended-extensions.ts +18 -0
- package/packages/shared/src/rest-api.ts +219 -1
- package/packages/shared/src/server-launcher.ts +277 -0
- package/packages/shared/src/skill-block-parser.ts +1 -1
- package/packages/shared/src/tool-registry/__tests__/pi-ai-registration.test.ts +124 -0
- package/packages/shared/src/tool-registry/definitions.ts +15 -3
- package/packages/shared/src/types.ts +62 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +0 -53
- package/packages/shared/src/resolve-jiti.ts +0 -102
|
@@ -57,6 +57,8 @@ export interface AuthConfig {
|
|
|
57
57
|
allowedUsers?: string[];
|
|
58
58
|
bypassUrls?: string[];
|
|
59
59
|
bypassHosts?: string[];
|
|
60
|
+
/** Admin email override — can list/revoke every user's proxy API keys. */
|
|
61
|
+
admin?: string;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
export interface MemoryLimitsConfig {
|
|
@@ -75,6 +77,16 @@ export const DEFAULT_MEMORY_LIMITS: MemoryLimitsConfig = {
|
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
export interface OpenSpecPollConfig {
|
|
80
|
+
/**
|
|
81
|
+
* Master gate. When `false`, the dashboard treats OpenSpec as fully disabled
|
|
82
|
+
* across the dashboard — no polling, the OPENSPEC session-card subcard hides
|
|
83
|
+
* everywhere, and `openspec_refresh` is a no-op. Other tuning fields below
|
|
84
|
+
* retain their meaning but are ignored at runtime when this is `false`.
|
|
85
|
+
*
|
|
86
|
+
* Default `true` for backwards compatibility. Existing configs without this
|
|
87
|
+
* field behave exactly as before. See change: auto-hide-empty-session-subcards.
|
|
88
|
+
*/
|
|
89
|
+
enabled: boolean;
|
|
78
90
|
/** Poll interval in seconds. Default 30. Clamped to [5, 3600]. */
|
|
79
91
|
pollIntervalSeconds: number;
|
|
80
92
|
/** Max concurrent `openspec` CLI invocations across all dirs. Default 3. Clamped to [1, 16]. */
|
|
@@ -86,6 +98,7 @@ export interface OpenSpecPollConfig {
|
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
export const DEFAULT_OPENSPEC_POLL: OpenSpecPollConfig = {
|
|
101
|
+
enabled: true,
|
|
89
102
|
pollIntervalSeconds: 30,
|
|
90
103
|
maxConcurrentSpawns: 3,
|
|
91
104
|
changeDetection: "mtime",
|
|
@@ -113,6 +126,47 @@ export interface KnownServer {
|
|
|
113
126
|
addedAt: string; // ISO timestamp
|
|
114
127
|
}
|
|
115
128
|
|
|
129
|
+
// ── Model Proxy ─────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export interface ProxyApiKey {
|
|
132
|
+
id: string;
|
|
133
|
+
label: string;
|
|
134
|
+
createdBy?: string;
|
|
135
|
+
scopes?: string[];
|
|
136
|
+
createdAt: number;
|
|
137
|
+
lastUsedAt?: number;
|
|
138
|
+
expiresAt?: number;
|
|
139
|
+
revokedAt?: number;
|
|
140
|
+
hash: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ModelProxyConfig {
|
|
144
|
+
/** Master toggle. Default true. */
|
|
145
|
+
enabled: boolean;
|
|
146
|
+
/** Default model for requests that omit it. */
|
|
147
|
+
defaultModel?: string;
|
|
148
|
+
/** Optional second port for /v1/* routes (for SDKs that hardcode path-prefix-less base URLs). */
|
|
149
|
+
secondPort?: number;
|
|
150
|
+
/** Server-wide max concurrent streams. Default 16. Clamped [1, 256]. */
|
|
151
|
+
maxConcurrentStreams: number;
|
|
152
|
+
/** Per-API-key max concurrent streams. Default 4. Clamped [1, 64]. */
|
|
153
|
+
perKeyConcurrentStreams: number;
|
|
154
|
+
/** Per-provider concurrency caps. Keys are provider names. */
|
|
155
|
+
perProviderCaps?: Record<string, number>;
|
|
156
|
+
/** Enable JSONL request logging. Default false. */
|
|
157
|
+
logRequests: boolean;
|
|
158
|
+
/** Proxy API keys (stored hashed). */
|
|
159
|
+
apiKeys: ProxyApiKey[];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const DEFAULT_MODEL_PROXY: ModelProxyConfig = {
|
|
163
|
+
enabled: true,
|
|
164
|
+
maxConcurrentStreams: 16,
|
|
165
|
+
perKeyConcurrentStreams: 4,
|
|
166
|
+
logRequests: false,
|
|
167
|
+
apiKeys: [],
|
|
168
|
+
};
|
|
169
|
+
|
|
116
170
|
/**
|
|
117
171
|
* Plugin-specific config namespace.
|
|
118
172
|
* Lives at ~/.pi/dashboard/config.json#plugins.<id>.*
|
|
@@ -126,7 +180,16 @@ export interface DashboardConfig {
|
|
|
126
180
|
autoShutdown: boolean;
|
|
127
181
|
shutdownIdleSeconds: number;
|
|
128
182
|
spawnStrategy: SpawnStrategy;
|
|
129
|
-
tunnel: {
|
|
183
|
+
tunnel: {
|
|
184
|
+
enabled: boolean;
|
|
185
|
+
reservedToken?: string;
|
|
186
|
+
watchdog?: {
|
|
187
|
+
enabled: boolean;
|
|
188
|
+
intervalMs: number;
|
|
189
|
+
failureThreshold: number;
|
|
190
|
+
probeTimeoutMs: number;
|
|
191
|
+
};
|
|
192
|
+
};
|
|
130
193
|
devBuildOnReload: boolean;
|
|
131
194
|
auth?: AuthConfig;
|
|
132
195
|
defaultModel: string;
|
|
@@ -165,6 +228,17 @@ export interface DashboardConfig {
|
|
|
165
228
|
* See change: spawn-failure-diagnostics.
|
|
166
229
|
*/
|
|
167
230
|
spawnRegisterTimeoutMs: number;
|
|
231
|
+
/**
|
|
232
|
+
* EXPERIMENTAL: when true, headless pi sessions are spawned through the
|
|
233
|
+
* RPC keeper sidecar instead of the legacy `sh -c "tail -f /dev/null | pi"`
|
|
234
|
+
* (Unix) or direct-pipe (Windows) paths. The keeper owns pi's stdin and
|
|
235
|
+
* exposes a per-session UDS / named pipe the server writes RPC `prompt`
|
|
236
|
+
* lines to. Enables typed extension slash commands (`/ctx-stats`,
|
|
237
|
+
* `/curator`, `/agents`, `/flows:*`) to dispatch in headless sessions
|
|
238
|
+
* against pi versions that do not yet expose `pi.dispatchCommand`.
|
|
239
|
+
* Default `false`. See change: add-rpc-stdin-dispatch-with-keeper-sidecar.
|
|
240
|
+
*/
|
|
241
|
+
useRpcKeeper: boolean;
|
|
168
242
|
/**
|
|
169
243
|
* Per-plugin config namespaces. Reserved top-level key.
|
|
170
244
|
* Each plugin's config lives at plugins.<id>.*
|
|
@@ -172,6 +246,8 @@ export interface DashboardConfig {
|
|
|
172
246
|
* until each extract-*-as-plugin change migrates them.
|
|
173
247
|
*/
|
|
174
248
|
plugins: PluginsConfig;
|
|
249
|
+
/** Model proxy configuration (OpenAI/Anthropic-compatible /v1/* endpoints). */
|
|
250
|
+
modelProxy: ModelProxyConfig;
|
|
175
251
|
}
|
|
176
252
|
|
|
177
253
|
export interface CorsConfig {
|
|
@@ -193,13 +269,22 @@ export function clampSpawnRegisterTimeoutMs(v: unknown): number {
|
|
|
193
269
|
|
|
194
270
|
const DEFAULTS: DashboardConfig = {
|
|
195
271
|
plugins: {},
|
|
272
|
+
modelProxy: { ...DEFAULT_MODEL_PROXY },
|
|
196
273
|
port: 8000,
|
|
197
274
|
piPort: 9999,
|
|
198
275
|
autoStart: true,
|
|
199
276
|
autoShutdown: false,
|
|
200
277
|
shutdownIdleSeconds: 300,
|
|
201
278
|
spawnStrategy: "headless",
|
|
202
|
-
tunnel: {
|
|
279
|
+
tunnel: {
|
|
280
|
+
enabled: true,
|
|
281
|
+
watchdog: {
|
|
282
|
+
enabled: true,
|
|
283
|
+
intervalMs: 60000,
|
|
284
|
+
failureThreshold: 2,
|
|
285
|
+
probeTimeoutMs: 10000,
|
|
286
|
+
},
|
|
287
|
+
},
|
|
203
288
|
devBuildOnReload: false,
|
|
204
289
|
defaultModel: "",
|
|
205
290
|
memoryLimits: { ...DEFAULT_MEMORY_LIMITS },
|
|
@@ -213,6 +298,7 @@ const DEFAULTS: DashboardConfig = {
|
|
|
213
298
|
askUserPromptTimeoutSeconds: DEFAULT_ASK_USER_PROMPT_TIMEOUT_SECONDS,
|
|
214
299
|
reattachPlacement: DEFAULT_REATTACH_PLACEMENT,
|
|
215
300
|
spawnRegisterTimeoutMs: 30000,
|
|
301
|
+
useRpcKeeper: false,
|
|
216
302
|
};
|
|
217
303
|
|
|
218
304
|
/**
|
|
@@ -274,6 +360,7 @@ function parseAuthConfig(raw: any): AuthConfig | undefined {
|
|
|
274
360
|
...(Array.isArray(raw.allowedUsers) ? { allowedUsers: raw.allowedUsers } : Array.isArray(raw.allowedEmails) ? { allowedUsers: raw.allowedEmails } : {}),
|
|
275
361
|
bypassUrls: Array.isArray(raw.bypassUrls) ? raw.bypassUrls.filter((u: unknown) => typeof u === "string") : [],
|
|
276
362
|
bypassHosts: Array.isArray(raw.bypassHosts) ? raw.bypassHosts.filter((u: unknown) => typeof u === "string") : [],
|
|
363
|
+
...(typeof raw.admin === "string" && raw.admin ? { admin: raw.admin } : {}),
|
|
277
364
|
};
|
|
278
365
|
}
|
|
279
366
|
|
|
@@ -300,6 +387,8 @@ function parseOpenSpecPollConfig(raw: any): OpenSpecPollConfig {
|
|
|
300
387
|
? raw.changeDetection
|
|
301
388
|
: DEFAULT_OPENSPEC_POLL.changeDetection;
|
|
302
389
|
return {
|
|
390
|
+
enabled:
|
|
391
|
+
typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_OPENSPEC_POLL.enabled,
|
|
303
392
|
pollIntervalSeconds: clampNumber(raw.pollIntervalSeconds, DEFAULT_OPENSPEC_POLL.pollIntervalSeconds, 5, 3600),
|
|
304
393
|
maxConcurrentSpawns: clampNumber(raw.maxConcurrentSpawns, DEFAULT_OPENSPEC_POLL.maxConcurrentSpawns, 1, 16),
|
|
305
394
|
changeDetection,
|
|
@@ -346,6 +435,70 @@ export function getPluginConfig(
|
|
|
346
435
|
return config.plugins?.[pluginId] ?? {};
|
|
347
436
|
}
|
|
348
437
|
|
|
438
|
+
export function parseModelProxyConfig(raw: any): ModelProxyConfig {
|
|
439
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULT_MODEL_PROXY };
|
|
440
|
+
|
|
441
|
+
const apiKeys: ProxyApiKey[] = [];
|
|
442
|
+
if (Array.isArray(raw.apiKeys)) {
|
|
443
|
+
for (const entry of raw.apiKeys) {
|
|
444
|
+
if (
|
|
445
|
+
entry &&
|
|
446
|
+
typeof entry === "object" &&
|
|
447
|
+
typeof entry.id === "string" &&
|
|
448
|
+
typeof entry.label === "string" &&
|
|
449
|
+
typeof entry.hash === "string" &&
|
|
450
|
+
typeof entry.createdAt === "number"
|
|
451
|
+
) {
|
|
452
|
+
apiKeys.push({
|
|
453
|
+
id: entry.id,
|
|
454
|
+
label: entry.label,
|
|
455
|
+
hash: entry.hash,
|
|
456
|
+
createdAt: entry.createdAt,
|
|
457
|
+
...(typeof entry.createdBy === "string" ? { createdBy: entry.createdBy } : {}),
|
|
458
|
+
...(Array.isArray(entry.scopes) ? { scopes: entry.scopes.filter((s: unknown) => typeof s === "string") } : {}),
|
|
459
|
+
...(typeof entry.lastUsedAt === "number" ? { lastUsedAt: entry.lastUsedAt } : {}),
|
|
460
|
+
...(typeof entry.expiresAt === "number" ? { expiresAt: entry.expiresAt } : {}),
|
|
461
|
+
...(typeof entry.revokedAt === "number" ? { revokedAt: entry.revokedAt } : {}),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let perProviderCaps: Record<string, number> | undefined;
|
|
468
|
+
if (raw.perProviderCaps && typeof raw.perProviderCaps === "object" && !Array.isArray(raw.perProviderCaps)) {
|
|
469
|
+
perProviderCaps = {};
|
|
470
|
+
for (const [key, val] of Object.entries(raw.perProviderCaps)) {
|
|
471
|
+
if (typeof val === "number" && Number.isFinite(val) && val >= 1) {
|
|
472
|
+
perProviderCaps[key] = Math.min(val, 256);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_MODEL_PROXY.enabled,
|
|
479
|
+
...(typeof raw.defaultModel === "string" ? { defaultModel: raw.defaultModel } : {}),
|
|
480
|
+
...(typeof raw.secondPort === "number" && raw.secondPort >= 1024 && raw.secondPort <= 65535
|
|
481
|
+
? { secondPort: raw.secondPort }
|
|
482
|
+
: {}),
|
|
483
|
+
maxConcurrentStreams: clampNumber(
|
|
484
|
+
raw.maxConcurrentStreams,
|
|
485
|
+
DEFAULT_MODEL_PROXY.maxConcurrentStreams,
|
|
486
|
+
1,
|
|
487
|
+
256,
|
|
488
|
+
),
|
|
489
|
+
perKeyConcurrentStreams: clampNumber(
|
|
490
|
+
raw.perKeyConcurrentStreams,
|
|
491
|
+
DEFAULT_MODEL_PROXY.perKeyConcurrentStreams,
|
|
492
|
+
1,
|
|
493
|
+
64,
|
|
494
|
+
),
|
|
495
|
+
...(perProviderCaps ? { perProviderCaps } : {}),
|
|
496
|
+
logRequests:
|
|
497
|
+
typeof raw.logRequests === "boolean" ? raw.logRequests : DEFAULT_MODEL_PROXY.logRequests,
|
|
498
|
+
apiKeys,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
349
502
|
function parseKnownServers(raw: any): KnownServer[] {
|
|
350
503
|
if (!Array.isArray(raw)) return [];
|
|
351
504
|
return raw
|
|
@@ -391,6 +544,21 @@ export function loadConfig(): DashboardConfig {
|
|
|
391
544
|
tunnel: {
|
|
392
545
|
enabled: parsed.tunnel?.enabled ?? defaults.tunnel.enabled,
|
|
393
546
|
...(parsed.tunnel?.reservedToken ? { reservedToken: parsed.tunnel.reservedToken } : {}),
|
|
547
|
+
watchdog: {
|
|
548
|
+
enabled: parsed.tunnel?.watchdog?.enabled ?? defaults.tunnel.watchdog!.enabled,
|
|
549
|
+
intervalMs:
|
|
550
|
+
typeof parsed.tunnel?.watchdog?.intervalMs === "number" && parsed.tunnel.watchdog.intervalMs > 0
|
|
551
|
+
? parsed.tunnel.watchdog.intervalMs
|
|
552
|
+
: defaults.tunnel.watchdog!.intervalMs,
|
|
553
|
+
failureThreshold:
|
|
554
|
+
typeof parsed.tunnel?.watchdog?.failureThreshold === "number" && parsed.tunnel.watchdog.failureThreshold > 0
|
|
555
|
+
? Math.floor(parsed.tunnel.watchdog.failureThreshold)
|
|
556
|
+
: defaults.tunnel.watchdog!.failureThreshold,
|
|
557
|
+
probeTimeoutMs:
|
|
558
|
+
typeof parsed.tunnel?.watchdog?.probeTimeoutMs === "number" && parsed.tunnel.watchdog.probeTimeoutMs > 0
|
|
559
|
+
? parsed.tunnel.watchdog.probeTimeoutMs
|
|
560
|
+
: defaults.tunnel.watchdog!.probeTimeoutMs,
|
|
561
|
+
},
|
|
394
562
|
},
|
|
395
563
|
devBuildOnReload: parsed.devBuildOnReload ?? defaults.devBuildOnReload,
|
|
396
564
|
defaultModel: typeof parsed.defaultModel === "string" ? parsed.defaultModel : defaults.defaultModel,
|
|
@@ -414,6 +582,8 @@ export function loadConfig(): DashboardConfig {
|
|
|
414
582
|
? parsed.askUserPromptTimeoutSeconds
|
|
415
583
|
: defaults.askUserPromptTimeoutSeconds,
|
|
416
584
|
spawnRegisterTimeoutMs: clampSpawnRegisterTimeoutMs(parsed.spawnRegisterTimeoutMs),
|
|
585
|
+
useRpcKeeper: parsed.useRpcKeeper === true,
|
|
586
|
+
modelProxy: parseModelProxyConfig(parsed.modelProxy),
|
|
417
587
|
};
|
|
418
588
|
|
|
419
589
|
// Compute resolvedTrustedNetworks: merge trustedNetworks + auth.bypassHosts
|
|
@@ -21,8 +21,23 @@ export interface PluginClaim {
|
|
|
21
21
|
tab?: SettingsTab;
|
|
22
22
|
/** Slot-specific extra config. */
|
|
23
23
|
config?: Record<string, unknown>;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Optional exported predicate function name for filtering contributions.
|
|
26
|
+
* Answers "does this claim apply to this target?" — a claim failing its
|
|
27
|
+
* predicate is removed from the slot's claim list entirely.
|
|
28
|
+
*/
|
|
25
29
|
predicate?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Optional exported sync function name. Answers "will this claim's component
|
|
32
|
+
* produce visible output for this target?" — a claim whose shouldRender
|
|
33
|
+
* returns false is NOT mounted and counts as absent for the wrapper-gate
|
|
34
|
+
* helpers (e.g. `useSlotHasClaimsForSession`), so parent subcards hide
|
|
35
|
+
* cleanly. Use when the component conditionally returns `null` based on
|
|
36
|
+
* dynamic state (e.g. "extension not installed"). MUST be synchronous.
|
|
37
|
+
*
|
|
38
|
+
* See change: auto-hide-empty-session-subcards.
|
|
39
|
+
*/
|
|
40
|
+
shouldRender?: string;
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
/**
|
|
@@ -38,6 +38,14 @@ export interface SlotPropsMap {
|
|
|
38
38
|
session: DashboardSession;
|
|
39
39
|
pluginContext: AnyPluginContext;
|
|
40
40
|
};
|
|
41
|
+
"session-card-memory": {
|
|
42
|
+
session: DashboardSession;
|
|
43
|
+
pluginContext: AnyPluginContext;
|
|
44
|
+
};
|
|
45
|
+
"workspace-action-bar": {
|
|
46
|
+
session: DashboardSession;
|
|
47
|
+
pluginContext: AnyPluginContext;
|
|
48
|
+
};
|
|
41
49
|
"content-view": {
|
|
42
50
|
session: DashboardSession;
|
|
43
51
|
routeParams: Record<string, string>;
|
|
@@ -6,17 +6,22 @@
|
|
|
6
6
|
* Adding a slot: minor (non-breaking).
|
|
7
7
|
* Removing or renaming a slot: major (breaking).
|
|
8
8
|
*/
|
|
9
|
+
import type { DashboardSession } from "../types.js";
|
|
10
|
+
import type { FolderDescriptor } from "./slot-props.js";
|
|
9
11
|
|
|
10
12
|
/** All valid slot ids (frozen for v0.x). */
|
|
11
13
|
export type SlotId =
|
|
12
14
|
// React-only slots
|
|
13
15
|
| "sidebar-folder-section"
|
|
14
16
|
| "session-card-action-bar"
|
|
17
|
+
| "workspace-action-bar"
|
|
18
|
+
// (session-card-memory is also react-only; declared below for ordering)
|
|
15
19
|
| "content-inline-footer"
|
|
16
20
|
| "anchored-popover"
|
|
17
21
|
| "command-route"
|
|
18
22
|
| "tool-renderer"
|
|
19
23
|
// React-or-descriptor slots
|
|
24
|
+
| "session-card-memory"
|
|
20
25
|
| "session-card-badge"
|
|
21
26
|
| "content-view"
|
|
22
27
|
| "content-header-sticky"
|
|
@@ -59,6 +64,16 @@ export const SLOT_DEFINITIONS: Record<SlotId, SlotDefinition> = {
|
|
|
59
64
|
payloadTier: "react-only",
|
|
60
65
|
description: "Action buttons on a session card",
|
|
61
66
|
},
|
|
67
|
+
"session-card-memory": {
|
|
68
|
+
multiplicity: "many",
|
|
69
|
+
payloadTier: "react-only",
|
|
70
|
+
description: "Memory/Honcho contributions inside the MEMORY subcard of a session card",
|
|
71
|
+
},
|
|
72
|
+
"workspace-action-bar": {
|
|
73
|
+
multiplicity: "many",
|
|
74
|
+
payloadTier: "react-only",
|
|
75
|
+
description: "Action buttons inside the WORKSPACE subcard of a session card (jj/git workspace tooling)",
|
|
76
|
+
},
|
|
62
77
|
"content-view": {
|
|
63
78
|
multiplicity: "one-active",
|
|
64
79
|
payloadTier: "react-or-descriptor",
|
|
@@ -149,3 +164,45 @@ export const VALID_SETTINGS_TABS: SettingsTab[] = [
|
|
|
149
164
|
"security",
|
|
150
165
|
"advanced",
|
|
151
166
|
];
|
|
167
|
+
|
|
168
|
+
// ── Predicate input classification ──────────────────────────────────────────
|
|
169
|
+
//
|
|
170
|
+
// `ClaimEntry.predicate` / `.shouldRender` are invoked by exactly two filter
|
|
171
|
+
// helpers in the runtime (`forSession`, `forFolder`). The argument shape is
|
|
172
|
+
// therefore determined by the slot id. `SlotPredicateInput<S>` exposes that
|
|
173
|
+
// classification at the type level so the registry contract can be tightened.
|
|
174
|
+
//
|
|
175
|
+
// See change: slot-generic-claim-entry.
|
|
176
|
+
|
|
177
|
+
/** Slot ids whose predicates receive a session. */
|
|
178
|
+
type SessionScopedSlot =
|
|
179
|
+
| "session-card-badge"
|
|
180
|
+
| "session-card-action-bar"
|
|
181
|
+
| "session-card-memory"
|
|
182
|
+
| "workspace-action-bar"
|
|
183
|
+
| "content-view"
|
|
184
|
+
| "content-header-sticky"
|
|
185
|
+
| "content-inline-footer"
|
|
186
|
+
| "command-route";
|
|
187
|
+
|
|
188
|
+
/** Slot ids whose predicates receive a folder descriptor. */
|
|
189
|
+
type FolderScopedSlot = "sidebar-folder-section";
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Map of slot id → predicate input shape. Resolves to `never` for slots that
|
|
193
|
+
* are not filtered by predicate (settings-section, tool-renderer, anchored-
|
|
194
|
+
* popover, every descriptor-only slot). Registering a predicate on a `never`-
|
|
195
|
+
* input slot is therefore a compile-time error.
|
|
196
|
+
*/
|
|
197
|
+
export type SlotPredicateInput<S extends SlotId> =
|
|
198
|
+
S extends SessionScopedSlot ? DashboardSession | null | undefined
|
|
199
|
+
: S extends FolderScopedSlot ? FolderDescriptor
|
|
200
|
+
: never;
|
|
201
|
+
|
|
202
|
+
// Type-level test: assert every SlotId is reachable through SlotPredicateInput,
|
|
203
|
+
// either by mapping to a concrete input or explicitly to `never`. This forces a
|
|
204
|
+
// build failure when a new slot id is added without classifying it.
|
|
205
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
206
|
+
type _AssertAllSlotsPredicateClassified = {
|
|
207
|
+
[K in SlotId]: SlotPredicateInput<K>;
|
|
208
|
+
};
|
|
@@ -5,10 +5,117 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { execSync, spawnSync, buildSafeArgv } from "./exec.js";
|
|
7
7
|
import { existsSync, realpathSync } from "node:fs";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
8
10
|
import path from "node:path";
|
|
9
11
|
import os from "node:os";
|
|
10
12
|
import { MANAGED_BIN, MANAGED_DIR } from "../managed-paths.js";
|
|
11
13
|
|
|
14
|
+
// ── jiti loader resolution constants ──────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pi-coding-agent package names searched for a managed-install jiti.
|
|
18
|
+
* Upstream first, legacy fork second. Mirrors the prior
|
|
19
|
+
* `resolveJitiFromPi` wrapper that lived in two electron files.
|
|
20
|
+
*/
|
|
21
|
+
export const MANAGED_PI_PACKAGES = [
|
|
22
|
+
"@earendil-works/pi-coding-agent",
|
|
23
|
+
"@mariozechner/pi-coding-agent",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* jiti provider package names tried inside every anchor's resolution
|
|
28
|
+
* chain. Upstream `jiti` first; legacy `@mariozechner/jiti` fallback.
|
|
29
|
+
* Carried over verbatim from `resolve-jiti.ts`.
|
|
30
|
+
*/
|
|
31
|
+
export const JITI_PACKAGES = ["jiti", "@mariozechner/jiti"] as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Test seam: a function that takes a package specifier (e.g.
|
|
35
|
+
* `"jiti/package.json"`) and returns the resolved path. Production
|
|
36
|
+
* supplies `createRequire(<anchor>).resolve`; tests supply a stub.
|
|
37
|
+
* Carried over verbatim from `resolve-jiti.ts`.
|
|
38
|
+
*/
|
|
39
|
+
export type JitiResolver = (specifier: string) => string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Pure helper: given a jiti package.json path, return the file:// URL
|
|
43
|
+
* of its register hook. Handles Windows-style drive letters regardless
|
|
44
|
+
* of host OS (`pathToFileURL` on POSIX URL-encodes backslashes).
|
|
45
|
+
*
|
|
46
|
+
* Mirrors the prior helper from the deleted `resolve-jiti.ts`.
|
|
47
|
+
*/
|
|
48
|
+
function jitiRegisterUrl(pkgJsonPath: string): string {
|
|
49
|
+
const isWindowsStyle = /^[A-Za-z]:[\\/]/.test(pkgJsonPath);
|
|
50
|
+
if (isWindowsStyle) {
|
|
51
|
+
const registerPath = path.win32.join(
|
|
52
|
+
path.win32.dirname(pkgJsonPath),
|
|
53
|
+
"lib",
|
|
54
|
+
"jiti-register.mjs",
|
|
55
|
+
);
|
|
56
|
+
return `file:///${registerPath.replace(/\\/g, "/")}`;
|
|
57
|
+
}
|
|
58
|
+
const registerPath = path.join(path.dirname(pkgJsonPath), "lib", "jiti-register.mjs");
|
|
59
|
+
return pathToFileURL(registerPath).href;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Walk JITI_PACKAGES with the supplied resolver and return the first
|
|
64
|
+
* hit's register URL. Optional `pathExists` guard rejects hits whose
|
|
65
|
+
* `lib/jiti-register.mjs` is absent on disk (corrupt installs). When
|
|
66
|
+
* `pathExists` is omitted, the package.json hit alone is sufficient
|
|
67
|
+
* — used by the argv/system-pi anchors which already realpath'd a
|
|
68
|
+
* trustworthy node_modules tree.
|
|
69
|
+
*/
|
|
70
|
+
function walkJiti(
|
|
71
|
+
resolver: JitiResolver,
|
|
72
|
+
pathExists?: (p: string) => boolean,
|
|
73
|
+
): string | null {
|
|
74
|
+
for (const jiti of JITI_PACKAGES) {
|
|
75
|
+
try {
|
|
76
|
+
const pkgJson = resolver(`${jiti}/package.json`);
|
|
77
|
+
if (pathExists) {
|
|
78
|
+
const registerPath = path.join(path.dirname(pkgJson), "lib", "jiti-register.mjs");
|
|
79
|
+
if (!pathExists(registerPath)) continue;
|
|
80
|
+
}
|
|
81
|
+
return jitiRegisterUrl(pkgJson);
|
|
82
|
+
} catch { /* next */ }
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Internal deps for `ToolResolver.resolveJiti`. Underscore-prefixed
|
|
89
|
+
* fields are test seams — production callers pass only `anchor` /
|
|
90
|
+
* `resolver`.
|
|
91
|
+
*/
|
|
92
|
+
export interface ResolveJitiOpts {
|
|
93
|
+
/** Caller-supplied anchor inside a node_modules tree (e.g. cliPath). */
|
|
94
|
+
anchor?: string;
|
|
95
|
+
/**
|
|
96
|
+
* When true, skip the managed-pi / system-pi / argv anchors and try
|
|
97
|
+
* ONLY `opts.anchor`. Used for health-probing a specific extracted
|
|
98
|
+
* tree where finding jiti elsewhere does not prove the tree itself
|
|
99
|
+
* is healthy.
|
|
100
|
+
*/
|
|
101
|
+
anchorOnly?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Test seam: replaces `createRequire(<anchor>).resolve` at every
|
|
104
|
+
* probe site. Production omits — actual `createRequire` is used.
|
|
105
|
+
*/
|
|
106
|
+
resolver?: JitiResolver;
|
|
107
|
+
/** Test seam: replaces `existsSync` for managed-pi pkg.json + register guards. */
|
|
108
|
+
_pathExists?: (p: string) => boolean;
|
|
109
|
+
/** Test seam: replaces `realpathSync` for system-pi + argv anchors. */
|
|
110
|
+
_realpath?: (p: string) => string;
|
|
111
|
+
/** Test seam: replaces this resolver's `which("pi")` call. */
|
|
112
|
+
_whichPi?: () => string | null;
|
|
113
|
+
/** Test seam: replaces `process.argv[1]`. */
|
|
114
|
+
_argv1?: string | undefined;
|
|
115
|
+
/** Test seam: replaces `~/.pi-dashboard` root used to probe managed-pi. */
|
|
116
|
+
_managedDir?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
12
119
|
// ── AppImage self-hit guard (Linux power-user mode safety) ────────────────
|
|
13
120
|
|
|
14
121
|
/**
|
|
@@ -235,6 +342,103 @@ export class ToolResolver {
|
|
|
235
342
|
return this.which("node");
|
|
236
343
|
}
|
|
237
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Resolve pi's jiti register hook as a `file://` URL.
|
|
347
|
+
*
|
|
348
|
+
* Resolution order (first hit wins):
|
|
349
|
+
* 1. Managed pi install — `~/.pi-dashboard/node_modules/<pkg>` for
|
|
350
|
+
* every entry of `MANAGED_PI_PACKAGES` (upstream first, legacy
|
|
351
|
+
* fallback). Each candidate's pkg.json is the createRequire
|
|
352
|
+
* anchor; walk `JITI_PACKAGES` from there.
|
|
353
|
+
* 2. System pi via `this.which("pi")` (realpathed to escape
|
|
354
|
+
* symlinks like `/usr/local/bin/pi → .../dist/cli.js`).
|
|
355
|
+
* 3. Caller-supplied `opts.anchor` (e.g. an electron `cliPath`).
|
|
356
|
+
* 4. `process.argv[1]` (the running pi/node entry; populated for
|
|
357
|
+
* bridge-extension callers).
|
|
358
|
+
*
|
|
359
|
+
* Returns null when none yield a jiti install. Preserves the
|
|
360
|
+
* Windows drive-letter URL-wrapping contract that previously lived
|
|
361
|
+
* on the prior `buildJitiRegisterUrl` helper in `resolve-jiti.ts`.
|
|
362
|
+
*
|
|
363
|
+
* Tests inject `_pathExists` / `_realpath` / `_whichPi` / `_argv1`
|
|
364
|
+
* / `_managedDir` and a flat `resolver` to exercise individual
|
|
365
|
+
* anchors deterministically.
|
|
366
|
+
*
|
|
367
|
+
* Subsumes the previous `resolveJitiImport`, `resolveJitiFromAnchor`,
|
|
368
|
+
* `pickJitiRegisterUrl`, `pickJitiFromAnchor`, `buildJitiRegisterUrl`,
|
|
369
|
+
* and the duplicate `resolveJitiFromPi` wrappers in electron.
|
|
370
|
+
*/
|
|
371
|
+
resolveJiti(opts: ResolveJitiOpts = {}): string | null {
|
|
372
|
+
const pathExists = opts._pathExists ?? existsSync;
|
|
373
|
+
const realpath = opts._realpath ?? realpathSync;
|
|
374
|
+
const whichPi = opts._whichPi ?? (() => this.which("pi"));
|
|
375
|
+
const argv1 = "_argv1" in opts ? opts._argv1 : process.argv[1];
|
|
376
|
+
const managedDir = opts._managedDir ?? MANAGED_DIR;
|
|
377
|
+
|
|
378
|
+
const makeResolver = (anchor: string): JitiResolver | null => {
|
|
379
|
+
if (opts.resolver) return opts.resolver;
|
|
380
|
+
try {
|
|
381
|
+
const req = createRequire(anchor);
|
|
382
|
+
return (spec) => req.resolve(spec);
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// anchor-only short-circuit: skip every anchor except opts.anchor.
|
|
389
|
+
if (opts.anchorOnly) {
|
|
390
|
+
if (!opts.anchor) return null;
|
|
391
|
+
if (!pathExists(opts.anchor)) return null;
|
|
392
|
+
const r = makeResolver(opts.anchor);
|
|
393
|
+
if (!r) return null;
|
|
394
|
+
return walkJiti(r, pathExists);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 1. Managed pi installs.
|
|
398
|
+
for (const pkg of MANAGED_PI_PACKAGES) {
|
|
399
|
+
const pkgJson = path.join(managedDir, "node_modules", pkg, "package.json");
|
|
400
|
+
if (!pathExists(pkgJson)) continue;
|
|
401
|
+
const resolver = makeResolver(pkgJson);
|
|
402
|
+
if (!resolver) continue;
|
|
403
|
+
const url = walkJiti(resolver, pathExists);
|
|
404
|
+
if (url) return url;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 2. System pi.
|
|
408
|
+
const piPath = whichPi();
|
|
409
|
+
if (piPath) {
|
|
410
|
+
let realPi: string;
|
|
411
|
+
try { realPi = realpath(piPath); } catch { realPi = piPath; }
|
|
412
|
+
const resolver = makeResolver(realPi);
|
|
413
|
+
if (resolver) {
|
|
414
|
+
const url = walkJiti(resolver, pathExists);
|
|
415
|
+
if (url) return url;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// 3. Caller-supplied anchor.
|
|
420
|
+
if (opts.anchor && pathExists(opts.anchor)) {
|
|
421
|
+
const resolver = makeResolver(opts.anchor);
|
|
422
|
+
if (resolver) {
|
|
423
|
+
const url = walkJiti(resolver, pathExists);
|
|
424
|
+
if (url) return url;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 4. process.argv[1] (or test override).
|
|
429
|
+
if (argv1) {
|
|
430
|
+
let realArgv: string;
|
|
431
|
+
try { realArgv = realpath(argv1); } catch { realArgv = argv1; }
|
|
432
|
+
const resolver = makeResolver(realArgv);
|
|
433
|
+
if (resolver) {
|
|
434
|
+
const url = walkJiti(resolver, pathExists);
|
|
435
|
+
if (url) return url;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
238
442
|
/**
|
|
239
443
|
* Build a spawn environment with managed bin, node bin, extra dirs,
|
|
240
444
|
* and common user bin dirs prepended to PATH.
|