@legioncodeinc/hive 0.3.1 → 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/README.md +1 -1
- package/dist/daemon/dashboard/app.js +12 -12
- package/dist/daemon/installer/config.d.ts +2 -0
- package/dist/daemon/installer/config.js +5 -3
- package/dist/daemon/installer/config.js.map +1 -1
- package/dist/daemon/installer/token.d.ts +8 -7
- package/dist/daemon/installer/token.js +40 -18
- package/dist/daemon/installer/token.js.map +1 -1
- package/dist/daemon/registry.d.ts +6 -0
- package/dist/daemon/registry.js +15 -17
- package/dist/daemon/registry.js.map +1 -1
- package/dist/daemon/server.d.ts +11 -0
- package/dist/daemon/server.js +18 -0
- package/dist/daemon/server.js.map +1 -1
- package/dist/dashboard/web/pages/hive-graph.js +128 -42
- package/dist/dashboard/web/pages/hive-graph.js.map +1 -1
- package/dist/dashboard/web/wire.d.ts +91 -14
- package/dist/dashboard/web/wire.js +71 -0
- package/dist/dashboard/web/wire.js.map +1 -1
- package/dist/install/registry.d.ts +2 -2
- package/dist/install/registry.js +14 -7
- package/dist/install/registry.js.map +1 -1
- package/dist/lock.d.ts +5 -2
- package/dist/lock.js +36 -1
- package/dist/lock.js.map +1 -1
- package/dist/service/index.d.ts +5 -0
- package/dist/service/index.js +9 -1
- package/dist/service/index.js.map +1 -1
- package/dist/service/templates.d.ts +22 -3
- package/dist/service/templates.js +47 -10
- package/dist/service/templates.js.map +1 -1
- package/dist/shared/apiary-root.d.ts +28 -0
- package/dist/shared/apiary-root.js +71 -0
- package/dist/shared/apiary-root.js.map +1 -0
- package/dist/shared/constants.d.ts +0 -1
- package/dist/shared/constants.js +3 -5
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/legacy-paths.d.ts +22 -0
- package/dist/shared/legacy-paths.js +48 -0
- package/dist/shared/legacy-paths.js.map +1 -0
- package/dist/shared/registry-paths.d.ts +19 -0
- package/dist/shared/registry-paths.js +52 -0
- package/dist/shared/registry-paths.js.map +1 -0
- package/dist/shared/state-migration.d.ts +13 -0
- package/dist/shared/state-migration.js +92 -0
- package/dist/shared/state-migration.js.map +1 -0
- package/dist/telemetry/emit.d.ts +10 -10
- package/dist/telemetry/emit.js +12 -3
- package/dist/telemetry/emit.js.map +1 -1
- package/package.json +1 -1
|
@@ -34,6 +34,8 @@ export interface InstallerConfig {
|
|
|
34
34
|
readonly manifestTimeoutMs: number;
|
|
35
35
|
/** The onboarding token file path (read lazily, deleted on completion). */
|
|
36
36
|
readonly tokenPath: string;
|
|
37
|
+
/** Legacy-window onboarding token path (mg-AC-9 dual read). */
|
|
38
|
+
readonly legacyTokenPath: string;
|
|
37
39
|
/** Existence check (a fake map in tests). */
|
|
38
40
|
readonly fileExists: (path: string) => boolean;
|
|
39
41
|
/** UTF-8 read returning `null` on any error (a fake map in tests). */
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
12
11
|
import { platform as osPlatform } from "node:process";
|
|
13
|
-
import { HIVE_VERSION
|
|
12
|
+
import { HIVE_VERSION } from "../../shared/constants.js";
|
|
13
|
+
import { resolveOnboardingTokenPath } from "../../shared/apiary-root.js";
|
|
14
|
+
import { LEGACY_ONBOARDING_TOKEN_PATH } from "../../shared/legacy-paths.js";
|
|
14
15
|
import { createNodeSpawn } from "./spawn.js";
|
|
15
16
|
/** The primary fleet manifest URL served by the install site (MV-2). */
|
|
16
17
|
export const MANIFEST_URL = "https://get.theapiary.sh/hive-release.json";
|
|
@@ -20,7 +21,7 @@ export const MANIFEST_URL = "https://get.theapiary.sh/hive-release.json";
|
|
|
20
21
|
*/
|
|
21
22
|
export const MANIFEST_FALLBACK_URL = "https://raw.githubusercontent.com/legioncodeinc/the-apiary/main/hive-release.json";
|
|
22
23
|
/** The bootstrap-minted one-time onboarding token file (mode 0600), read lazily per request (is-AC-9). */
|
|
23
|
-
export const ONBOARDING_TOKEN_PATH =
|
|
24
|
+
export const ONBOARDING_TOKEN_PATH = resolveOnboardingTokenPath();
|
|
24
25
|
/** The bounded timeout for the single network manifest fetch before falling back to the snapshot. */
|
|
25
26
|
export const MANIFEST_TIMEOUT_MS = 5000;
|
|
26
27
|
function defaultReadTextFile(path) {
|
|
@@ -59,6 +60,7 @@ export function createInstallerConfig(overrides = {}) {
|
|
|
59
60
|
manifestFetch: overrides.manifestFetch ?? ((input, init) => fetch(input, init)),
|
|
60
61
|
manifestTimeoutMs: overrides.manifestTimeoutMs ?? MANIFEST_TIMEOUT_MS,
|
|
61
62
|
tokenPath: overrides.tokenPath ?? ONBOARDING_TOKEN_PATH,
|
|
63
|
+
legacyTokenPath: overrides.legacyTokenPath ?? LEGACY_ONBOARDING_TOKEN_PATH,
|
|
62
64
|
fileExists: overrides.fileExists ?? ((path) => existsSync(path)),
|
|
63
65
|
readTextFile: overrides.readTextFile ?? defaultReadTextFile,
|
|
64
66
|
deleteFile: overrides.deleteFile ?? defaultDeleteFile,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/daemon/installer/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/daemon/installer/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PRD-009a: the daemon side of the one-time onboarding token contract (is-AC-9/10).
|
|
3
3
|
*
|
|
4
|
-
* The bootstrap mints the token and writes it to
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* The bootstrap mints the token and writes it to the hive onboarding-token path at mode 0600 BEFORE
|
|
5
|
+
* the daemon starts (PRD-009d). During the fleet-root migration window the daemon also reads the
|
|
6
|
+
* legacy path when the new path is absent (mg-AC-9). The daemon reads the token LAZILY at request
|
|
7
|
+
* time, not at startup, because on re-entry the daemon may already be running when a fresh bootstrap
|
|
8
|
+
* writes a new token. Comparison is constant time (`crypto.timingSafeEqual`). Completion invalidates
|
|
9
|
+
* the token: whichever path served it is deleted and an in-memory flag is set, so state-changing
|
|
10
|
+
* endpoints refuse all requests until a fresh bootstrap mints a new one (is-AC-10). The token is
|
|
11
|
+
* never logged, never returned, and never placed in an error body.
|
|
11
12
|
*/
|
|
12
13
|
import type { InstallerConfig } from "./config.js";
|
|
13
14
|
/** The token gate: whether onboarding is active, whether a presented token is valid, and invalidation. */
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PRD-009a: the daemon side of the one-time onboarding token contract (is-AC-9/10).
|
|
3
3
|
*
|
|
4
|
-
* The bootstrap mints the token and writes it to
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* The bootstrap mints the token and writes it to the hive onboarding-token path at mode 0600 BEFORE
|
|
5
|
+
* the daemon starts (PRD-009d). During the fleet-root migration window the daemon also reads the
|
|
6
|
+
* legacy path when the new path is absent (mg-AC-9). The daemon reads the token LAZILY at request
|
|
7
|
+
* time, not at startup, because on re-entry the daemon may already be running when a fresh bootstrap
|
|
8
|
+
* writes a new token. Comparison is constant time (`crypto.timingSafeEqual`). Completion invalidates
|
|
9
|
+
* the token: whichever path served it is deleted and an in-memory flag is set, so state-changing
|
|
10
|
+
* endpoints refuse all requests until a fresh bootstrap mints a new one (is-AC-10). The token is
|
|
11
|
+
* never logged, never returned, and never placed in an error body.
|
|
11
12
|
*/
|
|
12
13
|
import { timingSafeEqual } from "node:crypto";
|
|
13
14
|
/** Constant-time string comparison. Length is compared first (timingSafeEqual requires equal length). */
|
|
@@ -21,35 +22,56 @@ function constantTimeEquals(a, b) {
|
|
|
21
22
|
}
|
|
22
23
|
return timingSafeEqual(bufA, bufB);
|
|
23
24
|
}
|
|
25
|
+
function readTrimmed(config, path) {
|
|
26
|
+
const raw = config.readTextFile(path);
|
|
27
|
+
if (raw === null)
|
|
28
|
+
return null;
|
|
29
|
+
const trimmed = raw.trim();
|
|
30
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* mg-AC-9/10: new path first, legacy path second, and the fallback is ABSENCE-triggered only.
|
|
34
|
+
* When the new-path file EXISTS, the legacy path is never consulted, even if the new file is
|
|
35
|
+
* unreadable or empty (that reads as "no valid token", never as stale legacy data).
|
|
36
|
+
*/
|
|
37
|
+
function resolveTokenPathAndValue(config) {
|
|
38
|
+
if (config.fileExists(config.tokenPath)) {
|
|
39
|
+
const primary = readTrimmed(config, config.tokenPath);
|
|
40
|
+
return primary === null ? null : { path: config.tokenPath, value: primary };
|
|
41
|
+
}
|
|
42
|
+
const legacy = readTrimmed(config, config.legacyTokenPath);
|
|
43
|
+
if (legacy !== null)
|
|
44
|
+
return { path: config.legacyTokenPath, value: legacy };
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
24
47
|
/** Build the token store over the injected config seams. */
|
|
25
48
|
export function createTokenStore(config) {
|
|
26
49
|
let invalidated = false;
|
|
27
|
-
|
|
28
|
-
const raw = config.readTextFile(config.tokenPath);
|
|
29
|
-
if (raw === null)
|
|
30
|
-
return null;
|
|
31
|
-
const trimmed = raw.trim();
|
|
32
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
33
|
-
};
|
|
50
|
+
let activePath = null;
|
|
34
51
|
return {
|
|
35
52
|
isActive() {
|
|
36
53
|
if (invalidated)
|
|
37
54
|
return false;
|
|
38
|
-
return
|
|
55
|
+
return resolveTokenPathAndValue(config) !== null;
|
|
39
56
|
},
|
|
40
57
|
requireValid(provided) {
|
|
41
58
|
if (invalidated)
|
|
42
59
|
return false;
|
|
43
60
|
if (provided === null || provided === undefined || provided.length === 0)
|
|
44
61
|
return false;
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
62
|
+
const resolved = resolveTokenPathAndValue(config);
|
|
63
|
+
if (resolved === null)
|
|
47
64
|
return false;
|
|
48
|
-
|
|
65
|
+
activePath = resolved.path;
|
|
66
|
+
return constantTimeEquals(resolved.value, provided);
|
|
49
67
|
},
|
|
50
68
|
invalidate() {
|
|
51
69
|
invalidated = true;
|
|
52
70
|
config.deleteFile(config.tokenPath);
|
|
71
|
+
config.deleteFile(config.legacyTokenPath);
|
|
72
|
+
if (activePath !== null) {
|
|
73
|
+
config.deleteFile(activePath);
|
|
74
|
+
}
|
|
53
75
|
}
|
|
54
76
|
};
|
|
55
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/daemon/installer/token.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/daemon/installer/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { type DaemonBases, type DaemonName } from "../shared/daemon-routing.js";
|
|
2
|
+
/**
|
|
3
|
+
* Informational only: the fleet-root registry location snapshotted at module load. Readers must
|
|
4
|
+
* go through {@link readRegistryBody} (which applies the new-then-legacy window chain per call),
|
|
5
|
+
* never read this constant's path directly.
|
|
6
|
+
*/
|
|
2
7
|
export declare const DOCTOR_REGISTRY_PATH: string;
|
|
3
8
|
export interface ResolveDaemonBasesOptions {
|
|
4
9
|
readonly registryPath?: string;
|
|
@@ -29,3 +34,4 @@ export interface ResolveRegisteredServiceNamesOptions {
|
|
|
29
34
|
*/
|
|
30
35
|
export declare function resolveRegisteredServiceNames(options?: ResolveRegisteredServiceNamesOptions): readonly string[];
|
|
31
36
|
export declare function resolveDaemonBases(options?: ResolveDaemonBasesOptions): DaemonBases;
|
|
37
|
+
export { readRegistryBody } from "../shared/registry-paths.js";
|
package/dist/daemon/registry.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
2
|
+
import { resolveFleetRegistryPath } from "../shared/apiary-root.js";
|
|
3
|
+
import { readRegistryBody } from "../shared/registry-paths.js";
|
|
5
4
|
import { isLoopbackBaseUrl, normalizeDaemonBases } from "../shared/daemon-routing.js";
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Informational only: the fleet-root registry location snapshotted at module load. Readers must
|
|
7
|
+
* go through {@link readRegistryBody} (which applies the new-then-legacy window chain per call),
|
|
8
|
+
* never read this constant's path directly.
|
|
9
|
+
*/
|
|
10
|
+
export const DOCTOR_REGISTRY_PATH = resolveFleetRegistryPath();
|
|
7
11
|
const RegistryEntrySchema = z.object({
|
|
8
12
|
name: z.string().min(1),
|
|
9
13
|
healthUrl: z.string().url(),
|
|
@@ -97,24 +101,18 @@ export function parseRegisteredServiceNames(raw) {
|
|
|
97
101
|
* shows no tiles until the fleet-status/SSE feed enumerates services some other way).
|
|
98
102
|
*/
|
|
99
103
|
export function resolveRegisteredServiceNames(options = {}) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
return parseRegisteredServiceNames(readFile(registryPath));
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
104
|
+
const raw = readRegistryBody(options);
|
|
105
|
+
if (raw === null)
|
|
106
106
|
return [];
|
|
107
|
-
|
|
107
|
+
return parseRegisteredServiceNames(raw);
|
|
108
108
|
}
|
|
109
109
|
export function resolveDaemonBases(options = {}) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
return normalizeDaemonBases(parseDoctorRegistry(readFile(registryPath)));
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
110
|
+
const raw = readRegistryBody(options);
|
|
111
|
+
if (raw === null) {
|
|
116
112
|
// Missing or unreadable registry means no daemon has registered yet. Use documented loopback defaults.
|
|
117
113
|
return normalizeDaemonBases();
|
|
118
114
|
}
|
|
115
|
+
return normalizeDaemonBases(parseDoctorRegistry(raw));
|
|
119
116
|
}
|
|
117
|
+
export { readRegistryBody } from "../shared/registry-paths.js";
|
|
120
118
|
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/daemon/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/daemon/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon/server.d.ts
CHANGED
|
@@ -30,6 +30,17 @@ export interface CreateHiveOptions {
|
|
|
30
30
|
export interface StartHiveOptions extends CreateHiveOptions {
|
|
31
31
|
readonly lockPaths?: Partial<LockPaths>;
|
|
32
32
|
readonly serveFn?: ServeFunction;
|
|
33
|
+
/**
|
|
34
|
+
* PRD-010b boot-migration seam. Defaults to the real {@link migrateHiveState}; tests MUST
|
|
35
|
+
* inject a no-op (or the suite-wide isolated home applies) so no test touches the real home.
|
|
36
|
+
*/
|
|
37
|
+
readonly migrateState?: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* rc-AC-2/mg-AC-7 registry-upsert seam, invoked AFTER the lock is acquired and the pid file is
|
|
40
|
+
* written (same boot, pinned ordering). Defaults to the real {@link registerHiveWithDoctor};
|
|
41
|
+
* tests inject a no-op or a recorder.
|
|
42
|
+
*/
|
|
43
|
+
readonly registerWithDoctor?: () => void;
|
|
33
44
|
}
|
|
34
45
|
export interface HiveInstance {
|
|
35
46
|
readonly app: Hono;
|
package/dist/daemon/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { serve } from "@hono/node-server";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { acquireSingleInstanceLock, releaseSingleInstanceLock } from "../lock.js";
|
|
4
|
+
import { registerHiveWithDoctor } from "../install/registry.js";
|
|
5
|
+
import { migrateHiveState } from "../shared/state-migration.js";
|
|
4
6
|
import { DOCTOR_STATUS_URL, HIVE_HOST, HIVE_PORT, HIVE_VERSION } from "../shared/constants.js";
|
|
5
7
|
import { mountDashboardAssets, mountDashboardShellFallback, renderShell } from "./dashboard/host.js";
|
|
6
8
|
import { fetchFleetStatus } from "./fleet-status.js";
|
|
@@ -98,8 +100,24 @@ export function createHive(options = {}) {
|
|
|
98
100
|
}
|
|
99
101
|
export function startHive(options = {}) {
|
|
100
102
|
const serveFn = options.serveFn ?? serve;
|
|
103
|
+
const migrateState = options.migrateState ?? (() => {
|
|
104
|
+
migrateHiveState();
|
|
105
|
+
});
|
|
106
|
+
const registerWithDoctor = options.registerWithDoctor ?? (() => {
|
|
107
|
+
registerHiveWithDoctor();
|
|
108
|
+
});
|
|
109
|
+
migrateState();
|
|
101
110
|
const hive = createHive(options);
|
|
102
111
|
const lockPaths = acquireSingleInstanceLock(options.lockPaths);
|
|
112
|
+
// rc-AC-2/mg-AC-7 ordering: the lock is held and the new pid file is written; NOW upsert the
|
|
113
|
+
// registry entry naming that pid path, in the same boot. Best-effort: boot never fails when the
|
|
114
|
+
// registry file is mid-move.
|
|
115
|
+
try {
|
|
116
|
+
registerWithDoctor();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Fail-soft by design.
|
|
120
|
+
}
|
|
103
121
|
let server;
|
|
104
122
|
try {
|
|
105
123
|
server = serveFn({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EAE1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,YAAY,EACb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAkB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAmB,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,6BAA6B,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,4BAA4B,EAAuB,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAgC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,11 +12,97 @@ import { Panel } from "../panels.js";
|
|
|
12
12
|
import { Badge, Button } from "../primitives.js";
|
|
13
13
|
import { isTabHidden, PageFrame } from "../page-frame.js";
|
|
14
14
|
import { NeedsProjectSelection } from "../needs-project.js";
|
|
15
|
+
import { FolderPicker } from "../folder-picker.js";
|
|
15
16
|
import { useScope } from "../scope-context.js";
|
|
16
17
|
import { GraphCanvasFull, KindToggle, ToolButton, IDENTITY_TRANSFORM, GRAPH_VIEW, MIN_ZOOM, MAX_ZOOM, ZOOM_STEP, applyKindFilter, centerOn, distinctKinds, } from "./graph.js";
|
|
17
|
-
import { capGraphForRender, EMPTY_GRAPH, EMPTY_HIVE_GRAPH_STATUS, MAX_RENDER_NODES, } from "../wire.js";
|
|
18
|
+
import { capGraphForRender, EMPTY_GRAPH, EMPTY_HIVE_GRAPH_STATUS, MAX_RENDER_NODES, EMPTY_NECTAR_PROJECTS, } from "../wire.js";
|
|
18
19
|
/** Poll interval for graph + status widgets (mirrors Memory Graph discipline). */
|
|
19
20
|
const HIVE_GRAPH_POLL_MS = 8000;
|
|
21
|
+
const SURFACE = {
|
|
22
|
+
padding: 16,
|
|
23
|
+
background: "var(--bg-surface)",
|
|
24
|
+
border: "1px solid var(--border-default)",
|
|
25
|
+
borderRadius: "var(--radius-lg)",
|
|
26
|
+
};
|
|
27
|
+
function broodingLabel(state) {
|
|
28
|
+
switch (state) {
|
|
29
|
+
case "active":
|
|
30
|
+
return "brooding";
|
|
31
|
+
case "paused":
|
|
32
|
+
return "paused";
|
|
33
|
+
case "global-paused":
|
|
34
|
+
return "global-paused";
|
|
35
|
+
default: {
|
|
36
|
+
const _exhaustive = state;
|
|
37
|
+
return _exhaustive;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function broodingBadgeTone(state) {
|
|
42
|
+
switch (state) {
|
|
43
|
+
case "active":
|
|
44
|
+
return "verified";
|
|
45
|
+
case "paused":
|
|
46
|
+
return "neutral";
|
|
47
|
+
case "global-paused":
|
|
48
|
+
return "warning";
|
|
49
|
+
default: {
|
|
50
|
+
const _exhaustive = state;
|
|
51
|
+
return _exhaustive;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** PRD-019c — nectar active projects + brooding controls (polls `GET /api/hive-graph/projects`). */
|
|
56
|
+
function NectarProjectsPanel({ wire }) {
|
|
57
|
+
const [projectsWire, setProjectsWire] = React.useState(EMPTY_NECTAR_PROJECTS);
|
|
58
|
+
const [hydrated, setHydrated] = React.useState(false);
|
|
59
|
+
const [busyKey, setBusyKey] = React.useState(null);
|
|
60
|
+
const inFlightRef = React.useRef(false);
|
|
61
|
+
const reList = React.useCallback(async () => {
|
|
62
|
+
const next = await wire.nectarProjects();
|
|
63
|
+
setProjectsWire(next);
|
|
64
|
+
setHydrated(true);
|
|
65
|
+
}, [wire]);
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
let alive = true;
|
|
68
|
+
const tick = async () => {
|
|
69
|
+
if (!alive || isTabHidden())
|
|
70
|
+
return;
|
|
71
|
+
await reList();
|
|
72
|
+
};
|
|
73
|
+
void tick();
|
|
74
|
+
const id = setInterval(() => void tick(), HIVE_GRAPH_POLL_MS);
|
|
75
|
+
return () => {
|
|
76
|
+
alive = false;
|
|
77
|
+
clearInterval(id);
|
|
78
|
+
};
|
|
79
|
+
}, [reList]);
|
|
80
|
+
const runBroodingWrite = React.useCallback(async (body, busy) => {
|
|
81
|
+
if (inFlightRef.current || projectsWire.unreachable)
|
|
82
|
+
return;
|
|
83
|
+
inFlightRef.current = true;
|
|
84
|
+
setBusyKey(busy);
|
|
85
|
+
try {
|
|
86
|
+
const ack = await wire.setNectarBrooding(body);
|
|
87
|
+
if (!ack.unreachable) {
|
|
88
|
+
setProjectsWire(ack);
|
|
89
|
+
}
|
|
90
|
+
await reList();
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
inFlightRef.current = false;
|
|
94
|
+
setBusyKey(null);
|
|
95
|
+
}
|
|
96
|
+
}, [wire, reList, projectsWire.unreachable]);
|
|
97
|
+
const onBound = React.useCallback(() => {
|
|
98
|
+
void reList();
|
|
99
|
+
}, [reList]);
|
|
100
|
+
const controlsDisabled = !hydrated || projectsWire.unreachable || busyKey !== null;
|
|
101
|
+
return (_jsx(Panel, { title: "Nectar projects", right: _jsx(Button, { variant: "secondary", size: "sm", "data-testid": "nectar-global-brooding-toggle", disabled: controlsDisabled, onClick: () => void runBroodingWrite({ global: projectsWire.globalBrooding === "on" ? "paused" : "on" }, "global"), children: projectsWire.globalBrooding === "on" ? "Pause all brooding" : "Resume all brooding" }), children: projectsWire.unreachable ? (_jsx("div", { "data-testid": "nectar-projects-unreachable", style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--severity-critical)" }, children: "Nectar is unreachable \u2014 project brooding controls are disabled." })) : !hydrated ? (_jsx("span", { style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)" }, children: "loading\u2026" })) : projectsWire.projects.length === 0 ? (_jsxs("div", { "data-testid": "nectar-needs-project", style: { display: "flex", flexDirection: "column", gap: 12 }, children: [_jsx("div", { style: { fontSize: 14, color: "var(--text-secondary)" }, children: "No active nectar projects yet. Pick a folder to activate brooding for a project directory." }), _jsx(FolderPicker, { wire: wire, onBound: onBound })] })) : (_jsx("div", { "data-testid": "nectar-projects-list", style: { display: "flex", flexDirection: "column", gap: 10 }, children: projectsWire.projects.map((project) => (_jsxs("div", { "data-testid": "nectar-project-row", "data-project-id": project.projectId, style: { ...SURFACE, display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }, children: [_jsx("span", { "data-testid": "nectar-project-name", style: { fontFamily: "var(--font-mono)", fontSize: 14, fontWeight: 600, color: "var(--honey)" }, children: project.name !== "" ? project.name : project.projectId }), _jsx(Badge, { tone: broodingBadgeTone(project.brooding), mono: true, children: _jsx("span", { "data-testid": "nectar-brooding-badge", children: broodingLabel(project.brooding) }) }), _jsx("span", { style: { flex: 1 } }), _jsx(Button, { variant: "ghost", size: "sm", "data-testid": "nectar-brooding-toggle", disabled: controlsDisabled, onClick: () => void runBroodingWrite({
|
|
102
|
+
projectId: project.projectId,
|
|
103
|
+
brooding: project.brooding === "active" ? "off" : "on",
|
|
104
|
+
}, project.projectId), children: project.brooding === "active" ? "Turn off brooding" : "Turn on brooding" })] }), _jsx("span", { "data-testid": "nectar-project-path", style: { fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-tertiary)", wordBreak: "break-all" }, children: project.path })] }, project.projectId))) })) }));
|
|
105
|
+
}
|
|
20
106
|
function clamp(n, lo, hi) {
|
|
21
107
|
return Math.min(hi, Math.max(lo, n));
|
|
22
108
|
}
|
|
@@ -191,51 +277,51 @@ export function HiveGraphPage({ wire }) {
|
|
|
191
277
|
const onSelect = React.useCallback((id) => setSelected((cur) => (cur === id ? null : id)), []);
|
|
192
278
|
const clearSelection = React.useCallback(() => setSelected(null), []);
|
|
193
279
|
const eyebrow = `${rendered.nodes.length} files · ${rendered.edges.length} provenance links`;
|
|
194
|
-
return (_jsx(PageFrame, { title: "Hive Graph", eyebrow: project === undefined ? "hive graph" : eyebrow, children:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
height: 30,
|
|
200
|
-
padding: "0 12px",
|
|
201
|
-
background: "var(--bg-surface)",
|
|
202
|
-
border: "1px solid var(--border-default)",
|
|
203
|
-
borderRadius: "var(--radius-md)",
|
|
204
|
-
color: "var(--text-primary)",
|
|
205
|
-
fontFamily: "var(--font-mono)",
|
|
206
|
-
fontSize: 13,
|
|
207
|
-
} }), _jsx(Button, { variant: "primary", onClick: () => void onSearchSubmit(), disabled: searching || searchQuery.trim() === "", "data-testid": "hive-graph-search-submit", children: searching ? "Searching…" : "Search" })] }), searchResults !== null && _jsx(SearchResults, { results: searchResults, onSelect: (hit) => focusNode(hit.id) })] }), graphUnreachable || !graph.built ? (_jsx(HiveGraphEmptyState, { unreachable: graphUnreachable })) : (_jsxs(_Fragment, { children: [capped && (_jsxs("div", { "data-testid": "hive-graph-truncation-notice", style: {
|
|
208
|
-
display: "flex",
|
|
209
|
-
alignItems: "center",
|
|
210
|
-
gap: 8,
|
|
211
|
-
padding: "9px 12px",
|
|
212
|
-
background: "var(--bg-elevated)",
|
|
213
|
-
border: "1px solid var(--border-subtle)",
|
|
214
|
-
borderRadius: "var(--radius-md)",
|
|
215
|
-
fontFamily: "var(--font-mono)",
|
|
216
|
-
fontSize: 12,
|
|
217
|
-
color: "var(--text-tertiary)",
|
|
218
|
-
}, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: "50%", background: "var(--severity-warning)", flex: "none" } }), "Rendering is capped at ", MAX_RENDER_NODES.toLocaleString(), " nodes. Use search and kind filters to focus."] })), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [_jsx("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: kinds.map((k) => {
|
|
219
|
-
const count = graph.nodes.filter((n) => n.kind === k).length;
|
|
220
|
-
return _jsx(KindToggle, { kind: k, count: count, hidden: hiddenKinds.has(k), onToggle: () => toggleKind(k) }, k);
|
|
221
|
-
}) }), _jsx("span", { style: { flex: 1 } }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [_jsx(ToolButton, { label: "\u2212", ariaLabel: "zoom out", onClick: zoomOut }), _jsx(ToolButton, { label: "+", ariaLabel: "zoom in", onClick: zoomIn }), _jsx("button", { type: "button", "data-testid": "hive-graph-fit-view", onClick: fit, style: {
|
|
280
|
+
return (_jsx(PageFrame, { title: "Hive Graph", eyebrow: project === undefined ? "hive graph" : eyebrow, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [_jsx(NectarProjectsPanel, { wire: wire }), project === undefined ? (_jsx(NeedsProjectSelection, { surface: "Hive Graph" })) : (_jsxs(_Fragment, { children: [_jsx(Panel, { title: "Pipeline status", right: _jsx(HiveGraphBuildButton, { onBuild: () => wire.hiveGraphBuild() }), children: _jsx(StatusWidgets, { status: status }) }), _jsxs(Panel, { title: "Search", children: [_jsxs("div", { style: { display: "flex", gap: 8, marginBottom: 12 }, children: [_jsx("input", { "aria-label": "search hive graph", "data-testid": "hive-graph-search-input", type: "text", value: searchQuery, disabled: project === undefined, placeholder: "search files and descriptions\u2026", onChange: (e) => setSearchQuery(e.target.value), onKeyDown: (e) => {
|
|
281
|
+
if (e.key === "Enter")
|
|
282
|
+
void onSearchSubmit();
|
|
283
|
+
}, style: {
|
|
284
|
+
flex: 1,
|
|
222
285
|
height: 30,
|
|
223
286
|
padding: "0 12px",
|
|
224
|
-
background: "
|
|
287
|
+
background: "var(--bg-surface)",
|
|
225
288
|
border: "1px solid var(--border-default)",
|
|
226
289
|
borderRadius: "var(--radius-md)",
|
|
227
|
-
color: "var(--text-
|
|
290
|
+
color: "var(--text-primary)",
|
|
228
291
|
fontFamily: "var(--font-mono)",
|
|
229
|
-
fontSize:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
background: "var(--bg-
|
|
236
|
-
border: "1px solid var(--border-
|
|
237
|
-
borderRadius: "var(--radius-
|
|
238
|
-
|
|
239
|
-
|
|
292
|
+
fontSize: 13,
|
|
293
|
+
} }), _jsx(Button, { variant: "primary", onClick: () => void onSearchSubmit(), disabled: searching || searchQuery.trim() === "", "data-testid": "hive-graph-search-submit", children: searching ? "Searching…" : "Search" })] }), searchResults !== null && _jsx(SearchResults, { results: searchResults, onSelect: (hit) => focusNode(hit.id) })] }), graphUnreachable || !graph.built ? (_jsx(HiveGraphEmptyState, { unreachable: graphUnreachable })) : (_jsxs(_Fragment, { children: [capped && (_jsxs("div", { "data-testid": "hive-graph-truncation-notice", style: {
|
|
294
|
+
display: "flex",
|
|
295
|
+
alignItems: "center",
|
|
296
|
+
gap: 8,
|
|
297
|
+
padding: "9px 12px",
|
|
298
|
+
background: "var(--bg-elevated)",
|
|
299
|
+
border: "1px solid var(--border-subtle)",
|
|
300
|
+
borderRadius: "var(--radius-md)",
|
|
301
|
+
fontFamily: "var(--font-mono)",
|
|
302
|
+
fontSize: 12,
|
|
303
|
+
color: "var(--text-tertiary)",
|
|
304
|
+
}, children: [_jsx("span", { style: { width: 7, height: 7, borderRadius: "50%", background: "var(--severity-warning)", flex: "none" } }), "Rendering is capped at ", MAX_RENDER_NODES.toLocaleString(), " nodes. Use search and kind filters to focus."] })), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [_jsx("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }, children: kinds.map((k) => {
|
|
305
|
+
const count = graph.nodes.filter((n) => n.kind === k).length;
|
|
306
|
+
return _jsx(KindToggle, { kind: k, count: count, hidden: hiddenKinds.has(k), onToggle: () => toggleKind(k) }, k);
|
|
307
|
+
}) }), _jsx("span", { style: { flex: 1 } }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [_jsx(ToolButton, { label: "\u2212", ariaLabel: "zoom out", onClick: zoomOut }), _jsx(ToolButton, { label: "+", ariaLabel: "zoom in", onClick: zoomIn }), _jsx("button", { type: "button", "data-testid": "hive-graph-fit-view", onClick: fit, style: {
|
|
308
|
+
height: 30,
|
|
309
|
+
padding: "0 12px",
|
|
310
|
+
background: "transparent",
|
|
311
|
+
border: "1px solid var(--border-default)",
|
|
312
|
+
borderRadius: "var(--radius-md)",
|
|
313
|
+
color: "var(--text-secondary)",
|
|
314
|
+
fontFamily: "var(--font-mono)",
|
|
315
|
+
fontSize: 12,
|
|
316
|
+
cursor: "pointer",
|
|
317
|
+
}, children: "fit" })] })] }), _jsxs("div", { style: { display: "flex", gap: 16, alignItems: "stretch" }, children: [_jsx("div", { style: {
|
|
318
|
+
flex: 1,
|
|
319
|
+
minWidth: 0,
|
|
320
|
+
height: "70vh",
|
|
321
|
+
background: "var(--bg-surface)",
|
|
322
|
+
border: "1px solid var(--border-default)",
|
|
323
|
+
borderRadius: "var(--radius-lg)",
|
|
324
|
+
overflow: "hidden",
|
|
325
|
+
}, children: _jsx(GraphCanvasFull, { graph: rendered, selected: selectedNode?.id ?? null, transform: transform, onSelect: onSelect, onClear: clearSelection, onPanZoom: setTransform }) }), selectedNode !== null && (_jsx(FileNodeDetailPanel, { node: selectedNode, file: files[selectedNode.id], derived: derived[selectedNode.id], onClear: clearSelection }))] })] }))] }))] }) }));
|
|
240
326
|
}
|
|
241
327
|
//# sourceMappingURL=hive-graph.js.map
|