@blackbelt-technology/pi-agent-dashboard 0.3.0 → 0.4.0
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 +67 -116
- package/README.md +93 -7
- package/docs/architecture.md +408 -9
- package/package.json +6 -4
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
- package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
- package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
- package/packages/extension/src/bridge.ts +69 -2
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/pi-env.d.ts +1 -0
- package/packages/extension/src/process-scanner.ts +72 -38
- package/packages/extension/src/provider-register.ts +304 -16
- package/packages/extension/src/server-auto-start.ts +27 -1
- package/packages/extension/src/server-launcher.ts +71 -27
- package/packages/server/package.json +16 -2
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +120 -0
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +125 -0
- package/packages/server/src/__tests__/bootstrap-state.test.ts +119 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +17 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +11 -0
- package/packages/server/src/__tests__/concurrent-launch.test.ts +110 -0
- package/packages/server/src/__tests__/config-api.test.ts +68 -0
- package/packages/server/src/__tests__/crash-recovery.test.ts +88 -0
- package/packages/server/src/__tests__/directory-service.test.ts +234 -8
- package/packages/server/src/__tests__/editor-registry.test.ts +28 -15
- package/packages/server/src/__tests__/extension-register-appimage.test.ts +5 -1
- package/packages/server/src/__tests__/extension-register.test.ts +3 -1
- package/packages/server/src/__tests__/find-port-holders.test.ts +94 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/home-lock-escape-hatch.test.ts +60 -0
- package/packages/server/src/__tests__/home-lock-release.test.ts +85 -0
- package/packages/server/src/__tests__/home-lock.test.ts +308 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +36 -0
- package/packages/server/src/__tests__/node-guard.test.ts +85 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +5 -1
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
- package/packages/server/src/__tests__/pi-version-skew.test.ts +165 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
- package/packages/server/src/__tests__/process-manager.test.ts +45 -18
- package/packages/server/src/__tests__/provider-probe.test.ts +287 -0
- package/packages/server/src/__tests__/provider-test-route.test.ts +149 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +83 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
- package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
- package/packages/server/src/__tests__/tool-routes.test.ts +277 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +19 -0
- package/packages/server/src/__tests__/trusted-networks-no-oauth-roundtrip.test.ts +126 -0
- package/packages/server/src/__tests__/tunnel-cleanup.test.ts +90 -0
- package/packages/server/src/__tests__/tunnel.test.ts +13 -7
- package/packages/server/src/__tests__/wsl-tmux-probe-cache.test.ts +44 -0
- package/packages/server/src/bootstrap-queue.ts +130 -0
- package/packages/server/src/bootstrap-state.ts +131 -0
- package/packages/server/src/browse.ts +8 -3
- package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
- package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
- package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
- package/packages/server/src/cli.ts +256 -32
- package/packages/server/src/config-api.ts +16 -0
- package/packages/server/src/directory-service.ts +270 -39
- package/packages/server/src/editor-detection.ts +12 -9
- package/packages/server/src/editor-manager.ts +19 -4
- package/packages/server/src/editor-pid-registry.ts +9 -8
- package/packages/server/src/editor-registry.ts +22 -25
- package/packages/server/src/git-operations.ts +1 -1
- package/packages/server/src/headless-pid-registry.ts +7 -20
- package/packages/server/src/home-lock-release.ts +72 -0
- package/packages/server/src/home-lock.ts +389 -0
- package/packages/server/src/node-guard.ts +52 -0
- package/packages/server/src/package-manager-wrapper.ts +207 -47
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-core-updater.ts +7 -1
- package/packages/server/src/pi-resource-scanner.ts +5 -8
- package/packages/server/src/pi-version-skew.ts +196 -0
- package/packages/server/src/preferences-store.ts +17 -3
- package/packages/server/src/process-manager.ts +403 -222
- package/packages/server/src/provider-probe.ts +234 -0
- package/packages/server/src/restart-helper.ts +130 -0
- package/packages/server/src/routes/bootstrap-routes.ts +88 -0
- package/packages/server/src/routes/openspec-routes.ts +25 -1
- package/packages/server/src/routes/pi-core-routes.ts +24 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -8
- package/packages/server/src/routes/provider-routes.ts +43 -0
- package/packages/server/src/routes/recommended-routes.ts +10 -12
- package/packages/server/src/routes/system-routes.ts +20 -33
- package/packages/server/src/routes/tool-routes.ts +153 -0
- package/packages/server/src/server-pid.ts +5 -9
- package/packages/server/src/server.ts +211 -10
- package/packages/server/src/session-api.ts +77 -8
- package/packages/server/src/session-bootstrap.ts +17 -3
- package/packages/server/src/session-diff.ts +21 -21
- package/packages/server/src/terminal-manager.ts +61 -20
- package/packages/server/src/tunnel.ts +42 -28
- package/packages/shared/package.json +10 -3
- package/packages/shared/src/__tests__/{tool-resolver.test.ts → binary-lookup.test.ts} +32 -12
- package/packages/shared/src/__tests__/bootstrap/README.md +133 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +370 -0
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +136 -0
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +47 -0
- package/packages/shared/src/__tests__/bootstrap/cube.ts +66 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +83 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +89 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +20 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +46 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +12 -0
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +156 -0
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +157 -0
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +102 -0
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +76 -0
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +94 -0
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +87 -0
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +143 -0
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +64 -0
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +77 -0
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +19 -0
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +50 -0
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +272 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +58 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +84 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +9 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +85 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +122 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +36 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +39 -0
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +220 -0
- package/packages/shared/src/__tests__/bootstrap/harness.ts +413 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +125 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +132 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +29 -6
- package/packages/shared/src/__tests__/config-openspec.test.ts +106 -0
- package/packages/shared/src/__tests__/config.test.ts +56 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +243 -0
- package/packages/shared/src/__tests__/managed-paths.test.ts +60 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +112 -0
- package/packages/shared/src/__tests__/no-direct-platform-branch.test.ts +174 -0
- package/packages/shared/src/__tests__/no-direct-process-kill.test.ts +105 -0
- package/packages/shared/src/__tests__/platform-commands.test.ts +108 -0
- package/packages/shared/src/__tests__/platform-exec.test.ts +103 -0
- package/packages/shared/src/__tests__/platform-git.test.ts +194 -0
- package/packages/shared/src/__tests__/platform-npm.test.ts +137 -0
- package/packages/shared/src/__tests__/platform-openspec.test.ts +92 -0
- package/packages/shared/src/__tests__/platform-paths.test.ts +284 -0
- package/packages/shared/src/__tests__/platform-process-scan.test.ts +55 -0
- package/packages/shared/src/__tests__/platform-process.test.ts +160 -0
- package/packages/shared/src/__tests__/platform-runner.test.ts +173 -0
- package/packages/shared/src/__tests__/platform-shell.test.ts +74 -0
- package/packages/shared/src/__tests__/process-identify.test.ts +113 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +40 -7
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
- package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
- package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
- package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
- package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
- package/packages/shared/src/bootstrap-install.ts +212 -0
- package/packages/shared/src/bridge-register.ts +87 -20
- package/packages/shared/src/browser-protocol.ts +71 -1
- package/packages/shared/src/config.ts +87 -15
- package/packages/shared/src/managed-paths.ts +31 -4
- package/packages/shared/src/openspec-poller.ts +63 -46
- package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
- package/packages/shared/src/platform/commands.ts +100 -0
- package/packages/shared/src/platform/detached-spawn.ts +305 -0
- package/packages/shared/src/platform/exec.ts +220 -0
- package/packages/shared/src/platform/git.ts +155 -0
- package/packages/shared/src/platform/index.ts +15 -0
- package/packages/shared/src/platform/npm.ts +162 -0
- package/packages/shared/src/platform/openspec.ts +91 -0
- package/packages/shared/src/platform/paths.ts +276 -0
- package/packages/shared/src/platform/process-identify.ts +126 -0
- package/packages/shared/src/platform/process-scan.ts +94 -0
- package/packages/shared/src/platform/process.ts +168 -0
- package/packages/shared/src/platform/runner.ts +369 -0
- package/packages/shared/src/platform/shell.ts +44 -0
- package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
- package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
- package/packages/shared/src/recommended-extensions.ts +18 -2
- package/packages/shared/src/resolve-jiti.ts +62 -3
- package/packages/shared/src/rest-api.ts +26 -0
- package/packages/shared/src/semaphore.ts +83 -0
- package/packages/shared/src/tool-registry/definitions.ts +342 -0
- package/packages/shared/src/tool-registry/index.ts +56 -0
- package/packages/shared/src/tool-registry/overrides.ts +118 -0
- package/packages/shared/src/tool-registry/registry.ts +262 -0
- package/packages/shared/src/tool-registry/strategies.ts +198 -0
- package/packages/shared/src/tool-registry/types.ts +180 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory queue for pi-dependent operations deferred during bootstrap
|
|
3
|
+
* install. When `bootstrapState.status === "installing"`, callers should
|
|
4
|
+
* enqueue their handler and return a 202 Accepted with the ticketId.
|
|
5
|
+
* When status transitions to "ready", `flushAll()` runs every queued
|
|
6
|
+
* handler sequentially in enqueue order.
|
|
7
|
+
*
|
|
8
|
+
* Queue is process-local and NOT persisted. If the dashboard crashes
|
|
9
|
+
* mid-install, queued requests are lost — documented as a known
|
|
10
|
+
* limitation in design.md §16.2.
|
|
11
|
+
*
|
|
12
|
+
* See change: unified-bootstrap-install.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
|
|
16
|
+
export interface QueuedTicket<T> {
|
|
17
|
+
ticketId: string;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves when the queued handler runs (or rejects if it throws).
|
|
20
|
+
* Call sites can await this when they want to synchronously return
|
|
21
|
+
* the eventual result — but 202-Accepted flows MUST NOT await, they
|
|
22
|
+
* return the ticketId to the client immediately.
|
|
23
|
+
*/
|
|
24
|
+
result: Promise<T>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BootstrapQueue {
|
|
28
|
+
enqueue<T>(handler: () => Promise<T>): QueuedTicket<T>;
|
|
29
|
+
flushAll(): Promise<void>;
|
|
30
|
+
/** Number of currently pending tickets. */
|
|
31
|
+
size(): number;
|
|
32
|
+
/** Drop all pending tickets without running them (used at shutdown). */
|
|
33
|
+
clear(reason?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Register a listener invoked after each ticket runs (success or
|
|
36
|
+
* failure). The server wires this to a `bootstrap_ticket_complete`
|
|
37
|
+
* WS broadcast so browser clients can correlate the outcome of a
|
|
38
|
+
* 202-accepted request via their stored ticketId.
|
|
39
|
+
* See change: unified-bootstrap-install.
|
|
40
|
+
*/
|
|
41
|
+
onTicketComplete(
|
|
42
|
+
listener: (evt: { ticketId: string; success: boolean; error?: string }) => void,
|
|
43
|
+
): () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface PendingEntry {
|
|
47
|
+
ticketId: string;
|
|
48
|
+
run: () => Promise<void>;
|
|
49
|
+
/** Reject the caller's `result` promise. Called by `clear()` to
|
|
50
|
+
* drain tickets at shutdown. */
|
|
51
|
+
reject: (err: unknown) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createBootstrapQueue(): BootstrapQueue {
|
|
55
|
+
const pending: PendingEntry[] = [];
|
|
56
|
+
const listeners = new Set<
|
|
57
|
+
(evt: { ticketId: string; success: boolean; error?: string }) => void
|
|
58
|
+
>();
|
|
59
|
+
|
|
60
|
+
function notify(evt: { ticketId: string; success: boolean; error?: string }): void {
|
|
61
|
+
for (const l of listeners) {
|
|
62
|
+
try {
|
|
63
|
+
l(evt);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error("[bootstrap-queue] ticket-complete listener threw:", err);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
enqueue<T>(handler: () => Promise<T>): QueuedTicket<T> {
|
|
72
|
+
const ticketId = randomUUID();
|
|
73
|
+
let resolve!: (value: T) => void;
|
|
74
|
+
let reject!: (err: unknown) => void;
|
|
75
|
+
const result = new Promise<T>((res, rej) => {
|
|
76
|
+
resolve = res;
|
|
77
|
+
reject = rej;
|
|
78
|
+
});
|
|
79
|
+
pending.push({
|
|
80
|
+
ticketId,
|
|
81
|
+
reject,
|
|
82
|
+
run: async () => {
|
|
83
|
+
try {
|
|
84
|
+
const value = await handler();
|
|
85
|
+
resolve(value);
|
|
86
|
+
notify({ ticketId, success: true });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
reject(err);
|
|
89
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
90
|
+
notify({ ticketId, success: false, error: message });
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return { ticketId, result };
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async flushAll(): Promise<void> {
|
|
98
|
+
while (pending.length > 0) {
|
|
99
|
+
const entry = pending.shift();
|
|
100
|
+
if (!entry) break;
|
|
101
|
+
try {
|
|
102
|
+
await entry.run();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
// Handler errors propagate via the ticket's `result` promise;
|
|
105
|
+
// they should never reach here unless there's a bug in `run`.
|
|
106
|
+
console.error(`[bootstrap-queue] ticket ${entry.ticketId} run threw:`, err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
size() {
|
|
112
|
+
return pending.length;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
clear(reason = "queue cleared") {
|
|
116
|
+
const drained = pending.splice(0, pending.length);
|
|
117
|
+
for (const entry of drained) {
|
|
118
|
+
// Reject the caller's `result` promise directly and broadcast the
|
|
119
|
+
// completion so any browser holding the ticketId learns the
|
|
120
|
+
// outcome.
|
|
121
|
+
entry.reject(new Error(reason));
|
|
122
|
+
notify({ ticketId: entry.ticketId, success: false, error: reason });
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
onTicketComplete(listener) {
|
|
126
|
+
listeners.add(listener);
|
|
127
|
+
return () => listeners.delete(listener);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory bootstrap state store for the dashboard server.
|
|
3
|
+
*
|
|
4
|
+
* Tracks the degraded-mode status during first-run pi install, upgrade
|
|
5
|
+
* operations, and version-skew detection. Subscribers (browser gateway,
|
|
6
|
+
* CLI progress printer) receive a snapshot on every `set()` call.
|
|
7
|
+
*
|
|
8
|
+
* See change: unified-bootstrap-install.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type BootstrapStatus = "ready" | "installing" | "failed";
|
|
12
|
+
|
|
13
|
+
export interface BootstrapProgress {
|
|
14
|
+
/** Package / phase being processed (e.g. "pi-coding-agent", "bridge-register"). */
|
|
15
|
+
step: string;
|
|
16
|
+
/** Optional completion percentage (0..100). */
|
|
17
|
+
pct?: number;
|
|
18
|
+
/** Last line of npm output or other streaming context. */
|
|
19
|
+
output?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BootstrapError {
|
|
23
|
+
message: string;
|
|
24
|
+
stack?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BootstrapVersions {
|
|
28
|
+
pi?: string;
|
|
29
|
+
openspec?: string;
|
|
30
|
+
tsx?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BootstrapCompatibility {
|
|
34
|
+
minimum: string;
|
|
35
|
+
recommended: string;
|
|
36
|
+
/** null = no upper bound enforced yet. */
|
|
37
|
+
maximum: string | null;
|
|
38
|
+
/** Current resolved pi version, or undefined when pi is unresolved. */
|
|
39
|
+
current?: string;
|
|
40
|
+
/** Hint that the user should upgrade pi (below recommended). */
|
|
41
|
+
upgradeRecommended?: boolean;
|
|
42
|
+
/** Hint that the user should upgrade the dashboard itself (above maximum). */
|
|
43
|
+
upgradeDashboard?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface BootstrapState {
|
|
47
|
+
status: BootstrapStatus;
|
|
48
|
+
progress?: BootstrapProgress;
|
|
49
|
+
error?: BootstrapError;
|
|
50
|
+
version?: BootstrapVersions;
|
|
51
|
+
compatibility?: BootstrapCompatibility;
|
|
52
|
+
/** Set when `registerBridgeExtension` fails after a successful install. */
|
|
53
|
+
bridgeRegistrationError?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type BootstrapListener = (state: BootstrapState) => void;
|
|
57
|
+
|
|
58
|
+
export interface BootstrapStateStore {
|
|
59
|
+
get(): BootstrapState;
|
|
60
|
+
/**
|
|
61
|
+
* Merge `partial` into the current state. Passing `undefined` for a
|
|
62
|
+
* key explicitly clears it (e.g. `set({ progress: undefined })` removes
|
|
63
|
+
* the progress line after completion). Broadcasts to all subscribers.
|
|
64
|
+
*/
|
|
65
|
+
set(partial: Partial<BootstrapState>): void;
|
|
66
|
+
subscribe(listener: BootstrapListener): () => void;
|
|
67
|
+
/** Clear all listeners (used in tests + server shutdown). */
|
|
68
|
+
dispose(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Record the package list used by the most recent `bootstrapInstall`
|
|
71
|
+
* call. Used by `POST /api/bootstrap/retry` to re-run the exact failed
|
|
72
|
+
* set rather than a hard-coded default. Not part of the WS-broadcast
|
|
73
|
+
* snapshot — it's purely side-channel metadata for the server.
|
|
74
|
+
* See change: unified-bootstrap-install (verification follow-up).
|
|
75
|
+
*/
|
|
76
|
+
setLastInstallPackages(packages: readonly string[]): void;
|
|
77
|
+
/** Read the last install set. Returns a fresh copy. */
|
|
78
|
+
getLastInstallPackages(): string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a fresh bootstrap state store. `initial` is merged over the
|
|
83
|
+
* default `{ status: "ready" }`.
|
|
84
|
+
*/
|
|
85
|
+
export function createBootstrapState(
|
|
86
|
+
initial?: Partial<BootstrapState>,
|
|
87
|
+
): BootstrapStateStore {
|
|
88
|
+
let state: BootstrapState = { status: "ready", ...initial };
|
|
89
|
+
let lastInstallPackages: string[] = [];
|
|
90
|
+
const listeners = new Set<BootstrapListener>();
|
|
91
|
+
|
|
92
|
+
function notify(): void {
|
|
93
|
+
const snapshot = { ...state };
|
|
94
|
+
for (const l of listeners) {
|
|
95
|
+
try {
|
|
96
|
+
l(snapshot);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
// Listener errors are non-fatal — log but continue.
|
|
99
|
+
console.error("[bootstrap-state] listener threw:", err);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
get() {
|
|
106
|
+
return { ...state };
|
|
107
|
+
},
|
|
108
|
+
set(partial) {
|
|
109
|
+
// Merge: explicit `undefined` in partial clears the field.
|
|
110
|
+
state = { ...state, ...partial } as BootstrapState;
|
|
111
|
+
// Strip keys whose value is undefined to keep the snapshot tidy.
|
|
112
|
+
for (const key of Object.keys(partial) as (keyof BootstrapState)[]) {
|
|
113
|
+
if (partial[key] === undefined) delete state[key];
|
|
114
|
+
}
|
|
115
|
+
notify();
|
|
116
|
+
},
|
|
117
|
+
subscribe(listener) {
|
|
118
|
+
listeners.add(listener);
|
|
119
|
+
return () => listeners.delete(listener);
|
|
120
|
+
},
|
|
121
|
+
dispose() {
|
|
122
|
+
listeners.clear();
|
|
123
|
+
},
|
|
124
|
+
setLastInstallPackages(packages) {
|
|
125
|
+
lastInstallPackages = [...packages];
|
|
126
|
+
},
|
|
127
|
+
getLastInstallPackages() {
|
|
128
|
+
return [...lastInstallPackages];
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -5,6 +5,7 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import type { BrowseEntry, BrowseResult } from "@blackbelt-technology/pi-dashboard-shared/rest-api.js";
|
|
8
|
+
import { isFilesystemRoot } from "@blackbelt-technology/pi-dashboard-shared/platform/paths.js";
|
|
8
9
|
|
|
9
10
|
const MAX_ENTRIES = 200;
|
|
10
11
|
const WORD_BOUNDARY_CHARS = new Set(["-", "_", ".", " ", "/"]);
|
|
@@ -85,10 +86,14 @@ export async function listDirectories(dirPath?: string, q?: string): Promise<Bro
|
|
|
85
86
|
})
|
|
86
87
|
);
|
|
87
88
|
|
|
88
|
-
// Parent: null for root
|
|
89
|
-
|
|
89
|
+
// Parent: null for any filesystem root (`/`, `C:\`, `\\server\share\`).
|
|
90
|
+
// Previously this was `resolved === "/"`, which only recognized the Unix
|
|
91
|
+
// root — on Windows `path.dirname("B:\\")` returns `"B:\\"`, so the
|
|
92
|
+
// picker showed a useless `..` entry at drive roots.
|
|
93
|
+
// See change: platform-path-normalization.
|
|
94
|
+
const parent = isFilesystemRoot(resolved) ? null : path.dirname(resolved);
|
|
90
95
|
|
|
91
|
-
return { entries, parent, current: resolved };
|
|
96
|
+
return { entries, parent, current: resolved, platform: process.platform };
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
/**
|
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
import type { BrowserToServerMessage } from "@blackbelt-technology/pi-dashboard-shared/browser-protocol.js";
|
|
5
5
|
import type { BrowserHandlerContext } from "./handler-context.js";
|
|
6
6
|
import { safeRealpathSync } from "../resolve-path.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { archiveCompleted as openspecArchiveCompleted } from "@blackbelt-technology/pi-dashboard-shared/platform/openspec.js";
|
|
8
|
+
import { normalizePath } from "@blackbelt-technology/pi-dashboard-shared/platform/paths.js";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Canonicalize a user-supplied path before storage: normalize separator /
|
|
12
|
+
* trailing-sep / case variants first, then resolve symlinks. Order matters
|
|
13
|
+
* — `realpath` can fail for not-yet-existing paths, so we keep its
|
|
14
|
+
* best-effort fallback but ensure we first have a sane string.
|
|
15
|
+
* See change: platform-path-normalization.
|
|
16
|
+
*/
|
|
17
|
+
function canonicalizePath(input: string): string {
|
|
18
|
+
return safeRealpathSync(normalizePath(input));
|
|
19
|
+
}
|
|
11
20
|
|
|
12
21
|
export function handlePinDirectory(
|
|
13
22
|
msg: Extract<BrowserToServerMessage, { type: "pin_directory" }>,
|
|
@@ -15,7 +24,7 @@ export function handlePinDirectory(
|
|
|
15
24
|
): void {
|
|
16
25
|
const { preferencesStore, directoryService, sessionManager, broadcast } = ctx;
|
|
17
26
|
if (!preferencesStore) return;
|
|
18
|
-
const resolved =
|
|
27
|
+
const resolved = canonicalizePath(msg.path);
|
|
19
28
|
preferencesStore.pinDirectory(resolved);
|
|
20
29
|
broadcast({ type: "pinned_dirs_updated", paths: preferencesStore.getPinnedDirectories() });
|
|
21
30
|
if (directoryService) {
|
|
@@ -48,7 +57,7 @@ export function handleUnpinDirectory(
|
|
|
48
57
|
ctx: BrowserHandlerContext,
|
|
49
58
|
): void {
|
|
50
59
|
if (ctx.preferencesStore) {
|
|
51
|
-
ctx.preferencesStore.unpinDirectory(
|
|
60
|
+
ctx.preferencesStore.unpinDirectory(canonicalizePath(msg.path));
|
|
52
61
|
ctx.broadcast({ type: "pinned_dirs_updated", paths: ctx.preferencesStore.getPinnedDirectories() });
|
|
53
62
|
}
|
|
54
63
|
}
|
|
@@ -58,7 +67,10 @@ export function handleReorderPinnedDirs(
|
|
|
58
67
|
ctx: BrowserHandlerContext,
|
|
59
68
|
): void {
|
|
60
69
|
if (ctx.preferencesStore) {
|
|
61
|
-
|
|
70
|
+
// Wrap in arrow fn: map's (elem, index, array) callback would pass
|
|
71
|
+
// the array index as canonicalizePath's 2nd arg, silently breaking
|
|
72
|
+
// platform detection. See platform-path-normalization.
|
|
73
|
+
ctx.preferencesStore.reorderPinnedDirs(msg.paths.map((p) => canonicalizePath(p)));
|
|
62
74
|
ctx.broadcast({ type: "pinned_dirs_updated", paths: ctx.preferencesStore.getPinnedDirectories() });
|
|
63
75
|
}
|
|
64
76
|
}
|
|
@@ -89,8 +101,11 @@ export function handleOpenSpecBulkArchive(
|
|
|
89
101
|
ctx: BrowserHandlerContext,
|
|
90
102
|
): void {
|
|
91
103
|
if (ctx.directoryService) {
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
// Delegate to the shared openspec tool module. The runner handles
|
|
105
|
+
// windowsHide, timeout, and argv-array escaping.
|
|
106
|
+
// See change: platform-command-executor.
|
|
107
|
+
openspecArchiveCompleted({ cwd: msg.cwd });
|
|
108
|
+
Promise.resolve()
|
|
94
109
|
.then(() => ctx.directoryService!.refreshOpenSpec(msg.cwd))
|
|
95
110
|
.then((data) => {
|
|
96
111
|
if (data) ctx.broadcast({ type: "openspec_update", cwd: msg.cwd, data });
|