@tpsdev-ai/flair 0.53.0 → 0.54.1
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 +4 -1
- package/dist/build-info.json +3 -3
- package/dist/cli.js +1791 -15648
- package/dist/commands/agent.js +453 -0
- package/dist/commands/attention.js +121 -0
- package/dist/commands/backup.js +115 -0
- package/dist/commands/bootstrap.js +91 -0
- package/dist/commands/bridge.js +608 -0
- package/dist/commands/deploy.js +180 -0
- package/dist/commands/doctor.js +1654 -0
- package/dist/commands/export.js +110 -0
- package/dist/commands/federation.js +1575 -0
- package/dist/commands/fleet.js +73 -0
- package/dist/commands/grant.js +109 -0
- package/dist/commands/hook.js +193 -0
- package/dist/commands/idp.js +193 -0
- package/dist/commands/import.js +134 -0
- package/dist/commands/init.js +1203 -0
- package/dist/commands/inspect.js +45 -0
- package/dist/commands/keys.js +187 -0
- package/dist/commands/mcp.js +707 -0
- package/dist/commands/memory.js +501 -0
- package/dist/commands/migrate-harness-memory.js +270 -0
- package/dist/commands/orgevent.js +138 -0
- package/dist/commands/presence.js +76 -0
- package/dist/commands/principal.js +338 -0
- package/dist/commands/quality.js +1164 -0
- package/dist/commands/reembed.js +296 -0
- package/dist/commands/relationship.js +76 -0
- package/dist/commands/rem.js +1048 -0
- package/dist/commands/restore.js +130 -0
- package/dist/commands/search.js +244 -0
- package/dist/commands/service.js +315 -0
- package/dist/commands/session.js +184 -0
- package/dist/commands/soul.js +155 -0
- package/dist/commands/status.js +914 -0
- package/dist/commands/test.js +93 -0
- package/dist/commands/uninstall.js +143 -0
- package/dist/commands/upgrade.js +1592 -0
- package/dist/commands/workspace.js +114 -0
- package/dist/deploy.js +24 -0
- package/dist/fabric-npm-install.js +87 -0
- package/dist/federation-verify.js +498 -0
- package/dist/fleet-verify.js +144 -21
- package/dist/install/clients.js +167 -0
- package/dist/lib/auth-resolve.js +76 -1
- package/dist/lib/daemon-liveness.js +131 -2
- package/dist/lib/doctor-config-path.js +61 -0
- package/dist/lib/doctor-federation-driver.js +189 -0
- package/dist/lib/doctor-run.js +40 -0
- package/dist/lib/entity-vocab-cli.js +3 -3
- package/dist/lib/federation-pair-identity.js +47 -0
- package/dist/lib/launchd-repair.js +5 -4
- package/dist/lib/ops-api-bind.js +115 -0
- package/dist/lib/owned-pins.js +219 -0
- package/dist/lib/uninstall-purge.js +218 -0
- package/dist/rem/restore.js +8 -10
- package/dist/resources/AgentReadPosition.js +74 -0
- package/dist/resources/Federation.js +8 -2
- package/dist/resources/Memory.js +4 -3
- package/dist/resources/MemoryBootstrap.js +41 -25
- package/dist/resources/MemoryCandidate.js +5 -6
- package/dist/resources/OrgEventCatchup.js +126 -47
- package/dist/resources/agent-read-position-lib.js +83 -0
- package/dist/resources/agent-read-position.js +120 -0
- package/dist/resources/embeddings-boot.js +32 -0
- package/dist/resources/federation-peer-liveness.js +73 -0
- package/dist/resources/health.js +68 -19
- package/dist/resources/mcp-tools.js +43 -279
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +59 -18
- package/dist/resources/migrations/embedding-stamp.js +20 -1
- package/dist/resources/migrations/recheck.js +43 -0
- package/dist/resources/migrations/runner.js +6 -1
- package/dist/resources/migrations/stamp-outstanding.js +171 -0
- package/dist/resources/migrations/visibility-backfill.js +2 -2
- package/dist/resources/org-event-catchup-lib.js +47 -0
- package/dist/resources/record-owner-guard.js +1 -0
- package/dist/stamp-migration-verify.js +163 -0
- package/dist/stamp-outstanding.js +144 -0
- package/docs/api-reference.md +4 -2
- package/docs/deploying-on-fabric.md +11 -10
- package/docs/deployment.md +3 -1
- package/docs/federation.md +19 -0
- package/docs/hosted-on-fabric.md +3 -3
- package/docs/quickstart.md +2 -1
- package/docs/releasing.md +15 -7
- package/docs/spoke-bringup.md +10 -5
- package/docs/standalone-local.md +3 -1
- package/docs/upgrade.md +25 -6
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/LICENSE +19 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/README.md +22 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/dist/index.d.ts +70 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/dist/index.js +665 -0
- package/node_modules/@tpsdev-ai/flair-tool-descriptors/package.json +46 -0
- package/package.json +9 -4
- package/schemas/agent.graphql +15 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure classifier for federation peer liveness — no Harper imports.
|
|
3
|
+
*
|
|
4
|
+
* flair#1499: HealthDetail used to count `status === "connected"` (a value
|
|
5
|
+
* pairing never writes — spokes stay `paired` even after a successful
|
|
6
|
+
* lastSyncAt advance) and then take the oldest lastSyncAt across ALL peers,
|
|
7
|
+
* including revoked. A healthy hub synced a minute ago plus a revoked row
|
|
8
|
+
* from June therefore rendered as "0 connected" and fired
|
|
9
|
+
* "federation peers all disconnected >24h".
|
|
10
|
+
*
|
|
11
|
+
* Three contact states, plus revoked (which never drives the staleness
|
|
12
|
+
* warning). Same class as #988: missing/unreadable evidence is UNKNOWN,
|
|
13
|
+
* never DISCONNECTED.
|
|
14
|
+
*
|
|
15
|
+
* (a) real lastSyncAt within the window → connected
|
|
16
|
+
* (b) real lastSyncAt older than the window → disconnected
|
|
17
|
+
* (c) no parseable lastSyncAt → unknown (never rendered as (b))
|
|
18
|
+
* revoked stored status → revoked (excluded from the >24h warning)
|
|
19
|
+
*/
|
|
20
|
+
export const PEER_STALE_MS = 24 * 3600 * 1000;
|
|
21
|
+
export const FEDERATION_PEERS_ALL_DISCONNECTED_WARNING = "federation peers all disconnected >24h";
|
|
22
|
+
/** Parse a written last-contact stamp. Missing / unparseable → null, never 0/epoch. */
|
|
23
|
+
export function parseLastContactMs(lastSyncAt) {
|
|
24
|
+
if (typeof lastSyncAt !== "string")
|
|
25
|
+
return null;
|
|
26
|
+
const trimmed = lastSyncAt.trim();
|
|
27
|
+
if (!trimmed)
|
|
28
|
+
return null;
|
|
29
|
+
const t = Date.parse(trimmed);
|
|
30
|
+
return Number.isFinite(t) ? t : null;
|
|
31
|
+
}
|
|
32
|
+
export function classifyPeerLiveness(peer, nowMs, windowMs = PEER_STALE_MS) {
|
|
33
|
+
if (peer.status === "revoked")
|
|
34
|
+
return "revoked";
|
|
35
|
+
const ts = parseLastContactMs(peer.lastSyncAt);
|
|
36
|
+
if (ts === null)
|
|
37
|
+
return "unknown";
|
|
38
|
+
// Future / equal-now stamps are contact within the window (clock skew).
|
|
39
|
+
if (nowMs - ts > windowMs)
|
|
40
|
+
return "disconnected";
|
|
41
|
+
return "connected";
|
|
42
|
+
}
|
|
43
|
+
export function summarizePeerLiveness(peers, nowMs, windowMs = PEER_STALE_MS) {
|
|
44
|
+
let connected = 0;
|
|
45
|
+
let disconnected = 0;
|
|
46
|
+
let revoked = 0;
|
|
47
|
+
let unknown = 0;
|
|
48
|
+
for (const peer of peers) {
|
|
49
|
+
const liveness = classifyPeerLiveness(peer, nowMs, windowMs);
|
|
50
|
+
if (liveness === "connected")
|
|
51
|
+
connected++;
|
|
52
|
+
else if (liveness === "disconnected")
|
|
53
|
+
disconnected++;
|
|
54
|
+
else if (liveness === "revoked")
|
|
55
|
+
revoked++;
|
|
56
|
+
else
|
|
57
|
+
unknown++;
|
|
58
|
+
}
|
|
59
|
+
const nonRevoked = peers.length - revoked;
|
|
60
|
+
return {
|
|
61
|
+
total: peers.length,
|
|
62
|
+
connected,
|
|
63
|
+
disconnected,
|
|
64
|
+
revoked,
|
|
65
|
+
unknown,
|
|
66
|
+
allNonRevokedDisconnected: nonRevoked > 0 && disconnected === nonRevoked,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function federationPeersAllDisconnectedWarning(summary) {
|
|
70
|
+
if (!summary.allNonRevokedDisconnected)
|
|
71
|
+
return null;
|
|
72
|
+
return { level: "warn", message: FEDERATION_PEERS_ALL_DISCONNECTED_WARNING };
|
|
73
|
+
}
|
package/dist/resources/health.js
CHANGED
|
@@ -11,7 +11,10 @@ import { REM_DEDUP_STATS_PATH } from "./dedup-cluster.js";
|
|
|
11
11
|
import { hybridEnabled } from "./bm25.js";
|
|
12
12
|
import { bm25IndexEnabled, bm25IndexStatus } from "./bm25-index-service.js";
|
|
13
13
|
import { normalizeStamp } from "./embedding-space-guard.js";
|
|
14
|
+
import { getModelId } from "./embeddings-provider.js";
|
|
15
|
+
import { describeStampOutstanding, EMBEDDING_STAMP_ID } from "./migrations/stamp-outstanding.js";
|
|
14
16
|
import { buildPublicHealthBody, resolveSearchReadiness } from "./search-readiness.js";
|
|
17
|
+
import { classifyPeerLiveness, federationPeersAllDisconnectedWarning, summarizePeerLiveness, } from "./federation-peer-liveness.js";
|
|
15
18
|
const db = databases;
|
|
16
19
|
const redactHome = (p) => {
|
|
17
20
|
const home = homedir();
|
|
@@ -160,6 +163,14 @@ export class HealthDetail extends Resource {
|
|
|
160
163
|
const stats = { ok: true };
|
|
161
164
|
const nowMs = Date.now();
|
|
162
165
|
const warnings = [];
|
|
166
|
+
// flair#1073: set while walking memories, consumed after the migrations
|
|
167
|
+
// snapshot so the outstanding-migration warning can name runner state.
|
|
168
|
+
// Never copied onto `stats` — it is not part of the /HealthDetail shape.
|
|
169
|
+
// `memoryScanOk` is the gate (not "mixed spaces"): a uniformly pre-flip
|
|
170
|
+
// corpus is one space and still outstanding against getModelId().
|
|
171
|
+
let mixedEmbeddingSpaces = false;
|
|
172
|
+
let memoryScanOk = false;
|
|
173
|
+
let memoryModelCounts = {};
|
|
163
174
|
// flair#1326: same search-ready signal as public /Health. HealthDetail
|
|
164
175
|
// stays HTTP 200 (it is a stats dump, not a traffic gate); the field
|
|
165
176
|
// and a warning name the lag so `flair status` / operators can see it.
|
|
@@ -258,13 +269,12 @@ export class HealthDetail extends Resource {
|
|
|
258
269
|
// re-embed) still trips it, and that is exactly when the query-time guard
|
|
259
270
|
// degrades recall to keyword-only.
|
|
260
271
|
const distinctSpaces = new Set(realModels.map((k) => normalizeStamp(k)).filter((s) => s !== null));
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
272
|
+
// Always keep counts — a uniformly stale pre-flip corpus is one space
|
|
273
|
+
// and must still name embedding-stamp (Bugbot High on flair#1606).
|
|
274
|
+
memoryScanOk = true;
|
|
275
|
+
memoryModelCounts = modelCounts;
|
|
276
|
+
if (distinctSpaces.size > 1)
|
|
277
|
+
mixedEmbeddingSpaces = true;
|
|
268
278
|
}
|
|
269
279
|
catch {
|
|
270
280
|
stats.memories = null;
|
|
@@ -378,11 +388,16 @@ export class HealthDetail extends Resource {
|
|
|
378
388
|
}
|
|
379
389
|
else {
|
|
380
390
|
const inst = instances[0];
|
|
391
|
+
// flair#1499: derive connected/down from lastSyncAt, not stored
|
|
392
|
+
// status. Pairing writes `paired`; a recent lastSyncAt is connected.
|
|
393
|
+
// Revoked rows are counted separately and never drive the >24h warning.
|
|
394
|
+
const liveness = summarizePeerLiveness(peers, nowMs);
|
|
381
395
|
const peersBlock = {
|
|
382
|
-
total:
|
|
383
|
-
connected:
|
|
384
|
-
disconnected:
|
|
385
|
-
revoked:
|
|
396
|
+
total: liveness.total,
|
|
397
|
+
connected: liveness.connected,
|
|
398
|
+
disconnected: liveness.disconnected,
|
|
399
|
+
revoked: liveness.revoked,
|
|
400
|
+
unknown: liveness.unknown,
|
|
386
401
|
};
|
|
387
402
|
const pendingTokens = tokens.filter((t) => !t.consumedBy && t.expiresAt && new Date(t.expiresAt).getTime() > nowMs).length;
|
|
388
403
|
stats.federation = {
|
|
@@ -395,17 +410,13 @@ export class HealthDetail extends Resource {
|
|
|
395
410
|
role: p.role,
|
|
396
411
|
status: p.status,
|
|
397
412
|
lastSyncAt: p.lastSyncAt ?? null,
|
|
413
|
+
liveness: classifyPeerLiveness(p, nowMs),
|
|
398
414
|
}))
|
|
399
415
|
: undefined,
|
|
400
416
|
};
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.reduce((a, b) => (a === 0 ? b : b === 0 ? a : Math.min(a, b)), 0);
|
|
405
|
-
if (oldest > 0 && nowMs - oldest > 24 * 3600 * 1000) {
|
|
406
|
-
warnings.push({ level: "warn", message: "federation peers all disconnected >24h" });
|
|
407
|
-
}
|
|
408
|
-
}
|
|
417
|
+
const disconnectWarn = federationPeersAllDisconnectedWarning(liveness);
|
|
418
|
+
if (disconnectWarn)
|
|
419
|
+
warnings.push(disconnectWarn);
|
|
409
420
|
if (pendingTokens > 0) {
|
|
410
421
|
warnings.push({ level: "info", message: `${pendingTokens} pairing token(s) unconsumed` });
|
|
411
422
|
}
|
|
@@ -660,9 +671,47 @@ export class HealthDetail extends Resource {
|
|
|
660
671
|
message: "migration boot cycle never fired on this instance (cyclePhase=idle) — no migration will run until this is resolved; see `flair doctor`",
|
|
661
672
|
});
|
|
662
673
|
}
|
|
674
|
+
if (memoryScanOk) {
|
|
675
|
+
const stamp = snapshot.migrations.find((m) => m.id === EMBEDDING_STAMP_ID);
|
|
676
|
+
const outstanding = describeStampOutstanding({
|
|
677
|
+
modelCounts: memoryModelCounts,
|
|
678
|
+
currentModelId: getModelId(),
|
|
679
|
+
migration: stamp,
|
|
680
|
+
cyclePhase: snapshot.cyclePhase,
|
|
681
|
+
lastCycleError: snapshot.lastCycleError,
|
|
682
|
+
});
|
|
683
|
+
if (outstanding.outstanding) {
|
|
684
|
+
warnings.push({ level: "warn", message: outstanding.warning });
|
|
685
|
+
}
|
|
686
|
+
else if (mixedEmbeddingSpaces) {
|
|
687
|
+
const list = Object.entries(memoryModelCounts)
|
|
688
|
+
.filter(([k, n]) => k !== "hash-512d" && n > 0)
|
|
689
|
+
.map(([k, n]) => `${k}:${n}`)
|
|
690
|
+
.join(", ");
|
|
691
|
+
warnings.push({
|
|
692
|
+
level: "warn",
|
|
693
|
+
message: `multiple embedding models in use (${list}) — cross-model search unreliable; run: flair reembed against one model`,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
663
697
|
}
|
|
664
698
|
catch {
|
|
665
699
|
stats.migrations = null;
|
|
700
|
+
if (memoryScanOk) {
|
|
701
|
+
const outstanding = describeStampOutstanding({
|
|
702
|
+
modelCounts: memoryModelCounts,
|
|
703
|
+
currentModelId: getModelId(),
|
|
704
|
+
});
|
|
705
|
+
if (outstanding.outstanding) {
|
|
706
|
+
warnings.push({ level: "warn", message: outstanding.warning });
|
|
707
|
+
}
|
|
708
|
+
else if (mixedEmbeddingSpaces) {
|
|
709
|
+
warnings.push({
|
|
710
|
+
level: "warn",
|
|
711
|
+
message: `multiple embedding models in use — cross-model search unreliable; run: flair reembed against one model`,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
666
715
|
}
|
|
667
716
|
// ── Disk ──
|
|
668
717
|
// flair#812: same shared read-only resolution as the migrations section
|