@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
|
@@ -26,15 +26,12 @@
|
|
|
26
26
|
* the resolved agent, never from the tool arguments — an agent can only act as
|
|
27
27
|
* itself (no forging of agentId / authorId in the body).
|
|
28
28
|
*
|
|
29
|
-
* NOTE (flair#
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* the legacy stdio proxy — adding it there would mean a separate package
|
|
36
|
-
* version bump + a new FlairClient method, out of scope for this query-only
|
|
37
|
-
* slice.
|
|
29
|
+
* NOTE (flair#1580): MCP-facing metadata (name, description, inputSchema,
|
|
30
|
+
* output shape) lives in `@tpsdev-ai/flair-tool-descriptors`. This registry
|
|
31
|
+
* binds each *native* descriptor to its Harper impl. The stdio adapter binds
|
|
32
|
+
* the same list (stdio surface) to FlairClient HTTP — tool-set drift is
|
|
33
|
+
* impossible by construction. One-sided tools (`attention`, archive verbs,
|
|
34
|
+
* `relationship_store`) are flagged on the descriptor, not hand-exempted.
|
|
38
35
|
*/
|
|
39
36
|
/**
|
|
40
37
|
* The delegated handler classes, held in a mutable registry, LAZILY loaded on
|
|
@@ -54,8 +51,9 @@
|
|
|
54
51
|
import { AUTHORITY_FIELDS } from "./authority-field-guard.js";
|
|
55
52
|
import { resolveVersion } from "./version.js";
|
|
56
53
|
import { agentContext, adminContext, collectionResource } from "./in-process.js";
|
|
57
|
-
import {
|
|
54
|
+
import { unionUsageMemoryIds } from "./usage-ids.js";
|
|
58
55
|
import { SKILL_TAG, isSkillWrite } from "./skill-write.js";
|
|
56
|
+
import { NATIVE_TOOL_DESCRIPTORS, toMcpToolDef, } from "@tpsdev-ai/flair-tool-descriptors";
|
|
59
57
|
const H = {};
|
|
60
58
|
const LOADERS = {
|
|
61
59
|
SemanticSearch: async () => (await import("./SemanticSearch.js")).SemanticSearch,
|
|
@@ -405,7 +403,8 @@ async function skillSearch(agent, args) {
|
|
|
405
403
|
* returns the same 404 as an unreadable id. Returning it would (a) make
|
|
406
404
|
* skill_get an alias for memory_get, and (b) reveal a readable non-skill
|
|
407
405
|
* memory's existence through a skill-shaped call; a uniform 404 does neither.
|
|
408
|
-
* The embedding fields are stripped
|
|
406
|
+
* The embedding fields are always stripped (flair#1593) — there is no
|
|
407
|
+
* includeEmbedding opt-in. memory_get keeps that flag (flair#1188).
|
|
409
408
|
*/
|
|
410
409
|
async function skillGet(agent, args) {
|
|
411
410
|
const Cls = await handler("Memory");
|
|
@@ -419,7 +418,9 @@ async function skillGet(agent, args) {
|
|
|
419
418
|
// returned (see the doc above).
|
|
420
419
|
if (!isSkillWrite(result))
|
|
421
420
|
return { error: "skill not found", status: 404 };
|
|
422
|
-
|
|
421
|
+
// flair#1593 — always strip. includeEmbedding used to bypass this and
|
|
422
|
+
// ride the raw vector; nothing in the skill_get contract needs it.
|
|
423
|
+
return stripInternalFields(result);
|
|
423
424
|
}
|
|
424
425
|
/**
|
|
425
426
|
* memory_update — id-targeted, dedup-BYPASSED overwrite/version path (memory-
|
|
@@ -850,24 +851,33 @@ export function mcpToolName(table, toolPrefix, verb) {
|
|
|
850
851
|
const override = TOOL_NAME_OVERRIDES[table]?.[verb];
|
|
851
852
|
return override ?? `${toolPrefix}_${verb}`;
|
|
852
853
|
}
|
|
853
|
-
|
|
854
|
+
/**
|
|
855
|
+
* Bind native descriptors to Harper impls + conformance contracts.
|
|
856
|
+
* A descriptor without a binding (or a binding without a native descriptor)
|
|
857
|
+
* throws at module load — the tool set is derived, not hand-copied.
|
|
858
|
+
*/
|
|
859
|
+
function bindNativeTools(bindings) {
|
|
860
|
+
const tools = {};
|
|
861
|
+
const missing = [];
|
|
862
|
+
for (const d of NATIVE_TOOL_DESCRIPTORS) {
|
|
863
|
+
const b = bindings[d.name];
|
|
864
|
+
if (!b) {
|
|
865
|
+
missing.push(d.name);
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
tools[d.name] = { def: toMcpToolDef(d), impl: b.impl, contract: b.contract };
|
|
869
|
+
}
|
|
870
|
+
if (missing.length > 0) {
|
|
871
|
+
throw new Error(`TOOLS missing Harper bindings for native descriptors: ${missing.join(", ")}`);
|
|
872
|
+
}
|
|
873
|
+
const extra = Object.keys(bindings).filter((n) => !Object.prototype.hasOwnProperty.call(tools, n)).sort();
|
|
874
|
+
if (extra.length > 0) {
|
|
875
|
+
throw new Error(`TOOLS bindings have no native descriptor: ${extra.join(", ")}`);
|
|
876
|
+
}
|
|
877
|
+
return tools;
|
|
878
|
+
}
|
|
879
|
+
export const TOOLS = bindNativeTools({
|
|
854
880
|
memory_search: {
|
|
855
|
-
def: {
|
|
856
|
-
name: "memory_search",
|
|
857
|
-
description: "Search memories by meaning. Understands temporal queries like 'what happened today'. Scoped to your agent's own + granted memories.",
|
|
858
|
-
annotations: { readOnlyHint: true },
|
|
859
|
-
inputSchema: {
|
|
860
|
-
type: "object",
|
|
861
|
-
properties: {
|
|
862
|
-
query: { type: "string", description: "Search query — natural language, semantic matching" },
|
|
863
|
-
limit: { type: "number", description: "Max results (default 5)" },
|
|
864
|
-
includeTrust: { type: "boolean", description: "Attach a per-result trust-evidence block (provenance, author, usage, freshness, supersession). Default false." },
|
|
865
|
-
abstain: { type: "boolean", description: "Opt into first-class abstention: when the best match is below a global confidence threshold, return { abstained: true, reason, bestScore } with no weak matches instead of the N weakest results. Default false." },
|
|
866
|
-
includeArchived: { type: "boolean", description: "Include basemented (archived) memories in results. Default false — archived memories are excluded from normal search. When true, archived memories are returned under the SAME read-scope gate as a normal search (never a wider scope)." },
|
|
867
|
-
},
|
|
868
|
-
required: ["query"],
|
|
869
|
-
},
|
|
870
|
-
},
|
|
871
881
|
impl: memorySearch,
|
|
872
882
|
contract: {
|
|
873
883
|
summary: "{ results: MemoryRecord[] } — semantic hits scoped to the caller's own + granted memories; each hit carries content, never the raw embedding.",
|
|
@@ -881,30 +891,6 @@ export const TOOLS = {
|
|
|
881
891
|
},
|
|
882
892
|
},
|
|
883
893
|
memory_store: {
|
|
884
|
-
def: {
|
|
885
|
-
name: "memory_store",
|
|
886
|
-
description: "Save information to persistent memory. Use for lessons, decisions, preferences, facts. Attributed to your authenticated agent.",
|
|
887
|
-
inputSchema: {
|
|
888
|
-
type: "object",
|
|
889
|
-
properties: {
|
|
890
|
-
content: { type: "string", description: "What to remember" },
|
|
891
|
-
type: { type: "string", enum: ["session", "lesson", "decision", "preference", "fact", "goal"], description: "Memory type (default session)" },
|
|
892
|
-
durability: { type: "string", enum: ["permanent", "persistent", "standard", "ephemeral"], description: "permanent > persistent > standard > ephemeral (default standard)" },
|
|
893
|
-
tags: { type: "array", items: { type: "string" }, description: "Tag strings" },
|
|
894
|
-
visibility: {
|
|
895
|
-
type: "string",
|
|
896
|
-
enum: ["private", "shared"],
|
|
897
|
-
description: "Writer-controlled sharing intent. Omit to use the server's durability-keyed default: " +
|
|
898
|
-
"permanent/persistent -> shared, standard/ephemeral -> private. " +
|
|
899
|
-
"private — owner-only, never visible to another agent, even one holding a memory grant. " +
|
|
900
|
-
"shared — visible to the owner and every other agent on this instance. " +
|
|
901
|
-
"The visibility the write actually landed on is returned in the result.",
|
|
902
|
-
},
|
|
903
|
-
usedMemoryIds: { type: "array", items: { type: "string" }, description: "IDs of memories that informed this write (citation-on-write). Credited via the same deduped usage ledger as record_usage. Optional." },
|
|
904
|
-
},
|
|
905
|
-
required: ["content"],
|
|
906
|
-
},
|
|
907
|
-
},
|
|
908
894
|
impl: memoryStore,
|
|
909
895
|
contract: {
|
|
910
896
|
summary: "Write echo { id, written:true, deduplicated } — the new id + confirmation. No internal embedding fields; round-trips via memory_get.",
|
|
@@ -916,24 +902,6 @@ export const TOOLS = {
|
|
|
916
902
|
},
|
|
917
903
|
},
|
|
918
904
|
skill_store: {
|
|
919
|
-
def: {
|
|
920
|
-
name: "skill_store",
|
|
921
|
-
description: "Write a skill (a reusable capability/procedure) as a skill-tagged memory. " +
|
|
922
|
-
"The `trigger` text is what the skill embeds from (the recall signal — 'when to use this'), " +
|
|
923
|
-
"and `content` is the full procedure. Skills are forced durability=persistent and are " +
|
|
924
|
-
"SkillScan-gated before the embed (a dangerous shell/network payload is rejected).",
|
|
925
|
-
inputSchema: {
|
|
926
|
-
type: "object",
|
|
927
|
-
properties: {
|
|
928
|
-
content: { type: "string", description: "The full procedure (markdown body of the SKILL.md)" },
|
|
929
|
-
trigger: { type: "string", description: "The 'when to use' text — the recall signal the skill embeds from" },
|
|
930
|
-
name: { type: "string", description: "Skill name (SKILL.md frontmatter; stored in metadata)" },
|
|
931
|
-
description: { type: "string", description: "Skill description (SKILL.md frontmatter; stored in metadata)" },
|
|
932
|
-
tags: { type: "array", items: { type: "string" }, description: "Additional tags (the 'skill' tag is added automatically)" },
|
|
933
|
-
},
|
|
934
|
-
required: ["content"],
|
|
935
|
-
},
|
|
936
|
-
},
|
|
937
905
|
impl: skillStore,
|
|
938
906
|
contract: {
|
|
939
907
|
summary: "Write echo { id, written:true, deduplicated } for the skill-tagged memory. No internal embedding fields; round-trips via memory_get.",
|
|
@@ -945,23 +913,6 @@ export const TOOLS = {
|
|
|
945
913
|
},
|
|
946
914
|
},
|
|
947
915
|
skill_search: {
|
|
948
|
-
def: {
|
|
949
|
-
name: "skill_search",
|
|
950
|
-
description: "Find skills (reusable capabilities/procedures) that apply to a task. " +
|
|
951
|
-
"Ranks skill-tagged memories by their `trigger` ('when to use') against your task text. " +
|
|
952
|
-
"Returns a lightweight CATALOG — id, name, trigger, description, tags, agentId — NOT the full " +
|
|
953
|
-
"procedure (fetch that with skill_get). Scoped to your own + shared skills; another agent's " +
|
|
954
|
-
"private skill is never returned.",
|
|
955
|
-
annotations: { readOnlyHint: true },
|
|
956
|
-
inputSchema: {
|
|
957
|
-
type: "object",
|
|
958
|
-
properties: {
|
|
959
|
-
task: { type: "string", description: "The task/context to match skills against — natural language; ranked against each skill's trigger" },
|
|
960
|
-
limit: { type: "number", description: "Max skills to return (default 5)" },
|
|
961
|
-
},
|
|
962
|
-
required: ["task"],
|
|
963
|
-
},
|
|
964
|
-
},
|
|
965
916
|
impl: skillSearch,
|
|
966
917
|
contract: {
|
|
967
918
|
summary: "{ results: SkillCard[] } — the skill catalog (lightweight id/name/trigger/description/tags/agentId, " +
|
|
@@ -979,25 +930,10 @@ export const TOOLS = {
|
|
|
979
930
|
},
|
|
980
931
|
},
|
|
981
932
|
skill_get: {
|
|
982
|
-
def: {
|
|
983
|
-
name: "skill_get",
|
|
984
|
-
description: "Retrieve a full skill by ID — the complete procedure (`content`) plus trigger and metadata. " +
|
|
985
|
-
"The disclosure step after skill_search's catalog. Read-scoped: you can only get your own or a " +
|
|
986
|
-
"shared skill, never another agent's private skill. A non-skill id returns not-found.",
|
|
987
|
-
annotations: { readOnlyHint: true },
|
|
988
|
-
inputSchema: {
|
|
989
|
-
type: "object",
|
|
990
|
-
properties: {
|
|
991
|
-
id: { type: "string", description: "Skill (memory) ID" },
|
|
992
|
-
includeEmbedding: { type: "boolean", description: "Include the raw embedding vector (large, rarely useful). Default false." },
|
|
993
|
-
},
|
|
994
|
-
required: ["id"],
|
|
995
|
-
},
|
|
996
|
-
},
|
|
997
933
|
impl: skillGet,
|
|
998
934
|
contract: {
|
|
999
935
|
summary: "The full skill record { id, agentId, content, trigger, tags, durability, metadata, createdAt, ... } for a " +
|
|
1000
|
-
"skill readable under the caller's read-scope — embedding + embeddingModel stripped
|
|
936
|
+
"skill readable under the caller's read-scope — embedding + embeddingModel always stripped. A non-owner " +
|
|
1001
937
|
"cannot read another agent's private skill, and a readable non-skill id is not found (both 404).",
|
|
1002
938
|
requiredFields: ["id", "agentId", "content", "createdAt"],
|
|
1003
939
|
fieldTypes: { id: "string", agentId: "string", content: "string" },
|
|
@@ -1007,21 +943,6 @@ export const TOOLS = {
|
|
|
1007
943
|
},
|
|
1008
944
|
},
|
|
1009
945
|
memory_update: {
|
|
1010
|
-
def: {
|
|
1011
|
-
name: "memory_update",
|
|
1012
|
-
description: "Update an existing memory by ID. Dedup-bypassed (this is an intentional overwrite, not a new write). " +
|
|
1013
|
-
"Default: overwrites the same id in place. Pass preserveHistory=true to instead write a new version " +
|
|
1014
|
-
"linked via `supersedes`, closing the old one's validity window.",
|
|
1015
|
-
inputSchema: {
|
|
1016
|
-
type: "object",
|
|
1017
|
-
properties: {
|
|
1018
|
-
id: { type: "string", description: "ID of the memory to update" },
|
|
1019
|
-
content: { type: "string", description: "New content" },
|
|
1020
|
-
preserveHistory: { type: "boolean", description: "Write a new version (supersedes-linked) instead of overwriting in place (default false)" },
|
|
1021
|
-
},
|
|
1022
|
-
required: ["id", "content"],
|
|
1023
|
-
},
|
|
1024
|
-
},
|
|
1025
946
|
impl: memoryUpdate,
|
|
1026
947
|
contract: {
|
|
1027
948
|
summary: "Write echo { id, written:true } for the in-place overwrite (or supersede). No internal embedding fields; the change round-trips via memory_get.",
|
|
@@ -1033,20 +954,6 @@ export const TOOLS = {
|
|
|
1033
954
|
},
|
|
1034
955
|
},
|
|
1035
956
|
memory_basement: {
|
|
1036
|
-
def: {
|
|
1037
|
-
name: "memory_basement",
|
|
1038
|
-
description: "Send a memory to the basement (archive it). Sets archived=true and stamps archivedAt. " +
|
|
1039
|
-
"The memory is removed from bootstrap and default search but remains retrievable via " +
|
|
1040
|
-
"memory_get and memory_search(includeArchived:true). Deliberate and GLOBAL — this is a " +
|
|
1041
|
-
"visibility flag, not a deletion: provenance and history are untouched. Scoped to your own memories only.",
|
|
1042
|
-
inputSchema: {
|
|
1043
|
-
type: "object",
|
|
1044
|
-
properties: {
|
|
1045
|
-
id: { type: "string", description: "ID of the memory to basement (archive)" },
|
|
1046
|
-
},
|
|
1047
|
-
required: ["id"],
|
|
1048
|
-
},
|
|
1049
|
-
},
|
|
1050
957
|
impl: memoryBasement,
|
|
1051
958
|
contract: {
|
|
1052
959
|
summary: "Write echo of the archived record { id, archived:true, archivedAt, ... }. No internal embedding fields; the flip round-trips via memory_get.",
|
|
@@ -1058,19 +965,6 @@ export const TOOLS = {
|
|
|
1058
965
|
},
|
|
1059
966
|
},
|
|
1060
967
|
memory_restore: {
|
|
1061
|
-
def: {
|
|
1062
|
-
name: "memory_restore",
|
|
1063
|
-
description: "Restore a basemented (archived) memory. Clears archived and archivedAt. Deliberate and GLOBAL — " +
|
|
1064
|
-
"this un-retires the memory for EVERY session, not a session-local view (per-session reuse is " +
|
|
1065
|
-
"drawers, which do not exist yet). Scoped to your own memories only.",
|
|
1066
|
-
inputSchema: {
|
|
1067
|
-
type: "object",
|
|
1068
|
-
properties: {
|
|
1069
|
-
id: { type: "string", description: "ID of the memory to restore (un-archive)" },
|
|
1070
|
-
},
|
|
1071
|
-
required: ["id"],
|
|
1072
|
-
},
|
|
1073
|
-
},
|
|
1074
968
|
impl: memoryRestore,
|
|
1075
969
|
contract: {
|
|
1076
970
|
summary: "Write echo of the restored record { id, archived:false, ... }. No internal embedding fields; the flip round-trips via memory_get.",
|
|
@@ -1082,20 +976,6 @@ export const TOOLS = {
|
|
|
1082
976
|
},
|
|
1083
977
|
},
|
|
1084
978
|
memory_get: {
|
|
1085
|
-
def: {
|
|
1086
|
-
name: "memory_get",
|
|
1087
|
-
description: "Retrieve a specific memory by ID. The record's raw embedding vector is omitted by default (it is large and not useful to a caller); pass includeEmbedding=true to include it.",
|
|
1088
|
-
annotations: { readOnlyHint: true },
|
|
1089
|
-
inputSchema: {
|
|
1090
|
-
type: "object",
|
|
1091
|
-
properties: {
|
|
1092
|
-
id: { type: "string", description: "Memory ID" },
|
|
1093
|
-
includeTrust: { type: "boolean", description: "Attach a trust-evidence block (provenance, author, usage, freshness, supersession) to the record. Default false." },
|
|
1094
|
-
includeEmbedding: { type: "boolean", description: "Include the raw embedding vector (hundreds of floats) in the returned record. Omitted by default because it is large and rarely useful to a caller. Default false." },
|
|
1095
|
-
},
|
|
1096
|
-
required: ["id"],
|
|
1097
|
-
},
|
|
1098
|
-
},
|
|
1099
979
|
impl: memoryGet,
|
|
1100
980
|
contract: {
|
|
1101
981
|
summary: "The full memory record { id, agentId, content, durability, createdAt, ... } for the caller's own id — embedding + embeddingModel stripped by default.",
|
|
@@ -1107,16 +987,6 @@ export const TOOLS = {
|
|
|
1107
987
|
},
|
|
1108
988
|
},
|
|
1109
989
|
memory_delete: {
|
|
1110
|
-
def: {
|
|
1111
|
-
name: "memory_delete",
|
|
1112
|
-
description: "Delete a memory by ID. You can only delete your own memories.",
|
|
1113
|
-
annotations: { destructiveHint: true },
|
|
1114
|
-
inputSchema: {
|
|
1115
|
-
type: "object",
|
|
1116
|
-
properties: { id: { type: "string", description: "Memory ID to delete" } },
|
|
1117
|
-
required: ["id"],
|
|
1118
|
-
},
|
|
1119
|
-
},
|
|
1120
990
|
impl: memoryDelete,
|
|
1121
991
|
contract: {
|
|
1122
992
|
summary: "Deletes the caller's own memory 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.",
|
|
@@ -1125,31 +995,6 @@ export const TOOLS = {
|
|
|
1125
995
|
},
|
|
1126
996
|
},
|
|
1127
997
|
bootstrap: {
|
|
1128
|
-
def: {
|
|
1129
|
-
name: "bootstrap",
|
|
1130
|
-
description: "Get session context: soul + memories + predicted context. Run at session start. Pass subjects for predictive loading.",
|
|
1131
|
-
annotations: { readOnlyHint: true },
|
|
1132
|
-
inputSchema: {
|
|
1133
|
-
type: "object",
|
|
1134
|
-
properties: {
|
|
1135
|
-
maxTokens: { type: "number", description: "Content-selection budget in tokens (default 4000): the hard cap on how much soul/memory/finding CONTENT is selected. The actual serialized response (reported by tokenEstimate) may exceed this by the structured-container JSON scaffolding — maxTokens bounds what is selected, not the raw output size. Raise it to include more content." },
|
|
1136
|
-
currentTask: { type: "string", description: "Current task — enables semantic search for relevant memories" },
|
|
1137
|
-
channel: { type: "string", description: "Channel name (discord, tps-mail, claude-code)" },
|
|
1138
|
-
surface: { type: "string", description: "Surface name (tps-build, tps-review, cli-session)" },
|
|
1139
|
-
subjects: { type: "array", items: { type: "string" }, description: "Entity names to preload context for" },
|
|
1140
|
-
entities: {
|
|
1141
|
-
type: "array",
|
|
1142
|
-
items: { type: "string" },
|
|
1143
|
-
description: "Your declared attention-plane vocabulary strings (e.g. \"issue:owner/repo#123\") for collision surfacing's 'Others in the room' block — teammates with overlapping active work. Falls back to your own most-recent workspace-state entities when omitted.",
|
|
1144
|
-
},
|
|
1145
|
-
includeTrust: { type: "boolean", description: "Also return a `trust` array with a per-included-memory trust-evidence block (provenance, author, usage, freshness, supersession). Default false." },
|
|
1146
|
-
abstain: { type: "boolean", description: "Opt into a task-relevance abstention verdict: also return an `abstention` object ({ abstained, bestScore, threshold }) reporting whether any memory covered `currentTask` above a global confidence threshold. Default false." },
|
|
1147
|
-
includeContext: { type: "boolean", description: "Also return the prose `context` string — a human-readable mirror of the structured soul/memories/predicted/teammateFindings containers (which are the canonical payload). Default false here: the structured fields already carry everything, so shipping the prose too would double the payload." },
|
|
1148
|
-
maxEvents: { type: "number", description: "Cap on how many recent org events to return (default 10). Events are counted against maxTokens like every other content section." },
|
|
1149
|
-
includeEventDetail: { type: "boolean", description: "Also include each org event's verbose `detail` JSON (migration internals, etc.). Default false: bootstrap ships lean events (id/kind/summary/createdAt/targetIds/scope); `detail` mostly restates the summary and is pure bloat for a connector." },
|
|
1150
|
-
},
|
|
1151
|
-
},
|
|
1152
|
-
},
|
|
1153
998
|
impl: bootstrap,
|
|
1154
999
|
contract: {
|
|
1155
1000
|
summary: "Session context: { agentId, soul, memories, predicted, teammateFindings, events, sections, tokenEstimate, memoriesIncluded, ..., context, flairVersion }. "
|
|
@@ -1159,6 +1004,7 @@ export const TOOLS = {
|
|
|
1159
1004
|
"sections", "tokenEstimate", "maxTokens", "memoriesIncluded", "memoriesAvailable",
|
|
1160
1005
|
"memoriesTruncated", "teammateFindingsIncluded", "teammateFindingsTruncated",
|
|
1161
1006
|
"teammateFindingsMatched", "context", "flairVersion",
|
|
1007
|
+
"eventWatermark", "eventsHasMore", "eventsRemaining",
|
|
1162
1008
|
// flair#1270 — the payload token LEDGER: every token-charged content
|
|
1163
1009
|
// class has a counter, so tokenEstimate ≈ scaffoldTokens + soulTokens +
|
|
1164
1010
|
// memoryTokens + trustTokens + eventsTokens decomposes from the payload
|
|
@@ -1174,6 +1020,7 @@ export const TOOLS = {
|
|
|
1174
1020
|
teammateFindingsMatched: "number", context: "string", flairVersion: "string",
|
|
1175
1021
|
soulTokens: "number", memoryTokens: "number", trustTokens: "number",
|
|
1176
1022
|
eventsTokens: "number", scaffoldTokens: "number",
|
|
1023
|
+
eventWatermark: "string", eventsHasMore: "boolean", eventsRemaining: "number",
|
|
1177
1024
|
},
|
|
1178
1025
|
invariants: {
|
|
1179
1026
|
// count == delivered — the historical count/charge/deliver drift.
|
|
@@ -1262,18 +1109,6 @@ export const TOOLS = {
|
|
|
1262
1109
|
},
|
|
1263
1110
|
},
|
|
1264
1111
|
soul_set: {
|
|
1265
|
-
def: {
|
|
1266
|
-
name: "soul_set",
|
|
1267
|
-
description: "Soul changes require operator credentials through the REST API or CLI; runtime tool calls are refused.",
|
|
1268
|
-
inputSchema: {
|
|
1269
|
-
type: "object",
|
|
1270
|
-
properties: {
|
|
1271
|
-
key: { type: "string", description: "Entry key (e.g. 'role', 'standards', 'project')" },
|
|
1272
|
-
value: { type: "string", description: "Entry value" },
|
|
1273
|
-
},
|
|
1274
|
-
required: ["key", "value"],
|
|
1275
|
-
},
|
|
1276
|
-
},
|
|
1277
1112
|
impl: soulSet,
|
|
1278
1113
|
contract: {
|
|
1279
1114
|
summary: "Refuses runtime Soul writes, including admin-agent delegation, with { error, status:403 }. Operators use the authenticated REST or CLI path.",
|
|
@@ -1282,16 +1117,6 @@ export const TOOLS = {
|
|
|
1282
1117
|
},
|
|
1283
1118
|
},
|
|
1284
1119
|
soul_get: {
|
|
1285
|
-
def: {
|
|
1286
|
-
name: "soul_get",
|
|
1287
|
-
description: "Get a personality or project context entry.",
|
|
1288
|
-
annotations: { readOnlyHint: true },
|
|
1289
|
-
inputSchema: {
|
|
1290
|
-
type: "object",
|
|
1291
|
-
properties: { key: { type: "string", description: "Entry key" } },
|
|
1292
|
-
required: ["key"],
|
|
1293
|
-
},
|
|
1294
|
-
},
|
|
1295
1120
|
impl: soulGet,
|
|
1296
1121
|
contract: {
|
|
1297
1122
|
summary: "The soul entry { id, agentId, key, value, createdAt } for the caller's own `${agentId}:${key}`.",
|
|
@@ -1301,22 +1126,6 @@ export const TOOLS = {
|
|
|
1301
1126
|
},
|
|
1302
1127
|
},
|
|
1303
1128
|
flair_workspace_set: {
|
|
1304
|
-
def: {
|
|
1305
|
-
name: "flair_workspace_set",
|
|
1306
|
-
description: "Set your agent's current workspace state in the Office Space coordination layer. Attributed to you — you can only write your own state.",
|
|
1307
|
-
inputSchema: {
|
|
1308
|
-
type: "object",
|
|
1309
|
-
properties: {
|
|
1310
|
-
ref: { type: "string", description: "Workspace ref — branch, worktree, or task ref" },
|
|
1311
|
-
label: { type: "string", description: "Human-readable label" },
|
|
1312
|
-
provider: { type: "string", description: "Provider/runtime (default mcp)" },
|
|
1313
|
-
task: { type: "string", description: "Task/issue id" },
|
|
1314
|
-
phase: { type: "string", description: "Current phase (design, implement, review)" },
|
|
1315
|
-
summary: { type: "string", description: "Short summary of current state" },
|
|
1316
|
-
},
|
|
1317
|
-
required: ["ref"],
|
|
1318
|
-
},
|
|
1319
|
-
},
|
|
1320
1129
|
impl: workspaceSet,
|
|
1321
1130
|
contract: {
|
|
1322
1131
|
summary: "Writes the caller's workspace state keyed `${agentId}:${ref}`, attributed to the caller (never the body). The echo is thin; persistence is verified in storage.",
|
|
@@ -1324,21 +1133,6 @@ export const TOOLS = {
|
|
|
1324
1133
|
},
|
|
1325
1134
|
},
|
|
1326
1135
|
flair_orgevent: {
|
|
1327
|
-
def: {
|
|
1328
|
-
name: "flair_orgevent",
|
|
1329
|
-
description: "Publish an org-wide coordination event (claim/release/status) to the Office Space. Attributed to you — you cannot publish as another agent.",
|
|
1330
|
-
inputSchema: {
|
|
1331
|
-
type: "object",
|
|
1332
|
-
properties: {
|
|
1333
|
-
kind: { type: "string", description: "Event kind (coord.claim, coord.release, status)" },
|
|
1334
|
-
summary: { type: "string", description: "Short summary of the event" },
|
|
1335
|
-
detail: { type: "string", description: "Longer detail payload" },
|
|
1336
|
-
scope: { type: "string", description: "Scope (an agent id, repo, or 'org')" },
|
|
1337
|
-
targets: { type: "array", items: { type: "string" }, description: "Recipient agent ids" },
|
|
1338
|
-
},
|
|
1339
|
-
required: ["kind", "summary"],
|
|
1340
|
-
},
|
|
1341
|
-
},
|
|
1342
1136
|
impl: orgEvent,
|
|
1343
1137
|
contract: {
|
|
1344
1138
|
summary: "Publishes an org event attributed to the caller (authorId from identity, never the body). The echo is thin; persistence is verified in storage.",
|
|
@@ -1346,21 +1140,6 @@ export const TOOLS = {
|
|
|
1346
1140
|
},
|
|
1347
1141
|
},
|
|
1348
1142
|
attention: {
|
|
1349
|
-
def: {
|
|
1350
|
-
name: "attention",
|
|
1351
|
-
description: "What's touching entity E in the last N days? A unified, grouped-by-source view across memories, " +
|
|
1352
|
-
"relationships, active work (WorkspaceState), teammate presence, and org events. Entity must be a " +
|
|
1353
|
-
"vocabulary string (e.g. 'repo:owner/name', 'issue:owner/repo#123', 'subsystem:embeddings').",
|
|
1354
|
-
annotations: { readOnlyHint: true },
|
|
1355
|
-
inputSchema: {
|
|
1356
|
-
type: "object",
|
|
1357
|
-
properties: {
|
|
1358
|
-
entity: { type: "string", description: "Vocabulary string, exact match (type:value — e.g. 'repo:tpsdev-ai/flair')" },
|
|
1359
|
-
days: { type: "number", description: "Window size in days (default 7)" },
|
|
1360
|
-
},
|
|
1361
|
-
required: ["entity"],
|
|
1362
|
-
},
|
|
1363
|
-
},
|
|
1364
1143
|
impl: attention,
|
|
1365
1144
|
contract: {
|
|
1366
1145
|
summary: "Grouped-by-source view { entity, windowDays, since, groups:{memory,relationship,workspaceState,presence,orgEvent}, counts } for entity E over N days.",
|
|
@@ -1373,21 +1152,6 @@ export const TOOLS = {
|
|
|
1373
1152
|
},
|
|
1374
1153
|
},
|
|
1375
1154
|
record_usage: {
|
|
1376
|
-
def: {
|
|
1377
|
-
name: "record_usage",
|
|
1378
|
-
description: "Report that one or more memories were actually USED — cited or relied on to ground an answer or decision. " +
|
|
1379
|
-
"Distinct from search (surfacing a memory is not usage). Drives the recall-quality usage signal; dedup'd " +
|
|
1380
|
-
"(you can only count once per memory) and rate-limited. " +
|
|
1381
|
-
RECORD_USAGE_ID_MERGE_CONTRACT,
|
|
1382
|
-
inputSchema: {
|
|
1383
|
-
type: "object",
|
|
1384
|
-
properties: {
|
|
1385
|
-
memoryIds: { type: "array", items: { type: "string" }, description: "IDs of the memories that were used (max 20 per call). Merged with memoryId when both are supplied." },
|
|
1386
|
-
memoryId: { type: "string", description: "Convenience alias for a single memory id. Merged with memoryIds when both are supplied — not dropped." },
|
|
1387
|
-
attribution: { type: "string", description: "Optional free-text note on what used it (opaque — stored for audit only, max 500 chars)" },
|
|
1388
|
-
},
|
|
1389
|
-
},
|
|
1390
|
-
},
|
|
1391
1155
|
impl: recordUsage,
|
|
1392
1156
|
contract: {
|
|
1393
1157
|
summary: "Invariant acknowledgement { recorded:true } — byte-identical regardless of how many ids counted (no id enumeration, Sherlock).",
|
|
@@ -1396,7 +1160,7 @@ export const TOOLS = {
|
|
|
1396
1160
|
invariants: { fullyResolved: true },
|
|
1397
1161
|
},
|
|
1398
1162
|
},
|
|
1399
|
-
};
|
|
1163
|
+
});
|
|
1400
1164
|
/** The tool definitions for a tools/list response (exactly the 12 curated tools). */
|
|
1401
1165
|
export function listToolDefs() {
|
|
1402
1166
|
return Object.values(TOOLS).map((t) => t.def);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - resources/memory-read-scope.ts's resolveReadScope() — the cross-agent
|
|
7
7
|
* READ scope every read path (Memory.search/get, SemanticSearch,
|
|
8
8
|
* MemoryBootstrap, the by-id auth-middleware guard) resolves through.
|
|
9
|
-
* - src/
|
|
9
|
+
* - src/commands/federation.ts's runFederationSyncOnce() — the federation-sync PUSH filter
|
|
10
10
|
* that must not replicate `private` memories to peer instances.
|
|
11
11
|
*
|
|
12
12
|
* Deliberately has ZERO imports — not even "harper". That is
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* imports `databases` from "harper", and that package's
|
|
17
17
|
* top-level init eagerly resolves storage paths and THROWS when there is no
|
|
18
18
|
* live Harper runtime backing it (confirmed empirically — it takes down
|
|
19
|
-
* even `flair --help`). So src/cli.ts
|
|
20
|
-
* resources/memory-read-scope.ts (or anything else that drags that
|
|
19
|
+
* even `flair --help`). So the CLI (src/cli.ts / src/commands/federation.ts)
|
|
20
|
+
* must never import resources/memory-read-scope.ts (or anything else that drags that
|
|
21
21
|
* side-effecting import in) directly. This module is the safe seam: a pure
|
|
22
22
|
* function + constant that both sides can import without dragging in
|
|
23
23
|
* "harper".
|
|
@@ -61,6 +61,7 @@ import { buildRegistry } from "./migrations/registry.js";
|
|
|
61
61
|
import { runMigrationCycle } from "./migrations/runner.js";
|
|
62
62
|
import { markIdleMigrationsFailed, seedIdleProgress, setCyclePhase } from "./migrations/progress.js";
|
|
63
63
|
import { describeUnresolvableDataDir, resolveWritableMigrationDataDir, } from "./migrations/data-dir.js";
|
|
64
|
+
import { scheduleFollowUpCycles } from "./migrations/recheck.js";
|
|
64
65
|
import { getMode } from "./embeddings-provider.js";
|
|
65
66
|
/** Same "resolve the running package's own version" idiom as resources/health.ts. */
|
|
66
67
|
export function resolveRunningVersion() {
|
|
@@ -147,6 +148,50 @@ function reportBootFailure(registry, reason) {
|
|
|
147
148
|
markIdleMigrationsFailed(registry.list().map((m) => m.id), reason);
|
|
148
149
|
}
|
|
149
150
|
let scheduled = false;
|
|
151
|
+
let followUp = null;
|
|
152
|
+
/**
|
|
153
|
+
* Runs one cycle against an already-resolved writable data dir. Shared by
|
|
154
|
+
* the boot-keyed first pass and the flair#1073 follow-up rechecks. Never
|
|
155
|
+
* throws. Returns whether a follow-up series should be armed (true unless
|
|
156
|
+
* this invocation is itself a follow-up, or the cycle was a hard failure
|
|
157
|
+
* that needs an operator — those retry on the next restart).
|
|
158
|
+
*/
|
|
159
|
+
async function invokeCycle(registry, dataDir, opts = { followUp: false }) {
|
|
160
|
+
try {
|
|
161
|
+
const result = await runMigrationCycle({
|
|
162
|
+
registry,
|
|
163
|
+
getTable,
|
|
164
|
+
dataDir,
|
|
165
|
+
runningVersion: resolveRunningVersion(),
|
|
166
|
+
});
|
|
167
|
+
// `nothing pending` is the healthy no-op; `single-flight` is the
|
|
168
|
+
// lock guard working as designed on a multi-threaded boot. Anything
|
|
169
|
+
// else is a cycle that WANTED to run and couldn't, and must be loud.
|
|
170
|
+
if (!result.ran && result.reason && !isBenignSkip(result.reason)) {
|
|
171
|
+
if (!opts.followUp) {
|
|
172
|
+
reportBootFailure(registry, `migration cycle did not run: ${result.reason}`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.error(`[flair-migrations] follow-up cycle did not run: ${result.reason}`);
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (opts.followUp && result.ran) {
|
|
180
|
+
console.info("[flair-migrations] follow-up cycle ran — embedding-stamp (or another pending migration) had work the boot pass missed");
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
// Defense-in-depth only — runMigrationCycle is documented to never
|
|
186
|
+
// throw. A boot-path exception must never surface here regardless.
|
|
187
|
+
const reason = `unexpected error from runMigrationCycle: ${err?.message ?? String(err)}`;
|
|
188
|
+
if (!opts.followUp)
|
|
189
|
+
reportBootFailure(registry, reason);
|
|
190
|
+
else
|
|
191
|
+
console.error(`[flair-migrations] ${reason}`);
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
150
195
|
export function scheduleMigrationBoot() {
|
|
151
196
|
if (scheduled)
|
|
152
197
|
return;
|
|
@@ -170,28 +215,22 @@ export function scheduleMigrationBoot() {
|
|
|
170
215
|
// failure at that point is reported back rather than thrown, which is
|
|
171
216
|
// precisely how flair#812 stayed invisible. See data-dir.ts.
|
|
172
217
|
const resolved = resolveWritableMigrationDataDir();
|
|
173
|
-
|
|
218
|
+
const dataDir = resolved.dataDir;
|
|
219
|
+
if (!dataDir) {
|
|
174
220
|
reportBootFailure(registry, describeUnresolvableDataDir(resolved.tried));
|
|
175
221
|
return;
|
|
176
222
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
223
|
+
const ok = await invokeCycle(registry, dataDir, { followUp: false });
|
|
224
|
+
// flair#1073: even a clean "nothing pending" can be a Fabric race
|
|
225
|
+
// (tables ready, rows not yet visible). Arm delayed rechecks so a
|
|
226
|
+
// split corpus heals in-process instead of sitting until the next
|
|
227
|
+
// restart. A hard boot failure does not arm them — those need an
|
|
228
|
+
// operator or a restart (same as before).
|
|
229
|
+
if (ok) {
|
|
230
|
+
followUp?.cancel();
|
|
231
|
+
followUp = scheduleFollowUpCycles({
|
|
232
|
+
run: () => invokeCycle(registry, dataDir, { followUp: true }).then(() => undefined),
|
|
183
233
|
});
|
|
184
|
-
// `nothing pending` is the healthy no-op; `single-flight` is the
|
|
185
|
-
// lock guard working as designed on a multi-threaded boot. Anything
|
|
186
|
-
// else is a cycle that WANTED to run and couldn't, and must be loud.
|
|
187
|
-
if (!result.ran && result.reason && !isBenignSkip(result.reason)) {
|
|
188
|
-
reportBootFailure(registry, `migration cycle did not run: ${result.reason}`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
catch (err) {
|
|
192
|
-
// Defense-in-depth only — runMigrationCycle is documented to never
|
|
193
|
-
// throw. A boot-path exception must never surface here regardless.
|
|
194
|
-
reportBootFailure(registry, `unexpected error from runMigrationCycle: ${err?.message ?? String(err)}`);
|
|
195
234
|
}
|
|
196
235
|
})();
|
|
197
236
|
});
|
|
@@ -209,5 +248,7 @@ export function isBenignSkip(reason) {
|
|
|
209
248
|
// ever boots once).
|
|
210
249
|
export function _resetMigrationBootForTests() {
|
|
211
250
|
scheduled = false;
|
|
251
|
+
followUp?.cancel();
|
|
252
|
+
followUp = null;
|
|
212
253
|
}
|
|
213
254
|
scheduleMigrationBoot();
|