@tpsdev-ai/flair 0.35.0 → 0.37.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/dist/cli.js +95 -22
- package/dist/deploy.js +123 -6
- package/dist/lib/auth-resolve.js +19 -0
- package/dist/lib/mcp-enable.js +107 -18
- package/dist/resources/Credential.js +55 -16
- package/dist/resources/Memory.js +43 -20
- package/dist/resources/MemoryFeed.js +40 -5
- package/dist/resources/Presence.js +8 -1
- package/dist/resources/WorkspaceState.js +7 -16
- package/dist/resources/agent-auth.js +41 -2
- package/dist/resources/auth-middleware.js +65 -36
- package/dist/resources/memory-visibility.js +44 -0
- package/dist/resources/record-type-kit.js +49 -0
- package/dist/version-check.js +45 -0
- package/docs/upgrade.md +31 -12
- package/package.json +7 -4
|
@@ -211,6 +211,55 @@ export function makeByIdReadGate(readScope) {
|
|
|
211
211
|
return record;
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
|
+
// ─── (c) Scoped search — makeScopedSearch ────────────────────────────────
|
|
215
|
+
/**
|
|
216
|
+
* Produces a scoped search() override that nests the caller's conditions
|
|
217
|
+
* inside the agent-scope condition as the OUTERMOST `and` block, so a
|
|
218
|
+
* caller-supplied `operator: "or"` cannot boolean-inject past the owner
|
|
219
|
+
* scope.
|
|
220
|
+
*
|
|
221
|
+
* This is the correct composition that MemoryCandidate.search() already
|
|
222
|
+
* applies (the only resource that got it right). Memory.search() and
|
|
223
|
+
* WorkspaceState.search() both used a flat prepend — `[agentCondition,
|
|
224
|
+
* ...query.conditions]` — which lets a caller-supplied `query.operator`
|
|
225
|
+
* survive and turn the scope condition into one OR-branch.
|
|
226
|
+
*
|
|
227
|
+
* Every table that composes this gets the correct nesting by default;
|
|
228
|
+
* a fifth table added later cannot accidentally reintroduce the flat-
|
|
229
|
+
* prepend bug.
|
|
230
|
+
*
|
|
231
|
+
* `superSearch` is a caller-supplied closure so the class's own
|
|
232
|
+
* `super.search()` (which cannot be referenced from outside the class
|
|
233
|
+
* body) stays exactly where it was.
|
|
234
|
+
*/
|
|
235
|
+
export function makeScopedSearch(readScope) {
|
|
236
|
+
return async function scopedSearch(agentId, query, superSearch) {
|
|
237
|
+
const scope = await readScope(agentId);
|
|
238
|
+
const agentCondition = scope.condition;
|
|
239
|
+
// Object with a conditions array — nest caller's conditions inside the
|
|
240
|
+
// scope condition as the outermost AND block so a caller-supplied
|
|
241
|
+
// `operator: "or"` cannot boolean-inject past the owner scope.
|
|
242
|
+
if (query && typeof query === "object" && !Array.isArray(query)) {
|
|
243
|
+
if (Array.isArray(query.conditions) && query.conditions.length > 0) {
|
|
244
|
+
return superSearch({
|
|
245
|
+
...query,
|
|
246
|
+
conditions: [agentCondition, { conditions: query.conditions, operator: query.operator || "and" }],
|
|
247
|
+
operator: "and",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// No (or empty) conditions array — just scope, preserving other query
|
|
251
|
+
// properties but NOT a caller-supplied operator (force "and").
|
|
252
|
+
const { conditions: _c, operator: _o, ...rest } = query;
|
|
253
|
+
return superSearch({ ...rest, conditions: [agentCondition], operator: "and" });
|
|
254
|
+
}
|
|
255
|
+
// Plain array or no query — wrap in an object with operator "and" so
|
|
256
|
+
// the scope condition is always the outermost AND.
|
|
257
|
+
const conditions = Array.isArray(query) && query.length > 0
|
|
258
|
+
? [agentCondition, { conditions: query, operator: "and" }]
|
|
259
|
+
: [agentCondition];
|
|
260
|
+
return superSearch({ conditions, operator: "and" });
|
|
261
|
+
};
|
|
262
|
+
}
|
|
214
263
|
export function stampAttribution(auth, content, field, mode, forbiddenMessage) {
|
|
215
264
|
if (auth.kind !== "agent")
|
|
216
265
|
return {}; // internal → always passthrough
|
package/dist/version-check.js
CHANGED
|
@@ -178,3 +178,48 @@ export function formatVersionNudge(result) {
|
|
|
178
178
|
`Upgrade: npm i -g ${FLAIR_PKG_NAME}@latest`;
|
|
179
179
|
return { severity: gap.severity, message };
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* The version an INSTANCE reports, or null when it cannot be determined.
|
|
183
|
+
*
|
|
184
|
+
* flair#1072. Every other line `doctor` prints about a remote target is
|
|
185
|
+
* genuinely remote; the currency claim was about the local CLI. This asks the
|
|
186
|
+
* instance instead.
|
|
187
|
+
*
|
|
188
|
+
* Returns null — never a fallback — when the instance is unreachable, answers
|
|
189
|
+
* without a version, or times out. The whole defect being fixed is a fallback
|
|
190
|
+
* to the number already in hand, and an older instance that does not expose its
|
|
191
|
+
* version is exactly the case where that fallback is most tempting and most
|
|
192
|
+
* wrong. A caller that gets null must say "unknown", not substitute its own
|
|
193
|
+
* version.
|
|
194
|
+
*
|
|
195
|
+
* Deliberately short-timeout and failure-swallowing: doctor runs against
|
|
196
|
+
* possibly-down instances by design, and "cannot determine" is a legitimate,
|
|
197
|
+
* reportable answer rather than an error to propagate.
|
|
198
|
+
*/
|
|
199
|
+
export async function probeInstanceVersion(baseUrl, timeoutMs = 5000, fetchImpl = fetch) {
|
|
200
|
+
const url = `${String(baseUrl).replace(/\/+$/, "")}/Health`;
|
|
201
|
+
const ctrl = new AbortController();
|
|
202
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetchImpl(url, { signal: ctrl.signal });
|
|
205
|
+
if (!res.ok)
|
|
206
|
+
return null;
|
|
207
|
+
const body = await res.json();
|
|
208
|
+
if (!body || typeof body !== "object")
|
|
209
|
+
return null;
|
|
210
|
+
const v = body.version;
|
|
211
|
+
// "dev" and other non-semver markers are real answers from a real server,
|
|
212
|
+
// but they cannot be compared against a published version. Treat them as
|
|
213
|
+
// undeterminable rather than feeding them to a semver comparison — a
|
|
214
|
+
// Fabric peer mid-failed-deploy reports exactly this (harper#2061).
|
|
215
|
+
if (typeof v !== "string" || !/^\d+\.\d+\.\d+/.test(v))
|
|
216
|
+
return null;
|
|
217
|
+
return v;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
}
|
|
225
|
+
}
|
package/docs/upgrade.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This page covers the mechanics of upgrading Flair — the general path, valid across
|
|
4
4
|
versions. For **what changed in a specific release** (behavior changes, new surfaces,
|
|
5
|
-
breaking changes), see [`CHANGELOG.md`](
|
|
5
|
+
breaking changes), see [`CHANGELOG.md`](https://github.com/tpsdev-ai/flair/blob/main/CHANGELOG.md) — each version has its own
|
|
6
6
|
`## [X.Y.Z]` section. Check the CHANGELOG entries between your current version and the
|
|
7
7
|
target version before upgrading anything you depend on in production.
|
|
8
8
|
|
|
@@ -78,18 +78,23 @@ actually running:
|
|
|
78
78
|
instead of retrying in a loop — see [Downgrade](#downgrade) for the
|
|
79
79
|
restore procedure.
|
|
80
80
|
|
|
81
|
-
### Pre-upgrade snapshot (opt-in)
|
|
81
|
+
### Pre-upgrade snapshot (opt-in for same-engine, unconditional on engine change)
|
|
82
82
|
|
|
83
83
|
flair#637 added a **physical**, byte-exact snapshot of `~/.flair/data` — the whole
|
|
84
84
|
directory (RocksDB files, keys, config, `admin-pass`), not just the logical records a
|
|
85
|
-
`flair backup` JSON export covers. As of 2026-07-08 this is **opt-in
|
|
86
|
-
`--snapshot` to `flair upgrade` to take one before the package swap.
|
|
87
|
-
default — matching how Harper's own upgrade CLI behaves (it recommends a
|
|
88
|
-
proceeding, but never auto-tars your data directory for you) — because
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
`flair backup` JSON export covers. As of 2026-07-08 this is **opt-in** for same-engine
|
|
86
|
+
upgrades: pass `--snapshot` to `flair upgrade` to take one before the package swap.
|
|
87
|
+
It's off by default — matching how Harper's own upgrade CLI behaves (it recommends a
|
|
88
|
+
backup before proceeding, but never auto-tars your data directory for you) — because
|
|
89
|
+
the downgrade-boot test (see below) covers same-engine downgrades, and the old opt-out
|
|
90
|
+
default meant every upgrade paid the cost (the data dir can be 800MB+; keep-last-3
|
|
91
|
+
retention meant up to ~2.5GB of snapshots sitting around) whether or not you wanted it.
|
|
92
|
+
|
|
93
|
+
**When the engine (Harper) version changes** (flair#1047), the snapshot is
|
|
94
|
+
**unconditional** — the tested-downgrade guarantee does not hold across engine version
|
|
95
|
+
boundaries, and the backwards-boot refusal + snapshot recovery path is the invariant
|
|
96
|
+
that applies. Opting out requires `--no-engine-snapshot` and prints what is being
|
|
97
|
+
given up.
|
|
93
98
|
|
|
94
99
|
```bash
|
|
95
100
|
flair upgrade --snapshot
|
|
@@ -365,7 +370,7 @@ flair restore ~/flair-backup-<date>.json
|
|
|
365
370
|
|
|
366
371
|
### Known issue — upgrading *from* an older version can still report a false rollback
|
|
367
372
|
|
|
368
|
-
The 0.25.1 fix (see [`CHANGELOG.md`](
|
|
373
|
+
The 0.25.1 fix (see [`CHANGELOG.md`](https://github.com/tpsdev-ai/flair/blob/main/CHANGELOG.md)) makes `flair upgrade` resolve a
|
|
369
374
|
credentials-only post-restart-verification failure to `healthy-unverified` instead of
|
|
370
375
|
rolling back. That fix is **forward-only**: it lives in the *new* CLI code, but an
|
|
371
376
|
upgrade's post-restart verification is run by the CLI that was already installed
|
|
@@ -489,6 +494,20 @@ current build, writes a memory and a presence row, stops it *without* wiping the
|
|
|
489
494
|
directory, then boots the last **npm-published** `@tpsdev-ai/flair` against that exact
|
|
490
495
|
same directory and confirms it comes up healthy and can read both rows back.
|
|
491
496
|
|
|
497
|
+
**The guarantee is now restated (flair#1050):** there is never a silent bad outcome.
|
|
498
|
+
Either the old binary boots and serves the corpus correctly, **or** it refuses to start
|
|
499
|
+
with a message naming what wrote the store, what is running, and how to recover — and a
|
|
500
|
+
pre-upgrade snapshot exists to recover *from*. The first branch (clean boot) holds for
|
|
501
|
+
same-engine upgrades; the second (refusal + snapshot) applies when the engine version
|
|
502
|
+
changes, which is the case where downgrade was never ours to guarantee.
|
|
503
|
+
|
|
504
|
+
**First known engine-version break:** Harper 5.1 → 5.2 (2026-08). 5.2.0 creates the
|
|
505
|
+
`hdb_secret` store on first boot against an existing data directory, and the older binary
|
|
506
|
+
will not start against it. Harper's 5.2.0 release notes document no rollback procedure.
|
|
507
|
+
The backwards-boot refusal (flair#1049) catches this: the old binary refuses to start,
|
|
508
|
+
naming both versions and the data directory, with recovery instructions. A pre-upgrade
|
|
509
|
+
snapshot exists at the named path. Restoring it returns the store to a working state.
|
|
510
|
+
|
|
492
511
|
**As observed when this suite was added (2026-07-08):** the npm-published baseline
|
|
493
512
|
(0.21.0) boots cleanly against data written by a HEAD build roughly 14 commits ahead of
|
|
494
513
|
it (several security-hardening and CLI-behavior changes, no Flair schema migration, and
|
|
@@ -507,7 +526,7 @@ you haven't personally tested.
|
|
|
507
526
|
|
|
508
527
|
## See also
|
|
509
528
|
|
|
510
|
-
- [`CHANGELOG.md`](
|
|
529
|
+
- [`CHANGELOG.md`](https://github.com/tpsdev-ai/flair/blob/main/CHANGELOG.md) — what actually changed, version by version.
|
|
511
530
|
- [`docs/releasing.md`](releasing.md) — how a release gets published in the first
|
|
512
531
|
place (staged npm publish with 2FA approval), if you're curious why a new version
|
|
513
532
|
shows up when it does.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
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",
|
|
@@ -52,8 +52,7 @@
|
|
|
52
52
|
"prepublishOnly": "npm run build && npm run build:cli",
|
|
53
53
|
"test": "bun test",
|
|
54
54
|
"test:e2e": "playwright test",
|
|
55
|
-
"release": "./scripts/release.sh"
|
|
56
|
-
"postinstall": "node -e \"try{const{chmodSync,statSync}=require('fs');for(const p of ['dist/cli-shim.cjs','dist/cli.js']){try{if(statSync(p).isFile()){chmodSync(p,0o755);console.error('@tpsdev-ai/flair: chmod +x ' + p + ' OK')}}catch(e){if(e.code!=='ENOENT')console.error('postinstall warn:',e.message)}}}catch(e){console.error('postinstall warn:',e.message)}\""
|
|
55
|
+
"release": "./scripts/release.sh"
|
|
57
56
|
},
|
|
58
57
|
"publishConfig": {
|
|
59
58
|
"access": "public"
|
|
@@ -73,7 +72,11 @@
|
|
|
73
72
|
"tweetnacl": "1.0.3"
|
|
74
73
|
},
|
|
75
74
|
"overrides": {
|
|
76
|
-
"react-native-fs": "npm:empty-npm-package@1.0.0"
|
|
75
|
+
"react-native-fs": "npm:empty-npm-package@1.0.0",
|
|
76
|
+
"brace-expansion": "^5.0.9",
|
|
77
|
+
"undici": "^8.9.0",
|
|
78
|
+
"fast-uri": "^4.1.2",
|
|
79
|
+
"hono": "^4.12.34"
|
|
77
80
|
},
|
|
78
81
|
"devDependencies": {
|
|
79
82
|
"@playwright/test": "1.59.1",
|