@blackbelt-technology/pi-agent-dashboard 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +104 -35
- package/README.md +390 -494
- package/docs/architecture.md +423 -20
- package/package.json +11 -8
- package/packages/extension/package.json +11 -4
- package/packages/extension/src/__tests__/ask-user-schema-discriminator.test.ts +141 -0
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +91 -15
- package/packages/extension/src/__tests__/bridge-entry-id-pi-070.test.ts +174 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +30 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +64 -76
- package/packages/extension/src/__tests__/multiselect-dashboard-routing.test.ts +203 -0
- package/packages/extension/src/__tests__/multiselect-list.test.ts +137 -0
- package/packages/extension/src/__tests__/multiselect-polyfill.test.ts +92 -0
- package/packages/extension/src/__tests__/no-session-replacement-calls.test.ts +99 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +81 -0
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +37 -0
- package/packages/extension/src/__tests__/ui-decorators.test.ts +309 -0
- package/packages/extension/src/__tests__/ui-modules.test.ts +293 -0
- package/packages/extension/src/ask-user-tool.ts +170 -61
- package/packages/extension/src/bridge.ts +199 -19
- package/packages/extension/src/multiselect-decode.ts +40 -0
- package/packages/extension/src/multiselect-list.ts +146 -0
- package/packages/extension/src/multiselect-polyfill.ts +73 -0
- package/packages/extension/src/server-launcher.ts +15 -3
- package/packages/extension/src/ui-modules.ts +272 -0
- package/packages/server/package.json +11 -5
- package/packages/server/src/__tests__/auto-attach.test.ts +61 -8
- package/packages/server/src/__tests__/browse-endpoint.test.ts +295 -19
- package/packages/server/src/__tests__/cli-bootstrap.test.ts +36 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +163 -0
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +315 -0
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +303 -0
- package/packages/server/src/__tests__/directory-service.test.ts +174 -0
- package/packages/server/src/__tests__/fixtures/fork-jsonl-roundtrip.jsonl +8 -0
- package/packages/server/src/__tests__/fork-jsonl-roundtrip.test.ts +49 -0
- package/packages/server/src/__tests__/installed-package-enricher.test.ts +225 -0
- package/packages/server/src/__tests__/package-manager-wrapper-move.test.ts +414 -0
- package/packages/server/src/__tests__/package-routes.test.ts +136 -3
- package/packages/server/src/__tests__/package-source-helpers.test.ts +101 -0
- package/packages/server/src/__tests__/pending-attach-registry.test.ts +123 -0
- package/packages/server/src/__tests__/pending-resume-intent-registry.test.ts +138 -0
- package/packages/server/src/__tests__/pi-core-checker.test.ts +73 -30
- package/packages/server/src/__tests__/pi-gateway-consume-pending-attach.test.ts +112 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +72 -0
- package/packages/server/src/__tests__/post-install-openspec-refresh.test.ts +180 -0
- package/packages/server/src/__tests__/post-install-rescan.test.ts +134 -0
- package/packages/server/src/__tests__/proposal-attach-naming.test.ts +79 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +34 -6
- package/packages/server/src/__tests__/session-action-handler-spawn-with-attach.test.ts +108 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +55 -0
- package/packages/server/src/__tests__/session-order-reboot.test.ts +242 -0
- package/packages/server/src/__tests__/session-scanner.test.ts +44 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +40 -0
- package/packages/server/src/__tests__/translate-path-source.test.ts +77 -0
- package/packages/server/src/__tests__/ui-decorators-replay.test.ts +209 -0
- package/packages/server/src/__tests__/ui-modules-replay.test.ts +221 -0
- package/packages/server/src/browse.ts +118 -13
- package/packages/server/src/browser-gateway.ts +19 -0
- package/packages/server/src/browser-handlers/__tests__/session-meta-handler.test.ts +183 -0
- package/packages/server/src/browser-handlers/directory-handler.ts +7 -1
- package/packages/server/src/browser-handlers/handler-context.ts +15 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +29 -3
- package/packages/server/src/browser-handlers/session-meta-handler.ts +46 -12
- package/packages/server/src/browser-handlers/subscription-handler.ts +46 -1
- package/packages/server/src/cli.ts +61 -15
- package/packages/server/src/directory-service.ts +156 -15
- package/packages/server/src/event-wiring.ts +111 -10
- package/packages/server/src/installed-package-enricher.ts +143 -0
- package/packages/server/src/package-manager-wrapper.ts +305 -8
- package/packages/server/src/package-source-helpers.ts +104 -0
- package/packages/server/src/pending-attach-registry.ts +112 -0
- package/packages/server/src/pending-resume-intent-registry.ts +107 -0
- package/packages/server/src/pi-core-checker.ts +9 -14
- package/packages/server/src/pi-gateway.ts +14 -0
- package/packages/server/src/pi-version-skew.ts +12 -1
- package/packages/server/src/proposal-attach-naming.ts +47 -0
- package/packages/server/src/restart-helper.ts +13 -2
- package/packages/server/src/routes/file-routes.ts +29 -3
- package/packages/server/src/routes/package-routes.ts +72 -3
- package/packages/server/src/routes/plugin-config-routes.ts +129 -0
- package/packages/server/src/routes/system-routes.ts +2 -0
- package/packages/server/src/server.ts +339 -10
- package/packages/server/src/session-api.ts +30 -5
- package/packages/server/src/session-order-manager.ts +22 -0
- package/packages/server/src/session-scanner.ts +10 -1
- package/packages/shared/package.json +9 -2
- package/packages/shared/src/__tests__/browser-protocol-types.test.ts +59 -0
- package/packages/shared/src/__tests__/config-plugins.test.ts +68 -0
- package/packages/shared/src/__tests__/extension-ui-module-shape.test.ts +265 -0
- package/packages/shared/src/__tests__/no-hardcoded-node-modules-paths.test.ts +176 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +146 -0
- package/packages/shared/src/__tests__/no-raw-openspec-status-in-skills.test.ts +81 -0
- package/packages/shared/src/__tests__/node-spawn.test.ts +210 -0
- package/packages/shared/src/__tests__/openspec-design-evidence.test.ts +288 -0
- package/packages/shared/src/__tests__/openspec-effective-status-script.test.ts +174 -0
- package/packages/shared/src/__tests__/openspec-poller-design-override.test.ts +225 -0
- package/packages/shared/src/__tests__/openspec-poller-specs-override.test.ts +284 -0
- package/packages/shared/src/__tests__/openspec-specs-evidence.test.ts +144 -0
- package/packages/shared/src/__tests__/platform/is-appimage-self-hit.test.ts +164 -0
- package/packages/shared/src/__tests__/plugin-bridge-register-extended.test.ts +72 -0
- package/packages/shared/src/__tests__/plugin-bridge-register.test.ts +113 -0
- package/packages/shared/src/__tests__/plugin-config-update-protocol.test.ts +41 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +5 -1
- package/packages/shared/src/__tests__/resolve-tool-cli.test.ts +105 -0
- package/packages/shared/src/__tests__/spawn-session-attach-proposal.test.ts +47 -0
- package/packages/shared/src/__tests__/state-replay-entry-id.test.ts +69 -0
- package/packages/shared/src/__tests__/tool-registry-strategies-appimage.test.ts +118 -0
- package/packages/shared/src/browser-protocol.ts +110 -4
- package/packages/shared/src/config.ts +45 -0
- package/packages/shared/src/dashboard-plugin/index.ts +11 -0
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +58 -0
- package/packages/shared/src/dashboard-plugin/plugin-status.ts +26 -0
- package/packages/shared/src/dashboard-plugin/slot-props.ts +92 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +151 -0
- package/packages/shared/src/openspec-activity-detector.ts +18 -22
- package/packages/shared/src/openspec-design-evidence.ts +109 -0
- package/packages/shared/src/openspec-poller.ts +117 -3
- package/packages/shared/src/openspec-specs-evidence.ts +79 -0
- package/packages/shared/src/platform/binary-lookup.ts +96 -1
- package/packages/shared/src/platform/index.ts +1 -0
- package/packages/shared/src/platform/node-spawn.ts +154 -0
- package/packages/shared/src/plugin-bridge-register.ts +139 -0
- package/packages/shared/src/protocol.ts +79 -2
- package/packages/shared/src/recommended-extensions.ts +7 -1
- package/packages/shared/src/rest-api.ts +68 -3
- package/packages/shared/src/state-replay.ts +20 -1
- package/packages/shared/src/tool-registry/definitions.ts +92 -0
- package/packages/shared/src/tool-registry/strategies.ts +17 -3
- package/packages/shared/src/types.ts +160 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Extension ↔ Server WebSocket protocol messages.
|
|
3
3
|
*/
|
|
4
|
-
import type { DashboardEvent, CommandInfo, FlowInfo, SessionSource, ImageContent, FileEntry, TurnUsage, ContextUsage, ModelInfo, PiSessionInfo, OpenSpecPhase, RoleInfo } from "./types.js";
|
|
4
|
+
import type { DashboardEvent, CommandInfo, FlowInfo, SessionSource, ImageContent, FileEntry, TurnUsage, ContextUsage, ModelInfo, PiSessionInfo, OpenSpecPhase, RoleInfo, ExtensionUiModule, DecoratorDescriptor } from "./types.js";
|
|
5
5
|
|
|
6
6
|
// ── Extension → Server ──────────────────────────────────────────────
|
|
7
7
|
|
|
@@ -57,6 +57,29 @@ export interface EventForwardMessage {
|
|
|
57
57
|
event: DashboardEvent;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Conventions on `event_forward` payloads relevant to per-message fork:
|
|
62
|
+
*
|
|
63
|
+
* - `message_start` and `message_end` events MAY carry an optional
|
|
64
|
+
* `data.nonce: string` stamped by the bridge. The reducer carries it
|
|
65
|
+
* onto the resulting ChatMessage so a later `entry_persisted` event
|
|
66
|
+
* can back-fill the entry id.
|
|
67
|
+
* - `entry_persisted` events have shape:
|
|
68
|
+
* {
|
|
69
|
+
* eventType: "entry_persisted",
|
|
70
|
+
* timestamp,
|
|
71
|
+
* data: { type: "entry_persisted", entryId: string, nonce: string }
|
|
72
|
+
* }
|
|
73
|
+
* They are emitted by the bridge after pi calls
|
|
74
|
+
* `sessionManager.appendMessage` and the entry id has been generated.
|
|
75
|
+
* See change: fix-per-message-fork.
|
|
76
|
+
*/
|
|
77
|
+
export interface EntryPersistedEventData {
|
|
78
|
+
type: "entry_persisted";
|
|
79
|
+
entryId: string;
|
|
80
|
+
nonce: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
export interface CommandsListMessage {
|
|
61
84
|
type: "commands_list";
|
|
62
85
|
sessionId: string;
|
|
@@ -205,6 +228,39 @@ export interface ProcessListMessage {
|
|
|
205
228
|
|
|
206
229
|
// LoadSessionEventsResultMessage and LoadSessionEventsErrorMessage removed — server loads directly
|
|
207
230
|
|
|
231
|
+
// ── Extension UI System (Phase 1) ──
|
|
232
|
+
// Pull-discovered, schema-driven UI modules. See change: add-extension-ui-modal.
|
|
233
|
+
|
|
234
|
+
export interface UiModulesListMessage {
|
|
235
|
+
type: "ui_modules_list";
|
|
236
|
+
sessionId: string;
|
|
237
|
+
modules: ExtensionUiModule[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface UiDataListMessage {
|
|
241
|
+
type: "ui_data_list";
|
|
242
|
+
sessionId: string;
|
|
243
|
+
/** Matches some `module.view.dataEvent`. */
|
|
244
|
+
event: string;
|
|
245
|
+
items: unknown[];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Extension UI System (Phase 2: live in-page decorations) ──
|
|
249
|
+
// See change: add-extension-ui-decorations.
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Extension → server: a single live decorator descriptor. Cache key
|
|
253
|
+
* `${kind}:${namespace}:${id}` MUST be unique within a session; `removed: true`
|
|
254
|
+
* deletes the cache entry instead of upserting.
|
|
255
|
+
*/
|
|
256
|
+
export interface ExtUiDecoratorMessage {
|
|
257
|
+
type: "ext_ui_decorator";
|
|
258
|
+
sessionId: string;
|
|
259
|
+
descriptor: DecoratorDescriptor;
|
|
260
|
+
/** When true, server deletes the cached descriptor under the matching key. */
|
|
261
|
+
removed?: boolean;
|
|
262
|
+
}
|
|
263
|
+
|
|
208
264
|
export type ExtensionToServerMessage =
|
|
209
265
|
| SessionRegisterMessage
|
|
210
266
|
| SessionUnregisterMessage
|
|
@@ -227,7 +283,10 @@ export type ExtensionToServerMessage =
|
|
|
227
283
|
| FirstMessageUpdateMessage
|
|
228
284
|
| RolesListMessage
|
|
229
285
|
| SpawnNewSessionMessage
|
|
230
|
-
| ProcessListMessage
|
|
286
|
+
| ProcessListMessage
|
|
287
|
+
| UiModulesListMessage
|
|
288
|
+
| UiDataListMessage
|
|
289
|
+
| ExtUiDecoratorMessage;
|
|
231
290
|
|
|
232
291
|
// ── Server → Extension ──────────────────────────────────────────────
|
|
233
292
|
|
|
@@ -378,6 +437,23 @@ export interface ExtensionUiResponseMessage {
|
|
|
378
437
|
cancelled?: boolean;
|
|
379
438
|
}
|
|
380
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Server → extension: a browser invoked an action / requested data on a
|
|
442
|
+
* Phase-1 management-modal module. The bridge re-emits this on `pi.events`
|
|
443
|
+
* as `pi.events.emit(msg.event, { ...msg.params, action: msg.action, _reply })`.
|
|
444
|
+
* Extensions either populate `data.items` synchronously (for `action: "list"`
|
|
445
|
+
* data fetches) or perform side-effects and emit `ui:invalidate` to refresh.
|
|
446
|
+
*/
|
|
447
|
+
export interface UiManagementMessage {
|
|
448
|
+
type: "ui_management";
|
|
449
|
+
sessionId: string;
|
|
450
|
+
/** Action id (matches some `UiAction.id`) or `"list"` for data fetch. */
|
|
451
|
+
action: string;
|
|
452
|
+
/** Event name to emit (matches `view.dataEvent` or `UiAction.event`). */
|
|
453
|
+
event: string;
|
|
454
|
+
params?: Record<string, unknown>;
|
|
455
|
+
}
|
|
456
|
+
|
|
381
457
|
export interface PromptResponseServerMessage {
|
|
382
458
|
type: "prompt_response";
|
|
383
459
|
sessionId: string;
|
|
@@ -412,4 +488,5 @@ export type ServerToExtensionMessage =
|
|
|
412
488
|
| RolePresetSaveExtensionMessage
|
|
413
489
|
| RolePresetDeleteExtensionMessage
|
|
414
490
|
| RequestRolesMessage
|
|
491
|
+
| UiManagementMessage
|
|
415
492
|
| KillProcessMessage;
|
|
@@ -180,7 +180,13 @@ export const RECOMMENDED_EXTENSIONS: readonly RecommendedExtension[] = [
|
|
|
180
180
|
*/
|
|
181
181
|
export const BUNDLED_EXTENSION_IDS: readonly string[] = [
|
|
182
182
|
"pi-anthropic-messages",
|
|
183
|
-
"pi-flows"
|
|
183
|
+
// "pi-flows" is intentionally NOT bundled until the upstream repo declares
|
|
184
|
+
// an SPDX-conformant license (`LICENSE` file or `package.json#license`).
|
|
185
|
+
// The bundle-recommended-extensions.sh license allowlist enforcement
|
|
186
|
+
// (MIT/Apache-2.0/BSD-2-Clause/BSD-3-Clause/ISC) correctly rejects it.
|
|
187
|
+
// Re-add this entry once https://github.com/BlackBeltTechnology/pi-flows
|
|
188
|
+
// has a license declared. See: openspec/changes/archive/
|
|
189
|
+
// 2026-04-21-bundle-first-party-extensions/design.md §"License blockers".
|
|
184
190
|
];
|
|
185
191
|
|
|
186
192
|
/** Retrieve a recommended entry by id, or `undefined`. */
|
|
@@ -64,17 +64,35 @@ export type FileReadResponse = ApiResponse<FileReadResult>;
|
|
|
64
64
|
export interface BrowseEntry {
|
|
65
65
|
name: string;
|
|
66
66
|
path: string;
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Set only when the request used `detect=1`. When the response was
|
|
69
|
+
* produced without `detect=1`, this field is absent (undefined) —
|
|
70
|
+
* meaning "not classified", NOT "classified as not-git". Consumers
|
|
71
|
+
* that need badges SHOULD call `GET /api/browse/flags` to fill in
|
|
72
|
+
* the flags lazily.
|
|
73
|
+
*
|
|
74
|
+
* See change: split-browse-flags.
|
|
75
|
+
*/
|
|
76
|
+
isGit?: boolean;
|
|
77
|
+
/** See `isGit` — same opt-in / detect-gated semantics. */
|
|
78
|
+
isPi?: boolean;
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
/**
|
|
72
|
-
* Response shape for `GET /api/browse?path=<dir>&q=<query>`.
|
|
82
|
+
* Response shape for `GET /api/browse?path=<dir>&q=<query>&detect=<0|1>`.
|
|
73
83
|
*
|
|
74
84
|
* The optional `q` query parameter, when present and non-empty, causes the
|
|
75
85
|
* server to filter entries by case-insensitive substring on `name` and rank
|
|
76
86
|
* them (exact → prefix → word-boundary → substring) before the 200-entry cap.
|
|
77
87
|
* When omitted or whitespace-only, entries are sorted alphabetically.
|
|
88
|
+
*
|
|
89
|
+
* The optional `detect` query parameter (only the literal string `"1"` is
|
|
90
|
+
* truthy) opts into eager `.git` / `.pi` classification on every entry. When
|
|
91
|
+
* absent (the default), per-entry `isGit` / `isPi` are omitted and no
|
|
92
|
+
* filesystem probes run — use the bulk `GET /api/browse/flags` endpoint to
|
|
93
|
+
* classify entries lazily.
|
|
94
|
+
*
|
|
95
|
+
* See change: split-browse-flags.
|
|
78
96
|
*/
|
|
79
97
|
export interface BrowseResult {
|
|
80
98
|
entries: BrowseEntry[];
|
|
@@ -94,6 +112,43 @@ export interface BrowseResult {
|
|
|
94
112
|
|
|
95
113
|
export type BrowseResponse = ApiResponse<BrowseResult>;
|
|
96
114
|
|
|
115
|
+
// ── Browse flags (bulk classifier) ──────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Per-path classification record returned by `GET /api/browse/flags`.
|
|
119
|
+
* Booleans only — any probe failure (ENOENT, EACCES, ELOOP, race-on-
|
|
120
|
+
* deletion, …) maps to `false` for that flag, never an error.
|
|
121
|
+
*
|
|
122
|
+
* See change: split-browse-flags.
|
|
123
|
+
*/
|
|
124
|
+
export interface BrowseFlagEntry {
|
|
125
|
+
isGit: boolean;
|
|
126
|
+
isPi: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Wire shape passed via the `paths` query parameter on
|
|
131
|
+
* `GET /api/browse/flags?paths=<json-array>`. The value MUST be a
|
|
132
|
+
* URL-encoded JSON array of absolute path strings (length ≤ 100).
|
|
133
|
+
* Provided here for type-only documentation — the request itself is a
|
|
134
|
+
* GET, so this interface is not serialized as a body.
|
|
135
|
+
*/
|
|
136
|
+
export interface BrowseFlagsRequest {
|
|
137
|
+
paths: string[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Successful response payload for `GET /api/browse/flags`. */
|
|
141
|
+
export interface BrowseFlagsResult {
|
|
142
|
+
/**
|
|
143
|
+
* Map keyed by the absolute paths that were requested. The key set
|
|
144
|
+
* SHALL equal the input `paths` set — one classification per input
|
|
145
|
+
* path, no extras, no omissions.
|
|
146
|
+
*/
|
|
147
|
+
flags: Record<string, BrowseFlagEntry>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type BrowseFlagsResponse = ApiResponse<BrowseFlagsResult>;
|
|
151
|
+
|
|
97
152
|
/** Request body for `POST /api/browse/mkdir`. */
|
|
98
153
|
export interface MkdirRequest {
|
|
99
154
|
parent: string;
|
|
@@ -254,6 +309,16 @@ export interface InstalledPackage {
|
|
|
254
309
|
installedPath?: string;
|
|
255
310
|
/** Set after check-updates: true if newer version available */
|
|
256
311
|
updateAvailable?: boolean;
|
|
312
|
+
/** Version read from `<installedPath>/package.json#version`. Undefined if missing/unreadable. */
|
|
313
|
+
version?: string;
|
|
314
|
+
/** Description read from `<installedPath>/package.json#description`. */
|
|
315
|
+
description?: string;
|
|
316
|
+
/** Friendly name. From RECOMMENDED_EXTENSIONS displayName when matched, else basename of source. */
|
|
317
|
+
displayName?: string;
|
|
318
|
+
/** True when this row matches a RECOMMENDED_EXTENSIONS entry (via sourcesMatch). */
|
|
319
|
+
isRecommended?: boolean;
|
|
320
|
+
/** True when isRecommended AND id is in BUNDLED_EXTENSION_IDS AND bundle subtree exists. */
|
|
321
|
+
isBundled?: boolean;
|
|
257
322
|
}
|
|
258
323
|
|
|
259
324
|
export type InstalledPackagesResponse = ApiResponse<InstalledPackage[]>;
|
|
@@ -13,10 +13,29 @@ import type { EventForwardMessage } from "./protocol.js";
|
|
|
13
13
|
* - message_update + message_end for assistant messages
|
|
14
14
|
* - tool_execution_start / tool_execution_end for tool calls
|
|
15
15
|
* - model_select for model changes
|
|
16
|
+
*
|
|
17
|
+
* NOTE on entryId (per change: fix-per-message-fork):
|
|
18
|
+
* Replay reads from the persisted JSONL, so each entry already has a
|
|
19
|
+
* stable `id`. We attach it directly as `entryId` on both `message_start`
|
|
20
|
+
* (user) and `message_end` (assistant) events. Replay therefore does NOT
|
|
21
|
+
* need to emit an `entry_persisted` follow-up — the back-fill protocol
|
|
22
|
+
* exists to bridge a timing gap that only happens for LIVE pi events on
|
|
23
|
+
* pi 0.69+, where the bridge sees `message_start` before pi has assigned
|
|
24
|
+
* the entry id. Replay has no such gap.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* @param knownContextWindow Optional override for the context window size,
|
|
28
|
+
* typically `session.contextWindow` from `.meta.json` (which was persisted
|
|
29
|
+
* from a live `turn_end` event). When provided, it is used in place of the
|
|
30
|
+
* `inferContextWindow(modelId)` heuristic for every synthesized
|
|
31
|
+
* `stats_update` event. The heuristic ignores Sonnet's 1M variant and
|
|
32
|
+
* pins Claude to 200k, so passing the persisted value avoids a brief
|
|
33
|
+
* 200k flicker on reload before the next live `turn_end` arrives.
|
|
16
34
|
*/
|
|
17
35
|
export function replayEntriesAsEvents(
|
|
18
36
|
sessionId: string,
|
|
19
37
|
entries: any[],
|
|
38
|
+
knownContextWindow?: number,
|
|
20
39
|
): EventForwardMessage[] {
|
|
21
40
|
const messages: EventForwardMessage[] = [];
|
|
22
41
|
const openToolCalls = new Set<string>(); // track tool calls without results
|
|
@@ -77,7 +96,7 @@ export function replayEntriesAsEvents(
|
|
|
77
96
|
if (totalTokens && totalTokens > 0) {
|
|
78
97
|
statsData.contextUsage = {
|
|
79
98
|
tokens: totalTokens,
|
|
80
|
-
contextWindow: inferContextWindow(currentModel),
|
|
99
|
+
contextWindow: knownContextWindow ?? inferContextWindow(currentModel),
|
|
81
100
|
};
|
|
82
101
|
}
|
|
83
102
|
messages.push(makeEvent(sessionId, "stats_update", ts, statsData));
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
overrideStrategy,
|
|
23
23
|
whereStrategy,
|
|
24
24
|
} from "./strategies.js";
|
|
25
|
+
import type { Strategy } from "./types.js";
|
|
25
26
|
|
|
26
27
|
// ── Classifier ──────────────────────────────────────────────────────────────
|
|
27
28
|
|
|
@@ -66,6 +67,68 @@ function moduleDefWithAliases(
|
|
|
66
67
|
return { name: canonicalName, kind: "module", strategies, classify };
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
// ── Build-time module definitions (electron, node-pty) ────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Bare-import strategy that resolves `<pkg>/package.json` and returns the
|
|
74
|
+
* containing directory. Used for build-time tools whose useful artifact is
|
|
75
|
+
* a sibling file of `package.json` (e.g. `electron/install.js`,
|
|
76
|
+
* `node-pty/prebuilds/`). Mirrors the semantics that build-time consumers
|
|
77
|
+
* (`publish.yml`, `Dockerfile.build`, `scripts/fix-pty-permissions.cjs`)
|
|
78
|
+
* need — see change: register-build-time-tools.
|
|
79
|
+
*
|
|
80
|
+
* `searchPaths` are passed to Node's resolver as the `paths` option,
|
|
81
|
+
* making the lookup work whether the package is hoisted to the repo root
|
|
82
|
+
* or nested under a workspace.
|
|
83
|
+
*/
|
|
84
|
+
function bareImportPackageDirStrategy(
|
|
85
|
+
pkgName: string,
|
|
86
|
+
searchPaths?: readonly string[],
|
|
87
|
+
deps?: StrategyDeps,
|
|
88
|
+
): Strategy {
|
|
89
|
+
const fallbackResolve = (id: string, from: string): string | null => {
|
|
90
|
+
try {
|
|
91
|
+
if (searchPaths && searchPaths.length > 0) {
|
|
92
|
+
const req = createRequire(from) as unknown as {
|
|
93
|
+
resolve(id: string, opts?: { paths?: readonly string[] }): string;
|
|
94
|
+
};
|
|
95
|
+
return req.resolve(id, { paths: searchPaths });
|
|
96
|
+
}
|
|
97
|
+
return createRequire(from).resolve(id);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const resolveModule = deps?.resolveModule ?? fallbackResolve;
|
|
103
|
+
return {
|
|
104
|
+
name: "bare-import",
|
|
105
|
+
run() {
|
|
106
|
+
const pkgJson = resolveModule(`${pkgName}/package.json`, import.meta.url);
|
|
107
|
+
if (!pkgJson) {
|
|
108
|
+
return { ok: false, reason: `cannot resolve ${pkgName}/package.json` };
|
|
109
|
+
}
|
|
110
|
+
return { ok: true, path: path.dirname(pkgJson) };
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Module def that returns the package directory (containing package.json). */
|
|
116
|
+
function packageDirModuleDef(
|
|
117
|
+
toolName: string,
|
|
118
|
+
pkgName: string,
|
|
119
|
+
options: { searchPaths?: readonly string[]; includeManaged?: boolean },
|
|
120
|
+
deps?: StrategyDeps,
|
|
121
|
+
): ToolDefinition {
|
|
122
|
+
const strategies: Strategy[] = [
|
|
123
|
+
overrideStrategy(toolName, deps),
|
|
124
|
+
bareImportPackageDirStrategy(pkgName, options.searchPaths, deps),
|
|
125
|
+
];
|
|
126
|
+
if (options.includeManaged) {
|
|
127
|
+
strategies.push(managedModuleStrategy(pkgName, "package.json", deps));
|
|
128
|
+
}
|
|
129
|
+
return { name: toolName, kind: "module", strategies, classify };
|
|
130
|
+
}
|
|
131
|
+
|
|
69
132
|
// ── Registration ─────────────────────────────────────────────────
|
|
70
133
|
|
|
71
134
|
// Tools intentionally NOT registered:
|
|
@@ -76,6 +139,14 @@ function moduleDefWithAliases(
|
|
|
76
139
|
// - `pi-dashboard` — that's the package this code is part of.
|
|
77
140
|
// "Is it installed" is a bootstrap concern handled directly in
|
|
78
141
|
// `packages/electron/src/lib/dependency-detector.ts`.
|
|
142
|
+
//
|
|
143
|
+
// Build-time tools (see change: register-build-time-tools):
|
|
144
|
+
// - `electron` — module, returns the package directory containing
|
|
145
|
+
// `install.js`. Resolved with paths anchored at
|
|
146
|
+
// `packages/electron` to handle hoisted vs. nested
|
|
147
|
+
// layouts uniformly.
|
|
148
|
+
// - `node-pty` — module, returns the package directory containing
|
|
149
|
+
// `prebuilds/`. Standard module resolution suffices.
|
|
79
150
|
// See change: consolidate-tool-resolution (follow-up).
|
|
80
151
|
|
|
81
152
|
/**
|
|
@@ -333,6 +404,27 @@ export function registerDefaultTools(registry: ToolRegistry, deps?: StrategyDeps
|
|
|
333
404
|
deps,
|
|
334
405
|
),
|
|
335
406
|
);
|
|
407
|
+
|
|
408
|
+
// Build-time tools (see change: register-build-time-tools).
|
|
409
|
+
registry.register(
|
|
410
|
+
packageDirModuleDef(
|
|
411
|
+
"electron",
|
|
412
|
+
"electron",
|
|
413
|
+
{
|
|
414
|
+
searchPaths: [path.resolve("packages/electron")],
|
|
415
|
+
includeManaged: true,
|
|
416
|
+
},
|
|
417
|
+
deps,
|
|
418
|
+
),
|
|
419
|
+
);
|
|
420
|
+
registry.register(
|
|
421
|
+
packageDirModuleDef(
|
|
422
|
+
"node-pty",
|
|
423
|
+
"node-pty",
|
|
424
|
+
{ includeManaged: false },
|
|
425
|
+
deps,
|
|
426
|
+
),
|
|
427
|
+
);
|
|
336
428
|
}
|
|
337
429
|
|
|
338
430
|
/** Handy re-exports for callers that want raw definitions for testing. */
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
13
|
import path from "node:path";
|
|
14
|
-
import { ToolResolver } from "../platform/binary-lookup.js";
|
|
14
|
+
import { ToolResolver, isAppImageSelfHit } from "../platform/binary-lookup.js";
|
|
15
15
|
import { getManagedBin, getManagedDir } from "../managed-paths.js";
|
|
16
16
|
import * as npm from "../platform/npm.js";
|
|
17
17
|
import type { Strategy, StrategyCtx, StrategyResult } from "./types.js";
|
|
@@ -152,6 +152,17 @@ export function npmGlobalStrategy(
|
|
|
152
152
|
/**
|
|
153
153
|
* PATH search via `ToolResolver.which()`. This is the plain-old "is it
|
|
154
154
|
* on PATH" strategy and should appear last in most chains.
|
|
155
|
+
*
|
|
156
|
+
* Filters AppImage self-hits via `isAppImageSelfHit` — when the host
|
|
157
|
+
* runs as a Linux AppImage with `executableName: "pi-dashboard"`, the
|
|
158
|
+
* AppImage runtime prepends its squashfs mount to PATH, so the first
|
|
159
|
+
* `which pi-dashboard` hit can be the Electron launcher itself.
|
|
160
|
+
* Trusting that result spawns the Electron app recursively as if it
|
|
161
|
+
* were the dashboard CLI, which never opens the dashboard port and
|
|
162
|
+
* causes the loading screen to hang. Every tool registered via
|
|
163
|
+
* `whereStrategy` inherits this guard transparently.
|
|
164
|
+
*
|
|
165
|
+
* See change: fix-electron-appimage-cli-self-detection (D2).
|
|
155
166
|
*/
|
|
156
167
|
export function whereStrategy(binaryName: string, deps?: StrategyDeps): Strategy {
|
|
157
168
|
const { which } = d(deps);
|
|
@@ -159,8 +170,11 @@ export function whereStrategy(binaryName: string, deps?: StrategyDeps): Strategy
|
|
|
159
170
|
name: "where",
|
|
160
171
|
run(): StrategyResult {
|
|
161
172
|
const p = which(binaryName);
|
|
162
|
-
if (p) return { ok:
|
|
163
|
-
|
|
173
|
+
if (!p) return { ok: false, reason: `not found on PATH` };
|
|
174
|
+
if (isAppImageSelfHit(p)) {
|
|
175
|
+
return { ok: false, reason: `appimage-self-hit: ${p}` };
|
|
176
|
+
}
|
|
177
|
+
return { ok: true, path: p };
|
|
164
178
|
},
|
|
165
179
|
};
|
|
166
180
|
}
|
|
@@ -61,8 +61,168 @@ export interface DashboardSession {
|
|
|
61
61
|
/** Timestamp when metrics were last received */
|
|
62
62
|
updatedAt: number;
|
|
63
63
|
};
|
|
64
|
+
/** Extension-declared UI modules (Phase 1: management-modal slot). */
|
|
65
|
+
uiModules?: ExtensionUiModule[];
|
|
66
|
+
/** Cached row data per `view.dataEvent` for table/grid views. Per-event item cap is enforced server-side. */
|
|
67
|
+
uiDataMap?: Record<string, unknown[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Phase-2 live in-page decorations (footer-segment, agent-metric, breadcrumb,
|
|
70
|
+
* gate, toast). Keyed by `${kind}:${namespace}:${id}`. Last-write-wins on
|
|
71
|
+
* upsert; explicit removal via `ext_ui_decorator { removed: true }` deletes
|
|
72
|
+
* the entry. See change: add-extension-ui-decorations.
|
|
73
|
+
*/
|
|
74
|
+
uiDecorators?: Record<string, DecoratorDescriptor>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Extension UI System (Phase 1: management-modal slot) ───────────
|
|
78
|
+
// Per `extension-ui-system` design + `add-extension-ui-modal` change.
|
|
79
|
+
// Field/type names match PR #15 verbatim so any later archival diff stays small.
|
|
80
|
+
|
|
81
|
+
export type UiViewKind = "table" | "grid" | "form";
|
|
82
|
+
|
|
83
|
+
export type UiFieldKind =
|
|
84
|
+
| "text"
|
|
85
|
+
| "number"
|
|
86
|
+
| "boolean"
|
|
87
|
+
| "select"
|
|
88
|
+
| "code"
|
|
89
|
+
| "datetime"
|
|
90
|
+
| "textarea";
|
|
91
|
+
|
|
92
|
+
export interface UiField {
|
|
93
|
+
/** Dot-path into row / form-state. */
|
|
94
|
+
key: string;
|
|
95
|
+
label: string;
|
|
96
|
+
kind: UiFieldKind;
|
|
97
|
+
/** For kind: "select". */
|
|
98
|
+
options?: string[];
|
|
99
|
+
placeholder?: string;
|
|
100
|
+
required?: boolean;
|
|
101
|
+
readOnly?: boolean;
|
|
102
|
+
/** Legacy alias for kind: "textarea". Prefer `kind: "textarea"`. */
|
|
103
|
+
multiline?: boolean;
|
|
104
|
+
/** Display-only: table column width. */
|
|
105
|
+
width?: string | number;
|
|
106
|
+
/** For kind: "code". Hint to syntax highlighter. */
|
|
107
|
+
language?: string;
|
|
64
108
|
}
|
|
65
109
|
|
|
110
|
+
export interface UiAction {
|
|
111
|
+
/** Action id, echoed back as the `action` field on the `ui_management` message. */
|
|
112
|
+
id: string;
|
|
113
|
+
label: string;
|
|
114
|
+
/** MDI icon key from `@mdi/js` (e.g. `"mdiCheckCircle"`). Unknown keys render no icon. */
|
|
115
|
+
icon?: string;
|
|
116
|
+
variant?: "primary" | "secondary" | "danger";
|
|
117
|
+
/** Event name re-emitted on the extension's `pi.events` bus when the action fires. */
|
|
118
|
+
event: string;
|
|
119
|
+
params?: Record<string, unknown>;
|
|
120
|
+
/** If present, dashboard mounts ConfirmDialog with this message before dispatching. */
|
|
121
|
+
confirm?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface UiSection {
|
|
125
|
+
id: string;
|
|
126
|
+
title?: string;
|
|
127
|
+
description?: string;
|
|
128
|
+
fields: UiField[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface UiView {
|
|
132
|
+
kind: UiViewKind;
|
|
133
|
+
/** Table/grid columns; form fields when no `sections` provided. */
|
|
134
|
+
fields?: UiField[];
|
|
135
|
+
/** For form view: grouped fields. Mutually exclusive with top-level `fields`. */
|
|
136
|
+
sections?: UiSection[];
|
|
137
|
+
/** Event name to request rows; required for `table`/`grid`. */
|
|
138
|
+
dataEvent?: string;
|
|
139
|
+
/** Unique-row field for `table`/`grid` (default: `"id"`). */
|
|
140
|
+
rowKey?: string;
|
|
141
|
+
/** Per-row actions for `table`/`grid`. */
|
|
142
|
+
rowActions?: UiAction[];
|
|
143
|
+
/** Shown when `items.length === 0`. */
|
|
144
|
+
emptyState?: string;
|
|
145
|
+
/** Top-of-modal toolbar actions. */
|
|
146
|
+
actions?: UiAction[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ExtensionUiModule {
|
|
150
|
+
/** Phase 1: only `"management-modal"`. */
|
|
151
|
+
kind: "management-modal";
|
|
152
|
+
/** Unique within the session. Last-write-wins on collision. */
|
|
153
|
+
id: string;
|
|
154
|
+
/** Exact slash command (case-sensitive). */
|
|
155
|
+
command: string;
|
|
156
|
+
title: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
/** MDI icon key from `@mdi/js`. */
|
|
159
|
+
icon?: string;
|
|
160
|
+
/** Free-form group label (sidebar grouping in future). */
|
|
161
|
+
category?: string;
|
|
162
|
+
view: UiView;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Extension UI System (Phase 2: live in-page decorations) ──────
|
|
166
|
+
// Per `extension-ui-system` design + `add-extension-ui-decorations` change.
|
|
167
|
+
// Single discriminated union forwarded as one `ext_ui_decorator` message per
|
|
168
|
+
// descriptor. Cache key: `${kind}:${namespace}:${id}`. `namespace` MUST match
|
|
169
|
+
// `/^[a-z0-9-]+$/`; the bridge drops malformed namespaces with a warning.
|
|
170
|
+
|
|
171
|
+
export type DecoratorKind =
|
|
172
|
+
| "footer-segment"
|
|
173
|
+
| "agent-metric"
|
|
174
|
+
| "breadcrumb"
|
|
175
|
+
| "gate"
|
|
176
|
+
| "toast";
|
|
177
|
+
|
|
178
|
+
export interface FooterSegmentPayload {
|
|
179
|
+
text: string;
|
|
180
|
+
tooltip?: string;
|
|
181
|
+
/** MDI icon key from `@mdi/js`. Unknown keys render no icon. */
|
|
182
|
+
icon?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface AgentMetricPayload {
|
|
186
|
+
/** Matches the agent id rendered by `FlowAgentCard`. */
|
|
187
|
+
agentId: string;
|
|
188
|
+
text: string;
|
|
189
|
+
tooltip?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface BreadcrumbStep {
|
|
193
|
+
id: string;
|
|
194
|
+
label: string;
|
|
195
|
+
status: "pending" | "active" | "done" | "error";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface BreadcrumbPayload {
|
|
199
|
+
steps: BreadcrumbStep[];
|
|
200
|
+
/** Step id of the currently-active step (overrides `status: "active"` selection). */
|
|
201
|
+
current?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface GatePayload {
|
|
205
|
+
/** Matches the flow id rendered in `FlowLaunchDialog`. */
|
|
206
|
+
flowId: string;
|
|
207
|
+
available: boolean;
|
|
208
|
+
/** Reason rendered as a tooltip when `available: false`. */
|
|
209
|
+
reason?: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface ToastPayload {
|
|
213
|
+
level: "info" | "success" | "warn" | "error";
|
|
214
|
+
message: string;
|
|
215
|
+
/** Auto-dismiss after this many ms. Default 5000; `0` = sticky. */
|
|
216
|
+
durationMs?: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export type DecoratorDescriptor =
|
|
220
|
+
| { kind: "footer-segment"; namespace: string; id: string; payload: FooterSegmentPayload }
|
|
221
|
+
| { kind: "agent-metric"; namespace: string; id: string; payload: AgentMetricPayload }
|
|
222
|
+
| { kind: "breadcrumb"; namespace: string; id: string; payload: BreadcrumbPayload }
|
|
223
|
+
| { kind: "gate"; namespace: string; id: string; payload: GatePayload }
|
|
224
|
+
| { kind: "toast"; namespace: string; id: string; payload: ToastPayload };
|
|
225
|
+
|
|
66
226
|
/** An event forwarded from a pi session */
|
|
67
227
|
export interface DashboardEvent {
|
|
68
228
|
eventType: string;
|