@tpsdev-ai/flair 0.25.0 → 0.25.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/README.md CHANGED
@@ -134,6 +134,15 @@ flair memory search "native addon loading in sandboxed runtimes"
134
134
  # → [0.67] Harper v5 sandbox blocks node:module but process.dlopen works
135
135
  ```
136
136
 
137
+ ### Trust-Graded Recall
138
+ Recall can carry an opt-in **trust-evidence block** per result — provenance (verified vs claimed author), usage signal, freshness/validity, supersession — so an agent weighs *what to trust*, not just *what matched*. On top of it:
139
+
140
+ - **Confidence bands** (`matchQuality`): each result is labeled `strong` / `moderate` / `breadcrumb` from its absolute similarity — a weak-but-relevant hit is taken for what it is, not mistaken for a confident one.
141
+ - **First-class abstention** (`abstain`): when nothing clears a confidence floor, recall returns an honest "no memory covers this" verdict instead of the N weakest matches.
142
+ - **Citation-on-write** (`usedMemoryIds`) + **`record_usage`**: report which memories actually grounded an answer — a deduped, principal-bound usage signal (honest evidence, never retrieval-popularity) that strengthens future recall.
143
+
144
+ Opt-in and additive (`includeTrust` / `abstain` on the recall path) — off by default, byte-identical when unused. Reachable today via the authenticated HTTP API and the native `/mcp` tools; first-class exposure in the `flair` CLI, `@tpsdev-ai/flair-client`, and the `flair-mcp` bridge is a follow-up. Full arc in the [CHANGELOG](CHANGELOG.md) (flair#744).
145
+
137
146
  ### Tiered Durability
138
147
  Not all memories are equal:
139
148
 
@@ -340,7 +349,7 @@ Add to your `CLAUDE.md`:
340
349
 
341
350
  At the start of every session, run mcp__flair__bootstrap before responding.
342
351
 
343
- Your agent's memory **follows it across CLIs** — same Flair instance, same agent identity, switch from Claude Code to Gemini CLI to Codex CLI without losing state. The MCP server exposes `memory_store`, `memory_search`, `memory_get`, `memory_delete`, `bootstrap`, `soul_set`, `soul_get`.
352
+ Your agent's memory **follows it across CLIs** — same Flair instance, same agent identity, switch from Claude Code to Gemini CLI to Codex CLI without losing state. The `flair-mcp` server — the bridge these CLIs connect to — exposes `memory_store`, `memory_search`, `memory_update`, `memory_get`, `memory_delete`, `relationship_store`, `bootstrap`, `soul_set`, `soul_get`, `flair_workspace_set`, and `flair_orgevent`. (Flair's in-Harper native `/mcp` surface is a separate, still-experimental tool set with `attention` + `record_usage` — see Trust-Graded Recall above.)
344
353
 
345
354
  For per-CLI config snippets (Gemini CLI's `~/.gemini/settings.json`, Codex CLI's `~/.codex/config.toml`, etc.), see **[docs/mcp-clients.md](docs/mcp-clients.md)**. For a deeper Claude Code walk-through with `CLAUDE.md` patterns, see [docs/claude-code.md](docs/claude-code.md).
346
355
 
@@ -353,7 +362,7 @@ Use Flair as the memory backend for n8n's AI Agent. Same memories readable from
353
362
  @tpsdev-ai/n8n-nodes-flair
354
363
  ```
355
364
 
356
- Two nodes ship: **Flair Chat Memory** (Memory port, conversation buffer) and **Flair Search** (Tool port, semantic search + get-by-subject). Setup walkthrough, subject/sessionId patterns, and security guidance in **[docs/n8n.md](docs/n8n.md)**.
365
+ Three nodes ship: **Flair Chat Memory** (Memory port, conversation buffer), **Flair Search** (Tool port, semantic search + get-by-subject), and **Flair Write** (Tool port, store memories). Setup walkthrough, subject/sessionId patterns, and security guidance in **[docs/n8n.md](docs/n8n.md)**.
357
366
 
358
367
  ### JavaScript / TypeScript (Client Library)
359
368
 
@@ -541,13 +550,13 @@ Flair is in active development and daily use. We dogfood it — the agents that
541
550
  - ✅ Web admin UI (principals, connectors, IdPs, instance config)
542
551
  - ✅ Federation (hub-and-spoke sync with signed requests and pairing tokens)
543
552
  - ✅ OpenClaw memory plugin
544
- - ✅ MCP server for Claude Code / Cursor / Windsurf
553
+ - ✅ MCP server for Claude Code / Cursor / Codex / Gemini CLI
545
554
  - ✅ Lightweight client library (`@tpsdev-ai/flair-client`)
546
555
  - ✅ Portable agent identity (export/import between instances)
547
556
  - ✅ `flair --version`, `flair upgrade`
557
+ - ✅ First-run soul wizard (interactive personality setup)
548
558
 
549
559
  **What's next:**
550
- - [ ] First-run soul wizard (interactive personality setup)
551
560
  - [ ] Git-backed memory sync
552
561
  - [ ] Encryption at rest (opt-in AES-256-GCM per memory)
553
562
  - [ ] Harper Fabric deployment (managed multi-office)
package/dist/cli.js CHANGED
@@ -12,8 +12,8 @@ import { create as tarCreate, extract as tarExtract, list as tarList } from "tar
12
12
  import { keystore } from "./keystore.js";
13
13
  import { deploy as deployToFabric, validateOptions as validateDeployOptions, buildTargetUrl as buildDeployUrl } from "./deploy.js";
14
14
  import { fabricUpgrade } from "./fabric-upgrade.js";
15
- import { checkVersion, formatVersionNudge } from "./version-check.js";
16
- import { checkServerHandshake, formatHandshakeNudge } from "./version-handshake.js";
15
+ import { checkVersion, formatVersionNudge, primeVersionCheckCache, FLAIR_PKG_NAME } from "./version-check.js";
16
+ import { checkServerHandshake, formatHandshakeNudge, invalidateHandshakeCache } from "./version-handshake.js";
17
17
  import { probeInstance } from "./probe.js";
18
18
  import { sweepFleet, renderFleetSweepTable, FLEET_EXIT_OK, } from "./fleet-verify.js";
19
19
  import { markStale, sortOldestVersionFirst } from "./fleet-presence.js";
@@ -1641,6 +1641,23 @@ export function decideAfterVerify(result, previousVersion) {
1641
1641
  if (result.ok)
1642
1642
  return { kind: "ok" };
1643
1643
  const reason = result.error ?? "post-restart verification failed";
1644
+ // A HEALTHY instance whose ONLY failure was that the verifier couldn't
1645
+ // authenticate (401/403) is demonstrably UP: the public /Health answered
1646
+ // 2xx AND the server responded to the authenticated probe — it rejected our
1647
+ // credentials, it did not fail to respond. A version we couldn't READ is not
1648
+ // grounds to roll back a RUNNING instance. Rolling back here is destructive
1649
+ // AND self-defeating — the rollback's own re-verify hits the identical
1650
+ // missing-credential wall, producing the false "ROLLBACK ALSO FAILED / state
1651
+ // UNKNOWN" for an instance that was healthy the whole time. (The real
1652
+ // incident: /HealthDetail became a verified-read in flair#747, so a machine
1653
+ // with no admin-pass/agent key authenticates fine against the pre-upgrade
1654
+ // version but not post-restart — the pre-flight credential check can't
1655
+ // anticipate a version that changes /HealthDetail's auth requirement.)
1656
+ // Report it as up-but-unverified and NEVER roll back. This supersedes
1657
+ // flair#741 fix #3's "prefer the known-good version" default, which the
1658
+ // incident proved wrong for a healthy instance.
1659
+ if (isCredentialOnlyFailure(result))
1660
+ return { kind: "healthy-unverified", reason };
1644
1661
  if (!previousVersion)
1645
1662
  return { kind: "cannot-rollback", reason };
1646
1663
  return { kind: "rollback", reason, toVersion: previousVersion };
@@ -8036,6 +8053,12 @@ program
8036
8053
  continue;
8037
8054
  const data = await res.json();
8038
8055
  const latest = data.version ?? "unknown";
8056
+ if (name === FLAIR_PKG_NAME && latest !== "unknown") {
8057
+ try {
8058
+ primeVersionCheckCache(latest);
8059
+ }
8060
+ catch { /* best-effort */ }
8061
+ }
8039
8062
  const installed = probe();
8040
8063
  let status;
8041
8064
  if (installed === null) {
@@ -8352,18 +8375,22 @@ program
8352
8375
  console.log(`✅ verified: healthy, authenticated${verify.version ? `, running ${verify.version}` : ""}`);
8353
8376
  return;
8354
8377
  }
8355
- console.error(`❌ post-restart verification failed: ${verdict.reason}`);
8356
- if (isCredentialOnlyFailure(verify)) {
8357
- // flair#741 fix #3: a responding server that rejects the verifier's
8358
- // credentials proves the instance is UPit is not evidence the
8359
- // upgrade itself broke anything. Say so explicitly instead of leaving
8360
- // the reader to infer it from the raw error text. Rollback still
8361
- // proceeds below (a credential check that worked pre-upgrade see
8362
- // the pre-flight above and stopped working mid-run is an edge case
8363
- // ambiguous enough that "prefer the known-good version" is still the
8364
- // safer default), but the reason for it is now honest.
8365
- console.error(" The instance is up and responded — the verifier could not authenticate. This is not a sign the upgrade broke anything.");
8378
+ // flair#741 follow-through: a healthy instance the verifier just couldn't
8379
+ // authenticate against. The upgrade SUCCEEDED — the new version's server is
8380
+ // up (public /Health passed); we simply couldn't read its version over the
8381
+ // authenticated /HealthDetail. Report the caveat and STOPnever roll back
8382
+ // a running instance over a credentials gap. (decideAfterVerify only
8383
+ // returns this for isCredentialOnlyFailure(verify), so the old
8384
+ // "print an honest note but roll back anyway" branch that used to sit below
8385
+ // is gonethat credentials case can no longer reach the rollback path.)
8386
+ if (verdict.kind === "healthy-unverified") {
8387
+ console.log(`✅ upgrade complete: the instance is up and healthy${expectedFlairVersion ? ` on @tpsdev-ai/flair@${expectedFlairVersion}` : ""}.`);
8388
+ console.log(` The version could not be verified — the checker couldn't authenticate to /HealthDetail (${verdict.reason}).`);
8389
+ console.log(" The server is confirmed running (public /Health passed); this is a verification gap, not an upgrade failure — nothing was rolled back.");
8390
+ console.log(" To enable full post-upgrade verification: set FLAIR_ADMIN_PASS, or run `flair init` to provision ~/.flair/admin-pass or an agent key.");
8391
+ return;
8366
8392
  }
8393
+ console.error(`❌ post-restart verification failed: ${verdict.reason}`);
8367
8394
  if (verdict.kind === "cannot-rollback") {
8368
8395
  console.error(" Cannot roll back automatically: the previously-installed @tpsdev-ai/flair version is unknown.");
8369
8396
  console.error(" Check the instance now: flair doctor");
@@ -8701,6 +8728,15 @@ async function startFlairProcess(port) {
8701
8728
  async function restartFlair(port) {
8702
8729
  await stopFlairProcess(port);
8703
8730
  await startFlairProcess(port);
8731
+ // Bust the version-handshake cache so the next preAction nudge re-fetches
8732
+ // the LIVE version instead of the pre-restart cached one (the false
8733
+ // "server is running <old>" users hit for up to 60s post-upgrade+restart).
8734
+ // Same (rootPath, serverUrl) key the preAction hook computes (~line 2189)
8735
+ // — must match exactly, or this busts the wrong cache file.
8736
+ try {
8737
+ invalidateHandshakeCache(process.env.ROOTPATH ?? defaultDataDir(), `http://127.0.0.1:${port}`);
8738
+ }
8739
+ catch { /* best-effort — never fail a restart over cache cleanup */ }
8704
8740
  }
8705
8741
  program
8706
8742
  .command("restart")
@@ -116,6 +116,19 @@ export async function checkVersion(installed, injected = {}) {
116
116
  }
117
117
  return { installed, latest: null, source: "unavailable" };
118
118
  }
119
+ /**
120
+ * Primes the version-check cache with an already-known `latest` — e.g. right
121
+ * after `flair upgrade` fetches the true latest fresh via its own direct
122
+ * registry call, so the NEXT `checkVersion` (from `flair status`/`doctor`)
123
+ * reflects it immediately instead of serving a stale cached value for up to
124
+ * `DEFAULT_TTL_MS`. Reuses `writeCacheFile`, which is already best-effort/
125
+ * never-throws — a failed cache write must never surface as a command error.
126
+ */
127
+ export function primeVersionCheckCache(latest, injected = {}) {
128
+ const cachePath = injected.cachePath ?? DEFAULT_CACHE_PATH;
129
+ const now = injected.now ?? (() => Date.now());
130
+ writeCacheFile(cachePath, { latest, checkedAt: now() });
131
+ }
119
132
  const NO_GAP = { severity: "none", majorBehind: false, releasesBehind: 0 };
120
133
  /**
121
134
  * Classify how far `installed` is behind `latest` using major.minor.patch
@@ -14,7 +14,7 @@
14
14
  * ROOTPATH, or a --target-switched remote), and a mismatch cached for one
15
15
  * must never bleed into a nudge about a different one.
16
16
  */
17
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
18
18
  import { createHash } from "node:crypto";
19
19
  import { dirname, join } from "node:path";
20
20
  import { homedir } from "node:os";
@@ -110,6 +110,25 @@ export async function checkServerHandshake(cliVersion, rootPath, serverUrl, inje
110
110
  }
111
111
  return { cliVersion, runningVersion: null, mismatch: false, source: "unavailable" };
112
112
  }
113
+ /**
114
+ * Deletes the cached running-version entry for a given (rootPath, serverUrl)
115
+ * pair — e.g. after `flair restart`/`flair upgrade` bounces the server, so
116
+ * the NEXT `checkServerHandshake` call re-fetches the live version instead
117
+ * of returning the pre-restart one for up to `DEFAULT_HANDSHAKE_TTL_MS`.
118
+ * Best-effort, like the rest of this module: a failed cache delete (missing
119
+ * file, permissions, read-only $HOME) must never surface as an error.
120
+ */
121
+ export function invalidateHandshakeCache(rootPath, serverUrl, injected = {}) {
122
+ const cacheDir = injected.cacheDir ?? DEFAULT_HANDSHAKE_CACHE_DIR;
123
+ try {
124
+ const path = cacheFilePath(cacheDir, rootPath, serverUrl);
125
+ if (existsSync(path))
126
+ rmSync(path);
127
+ }
128
+ catch {
129
+ // Best-effort — a failed cache delete must never surface as an error.
130
+ }
131
+ }
113
132
  /**
114
133
  * The one-line stderr nudge, or null when there's nothing worth printing
115
134
  * (no mismatch, or nothing to compare against). Exact wording per the spec:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.25.0",
3
+ "version": "0.25.2",
4
4
  "packageManager": "bun@1.3.10",
5
5
  "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
6
6
  "type": "module",