@elizaos/plugin-relationships 2.0.3-beta.5 → 2.0.3-beta.7
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/actions/entity.d.ts +72 -0
- package/dist/actions/entity.d.ts.map +1 -0
- package/dist/actions/entity.js +312 -0
- package/dist/actions/entity.js.map +1 -0
- package/dist/components/relationships/RelationshipsSpatialView.d.ts +72 -0
- package/dist/components/relationships/RelationshipsSpatialView.d.ts.map +1 -0
- package/dist/components/relationships/RelationshipsSpatialView.js +120 -0
- package/dist/components/relationships/RelationshipsSpatialView.js.map +1 -0
- package/dist/components/relationships/RelationshipsView.d.ts +70 -0
- package/dist/components/relationships/RelationshipsView.d.ts.map +1 -0
- package/dist/components/relationships/RelationshipsView.js +193 -0
- package/dist/components/relationships/RelationshipsView.js.map +1 -0
- package/dist/components/relationships/relationships-view-bundle.d.ts +2 -0
- package/dist/components/relationships/relationships-view-bundle.d.ts.map +1 -0
- package/dist/components/relationships/relationships-view-bundle.js +5 -0
- package/dist/components/relationships/relationships-view-bundle.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +2 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +240 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +49 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +23 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +33 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/entity-graph.d.ts +20 -0
- package/dist/providers/entity-graph.d.ts.map +1 -0
- package/dist/providers/entity-graph.js +83 -0
- package/dist/providers/entity-graph.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +10 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +51 -0
- package/dist/types.js.map +1 -0
- package/dist/views/bundle.js +328 -0
- package/dist/views/bundle.js.map +1 -0
- package/package.json +8 -8
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `KNOWLEDGE_GRAPH` umbrella action — direct CRUD over the runtime
|
|
3
|
+
* knowledge graph.
|
|
4
|
+
*
|
|
5
|
+
* This is the relationships-plugin "extras" surface. The graph stores
|
|
6
|
+
* themselves live in the runtime: `@elizaos/agent`'s
|
|
7
|
+
* {@link KnowledgeGraphService} owns the per-agent `EntityStore` /
|
|
8
|
+
* `RelationshipStore`. This action resolves that service and dispatches
|
|
9
|
+
* graph operations onto it. No DB access, no merge engine, no LLM planning
|
|
10
|
+
* lives here — those are runtime / PA concerns respectively.
|
|
11
|
+
*
|
|
12
|
+
* Op-based dispatch:
|
|
13
|
+
* - `create` create a person/org/place/project/concept node
|
|
14
|
+
* - `read` fetch a single entity by id
|
|
15
|
+
* - `list` list known entities (optionally filtered by kind)
|
|
16
|
+
* - `log_interaction` record an inbound/outbound interaction on an entity
|
|
17
|
+
* - `set_identity` observe a verified (platform, handle) identity
|
|
18
|
+
* - `set_relationship` upsert a typed edge between two entities
|
|
19
|
+
* - `merge` fold duplicate entities into a target
|
|
20
|
+
*
|
|
21
|
+
* Owner-only (`roleGate.minRole: OWNER` + the {@link hasOwnerAccess} gate).
|
|
22
|
+
*
|
|
23
|
+
* NOTE on naming: this action is `KNOWLEDGE_GRAPH`, NOT `ENTITY`.
|
|
24
|
+
* `@elizaos/plugin-personal-assistant` registers the `ENTITY` action (a rich
|
|
25
|
+
* orchestration over the legacy Rolodex contact model with an LLM planner +
|
|
26
|
+
* voice-grounded replies). That stays in PA; this action is the thin runtime
|
|
27
|
+
* graph-CRUD surface that powers the relationships viewer. Registering it
|
|
28
|
+
* under a distinct name keeps exactly one `ENTITY` action at runtime.
|
|
29
|
+
*/
|
|
30
|
+
import type { Action } from "@elizaos/core";
|
|
31
|
+
import { type EntityOp } from "../types.js";
|
|
32
|
+
/**
|
|
33
|
+
* Parameter shape accepted by the action. The planner provides these via
|
|
34
|
+
* `options.parameters`; every field is optional and validated per-op.
|
|
35
|
+
*/
|
|
36
|
+
export interface EntityActionParameters {
|
|
37
|
+
/** Canonical op name. Planner may also provide `action` / `subaction`. */
|
|
38
|
+
op?: EntityOp;
|
|
39
|
+
subaction?: EntityOp;
|
|
40
|
+
action?: EntityOp;
|
|
41
|
+
/** Entity kind for `create` / `list` filter (person / organization / …). */
|
|
42
|
+
kind?: string;
|
|
43
|
+
/** Display name for `create`. */
|
|
44
|
+
name?: string;
|
|
45
|
+
/** Target entity id for `read` / `log_interaction` / `merge` target. */
|
|
46
|
+
entityId?: string;
|
|
47
|
+
/** Identity platform for `set_identity` (e.g. `discord`, `email`). */
|
|
48
|
+
platform?: string;
|
|
49
|
+
/** Handle on `platform` for `set_identity`. */
|
|
50
|
+
handle?: string;
|
|
51
|
+
/** Display name shown for an observed identity. */
|
|
52
|
+
displayName?: string;
|
|
53
|
+
/** Edge target id for `set_relationship`. */
|
|
54
|
+
toEntityId?: string;
|
|
55
|
+
/** Edge source id for `set_relationship`. Defaults to `self`. */
|
|
56
|
+
fromEntityId?: string;
|
|
57
|
+
/** Edge type label for `set_relationship` (e.g. `manages`). */
|
|
58
|
+
relationshipType?: string;
|
|
59
|
+
/** Source entity ids consumed when calling `merge`. */
|
|
60
|
+
sourceEntityIds?: string[];
|
|
61
|
+
/** Free-form evidence string for provenance trail. */
|
|
62
|
+
evidence?: string;
|
|
63
|
+
/** Interaction direction for `log_interaction`. Defaults to `outbound`. */
|
|
64
|
+
direction?: "inbound" | "outbound";
|
|
65
|
+
/** Interaction summary text for `log_interaction`. */
|
|
66
|
+
summary?: string;
|
|
67
|
+
/** Limit for `list`. */
|
|
68
|
+
limit?: number;
|
|
69
|
+
}
|
|
70
|
+
export declare const entityAction: Action;
|
|
71
|
+
export default entityAction;
|
|
72
|
+
//# sourceMappingURL=entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../src/actions/entity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EACV,MAAM,EAOP,MAAM,eAAe,CAAC;AAKvB,OAAO,EAEL,KAAK,QAAQ,EAId,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd,SAAS,CAAC,EAAE,QAAQ,CAAC;IACrB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACnC,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqCD,eAAO,MAAM,YAAY,EAAE,MA2T1B,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { hasOwnerAccess, resolveKnowledgeGraphService } from "@elizaos/agent";
|
|
2
|
+
import { logger } from "@elizaos/core";
|
|
3
|
+
import { SELF_ENTITY_ID } from "@elizaos/shared";
|
|
4
|
+
import {
|
|
5
|
+
ENTITY_OPS,
|
|
6
|
+
RELATIONSHIPS_ACTION_NAME,
|
|
7
|
+
RELATIONSHIPS_CONTEXTS,
|
|
8
|
+
RELATIONSHIPS_LOG_PREFIX
|
|
9
|
+
} from "../types.js";
|
|
10
|
+
function getParams(options) {
|
|
11
|
+
const params = options?.parameters;
|
|
12
|
+
return params ?? {};
|
|
13
|
+
}
|
|
14
|
+
function resolveOp(params) {
|
|
15
|
+
const candidate = params.op ?? params.subaction ?? params.action;
|
|
16
|
+
if (typeof candidate !== "string") return null;
|
|
17
|
+
return ENTITY_OPS.includes(candidate) ? candidate : null;
|
|
18
|
+
}
|
|
19
|
+
function trimmed(value) {
|
|
20
|
+
if (typeof value !== "string") return null;
|
|
21
|
+
const t = value.trim();
|
|
22
|
+
return t.length > 0 ? t : null;
|
|
23
|
+
}
|
|
24
|
+
function entitySummary(entity) {
|
|
25
|
+
return {
|
|
26
|
+
entityId: entity.entityId,
|
|
27
|
+
type: entity.type,
|
|
28
|
+
preferredName: entity.preferredName
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const ENTITY_KINDS_DEFAULT = "person";
|
|
32
|
+
const entityAction = {
|
|
33
|
+
name: RELATIONSHIPS_ACTION_NAME,
|
|
34
|
+
similes: ["ENTITY_CRUD", "GRAPH_ENTITY", "KNOWLEDGE_GRAPH_CRUD"],
|
|
35
|
+
description: "Direct CRUD over the runtime knowledge graph (entities + typed edges): create | read | list | log_interaction | set_identity | set_relationship | merge. Backs the relationships viewer. Contact orchestration with planning -> ENTITY (personal-assistant).",
|
|
36
|
+
descriptionCompressed: "KNOWLEDGE_GRAPH create|read|list|log_interaction|set_identity|set_relationship|merge",
|
|
37
|
+
tags: [
|
|
38
|
+
"domain:relationships",
|
|
39
|
+
"capability:read",
|
|
40
|
+
"capability:write",
|
|
41
|
+
"capability:update",
|
|
42
|
+
"capability:delete",
|
|
43
|
+
"surface:internal"
|
|
44
|
+
],
|
|
45
|
+
contexts: [...RELATIONSHIPS_CONTEXTS],
|
|
46
|
+
contextGate: { anyOf: [...RELATIONSHIPS_CONTEXTS] },
|
|
47
|
+
roleGate: { minRole: "OWNER" },
|
|
48
|
+
validate: async (runtime, message, _state) => {
|
|
49
|
+
if (!resolveKnowledgeGraphService(runtime)) return false;
|
|
50
|
+
return hasOwnerAccess(runtime, message);
|
|
51
|
+
},
|
|
52
|
+
handler: async (runtime, message, _state, options, callback) => {
|
|
53
|
+
if (!await hasOwnerAccess(runtime, message)) {
|
|
54
|
+
const text = "The knowledge graph is restricted to the owner.";
|
|
55
|
+
await callback?.({
|
|
56
|
+
text,
|
|
57
|
+
source: "action",
|
|
58
|
+
action: RELATIONSHIPS_ACTION_NAME
|
|
59
|
+
});
|
|
60
|
+
return { success: false, text, data: { error: "PERMISSION_DENIED" } };
|
|
61
|
+
}
|
|
62
|
+
const service = resolveKnowledgeGraphService(runtime);
|
|
63
|
+
if (!service) {
|
|
64
|
+
const text = "The knowledge graph service is not available.";
|
|
65
|
+
await callback?.({
|
|
66
|
+
text,
|
|
67
|
+
source: "action",
|
|
68
|
+
action: RELATIONSHIPS_ACTION_NAME
|
|
69
|
+
});
|
|
70
|
+
return { success: false, text, data: { error: "SERVICE_UNAVAILABLE" } };
|
|
71
|
+
}
|
|
72
|
+
const params = getParams(options);
|
|
73
|
+
const op = resolveOp(params);
|
|
74
|
+
if (!op) {
|
|
75
|
+
const text = "Tell me which knowledge-graph op: create, read, list, log_interaction, set_identity, set_relationship, or merge.";
|
|
76
|
+
await callback?.({
|
|
77
|
+
text,
|
|
78
|
+
source: "action",
|
|
79
|
+
action: RELATIONSHIPS_ACTION_NAME
|
|
80
|
+
});
|
|
81
|
+
return { success: false, text, data: { error: "MISSING_OP" } };
|
|
82
|
+
}
|
|
83
|
+
const entityStore = service.getEntityStore();
|
|
84
|
+
const relationshipStore = service.getRelationshipStore();
|
|
85
|
+
const reply = async (result) => {
|
|
86
|
+
await callback?.({
|
|
87
|
+
text: result.text,
|
|
88
|
+
source: "action",
|
|
89
|
+
action: RELATIONSHIPS_ACTION_NAME
|
|
90
|
+
});
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
logger.info(
|
|
94
|
+
`${RELATIONSHIPS_LOG_PREFIX} ${RELATIONSHIPS_ACTION_NAME} op=${op}`
|
|
95
|
+
);
|
|
96
|
+
switch (op) {
|
|
97
|
+
case "create": {
|
|
98
|
+
const name = trimmed(params.name);
|
|
99
|
+
if (!name) {
|
|
100
|
+
return reply({
|
|
101
|
+
success: false,
|
|
102
|
+
text: "I need a display name to create an entity.",
|
|
103
|
+
data: { op, error: "MISSING_FIELDS" }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const kind = trimmed(params.kind) ?? ENTITY_KINDS_DEFAULT;
|
|
107
|
+
const entity = await entityStore.upsert({
|
|
108
|
+
type: kind,
|
|
109
|
+
preferredName: name,
|
|
110
|
+
identities: [],
|
|
111
|
+
tags: [],
|
|
112
|
+
visibility: "owner_agent_admin",
|
|
113
|
+
state: {}
|
|
114
|
+
});
|
|
115
|
+
return reply({
|
|
116
|
+
success: true,
|
|
117
|
+
text: `Created ${kind} "${entity.preferredName}" (${entity.entityId}).`,
|
|
118
|
+
data: { op, entity: entitySummary(entity) }
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
case "read": {
|
|
122
|
+
const entityId = trimmed(params.entityId);
|
|
123
|
+
if (!entityId) {
|
|
124
|
+
return reply({
|
|
125
|
+
success: false,
|
|
126
|
+
text: "I need an entityId to read.",
|
|
127
|
+
data: { op, error: "MISSING_FIELDS" }
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const entity = await entityStore.get(entityId);
|
|
131
|
+
if (!entity) {
|
|
132
|
+
return reply({
|
|
133
|
+
success: false,
|
|
134
|
+
text: `No entity found with id ${entityId}.`,
|
|
135
|
+
data: { op, error: "NOT_FOUND", entityId }
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return reply({
|
|
139
|
+
success: true,
|
|
140
|
+
text: `${entity.preferredName} (${entity.type}) \u2014 ${entity.identities.length} identit${entity.identities.length === 1 ? "y" : "ies"}.`,
|
|
141
|
+
data: { op, entity }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
case "list": {
|
|
145
|
+
const kind = trimmed(params.kind);
|
|
146
|
+
const limit = typeof params.limit === "number" && params.limit > 0 ? Math.floor(params.limit) : 50;
|
|
147
|
+
const entities = await entityStore.list({
|
|
148
|
+
...kind ? { type: kind } : {},
|
|
149
|
+
limit
|
|
150
|
+
});
|
|
151
|
+
return reply({
|
|
152
|
+
success: true,
|
|
153
|
+
text: entities.length === 0 ? "No entities in the graph yet." : `${entities.length} entit${entities.length === 1 ? "y" : "ies"} in the graph.`,
|
|
154
|
+
data: { op, entities: entities.map(entitySummary) }
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
case "log_interaction": {
|
|
158
|
+
const entityId = trimmed(params.entityId);
|
|
159
|
+
if (!entityId) {
|
|
160
|
+
return reply({
|
|
161
|
+
success: false,
|
|
162
|
+
text: "I need an entityId to log an interaction.",
|
|
163
|
+
data: { op, error: "MISSING_FIELDS" }
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const entity = await entityStore.get(entityId);
|
|
167
|
+
if (!entity) {
|
|
168
|
+
return reply({
|
|
169
|
+
success: false,
|
|
170
|
+
text: `No entity found with id ${entityId}.`,
|
|
171
|
+
data: { op, error: "NOT_FOUND", entityId }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const platform = trimmed(params.platform) ?? entity.state.lastInteractionPlatform ?? "unknown";
|
|
175
|
+
const direction = params.direction === "inbound" ? "inbound" : "outbound";
|
|
176
|
+
await entityStore.recordInteraction(entityId, {
|
|
177
|
+
platform,
|
|
178
|
+
direction,
|
|
179
|
+
summary: trimmed(params.summary) ?? "",
|
|
180
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
181
|
+
});
|
|
182
|
+
return reply({
|
|
183
|
+
success: true,
|
|
184
|
+
text: `Logged ${direction} interaction with ${entity.preferredName} on ${platform}.`,
|
|
185
|
+
data: { op, entityId, platform, direction }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
case "set_identity": {
|
|
189
|
+
const platform = trimmed(params.platform);
|
|
190
|
+
const handle = trimmed(params.handle);
|
|
191
|
+
if (!platform || !handle) {
|
|
192
|
+
return reply({
|
|
193
|
+
success: false,
|
|
194
|
+
text: "I need both the platform and the handle to record an identity.",
|
|
195
|
+
data: { op, error: "MISSING_FIELDS" }
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const evidence = trimmed(params.evidence) ?? "user_chat";
|
|
199
|
+
const displayName = trimmed(params.displayName);
|
|
200
|
+
const observation = await entityStore.observeIdentity({
|
|
201
|
+
platform,
|
|
202
|
+
handle,
|
|
203
|
+
...displayName ? { displayName } : {},
|
|
204
|
+
evidence: [evidence],
|
|
205
|
+
confidence: 1
|
|
206
|
+
});
|
|
207
|
+
const verifiedIdentities = observation.entity.identities.map(
|
|
208
|
+
(identity) => identity.platform === platform && identity.handle === handle ? { ...identity, verified: true } : identity
|
|
209
|
+
);
|
|
210
|
+
const merged = await entityStore.upsert({
|
|
211
|
+
...observation.entity,
|
|
212
|
+
identities: verifiedIdentities
|
|
213
|
+
});
|
|
214
|
+
return reply({
|
|
215
|
+
success: true,
|
|
216
|
+
text: `Recorded identity ${platform}:${handle} on ${merged.preferredName}.`,
|
|
217
|
+
data: {
|
|
218
|
+
op,
|
|
219
|
+
entity: entitySummary(merged),
|
|
220
|
+
mergedFrom: observation.mergedFrom ?? null,
|
|
221
|
+
conflict: observation.conflict ?? false
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
case "set_relationship": {
|
|
226
|
+
const toEntityId = trimmed(params.toEntityId);
|
|
227
|
+
const relationshipType = trimmed(params.relationshipType);
|
|
228
|
+
if (!toEntityId || !relationshipType) {
|
|
229
|
+
return reply({
|
|
230
|
+
success: false,
|
|
231
|
+
text: "I need the target entity id and the relationship type (e.g. manages, colleague_of, works_at).",
|
|
232
|
+
data: { op, error: "MISSING_FIELDS" }
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const fromEntityId = trimmed(params.fromEntityId) ?? SELF_ENTITY_ID;
|
|
236
|
+
const evidence = trimmed(params.evidence) ?? "user_chat";
|
|
237
|
+
const edge = await relationshipStore.upsert({
|
|
238
|
+
fromEntityId,
|
|
239
|
+
toEntityId,
|
|
240
|
+
type: relationshipType,
|
|
241
|
+
metadata: {},
|
|
242
|
+
state: {},
|
|
243
|
+
evidence: [evidence],
|
|
244
|
+
confidence: 1,
|
|
245
|
+
source: "user_chat"
|
|
246
|
+
});
|
|
247
|
+
return reply({
|
|
248
|
+
success: true,
|
|
249
|
+
text: `Recorded ${fromEntityId} -[${relationshipType}]-> ${toEntityId}.`,
|
|
250
|
+
data: { op, relationship: edge }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
case "merge": {
|
|
254
|
+
const targetEntityId = trimmed(params.entityId);
|
|
255
|
+
const sourceEntityIds = (params.sourceEntityIds ?? []).filter(
|
|
256
|
+
(id) => typeof id === "string" && id.trim().length > 0
|
|
257
|
+
);
|
|
258
|
+
if (!targetEntityId || sourceEntityIds.length === 0) {
|
|
259
|
+
return reply({
|
|
260
|
+
success: false,
|
|
261
|
+
text: "I need a target entityId and at least one sourceEntityId to merge duplicates.",
|
|
262
|
+
data: { op, error: "MISSING_FIELDS" }
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const merged = await entityStore.merge(targetEntityId, sourceEntityIds);
|
|
266
|
+
return reply({
|
|
267
|
+
success: true,
|
|
268
|
+
text: `Merged ${sourceEntityIds.length} entit${sourceEntityIds.length === 1 ? "y" : "ies"} into ${merged.preferredName}.`,
|
|
269
|
+
data: {
|
|
270
|
+
op,
|
|
271
|
+
entity: entitySummary(merged),
|
|
272
|
+
sourceEntityIds
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
examples: [
|
|
279
|
+
[
|
|
280
|
+
{
|
|
281
|
+
name: "{{name1}}",
|
|
282
|
+
content: { text: "Add Alice as a person to my graph." }
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "{{agentName}}",
|
|
286
|
+
content: {
|
|
287
|
+
text: 'Created person "Alice".',
|
|
288
|
+
action: RELATIONSHIPS_ACTION_NAME
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
],
|
|
292
|
+
[
|
|
293
|
+
{
|
|
294
|
+
name: "{{name1}}",
|
|
295
|
+
content: { text: "Pat is my manager." }
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "{{agentName}}",
|
|
299
|
+
content: {
|
|
300
|
+
text: "Recorded self -[manages]-> Pat.",
|
|
301
|
+
action: RELATIONSHIPS_ACTION_NAME
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
var entity_default = entityAction;
|
|
308
|
+
export {
|
|
309
|
+
entity_default as default,
|
|
310
|
+
entityAction
|
|
311
|
+
};
|
|
312
|
+
//# sourceMappingURL=entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/actions/entity.ts"],"sourcesContent":["/**\n * `KNOWLEDGE_GRAPH` umbrella action — direct CRUD over the runtime\n * knowledge graph.\n *\n * This is the relationships-plugin \"extras\" surface. The graph stores\n * themselves live in the runtime: `@elizaos/agent`'s\n * {@link KnowledgeGraphService} owns the per-agent `EntityStore` /\n * `RelationshipStore`. This action resolves that service and dispatches\n * graph operations onto it. No DB access, no merge engine, no LLM planning\n * lives here — those are runtime / PA concerns respectively.\n *\n * Op-based dispatch:\n * - `create` create a person/org/place/project/concept node\n * - `read` fetch a single entity by id\n * - `list` list known entities (optionally filtered by kind)\n * - `log_interaction` record an inbound/outbound interaction on an entity\n * - `set_identity` observe a verified (platform, handle) identity\n * - `set_relationship` upsert a typed edge between two entities\n * - `merge` fold duplicate entities into a target\n *\n * Owner-only (`roleGate.minRole: OWNER` + the {@link hasOwnerAccess} gate).\n *\n * NOTE on naming: this action is `KNOWLEDGE_GRAPH`, NOT `ENTITY`.\n * `@elizaos/plugin-personal-assistant` registers the `ENTITY` action (a rich\n * orchestration over the legacy Rolodex contact model with an LLM planner +\n * voice-grounded replies). That stays in PA; this action is the thin runtime\n * graph-CRUD surface that powers the relationships viewer. Registering it\n * under a distinct name keeps exactly one `ENTITY` action at runtime.\n */\n\nimport { hasOwnerAccess, resolveKnowledgeGraphService } from \"@elizaos/agent\";\nimport type {\n Action,\n ActionResult,\n HandlerCallback,\n HandlerOptions,\n IAgentRuntime,\n Memory,\n State,\n} from \"@elizaos/core\";\nimport { logger } from \"@elizaos/core\";\nimport type { Entity, EntityIdentity } from \"@elizaos/shared\";\nimport { SELF_ENTITY_ID } from \"@elizaos/shared\";\n\nimport {\n ENTITY_OPS,\n type EntityOp,\n RELATIONSHIPS_ACTION_NAME,\n RELATIONSHIPS_CONTEXTS,\n RELATIONSHIPS_LOG_PREFIX,\n} from \"../types.js\";\n\n/**\n * Parameter shape accepted by the action. The planner provides these via\n * `options.parameters`; every field is optional and validated per-op.\n */\nexport interface EntityActionParameters {\n /** Canonical op name. Planner may also provide `action` / `subaction`. */\n op?: EntityOp;\n subaction?: EntityOp;\n action?: EntityOp;\n /** Entity kind for `create` / `list` filter (person / organization / …). */\n kind?: string;\n /** Display name for `create`. */\n name?: string;\n /** Target entity id for `read` / `log_interaction` / `merge` target. */\n entityId?: string;\n /** Identity platform for `set_identity` (e.g. `discord`, `email`). */\n platform?: string;\n /** Handle on `platform` for `set_identity`. */\n handle?: string;\n /** Display name shown for an observed identity. */\n displayName?: string;\n /** Edge target id for `set_relationship`. */\n toEntityId?: string;\n /** Edge source id for `set_relationship`. Defaults to `self`. */\n fromEntityId?: string;\n /** Edge type label for `set_relationship` (e.g. `manages`). */\n relationshipType?: string;\n /** Source entity ids consumed when calling `merge`. */\n sourceEntityIds?: string[];\n /** Free-form evidence string for provenance trail. */\n evidence?: string;\n /** Interaction direction for `log_interaction`. Defaults to `outbound`. */\n direction?: \"inbound\" | \"outbound\";\n /** Interaction summary text for `log_interaction`. */\n summary?: string;\n /** Limit for `list`. */\n limit?: number;\n}\n\nfunction getParams(\n options: HandlerOptions | undefined,\n): EntityActionParameters {\n const params = options?.parameters as EntityActionParameters | undefined;\n return params ?? {};\n}\n\nfunction resolveOp(params: EntityActionParameters): EntityOp | null {\n const candidate = params.op ?? params.subaction ?? params.action;\n if (typeof candidate !== \"string\") return null;\n return (ENTITY_OPS as readonly string[]).includes(candidate)\n ? (candidate as EntityOp)\n : null;\n}\n\nfunction trimmed(value: string | undefined): string | null {\n if (typeof value !== \"string\") return null;\n const t = value.trim();\n return t.length > 0 ? t : null;\n}\n\nfunction entitySummary(entity: Entity): {\n entityId: string;\n type: string;\n preferredName: string;\n} {\n return {\n entityId: entity.entityId,\n type: entity.type,\n preferredName: entity.preferredName,\n };\n}\n\nconst ENTITY_KINDS_DEFAULT = \"person\";\n\nexport const entityAction: Action = {\n name: RELATIONSHIPS_ACTION_NAME,\n similes: [\"ENTITY_CRUD\", \"GRAPH_ENTITY\", \"KNOWLEDGE_GRAPH_CRUD\"],\n description:\n \"Direct CRUD over the runtime knowledge graph (entities + typed edges): create | read | list | log_interaction | set_identity | set_relationship | merge. Backs the relationships viewer. Contact orchestration with planning -> ENTITY (personal-assistant).\",\n descriptionCompressed:\n \"KNOWLEDGE_GRAPH create|read|list|log_interaction|set_identity|set_relationship|merge\",\n tags: [\n \"domain:relationships\",\n \"capability:read\",\n \"capability:write\",\n \"capability:update\",\n \"capability:delete\",\n \"surface:internal\",\n ],\n contexts: [...RELATIONSHIPS_CONTEXTS],\n contextGate: { anyOf: [...RELATIONSHIPS_CONTEXTS] },\n roleGate: { minRole: \"OWNER\" },\n validate: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state?: State,\n ): Promise<boolean> => {\n if (!resolveKnowledgeGraphService(runtime)) return false;\n return hasOwnerAccess(runtime, message);\n },\n handler: async (\n runtime: IAgentRuntime,\n message: Memory,\n _state: State | undefined,\n options: HandlerOptions | undefined,\n callback: HandlerCallback | undefined,\n ): Promise<ActionResult> => {\n if (!(await hasOwnerAccess(runtime, message))) {\n const text = \"The knowledge graph is restricted to the owner.\";\n await callback?.({\n text,\n source: \"action\",\n action: RELATIONSHIPS_ACTION_NAME,\n });\n return { success: false, text, data: { error: \"PERMISSION_DENIED\" } };\n }\n\n const service = resolveKnowledgeGraphService(runtime);\n if (!service) {\n const text = \"The knowledge graph service is not available.\";\n await callback?.({\n text,\n source: \"action\",\n action: RELATIONSHIPS_ACTION_NAME,\n });\n return { success: false, text, data: { error: \"SERVICE_UNAVAILABLE\" } };\n }\n\n const params = getParams(options);\n const op = resolveOp(params);\n if (!op) {\n const text =\n \"Tell me which knowledge-graph op: create, read, list, log_interaction, set_identity, set_relationship, or merge.\";\n await callback?.({\n text,\n source: \"action\",\n action: RELATIONSHIPS_ACTION_NAME,\n });\n return { success: false, text, data: { error: \"MISSING_OP\" } };\n }\n\n const entityStore = service.getEntityStore();\n const relationshipStore = service.getRelationshipStore();\n\n const reply = async (\n result: ActionResult & { text: string },\n ): Promise<ActionResult> => {\n await callback?.({\n text: result.text,\n source: \"action\",\n action: RELATIONSHIPS_ACTION_NAME,\n });\n return result;\n };\n\n logger.info(\n `${RELATIONSHIPS_LOG_PREFIX} ${RELATIONSHIPS_ACTION_NAME} op=${op}`,\n );\n\n switch (op) {\n case \"create\": {\n const name = trimmed(params.name);\n if (!name) {\n return reply({\n success: false,\n text: \"I need a display name to create an entity.\",\n data: { op, error: \"MISSING_FIELDS\" },\n });\n }\n const kind = trimmed(params.kind) ?? ENTITY_KINDS_DEFAULT;\n const entity = await entityStore.upsert({\n type: kind,\n preferredName: name,\n identities: [],\n tags: [],\n visibility: \"owner_agent_admin\",\n state: {},\n });\n return reply({\n success: true,\n text: `Created ${kind} \"${entity.preferredName}\" (${entity.entityId}).`,\n data: { op, entity: entitySummary(entity) },\n });\n }\n\n case \"read\": {\n const entityId = trimmed(params.entityId);\n if (!entityId) {\n return reply({\n success: false,\n text: \"I need an entityId to read.\",\n data: { op, error: \"MISSING_FIELDS\" },\n });\n }\n const entity = await entityStore.get(entityId);\n if (!entity) {\n return reply({\n success: false,\n text: `No entity found with id ${entityId}.`,\n data: { op, error: \"NOT_FOUND\", entityId },\n });\n }\n return reply({\n success: true,\n text: `${entity.preferredName} (${entity.type}) — ${entity.identities.length} identit${entity.identities.length === 1 ? \"y\" : \"ies\"}.`,\n data: { op, entity },\n });\n }\n\n case \"list\": {\n const kind = trimmed(params.kind);\n const limit =\n typeof params.limit === \"number\" && params.limit > 0\n ? Math.floor(params.limit)\n : 50;\n const entities = await entityStore.list({\n ...(kind ? { type: kind } : {}),\n limit,\n });\n return reply({\n success: true,\n text:\n entities.length === 0\n ? \"No entities in the graph yet.\"\n : `${entities.length} entit${entities.length === 1 ? \"y\" : \"ies\"} in the graph.`,\n data: { op, entities: entities.map(entitySummary) },\n });\n }\n\n case \"log_interaction\": {\n const entityId = trimmed(params.entityId);\n if (!entityId) {\n return reply({\n success: false,\n text: \"I need an entityId to log an interaction.\",\n data: { op, error: \"MISSING_FIELDS\" },\n });\n }\n const entity = await entityStore.get(entityId);\n if (!entity) {\n return reply({\n success: false,\n text: `No entity found with id ${entityId}.`,\n data: { op, error: \"NOT_FOUND\", entityId },\n });\n }\n const platform =\n trimmed(params.platform) ??\n entity.state.lastInteractionPlatform ??\n \"unknown\";\n const direction =\n params.direction === \"inbound\" ? \"inbound\" : \"outbound\";\n await entityStore.recordInteraction(entityId, {\n platform,\n direction,\n summary: trimmed(params.summary) ?? \"\",\n occurredAt: new Date().toISOString(),\n });\n return reply({\n success: true,\n text: `Logged ${direction} interaction with ${entity.preferredName} on ${platform}.`,\n data: { op, entityId, platform, direction },\n });\n }\n\n case \"set_identity\": {\n const platform = trimmed(params.platform);\n const handle = trimmed(params.handle);\n if (!platform || !handle) {\n return reply({\n success: false,\n text: \"I need both the platform and the handle to record an identity.\",\n data: { op, error: \"MISSING_FIELDS\" },\n });\n }\n const evidence = trimmed(params.evidence) ?? \"user_chat\";\n const displayName = trimmed(params.displayName);\n const observation = await entityStore.observeIdentity({\n platform,\n handle,\n ...(displayName ? { displayName } : {}),\n evidence: [evidence],\n confidence: 1,\n });\n // User-asserted identities are verified — mark this (platform, handle).\n const verifiedIdentities: EntityIdentity[] =\n observation.entity.identities.map((identity) =>\n identity.platform === platform && identity.handle === handle\n ? { ...identity, verified: true }\n : identity,\n );\n const merged = await entityStore.upsert({\n ...observation.entity,\n identities: verifiedIdentities,\n });\n return reply({\n success: true,\n text: `Recorded identity ${platform}:${handle} on ${merged.preferredName}.`,\n data: {\n op,\n entity: entitySummary(merged),\n mergedFrom: observation.mergedFrom ?? null,\n conflict: observation.conflict ?? false,\n },\n });\n }\n\n case \"set_relationship\": {\n const toEntityId = trimmed(params.toEntityId);\n const relationshipType = trimmed(params.relationshipType);\n if (!toEntityId || !relationshipType) {\n return reply({\n success: false,\n text: \"I need the target entity id and the relationship type (e.g. manages, colleague_of, works_at).\",\n data: { op, error: \"MISSING_FIELDS\" },\n });\n }\n const fromEntityId = trimmed(params.fromEntityId) ?? SELF_ENTITY_ID;\n const evidence = trimmed(params.evidence) ?? \"user_chat\";\n const edge = await relationshipStore.upsert({\n fromEntityId,\n toEntityId,\n type: relationshipType,\n metadata: {},\n state: {},\n evidence: [evidence],\n confidence: 1,\n source: \"user_chat\",\n });\n return reply({\n success: true,\n text: `Recorded ${fromEntityId} -[${relationshipType}]-> ${toEntityId}.`,\n data: { op, relationship: edge },\n });\n }\n\n case \"merge\": {\n const targetEntityId = trimmed(params.entityId);\n const sourceEntityIds = (params.sourceEntityIds ?? []).filter(\n (id): id is string => typeof id === \"string\" && id.trim().length > 0,\n );\n if (!targetEntityId || sourceEntityIds.length === 0) {\n return reply({\n success: false,\n text: \"I need a target entityId and at least one sourceEntityId to merge duplicates.\",\n data: { op, error: \"MISSING_FIELDS\" },\n });\n }\n const merged = await entityStore.merge(targetEntityId, sourceEntityIds);\n return reply({\n success: true,\n text: `Merged ${sourceEntityIds.length} entit${sourceEntityIds.length === 1 ? \"y\" : \"ies\"} into ${merged.preferredName}.`,\n data: {\n op,\n entity: entitySummary(merged),\n sourceEntityIds,\n },\n });\n }\n }\n },\n examples: [\n [\n {\n name: \"{{name1}}\",\n content: { text: \"Add Alice as a person to my graph.\" },\n },\n {\n name: \"{{agentName}}\",\n content: {\n text: 'Created person \"Alice\".',\n action: RELATIONSHIPS_ACTION_NAME,\n },\n },\n ],\n [\n {\n name: \"{{name1}}\",\n content: { text: \"Pat is my manager.\" },\n },\n {\n name: \"{{agentName}}\",\n content: {\n text: \"Recorded self -[manages]-> Pat.\",\n action: RELATIONSHIPS_ACTION_NAME,\n },\n },\n ],\n ],\n};\n\nexport default entityAction;\n"],"mappings":"AA8BA,SAAS,gBAAgB,oCAAoC;AAU7D,SAAS,cAAc;AAEvB,SAAS,sBAAsB;AAE/B;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAyCP,SAAS,UACP,SACwB;AACxB,QAAM,SAAS,SAAS;AACxB,SAAO,UAAU,CAAC;AACpB;AAEA,SAAS,UAAU,QAAiD;AAClE,QAAM,YAAY,OAAO,MAAM,OAAO,aAAa,OAAO;AAC1D,MAAI,OAAO,cAAc,SAAU,QAAO;AAC1C,SAAQ,WAAiC,SAAS,SAAS,IACtD,YACD;AACN;AAEA,SAAS,QAAQ,OAA0C;AACzD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,IAAI,MAAM,KAAK;AACrB,SAAO,EAAE,SAAS,IAAI,IAAI;AAC5B;AAEA,SAAS,cAAc,QAIrB;AACA,SAAO;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,MAAM,OAAO;AAAA,IACb,eAAe,OAAO;AAAA,EACxB;AACF;AAEA,MAAM,uBAAuB;AAEtB,MAAM,eAAuB;AAAA,EAClC,MAAM;AAAA,EACN,SAAS,CAAC,eAAe,gBAAgB,sBAAsB;AAAA,EAC/D,aACE;AAAA,EACF,uBACE;AAAA,EACF,MAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,UAAU,CAAC,GAAG,sBAAsB;AAAA,EACpC,aAAa,EAAE,OAAO,CAAC,GAAG,sBAAsB,EAAE;AAAA,EAClD,UAAU,EAAE,SAAS,QAAQ;AAAA,EAC7B,UAAU,OACR,SACA,SACA,WACqB;AACrB,QAAI,CAAC,6BAA6B,OAAO,EAAG,QAAO;AACnD,WAAO,eAAe,SAAS,OAAO;AAAA,EACxC;AAAA,EACA,SAAS,OACP,SACA,SACA,QACA,SACA,aAC0B;AAC1B,QAAI,CAAE,MAAM,eAAe,SAAS,OAAO,GAAI;AAC7C,YAAM,OAAO;AACb,YAAM,WAAW;AAAA,QACf;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,EAAE,SAAS,OAAO,MAAM,MAAM,EAAE,OAAO,oBAAoB,EAAE;AAAA,IACtE;AAEA,UAAM,UAAU,6BAA6B,OAAO;AACpD,QAAI,CAAC,SAAS;AACZ,YAAM,OAAO;AACb,YAAM,WAAW;AAAA,QACf;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,EAAE,SAAS,OAAO,MAAM,MAAM,EAAE,OAAO,sBAAsB,EAAE;AAAA,IACxE;AAEA,UAAM,SAAS,UAAU,OAAO;AAChC,UAAM,KAAK,UAAU,MAAM;AAC3B,QAAI,CAAC,IAAI;AACP,YAAM,OACJ;AACF,YAAM,WAAW;AAAA,QACf;AAAA,QACA,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,aAAO,EAAE,SAAS,OAAO,MAAM,MAAM,EAAE,OAAO,aAAa,EAAE;AAAA,IAC/D;AAEA,UAAM,cAAc,QAAQ,eAAe;AAC3C,UAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,UAAM,QAAQ,OACZ,WAC0B;AAC1B,YAAM,WAAW;AAAA,QACf,MAAM,OAAO;AAAA,QACb,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,GAAG,wBAAwB,IAAI,yBAAyB,OAAO,EAAE;AAAA,IACnE;AAEA,YAAQ,IAAI;AAAA,MACV,KAAK,UAAU;AACb,cAAM,OAAO,QAAQ,OAAO,IAAI;AAChC,YAAI,CAAC,MAAM;AACT,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM;AAAA,YACN,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAAA,UACtC,CAAC;AAAA,QACH;AACA,cAAM,OAAO,QAAQ,OAAO,IAAI,KAAK;AACrC,cAAM,SAAS,MAAM,YAAY,OAAO;AAAA,UACtC,MAAM;AAAA,UACN,eAAe;AAAA,UACf,YAAY,CAAC;AAAA,UACb,MAAM,CAAC;AAAA,UACP,YAAY;AAAA,UACZ,OAAO,CAAC;AAAA,QACV,CAAC;AACD,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MAAM,WAAW,IAAI,KAAK,OAAO,aAAa,MAAM,OAAO,QAAQ;AAAA,UACnE,MAAM,EAAE,IAAI,QAAQ,cAAc,MAAM,EAAE;AAAA,QAC5C,CAAC;AAAA,MACH;AAAA,MAEA,KAAK,QAAQ;AACX,cAAM,WAAW,QAAQ,OAAO,QAAQ;AACxC,YAAI,CAAC,UAAU;AACb,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM;AAAA,YACN,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAAA,UACtC,CAAC;AAAA,QACH;AACA,cAAM,SAAS,MAAM,YAAY,IAAI,QAAQ;AAC7C,YAAI,CAAC,QAAQ;AACX,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM,2BAA2B,QAAQ;AAAA,YACzC,MAAM,EAAE,IAAI,OAAO,aAAa,SAAS;AAAA,UAC3C,CAAC;AAAA,QACH;AACA,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MAAM,GAAG,OAAO,aAAa,KAAK,OAAO,IAAI,YAAO,OAAO,WAAW,MAAM,WAAW,OAAO,WAAW,WAAW,IAAI,MAAM,KAAK;AAAA,UACnI,MAAM,EAAE,IAAI,OAAO;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,MAEA,KAAK,QAAQ;AACX,cAAM,OAAO,QAAQ,OAAO,IAAI;AAChC,cAAM,QACJ,OAAO,OAAO,UAAU,YAAY,OAAO,QAAQ,IAC/C,KAAK,MAAM,OAAO,KAAK,IACvB;AACN,cAAM,WAAW,MAAM,YAAY,KAAK;AAAA,UACtC,GAAI,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAAA,UAC7B;AAAA,QACF,CAAC;AACD,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MACE,SAAS,WAAW,IAChB,kCACA,GAAG,SAAS,MAAM,SAAS,SAAS,WAAW,IAAI,MAAM,KAAK;AAAA,UACpE,MAAM,EAAE,IAAI,UAAU,SAAS,IAAI,aAAa,EAAE;AAAA,QACpD,CAAC;AAAA,MACH;AAAA,MAEA,KAAK,mBAAmB;AACtB,cAAM,WAAW,QAAQ,OAAO,QAAQ;AACxC,YAAI,CAAC,UAAU;AACb,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM;AAAA,YACN,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAAA,UACtC,CAAC;AAAA,QACH;AACA,cAAM,SAAS,MAAM,YAAY,IAAI,QAAQ;AAC7C,YAAI,CAAC,QAAQ;AACX,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM,2BAA2B,QAAQ;AAAA,YACzC,MAAM,EAAE,IAAI,OAAO,aAAa,SAAS;AAAA,UAC3C,CAAC;AAAA,QACH;AACA,cAAM,WACJ,QAAQ,OAAO,QAAQ,KACvB,OAAO,MAAM,2BACb;AACF,cAAM,YACJ,OAAO,cAAc,YAAY,YAAY;AAC/C,cAAM,YAAY,kBAAkB,UAAU;AAAA,UAC5C;AAAA,UACA;AAAA,UACA,SAAS,QAAQ,OAAO,OAAO,KAAK;AAAA,UACpC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,QACrC,CAAC;AACD,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MAAM,UAAU,SAAS,qBAAqB,OAAO,aAAa,OAAO,QAAQ;AAAA,UACjF,MAAM,EAAE,IAAI,UAAU,UAAU,UAAU;AAAA,QAC5C,CAAC;AAAA,MACH;AAAA,MAEA,KAAK,gBAAgB;AACnB,cAAM,WAAW,QAAQ,OAAO,QAAQ;AACxC,cAAM,SAAS,QAAQ,OAAO,MAAM;AACpC,YAAI,CAAC,YAAY,CAAC,QAAQ;AACxB,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM;AAAA,YACN,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAAA,UACtC,CAAC;AAAA,QACH;AACA,cAAM,WAAW,QAAQ,OAAO,QAAQ,KAAK;AAC7C,cAAM,cAAc,QAAQ,OAAO,WAAW;AAC9C,cAAM,cAAc,MAAM,YAAY,gBAAgB;AAAA,UACpD;AAAA,UACA;AAAA,UACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,UACrC,UAAU,CAAC,QAAQ;AAAA,UACnB,YAAY;AAAA,QACd,CAAC;AAED,cAAM,qBACJ,YAAY,OAAO,WAAW;AAAA,UAAI,CAAC,aACjC,SAAS,aAAa,YAAY,SAAS,WAAW,SAClD,EAAE,GAAG,UAAU,UAAU,KAAK,IAC9B;AAAA,QACN;AACF,cAAM,SAAS,MAAM,YAAY,OAAO;AAAA,UACtC,GAAG,YAAY;AAAA,UACf,YAAY;AAAA,QACd,CAAC;AACD,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MAAM,qBAAqB,QAAQ,IAAI,MAAM,OAAO,OAAO,aAAa;AAAA,UACxE,MAAM;AAAA,YACJ;AAAA,YACA,QAAQ,cAAc,MAAM;AAAA,YAC5B,YAAY,YAAY,cAAc;AAAA,YACtC,UAAU,YAAY,YAAY;AAAA,UACpC;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MAEA,KAAK,oBAAoB;AACvB,cAAM,aAAa,QAAQ,OAAO,UAAU;AAC5C,cAAM,mBAAmB,QAAQ,OAAO,gBAAgB;AACxD,YAAI,CAAC,cAAc,CAAC,kBAAkB;AACpC,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM;AAAA,YACN,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAAA,UACtC,CAAC;AAAA,QACH;AACA,cAAM,eAAe,QAAQ,OAAO,YAAY,KAAK;AACrD,cAAM,WAAW,QAAQ,OAAO,QAAQ,KAAK;AAC7C,cAAM,OAAO,MAAM,kBAAkB,OAAO;AAAA,UAC1C;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN,UAAU,CAAC;AAAA,UACX,OAAO,CAAC;AAAA,UACR,UAAU,CAAC,QAAQ;AAAA,UACnB,YAAY;AAAA,UACZ,QAAQ;AAAA,QACV,CAAC;AACD,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MAAM,YAAY,YAAY,MAAM,gBAAgB,OAAO,UAAU;AAAA,UACrE,MAAM,EAAE,IAAI,cAAc,KAAK;AAAA,QACjC,CAAC;AAAA,MACH;AAAA,MAEA,KAAK,SAAS;AACZ,cAAM,iBAAiB,QAAQ,OAAO,QAAQ;AAC9C,cAAM,mBAAmB,OAAO,mBAAmB,CAAC,GAAG;AAAA,UACrD,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,KAAK,EAAE,SAAS;AAAA,QACrE;AACA,YAAI,CAAC,kBAAkB,gBAAgB,WAAW,GAAG;AACnD,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM;AAAA,YACN,MAAM,EAAE,IAAI,OAAO,iBAAiB;AAAA,UACtC,CAAC;AAAA,QACH;AACA,cAAM,SAAS,MAAM,YAAY,MAAM,gBAAgB,eAAe;AACtE,eAAO,MAAM;AAAA,UACX,SAAS;AAAA,UACT,MAAM,UAAU,gBAAgB,MAAM,SAAS,gBAAgB,WAAW,IAAI,MAAM,KAAK,SAAS,OAAO,aAAa;AAAA,UACtH,MAAM;AAAA,YACJ;AAAA,YACA,QAAQ,cAAc,MAAM;AAAA,YAC5B;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,SAAS,EAAE,MAAM,qCAAqC;AAAA,MACxD;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,UACP,MAAM;AAAA,UACN,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,SAAS,EAAE,MAAM,qBAAqB;AAAA,MACxC;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,UACP,MAAM;AAAA,UACN,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":[]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelationshipsSpatialView — the entity / relationship knowledge-graph viewer
|
|
3
|
+
* authored once with the spatial vocabulary, so it renders correctly wherever it
|
|
4
|
+
* is displayed:
|
|
5
|
+
*
|
|
6
|
+
* - GUI / XR — mounted in `<SpatialSurface>` (DOM; XR scales up).
|
|
7
|
+
* - TUI — rendered to real terminal lines by the agent terminal, via
|
|
8
|
+
* `registerSpatialTerminalView` (see `register-terminal-view.tsx`).
|
|
9
|
+
*
|
|
10
|
+
* It is purely presentational (a snapshot + an action callback in, primitives
|
|
11
|
+
* out) and imports only the cross-modality primitives, so it is safe to render
|
|
12
|
+
* in the Node agent process where the terminal lives (no browser/client import).
|
|
13
|
+
*
|
|
14
|
+
* The two graph payloads (entities + their outbound edges) are joined and
|
|
15
|
+
* projected to {@link EntityNode}s in the data wrapper ({@link ./RelationshipsView.tsx});
|
|
16
|
+
* this component never fetches or computes the graph — it displays the snapshot
|
|
17
|
+
* and dispatches actions. The entity-kind filter is the one piece of interactive
|
|
18
|
+
* state it owns locally (via {@link useSpatialState}); filtering the already-built
|
|
19
|
+
* node list is presentation-only and works on every surface.
|
|
20
|
+
*/
|
|
21
|
+
/** A typed edge shown under its source entity, already projected for display. */
|
|
22
|
+
export interface RelationshipEdge {
|
|
23
|
+
id: string;
|
|
24
|
+
/** Resolved target display name (or the raw id when unresolved). */
|
|
25
|
+
toName: string;
|
|
26
|
+
/** Pre-formatted meta line: `type · every Nd · last <date>`. */
|
|
27
|
+
meta: string;
|
|
28
|
+
}
|
|
29
|
+
/** An entity node: identity + kind + its outbound edges. */
|
|
30
|
+
export interface EntityNode {
|
|
31
|
+
id: string;
|
|
32
|
+
/** Raw entity kind (e.g. "person", "organization"). */
|
|
33
|
+
kind: string;
|
|
34
|
+
/** Human label for the kind (e.g. "People", "Organizations"). */
|
|
35
|
+
kindLabel: string;
|
|
36
|
+
name: string;
|
|
37
|
+
/** Pre-joined identity claims line (`discord:pat#1 · x:@pat`), or empty. */
|
|
38
|
+
identityLine: string;
|
|
39
|
+
edges: RelationshipEdge[];
|
|
40
|
+
}
|
|
41
|
+
/** A selectable kind filter offered above the graph. */
|
|
42
|
+
export interface KindFilter {
|
|
43
|
+
/** Raw kind value used to match nodes. */
|
|
44
|
+
kind: string;
|
|
45
|
+
/** Human label shown on the chip. */
|
|
46
|
+
label: string;
|
|
47
|
+
}
|
|
48
|
+
/** Which render state the graph is in. */
|
|
49
|
+
export type RelationshipsViewState = "loading" | "error" | "empty" | "ready";
|
|
50
|
+
export interface RelationshipsSnapshot {
|
|
51
|
+
/** The graph state machine. */
|
|
52
|
+
state: RelationshipsViewState;
|
|
53
|
+
/** The entity nodes (only meaningful when state === "ready"). */
|
|
54
|
+
nodes: EntityNode[];
|
|
55
|
+
/** The kind filters offered above the graph. */
|
|
56
|
+
filters: KindFilter[];
|
|
57
|
+
/** Error message when state === "error". */
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare const EMPTY_RELATIONSHIPS: RelationshipsSnapshot;
|
|
61
|
+
export interface RelationshipsSpatialViewProps {
|
|
62
|
+
snapshot: RelationshipsSnapshot;
|
|
63
|
+
/**
|
|
64
|
+
* Dispatch by action id:
|
|
65
|
+
* - `retry` — reload after an error,
|
|
66
|
+
* - `add` — route an add-a-person request through chat,
|
|
67
|
+
* - `open:<entityId>` — focus an entity node.
|
|
68
|
+
*/
|
|
69
|
+
onAction?: (action: string) => void;
|
|
70
|
+
}
|
|
71
|
+
export declare function RelationshipsSpatialView({ snapshot, onAction, }: RelationshipsSpatialViewProps): import("react/jsx-runtime").JSX.Element;
|
|
72
|
+
//# sourceMappingURL=RelationshipsSpatialView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RelationshipsSpatialView.d.ts","sourceRoot":"","sources":["../../../src/components/relationships/RelationshipsSpatialView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAYH,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0CAA0C;AAC1C,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,KAAK,EAAE,sBAAsB,CAAC;IAC9B,iEAAiE;IACjE,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,gDAAgD;IAChD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,mBAAmB,EAAE,qBAIjC,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,qBAAqB,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,QAAQ,GACT,EAAE,6BAA6B,2CAkB/B"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
HStack,
|
|
6
|
+
List,
|
|
7
|
+
Text,
|
|
8
|
+
useSpatialState,
|
|
9
|
+
VStack
|
|
10
|
+
} from "@elizaos/ui/spatial";
|
|
11
|
+
const EMPTY_RELATIONSHIPS = {
|
|
12
|
+
state: "loading",
|
|
13
|
+
nodes: [],
|
|
14
|
+
filters: []
|
|
15
|
+
};
|
|
16
|
+
function RelationshipsSpatialView({
|
|
17
|
+
snapshot,
|
|
18
|
+
onAction
|
|
19
|
+
}) {
|
|
20
|
+
const dispatch = (action) => () => onAction?.(action);
|
|
21
|
+
return /* @__PURE__ */ jsx(Card, { gap: 1, padding: 1, children: snapshot.state === "loading" ? /* @__PURE__ */ jsx(Text, { tone: "muted", align: "center", style: "caption", children: "Loading relationships" }) : snapshot.state === "error" ? /* @__PURE__ */ jsx(RelationshipsErrorBody, { snapshot, dispatch }) : snapshot.state === "empty" ? /* @__PURE__ */ jsx(RelationshipsEmptyBody, { dispatch }) : /* @__PURE__ */ jsx(RelationshipsReadyBody, { snapshot, onAction }) });
|
|
22
|
+
}
|
|
23
|
+
function RelationshipsErrorBody({
|
|
24
|
+
snapshot,
|
|
25
|
+
dispatch
|
|
26
|
+
}) {
|
|
27
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
28
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Could not load relationships" }),
|
|
29
|
+
/* @__PURE__ */ jsx(Text, { tone: "danger", style: "caption", children: snapshot.error ?? "Could not load relationships." }),
|
|
30
|
+
/* @__PURE__ */ jsx(HStack, { gap: 1, children: /* @__PURE__ */ jsx(Button, { agent: "retry", onPress: dispatch("retry"), children: "Retry" }) })
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
function RelationshipsEmptyBody({
|
|
34
|
+
dispatch
|
|
35
|
+
}) {
|
|
36
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "None" }),
|
|
38
|
+
/* @__PURE__ */ jsx(HStack, { gap: 1, children: /* @__PURE__ */ jsx(Button, { agent: "add", onPress: dispatch("add"), children: "Add someone" }) })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
function RelationshipsReadyBody({
|
|
42
|
+
snapshot,
|
|
43
|
+
onAction
|
|
44
|
+
}) {
|
|
45
|
+
const [activeKind, setActiveKind] = useSpatialState("");
|
|
46
|
+
const visible = activeKind === "" ? snapshot.nodes : snapshot.nodes.filter((node) => node.kind === activeKind);
|
|
47
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
48
|
+
snapshot.filters.length > 0 ? /* @__PURE__ */ jsx(
|
|
49
|
+
KindFilters,
|
|
50
|
+
{
|
|
51
|
+
filters: snapshot.filters,
|
|
52
|
+
active: activeKind,
|
|
53
|
+
onSelect: setActiveKind
|
|
54
|
+
}
|
|
55
|
+
) : null,
|
|
56
|
+
/* @__PURE__ */ jsxs(Text, { style: "caption", tone: "muted", children: [
|
|
57
|
+
"Graph (",
|
|
58
|
+
visible.length,
|
|
59
|
+
")"
|
|
60
|
+
] }),
|
|
61
|
+
visible.length === 0 ? /* @__PURE__ */ jsx(Text, { tone: "muted", style: "caption", children: "None" }) : /* @__PURE__ */ jsx(List, { gap: 1, children: visible.map((node) => /* @__PURE__ */ jsx(EntityNodeBlock, { node, onAction }, node.id)) })
|
|
62
|
+
] });
|
|
63
|
+
}
|
|
64
|
+
function KindFilters({
|
|
65
|
+
filters,
|
|
66
|
+
active,
|
|
67
|
+
onSelect
|
|
68
|
+
}) {
|
|
69
|
+
return /* @__PURE__ */ jsxs(HStack, { gap: 1, wrap: true, align: "center", children: [
|
|
70
|
+
/* @__PURE__ */ jsx(
|
|
71
|
+
Button,
|
|
72
|
+
{
|
|
73
|
+
agent: "relationships-kind-all",
|
|
74
|
+
variant: active === "" ? "solid" : "ghost",
|
|
75
|
+
onPress: () => onSelect(""),
|
|
76
|
+
children: "All"
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
filters.map((filter) => /* @__PURE__ */ jsx(
|
|
80
|
+
Button,
|
|
81
|
+
{
|
|
82
|
+
agent: `relationships-kind-${filter.kind}`,
|
|
83
|
+
variant: active === filter.kind ? "solid" : "ghost",
|
|
84
|
+
onPress: () => onSelect(active === filter.kind ? "" : filter.kind),
|
|
85
|
+
children: filter.label
|
|
86
|
+
},
|
|
87
|
+
filter.kind
|
|
88
|
+
))
|
|
89
|
+
] });
|
|
90
|
+
}
|
|
91
|
+
function EntityNodeBlock({
|
|
92
|
+
node,
|
|
93
|
+
onAction
|
|
94
|
+
}) {
|
|
95
|
+
return /* @__PURE__ */ jsxs(VStack, { gap: 0, agent: `rel-${node.id}`, children: [
|
|
96
|
+
/* @__PURE__ */ jsxs(HStack, { gap: 1, align: "center", children: [
|
|
97
|
+
/* @__PURE__ */ jsx(VStack, { gap: 0, grow: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, wrap: false, children: node.name }) }),
|
|
98
|
+
/* @__PURE__ */ jsx(Text, { style: "caption", tone: "primary", wrap: false, children: node.kindLabel }),
|
|
99
|
+
/* @__PURE__ */ jsx(
|
|
100
|
+
Button,
|
|
101
|
+
{
|
|
102
|
+
agent: `open-${node.id}`,
|
|
103
|
+
onPress: () => onAction?.(`open:${node.id}`),
|
|
104
|
+
children: "\u203A"
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
] }),
|
|
108
|
+
node.identityLine ? /* @__PURE__ */ jsx(Text, { style: "caption", tone: "muted", wrap: false, children: node.identityLine }) : null,
|
|
109
|
+
node.edges.length > 0 ? node.edges.map((edge) => /* @__PURE__ */ jsxs(HStack, { gap: 1, align: "center", children: [
|
|
110
|
+
/* @__PURE__ */ jsx(Text, { tone: "muted", wrap: false, children: "\u203A" }),
|
|
111
|
+
/* @__PURE__ */ jsx(VStack, { gap: 0, grow: 1, children: /* @__PURE__ */ jsx(Text, { wrap: false, children: edge.toName }) }),
|
|
112
|
+
/* @__PURE__ */ jsx(Text, { style: "caption", tone: "muted", wrap: false, children: edge.meta })
|
|
113
|
+
] }, edge.id)) : null
|
|
114
|
+
] });
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
EMPTY_RELATIONSHIPS,
|
|
118
|
+
RelationshipsSpatialView
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=RelationshipsSpatialView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/relationships/RelationshipsSpatialView.tsx"],"sourcesContent":["/**\n * RelationshipsSpatialView — the entity / relationship knowledge-graph viewer\n * authored once with the spatial vocabulary, so it renders correctly wherever it\n * is displayed:\n *\n * - GUI / XR — mounted in `<SpatialSurface>` (DOM; XR scales up).\n * - TUI — rendered to real terminal lines by the agent terminal, via\n * `registerSpatialTerminalView` (see `register-terminal-view.tsx`).\n *\n * It is purely presentational (a snapshot + an action callback in, primitives\n * out) and imports only the cross-modality primitives, so it is safe to render\n * in the Node agent process where the terminal lives (no browser/client import).\n *\n * The two graph payloads (entities + their outbound edges) are joined and\n * projected to {@link EntityNode}s in the data wrapper ({@link ./RelationshipsView.tsx});\n * this component never fetches or computes the graph — it displays the snapshot\n * and dispatches actions. The entity-kind filter is the one piece of interactive\n * state it owns locally (via {@link useSpatialState}); filtering the already-built\n * node list is presentation-only and works on every surface.\n */\n\nimport {\n Button,\n Card,\n HStack,\n List,\n Text,\n useSpatialState,\n VStack,\n} from \"@elizaos/ui/spatial\";\n\n/** A typed edge shown under its source entity, already projected for display. */\nexport interface RelationshipEdge {\n id: string;\n /** Resolved target display name (or the raw id when unresolved). */\n toName: string;\n /** Pre-formatted meta line: `type · every Nd · last <date>`. */\n meta: string;\n}\n\n/** An entity node: identity + kind + its outbound edges. */\nexport interface EntityNode {\n id: string;\n /** Raw entity kind (e.g. \"person\", \"organization\"). */\n kind: string;\n /** Human label for the kind (e.g. \"People\", \"Organizations\"). */\n kindLabel: string;\n name: string;\n /** Pre-joined identity claims line (`discord:pat#1 · x:@pat`), or empty. */\n identityLine: string;\n edges: RelationshipEdge[];\n}\n\n/** A selectable kind filter offered above the graph. */\nexport interface KindFilter {\n /** Raw kind value used to match nodes. */\n kind: string;\n /** Human label shown on the chip. */\n label: string;\n}\n\n/** Which render state the graph is in. */\nexport type RelationshipsViewState = \"loading\" | \"error\" | \"empty\" | \"ready\";\n\nexport interface RelationshipsSnapshot {\n /** The graph state machine. */\n state: RelationshipsViewState;\n /** The entity nodes (only meaningful when state === \"ready\"). */\n nodes: EntityNode[];\n /** The kind filters offered above the graph. */\n filters: KindFilter[];\n /** Error message when state === \"error\". */\n error?: string;\n}\n\nexport const EMPTY_RELATIONSHIPS: RelationshipsSnapshot = {\n state: \"loading\",\n nodes: [],\n filters: [],\n};\n\nexport interface RelationshipsSpatialViewProps {\n snapshot: RelationshipsSnapshot;\n /**\n * Dispatch by action id:\n * - `retry` — reload after an error,\n * - `add` — route an add-a-person request through chat,\n * - `open:<entityId>` — focus an entity node.\n */\n onAction?: (action: string) => void;\n}\n\nexport function RelationshipsSpatialView({\n snapshot,\n onAction,\n}: RelationshipsSpatialViewProps) {\n const dispatch = (action: string) => () => onAction?.(action);\n\n return (\n <Card gap={1} padding={1}>\n {snapshot.state === \"loading\" ? (\n <Text tone=\"muted\" align=\"center\" style=\"caption\">\n Loading relationships\n </Text>\n ) : snapshot.state === \"error\" ? (\n <RelationshipsErrorBody snapshot={snapshot} dispatch={dispatch} />\n ) : snapshot.state === \"empty\" ? (\n <RelationshipsEmptyBody dispatch={dispatch} />\n ) : (\n <RelationshipsReadyBody snapshot={snapshot} onAction={onAction} />\n )}\n </Card>\n );\n}\n\nfunction RelationshipsErrorBody({\n snapshot,\n dispatch,\n}: {\n snapshot: RelationshipsSnapshot;\n dispatch: (action: string) => () => void;\n}) {\n return (\n <>\n <Text bold>Could not load relationships</Text>\n <Text tone=\"danger\" style=\"caption\">\n {snapshot.error ?? \"Could not load relationships.\"}\n </Text>\n <HStack gap={1}>\n <Button agent=\"retry\" onPress={dispatch(\"retry\")}>\n Retry\n </Button>\n </HStack>\n </>\n );\n}\n\nfunction RelationshipsEmptyBody({\n dispatch,\n}: {\n dispatch: (action: string) => () => void;\n}) {\n return (\n <>\n <Text bold>None</Text>\n <HStack gap={1}>\n <Button agent=\"add\" onPress={dispatch(\"add\")}>\n Add someone\n </Button>\n </HStack>\n </>\n );\n}\n\nfunction RelationshipsReadyBody({\n snapshot,\n onAction,\n}: {\n snapshot: RelationshipsSnapshot;\n onAction?: (action: string) => void;\n}) {\n // The active kind filter is the one piece of interactive local state. Empty\n // string = \"all kinds\". A single selection keeps the chips and the rendered\n // cards in agreement on every surface.\n const [activeKind, setActiveKind] = useSpatialState<string>(\"\");\n\n const visible =\n activeKind === \"\"\n ? snapshot.nodes\n : snapshot.nodes.filter((node) => node.kind === activeKind);\n\n return (\n <>\n {snapshot.filters.length > 0 ? (\n <KindFilters\n filters={snapshot.filters}\n active={activeKind}\n onSelect={setActiveKind}\n />\n ) : null}\n <Text style=\"caption\" tone=\"muted\">\n Graph ({visible.length})\n </Text>\n {visible.length === 0 ? (\n <Text tone=\"muted\" style=\"caption\">\n None\n </Text>\n ) : (\n <List gap={1}>\n {visible.map((node) => (\n <EntityNodeBlock key={node.id} node={node} onAction={onAction} />\n ))}\n </List>\n )}\n </>\n );\n}\n\nfunction KindFilters({\n filters,\n active,\n onSelect,\n}: {\n filters: KindFilter[];\n active: string;\n onSelect: (kind: string) => void;\n}) {\n return (\n <HStack gap={1} wrap align=\"center\">\n <Button\n agent=\"relationships-kind-all\"\n variant={active === \"\" ? \"solid\" : \"ghost\"}\n onPress={() => onSelect(\"\")}\n >\n All\n </Button>\n {filters.map((filter) => (\n <Button\n key={filter.kind}\n agent={`relationships-kind-${filter.kind}`}\n variant={active === filter.kind ? \"solid\" : \"ghost\"}\n onPress={() => onSelect(active === filter.kind ? \"\" : filter.kind)}\n >\n {filter.label}\n </Button>\n ))}\n </HStack>\n );\n}\n\nfunction EntityNodeBlock({\n node,\n onAction,\n}: {\n node: EntityNode;\n onAction?: (action: string) => void;\n}) {\n return (\n <VStack gap={0} agent={`rel-${node.id}`}>\n <HStack gap={1} align=\"center\">\n <VStack gap={0} grow={1}>\n <Text bold wrap={false}>\n {node.name}\n </Text>\n </VStack>\n <Text style=\"caption\" tone=\"primary\" wrap={false}>\n {node.kindLabel}\n </Text>\n <Button\n agent={`open-${node.id}`}\n onPress={() => onAction?.(`open:${node.id}`)}\n >\n ›\n </Button>\n </HStack>\n {node.identityLine ? (\n <Text style=\"caption\" tone=\"muted\" wrap={false}>\n {node.identityLine}\n </Text>\n ) : null}\n {node.edges.length > 0\n ? node.edges.map((edge) => (\n <HStack key={edge.id} gap={1} align=\"center\">\n <Text tone=\"muted\" wrap={false}>\n ›\n </Text>\n <VStack gap={0} grow={1}>\n <Text wrap={false}>{edge.toName}</Text>\n </VStack>\n <Text style=\"caption\" tone=\"muted\" wrap={false}>\n {edge.meta}\n </Text>\n </HStack>\n ))\n : null}\n </VStack>\n );\n}\n"],"mappings":"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","names":[]}
|