@tpsdev-ai/flair-mcp 0.57.0 → 0.58.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/continuity.d.ts +69 -0
- package/dist/continuity.js +132 -14
- package/dist/env-guard.d.ts +2 -1
- package/dist/env-guard.js +2 -1
- package/dist/precompact-hook.d.ts +174 -0
- package/dist/precompact-hook.js +418 -0
- package/dist/precompact.d.ts +404 -0
- package/dist/precompact.js +895 -0
- package/dist/prompt-recall-hook.d.ts +289 -0
- package/dist/prompt-recall-hook.js +651 -0
- package/dist/session-start-hook.d.ts +7 -4
- package/dist/session-start-hook.js +30 -9
- package/dist/tool-descriptors/index.js +15 -15
- package/package.json +6 -4
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
* (missing FLAIR_AGENT_ID, malformed stdin, Flair unreachable, auth error, a
|
|
22
22
|
* hung daemon, an unexpected throw) exits 0. Malformed stdin is treated as
|
|
23
23
|
* empty input and can still yield bootstrap context. A failed bootstrap yields
|
|
24
|
-
* a continuity resume hint only when
|
|
25
|
-
*
|
|
26
|
-
* when bootstrap fails (flair#1943). The Codex command retains stderr and the
|
|
24
|
+
* a pre-compaction record and/or a continuity resume hint only when their
|
|
25
|
+
* separate lookups find one; otherwise stdout is `{}`. The hook attempts one
|
|
26
|
+
* stderr diagnostic when bootstrap fails (flair#1943). The Codex command retains stderr and the
|
|
27
27
|
* Claude Code command discards it; delivery depends on stderr being writable.
|
|
28
28
|
*
|
|
29
29
|
* A hard timeout (FLAIR_HOOK_TIMEOUT_MS, default 8s) wraps the bootstrap call
|
|
@@ -81,6 +81,7 @@ import { basename } from "node:path";
|
|
|
81
81
|
import { deriveActivity, postPresenceSafe, resolvePresenceTimeoutMs } from "./presence.js";
|
|
82
82
|
import { isProbeMode, readEnvOrUnset, stripInterpolationLiteralsFromEnv } from "./env-guard.js";
|
|
83
83
|
import { buildResumeHint, discoverResume, prepareContinuityBoot, resolveContinuityTimeoutMs, } from "./continuity.js";
|
|
84
|
+
import { fetchPreCompactRecord, formatPreCompactContext, resolvePreCompactLookup } from "./precompact.js";
|
|
84
85
|
/** Claude Code SessionStart additionalContext hard limit (chars). */
|
|
85
86
|
const MAX_CHARS = 10_000;
|
|
86
87
|
/** Token budget for the bootstrap call — matches the proven prototype. */
|
|
@@ -212,7 +213,10 @@ function hookOutput(context) {
|
|
|
212
213
|
/**
|
|
213
214
|
* Core hook logic, with injectable dependencies so it can be unit-tested
|
|
214
215
|
* without a live Flair daemon. Returns the exact string to print to stdout.
|
|
215
|
-
*
|
|
216
|
+
* A failed bootstrap can still return a pre-compaction record and a
|
|
217
|
+
* continuity resume hint. Without bootstrap context, a pre-compaction record
|
|
218
|
+
* or a resume hint, this returns NOOP_OUTPUT. The entry
|
|
219
|
+
* point catches unexpected exceptions.
|
|
216
220
|
*
|
|
217
221
|
* @param rawInput the raw stdin string (may be empty / malformed)
|
|
218
222
|
* @param makeClient factory for the bootstrap client (defaults to FlairClient)
|
|
@@ -264,6 +268,18 @@ export async function runHook(rawInput, makeClient = defaultClientFactory) {
|
|
|
264
268
|
const resumeHintDone = continuity.active && typeof client.request === "function"
|
|
265
269
|
? withTimeout(discoverResume(client, agentId, continuity.priorPointer).then((result) => buildResumeHint(result)), resolveContinuityTimeoutMs()).catch(() => null)
|
|
266
270
|
: Promise.resolve(null);
|
|
271
|
+
// Pre-compaction record (flair#2069): decided locally, fetched concurrently,
|
|
272
|
+
// the marker read and the fetch both bounded by the same continuity timeout;
|
|
273
|
+
// null (nothing shown) on any failure.
|
|
274
|
+
const precompactDone = typeof client.request === "function"
|
|
275
|
+
? withTimeout((async () => {
|
|
276
|
+
const lookup = await resolvePreCompactLookup(input, agentId, continuity);
|
|
277
|
+
if (!lookup)
|
|
278
|
+
return null;
|
|
279
|
+
const record = await fetchPreCompactRecord(client, agentId, lookup);
|
|
280
|
+
return record ? formatPreCompactContext(record) : null;
|
|
281
|
+
})(), resolveContinuityTimeoutMs()).catch(() => null)
|
|
282
|
+
: Promise.resolve(null);
|
|
267
283
|
let context = "";
|
|
268
284
|
try {
|
|
269
285
|
const res = await withTimeout(Promise.resolve(client.bootstrap({
|
|
@@ -277,16 +293,21 @@ export async function runHook(rawInput, makeClient = defaultClientFactory) {
|
|
|
277
293
|
context = ""; // flair unreachable / auth error / timeout → no bootstrap context
|
|
278
294
|
// flair#1943: keeping stderr open cannot reveal an error never written to
|
|
279
295
|
// it. Write ONE line to STDERR (never stdout — that is the hook payload),
|
|
280
|
-
// so a real failure stays visible instead of being swallowed.
|
|
281
|
-
//
|
|
282
|
-
//
|
|
296
|
+
// so a real failure stays visible instead of being swallowed. A failed
|
|
297
|
+
// bootstrap contributes no bootstrap context; a continuity resume hint may
|
|
298
|
+
// still be returned. The entry point preserves a successful exit.
|
|
283
299
|
reportBootstrapFailure(err);
|
|
284
300
|
}
|
|
285
301
|
const resumeHint = await resumeHintDone;
|
|
302
|
+
const precompactBlock = await precompactDone;
|
|
286
303
|
await presenceDone;
|
|
287
|
-
// Combine:
|
|
288
|
-
//
|
|
304
|
+
// Combine: the pre-compaction record FIRST (bounded, so the MAX_CHARS cut
|
|
305
|
+
// below can only shorten what follows it), then the bootstrap context, then
|
|
306
|
+
// AT MOST one continuity hint line. Any piece may be absent; all absent ⇒
|
|
307
|
+
// the inert no-op output.
|
|
289
308
|
const pieces = [];
|
|
309
|
+
if (precompactBlock)
|
|
310
|
+
pieces.push(precompactBlock);
|
|
290
311
|
if (context.trim())
|
|
291
312
|
pieces.push(context);
|
|
292
313
|
if (resumeHint)
|
|
@@ -51,7 +51,7 @@ export function descriptorNames(descriptors) {
|
|
|
51
51
|
export const TOOL_DESCRIPTORS = [
|
|
52
52
|
{
|
|
53
53
|
"name": "memory_search",
|
|
54
|
-
"description": "Search memories by meaning. Understands temporal queries like 'what happened today'.
|
|
54
|
+
"description": "Search memories by meaning. Understands temporal queries like 'what happened today'. Non-admin callers are scoped to their own and other agents' non-private memories; administrator requests may have broader access.",
|
|
55
55
|
"inputSchema": {
|
|
56
56
|
"type": "object",
|
|
57
57
|
"properties": {
|
|
@@ -80,7 +80,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
80
80
|
"query"
|
|
81
81
|
]
|
|
82
82
|
},
|
|
83
|
-
"outputShape": "{ results: MemoryRecord[] } — semantic hits
|
|
83
|
+
"outputShape": "{ results: MemoryRecord[] } — semantic hits subject to the caller's read scope; each hit carries content, never the raw embedding.",
|
|
84
84
|
"annotations": {
|
|
85
85
|
"readOnlyHint": true
|
|
86
86
|
},
|
|
@@ -135,7 +135,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
135
135
|
"private",
|
|
136
136
|
"shared"
|
|
137
137
|
],
|
|
138
|
-
"description": "Writer-controlled sharing intent. Omit to use the server's durability-keyed default: permanent/persistent -> shared, standard/ephemeral -> private. private — owner-
|
|
138
|
+
"description": "Writer-controlled sharing intent. Omit to use the server's durability-keyed default: permanent/persistent -> shared, standard/ephemeral -> private. private — readable by its owner and administrators; other non-admin agents cannot read it, including through a memory grant. shared — visible to the owner and every other agent on this instance. The visibility the write actually landed on is returned in the result."
|
|
139
139
|
},
|
|
140
140
|
"usedMemoryIds": {
|
|
141
141
|
"type": "array",
|
|
@@ -189,7 +189,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
191
|
"name": "skill_search",
|
|
192
|
-
"description": "Find skills (reusable capabilities/procedures) that apply to a task. Ranks skill-tagged memories by their `trigger` ('when to use') against your task text. Returns a lightweight CATALOG — id, name, trigger, description, tags, agentId — NOT the full procedure (fetch that with skill_get).
|
|
192
|
+
"description": "Find skills (reusable capabilities/procedures) that apply to a task. Ranks skill-tagged memories by their `trigger` ('when to use') against your task text. Returns a lightweight CATALOG — id, name, trigger, description, tags, agentId — NOT the full procedure (fetch that with skill_get). Non-admin callers can retrieve their own and other agents' non-private skills; administrators can also retrieve private skills.",
|
|
193
193
|
"inputSchema": {
|
|
194
194
|
"type": "object",
|
|
195
195
|
"properties": {
|
|
@@ -206,14 +206,14 @@ export const TOOL_DESCRIPTORS = [
|
|
|
206
206
|
"task"
|
|
207
207
|
]
|
|
208
208
|
},
|
|
209
|
-
"outputShape": "{ results: SkillCard[] } — the skill catalog (lightweight id/name/trigger/description/tags/agentId, ranked by trigger match); the full procedure and the raw embedding are never on a card.
|
|
209
|
+
"outputShape": "{ results: SkillCard[] } — the skill catalog (lightweight id/name/trigger/description/tags/agentId, ranked by trigger match); the full procedure and the raw embedding are never on a card. Non-admin callers can retrieve their own and other agents' non-private skills; administrators can also retrieve private skills.",
|
|
210
210
|
"annotations": {
|
|
211
211
|
"readOnlyHint": true
|
|
212
212
|
}
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"name": "skill_get",
|
|
216
|
-
"description": "Retrieve a full skill by ID — the complete procedure (`content`) plus trigger and metadata. The disclosure step after skill_search's catalog.
|
|
216
|
+
"description": "Retrieve a full skill by ID — the complete procedure (`content`) plus trigger and metadata. The disclosure step after skill_search's catalog. Reads follow the caller's authorization: non-admin callers can retrieve their own and other agents' non-private skills; administrators can also retrieve private skills. A non-skill id returns not-found. The raw embedding vector is never returned.",
|
|
217
217
|
"inputSchema": {
|
|
218
218
|
"type": "object",
|
|
219
219
|
"properties": {
|
|
@@ -226,7 +226,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
226
226
|
"id"
|
|
227
227
|
]
|
|
228
228
|
},
|
|
229
|
-
"outputShape": "The full skill record { id, agentId, content, trigger, tags, durability, metadata, createdAt, ... } for a skill readable under the caller's read-scope — embedding + embeddingModel always stripped. A non-
|
|
229
|
+
"outputShape": "The full skill record { id, agentId, content, trigger, tags, durability, metadata, createdAt, ... } for a skill readable under the caller's read-scope — embedding + embeddingModel always stripped. A non-admin caller cannot read another agent's private skill; administrators retain access. A readable non-skill id is reported as not found.",
|
|
230
230
|
"annotations": {
|
|
231
231
|
"readOnlyHint": true
|
|
232
232
|
}
|
|
@@ -268,7 +268,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
268
268
|
},
|
|
269
269
|
{
|
|
270
270
|
"name": "memory_basement",
|
|
271
|
-
"description": "Send a memory to the basement (archive it). Sets archived=true and stamps archivedAt. The memory is removed from bootstrap and default search but remains retrievable via memory_get and memory_search(includeArchived:true). Deliberate and GLOBAL — this is a visibility flag, not a deletion: provenance and history are untouched.
|
|
271
|
+
"description": "Send a memory to the basement (archive it). Sets archived=true and stamps archivedAt. The memory is removed from bootstrap and default search but remains retrievable via memory_get and memory_search(includeArchived:true). Deliberate and GLOBAL — this is a visibility flag, not a deletion: provenance and history are untouched. Non-admin callers can modify only their own memories; administrators can also modify other agents' memories.",
|
|
272
272
|
"inputSchema": {
|
|
273
273
|
"type": "object",
|
|
274
274
|
"properties": {
|
|
@@ -286,7 +286,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
286
286
|
},
|
|
287
287
|
{
|
|
288
288
|
"name": "memory_restore",
|
|
289
|
-
"description": "Restore a basemented (archived) memory. Clears archived and archivedAt. Deliberate and GLOBAL — this un-retires the memory for EVERY session, not a session-local view (per-session reuse is drawers, which do not exist yet).
|
|
289
|
+
"description": "Restore a basemented (archived) memory. Clears archived and archivedAt. Deliberate and GLOBAL — this un-retires the memory for EVERY session, not a session-local view (per-session reuse is drawers, which do not exist yet). Non-admin callers can modify only their own memories; administrators can also modify other agents' memories.",
|
|
290
290
|
"inputSchema": {
|
|
291
291
|
"type": "object",
|
|
292
292
|
"properties": {
|
|
@@ -325,7 +325,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
325
325
|
"id"
|
|
326
326
|
]
|
|
327
327
|
},
|
|
328
|
-
"outputShape": "The full memory record { id, agentId, content, durability, createdAt, ... } for the caller's
|
|
328
|
+
"outputShape": "The full memory record { id, agentId, content, durability, createdAt, ... } for the requested ID, subject to the caller's read scope; embedding and embeddingModel are stripped by default.",
|
|
329
329
|
"annotations": {
|
|
330
330
|
"readOnlyHint": true
|
|
331
331
|
},
|
|
@@ -336,7 +336,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
336
336
|
},
|
|
337
337
|
{
|
|
338
338
|
"name": "memory_delete",
|
|
339
|
-
"description": "Delete a memory by ID.
|
|
339
|
+
"description": "Delete a memory by ID. Non-admin callers can delete only their own memories; administrators can also delete other agents' memories.",
|
|
340
340
|
"inputSchema": {
|
|
341
341
|
"type": "object",
|
|
342
342
|
"properties": {
|
|
@@ -349,7 +349,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
349
349
|
"id"
|
|
350
350
|
]
|
|
351
351
|
},
|
|
352
|
-
"outputShape": "Deletes
|
|
352
|
+
"outputShape": "Deletes a memory by ID when authorized, at any durability tier (success echo is thin). Cross-owner deletion returns { error, status:403 } for a non-admin; a deleted row round-trips as gone via memory_get.",
|
|
353
353
|
"annotations": {
|
|
354
354
|
"destructiveHint": true
|
|
355
355
|
}
|
|
@@ -490,7 +490,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
490
490
|
]
|
|
491
491
|
},
|
|
492
492
|
"outputShape": "Refuses runtime Soul writes, including admin-agent delegation, with { error, status:403 }. Operators use the authenticated REST or CLI path.",
|
|
493
|
-
"stdioDescription": "Set a personality or project context entry
|
|
493
|
+
"stdioDescription": "Set a personality or project context entry, included in every bootstrap. Soul writes require verified administrator Basic credentials; Ed25519 agent requests are refused. Operators should use the REST API or CLI."
|
|
494
494
|
},
|
|
495
495
|
{
|
|
496
496
|
"name": "soul_get",
|
|
@@ -588,7 +588,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
588
588
|
},
|
|
589
589
|
{
|
|
590
590
|
"name": "flair_catchup",
|
|
591
|
-
"description": "Drain
|
|
591
|
+
"description": "Drain the catch-up feed for the configured `FLAIR_AGENT_ID`: directed or broadcast org events after the effective cursor. Omit `after` to start at that agent's durable watermark, or pass `after` to choose an exclusive cursor. Pass `ack` to advance the watermark before this call reads a page; the response includes `nextAfter` for paging. The tool has no argument to change the agent id. Agent requests are signed for the configured id; verified administrator Basic credentials may read that configured feed. Unacknowledged events remain eligible after restart; acknowledged events are skipped by default, but an explicit older `after` can replay them.",
|
|
592
592
|
"inputSchema": {
|
|
593
593
|
"type": "object",
|
|
594
594
|
"properties": {
|
|
@@ -606,7 +606,7 @@ export const TOOL_DESCRIPTORS = [
|
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
},
|
|
609
|
-
"outputShape": "{ events: OrgEvent[], after, nextAfter, watermark, hasMore, pageSize, acked? } — the
|
|
609
|
+
"outputShape": "{ events: OrgEvent[], after, nextAfter, watermark, hasMore, pageSize, acked? } — the configured agent's directed and broadcast events after the effective cursor; `ack` advances that agent's durable watermark monotonically.",
|
|
610
610
|
"native": false
|
|
611
611
|
},
|
|
612
612
|
{
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "MCP server for Flair — persistent memory for Claude Code, Cursor, and any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"flair-mcp": "dist/mcp-shim.cjs",
|
|
9
|
+
"flair-precompact": "dist/precompact-hook.js",
|
|
9
10
|
"flair-session-start": "dist/session-start-hook.js",
|
|
10
|
-
"flair-continuity-capture": "dist/continuity-capture-hook.js"
|
|
11
|
+
"flair-continuity-capture": "dist/continuity-capture-hook.js",
|
|
12
|
+
"flair-prompt-recall": "dist/prompt-recall-hook.js"
|
|
11
13
|
},
|
|
12
14
|
"files": [
|
|
13
15
|
"dist/",
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
"prebuild": "node ../../scripts/vendor-tool-descriptors.mjs packages/flair-mcp/src/tool-descriptors",
|
|
20
22
|
"test": "bun test",
|
|
21
23
|
"prepack": "npm run build",
|
|
22
|
-
"postinstall": "node -e \"try{const{chmodSync,statSync}=require('fs');for(const p of ['dist/mcp-shim.cjs','dist/index.js','dist/session-start-hook.js','dist/continuity-capture-hook.js']){try{if(statSync(p).isFile()){chmodSync(p,0o755);console.error('@tpsdev-ai/flair-mcp: chmod +x ' + p + ' OK')}}catch(e){if(e.code!=='ENOENT')console.error('postinstall warn:',e.message)}}}catch(e){console.error('postinstall warn:',e.message)}\""
|
|
24
|
+
"postinstall": "node -e \"try{const{chmodSync,statSync}=require('fs');for(const p of ['dist/mcp-shim.cjs','dist/index.js','dist/session-start-hook.js','dist/continuity-capture-hook.js','dist/prompt-recall-hook.js','dist/precompact-hook.js']){try{if(statSync(p).isFile()){chmodSync(p,0o755);console.error('@tpsdev-ai/flair-mcp: chmod +x ' + p + ' OK')}}catch(e){if(e.code!=='ENOENT')console.error('postinstall warn:',e.message)}}}catch(e){console.error('postinstall warn:',e.message)}\""
|
|
23
25
|
},
|
|
24
26
|
"publishConfig": {
|
|
25
27
|
"access": "public"
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
},
|
|
30
32
|
"dependencies": {
|
|
31
33
|
"@modelcontextprotocol/sdk": "1.27.1",
|
|
32
|
-
"@tpsdev-ai/flair-client": "0.
|
|
34
|
+
"@tpsdev-ai/flair-client": "0.58.0",
|
|
33
35
|
"zod": "4.3.6"
|
|
34
36
|
},
|
|
35
37
|
"license": "Apache-2.0",
|