@happyvertical/smrt-personas 0.38.18
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/AGENTS.md +85 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/dist/index.d.ts +294 -0
- package/dist/index.js +290 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +397 -0
- package/dist/smrt-knowledge.json +311 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, field, getClassName, smrt } from "@happyvertical/smrt-core";
|
|
2
|
+
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
3
|
+
//#region src/__smrt-register__.ts
|
|
4
|
+
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1783560123595,\"packageName\":\"@happyvertical/smrt-personas\",\"packageVersion\":\"0.38.18\",\"objects\":{\"@happyvertical/smrt-personas:AgentPersona\":{\"name\":\"agentpersona\",\"className\":\"AgentPersona\",\"qualifiedName\":\"@happyvertical/smrt-personas:AgentPersona\",\"collection\":\"agentpersonas\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/personas/src/agent-persona.ts\",\"packageName\":\"@happyvertical/smrt-personas\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":true,\"autoPopulate\":true,\"nullable\":false,\"mode\":\"required\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"agentClass\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"name\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"contextType\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"contextId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"instructions\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"allowedTools\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"runAsUserId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-users:User\"},\"actsAsProfileId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-profiles:Profile\",\"_meta\":{\"nullable\":true}},\"memoryScope\":{\"type\":\"text\",\"required\":false,\"_meta\":{}},\"priority\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"enabled\":{\"type\":\"boolean\",\"required\":false,\"_meta\":{}}},\"methods\":{\"getAllowedTools\":{\"name\":\"getAllowedTools\",\"async\":false,\"parameters\":[],\"returnType\":\"string[]\",\"isStatic\":false,\"isPublic\":true},\"setAllowedTools\":{\"name\":\"setAllowedTools\",\"async\":false,\"parameters\":[{\"name\":\"tools\",\"type\":\"string[]\",\"optional\":false}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"agent_personas\",\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"conflictColumns\":[\"tenant_id\",\"agent_class\",\"name\"],\"tenantScoped\":{\"mode\":\"required\"}},\"extends\":\"SmrtObject\",\"exportName\":\"AgentPersona\",\"collectionExportName\":\"AgentPersonaCollection\",\"validationRules\":[{\"field\":\"tenantId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"runAsUserId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"}],\"schema\":{\"tableName\":\"agent_personas\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"agent_personas\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID NOT NULL,\\n \\\"agent_class\\\" TEXT,\\n \\\"name\\\" TEXT DEFAULT '',\\n \\\"context_type\\\" TEXT,\\n \\\"context_id\\\" TEXT,\\n \\\"instructions\\\" TEXT,\\n \\\"allowed_tools\\\" TEXT,\\n \\\"run_as_user_id\\\" UUID NOT NULL,\\n \\\"acts_as_profile_id\\\" UUID,\\n \\\"memory_scope\\\" TEXT,\\n \\\"priority\\\" INTEGER DEFAULT 0,\\n \\\"enabled\\\" BOOLEAN\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":true,\"unique\":false},\"agent_class\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"context_type\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"context_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"instructions\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"allowed_tools\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"run_as_user_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"acts_as_profile_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false},\"memory_scope\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"priority\":{\"type\":\"INTEGER\",\"notNull\":false,\"unique\":false,\"default\":0},\"enabled\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"agent_personas_id_idx\",\"columns\":[\"id\"]},{\"name\":\"agent_personas_tenant_id_agent_class_idx\",\"columns\":[\"tenant_id\",\"agent_class\",\"name\"],\"unique\":true}],\"version\":\"71e955ef\"}},\"@happyvertical/smrt-personas:AgentPersonaCollection\":{\"name\":\"agentpersonacollection\",\"className\":\"AgentPersonaCollection\",\"qualifiedName\":\"@happyvertical/smrt-personas:AgentPersonaCollection\",\"collection\":\"agentpersonas\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/personas/src/agent-persona.ts\",\"packageName\":\"@happyvertical/smrt-personas\",\"fields\":{},\"methods\":{\"byTenantAndClass\":{\"name\":\"byTenantAndClass\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"agentClass\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<AgentPersona[]>\",\"isStatic\":false,\"isPublic\":true},\"findActive\":{\"name\":\"findActive\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"agentClass\",\"type\":\"string\",\"optional\":false},{\"name\":\"context\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<AgentPersona[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"agent_personas\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"AgentPersona\",\"exportName\":\"AgentPersonaCollection\",\"collectionExportName\":\"AgentPersonaCollectionCollection\",\"schema\":{\"tableName\":\"agent_personas\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"agent_personas\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"agent_personas_id_idx\",\"columns\":[\"id\"]},{\"name\":\"agent_personas_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"48e54c2f\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-agents\",\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/agent-persona.ts
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
10
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
11
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
12
|
+
if (kind && result) __defProp(target, key, result);
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
function canonicalAgentClass(name) {
|
|
16
|
+
const registered = ObjectRegistry.getClass(name);
|
|
17
|
+
return registered?.qualifiedName || registered?.name || name;
|
|
18
|
+
}
|
|
19
|
+
function agentClassAliases(name) {
|
|
20
|
+
const registered = ObjectRegistry.getClass(name);
|
|
21
|
+
const canonical = registered?.qualifiedName || registered?.name || name;
|
|
22
|
+
const simple = registered?.name || getClassName(name);
|
|
23
|
+
return Array.from(new Set([canonical, simple].filter(Boolean)));
|
|
24
|
+
}
|
|
25
|
+
function parseStringArray(raw) {
|
|
26
|
+
if (!raw) return [];
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string") : [];
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var AgentPersona = class extends SmrtObject {
|
|
35
|
+
tenantId = "";
|
|
36
|
+
agentClass = "";
|
|
37
|
+
/**
|
|
38
|
+
* Human-readable persona name, unique per `(tenant, agentClass)`
|
|
39
|
+
* (see `conflictColumns`). Inherited `name` from {@link SmrtObject} is the
|
|
40
|
+
* conflict column; declared here for documentation and typing clarity.
|
|
41
|
+
*/
|
|
42
|
+
name = "";
|
|
43
|
+
contextType = null;
|
|
44
|
+
contextId = null;
|
|
45
|
+
instructions = "";
|
|
46
|
+
allowedTools = "[]";
|
|
47
|
+
runAsUserId = "";
|
|
48
|
+
actsAsProfileId = null;
|
|
49
|
+
memoryScope = "";
|
|
50
|
+
/**
|
|
51
|
+
* Resolution priority — higher wins when several personas apply to the same
|
|
52
|
+
* context at the same tenant level. Integer.
|
|
53
|
+
*/
|
|
54
|
+
priority = 0;
|
|
55
|
+
enabled = true;
|
|
56
|
+
/**
|
|
57
|
+
* Allowed tool identifiers as an array.
|
|
58
|
+
*
|
|
59
|
+
* Tolerates malformed stored JSON by returning an empty array.
|
|
60
|
+
*/
|
|
61
|
+
getAllowedTools() {
|
|
62
|
+
return parseStringArray(this.allowedTools);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Replace the allowed tool identifiers.
|
|
66
|
+
*/
|
|
67
|
+
setAllowedTools(tools) {
|
|
68
|
+
this.allowedTools = JSON.stringify(tools ?? []);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
__decorateClass([tenantId()], AgentPersona.prototype, "tenantId", 2);
|
|
72
|
+
__decorateClass([field({ type: "text" })], AgentPersona.prototype, "agentClass", 2);
|
|
73
|
+
__decorateClass([field({
|
|
74
|
+
type: "text",
|
|
75
|
+
nullable: true
|
|
76
|
+
})], AgentPersona.prototype, "contextType", 2);
|
|
77
|
+
__decorateClass([field({
|
|
78
|
+
type: "text",
|
|
79
|
+
nullable: true
|
|
80
|
+
})], AgentPersona.prototype, "contextId", 2);
|
|
81
|
+
__decorateClass([field({ type: "text" })], AgentPersona.prototype, "instructions", 2);
|
|
82
|
+
__decorateClass([field({ type: "text" })], AgentPersona.prototype, "allowedTools", 2);
|
|
83
|
+
__decorateClass([crossPackageRef("@happyvertical/smrt-users:User", { required: true })], AgentPersona.prototype, "runAsUserId", 2);
|
|
84
|
+
__decorateClass([crossPackageRef("@happyvertical/smrt-profiles:Profile", { nullable: true })], AgentPersona.prototype, "actsAsProfileId", 2);
|
|
85
|
+
__decorateClass([field({ type: "text" })], AgentPersona.prototype, "memoryScope", 2);
|
|
86
|
+
__decorateClass([field({ type: "boolean" })], AgentPersona.prototype, "enabled", 2);
|
|
87
|
+
AgentPersona = __decorateClass([TenantScoped({ mode: "required" }), smrt({
|
|
88
|
+
tableName: "agent_personas",
|
|
89
|
+
api: { include: [
|
|
90
|
+
"list",
|
|
91
|
+
"get",
|
|
92
|
+
"create",
|
|
93
|
+
"update",
|
|
94
|
+
"delete"
|
|
95
|
+
] },
|
|
96
|
+
cli: { include: ["list", "get"] },
|
|
97
|
+
mcp: { include: ["list", "get"] },
|
|
98
|
+
conflictColumns: [
|
|
99
|
+
"tenant_id",
|
|
100
|
+
"agent_class",
|
|
101
|
+
"name"
|
|
102
|
+
]
|
|
103
|
+
})], AgentPersona);
|
|
104
|
+
var AgentPersonaCollection = class extends SmrtCollection {
|
|
105
|
+
static _itemClass = AgentPersona;
|
|
106
|
+
/**
|
|
107
|
+
* List personas for a tenant + agent class, matching either the canonical
|
|
108
|
+
* qualified name or the simple class name (see {@link agentClassAliases}).
|
|
109
|
+
*
|
|
110
|
+
* Ordered by descending priority then name.
|
|
111
|
+
*
|
|
112
|
+
* @param extraWhere - Additional equality filters merged into the query
|
|
113
|
+
* (e.g. `{ enabled: true }`).
|
|
114
|
+
*/
|
|
115
|
+
async listForClass(tenantId2, agentClass, extraWhere = {}) {
|
|
116
|
+
const aliases = agentClassAliases(agentClass);
|
|
117
|
+
const where = aliases.length > 1 ? {
|
|
118
|
+
tenantId: tenantId2,
|
|
119
|
+
"agentClass in": aliases,
|
|
120
|
+
...extraWhere
|
|
121
|
+
} : {
|
|
122
|
+
tenantId: tenantId2,
|
|
123
|
+
agentClass: aliases[0],
|
|
124
|
+
...extraWhere
|
|
125
|
+
};
|
|
126
|
+
return sortByPriorityThenName(await this.list({ where }));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* List every persona bound to a tenant + agent class.
|
|
130
|
+
*
|
|
131
|
+
* Ordered by descending priority then name so callers that don't need the
|
|
132
|
+
* full resolver still get a deterministic, priority-first ordering.
|
|
133
|
+
*/
|
|
134
|
+
async byTenantAndClass(tenantId2, agentClass) {
|
|
135
|
+
return this.listForClass(tenantId2, agentClass);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* List the enabled personas for a tenant + agent class, filtered to those
|
|
139
|
+
* applicable to a context.
|
|
140
|
+
*
|
|
141
|
+
* `enabled` is filtered in the query; the context predicate (tenant-wide vs
|
|
142
|
+
* type-scoped vs exact) is applied in memory since it is an OR over several
|
|
143
|
+
* columns. A persona is applicable when it is a tenant-wide default (no
|
|
144
|
+
* `contextType`) or its context matches the requested one. Ordered by
|
|
145
|
+
* descending priority then name.
|
|
146
|
+
*
|
|
147
|
+
* @param context - Optional `{ contextType, contextId }` to filter by.
|
|
148
|
+
*/
|
|
149
|
+
async findActive(tenantId2, agentClass, context = {}) {
|
|
150
|
+
return (await this.listForClass(tenantId2, agentClass, { enabled: true })).filter((persona) => personaAppliesToContext(persona, context));
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function personaAppliesToContext(persona, context) {
|
|
154
|
+
if (!persona.contextType) return true;
|
|
155
|
+
if (persona.contextType !== (context.contextType ?? null)) return false;
|
|
156
|
+
if (persona.contextId == null) return true;
|
|
157
|
+
return persona.contextId === (context.contextId ?? null);
|
|
158
|
+
}
|
|
159
|
+
function personaContextRank(persona) {
|
|
160
|
+
if (!persona.contextType) return 0;
|
|
161
|
+
return persona.contextId == null ? 1 : 2;
|
|
162
|
+
}
|
|
163
|
+
function sortByPriorityThenName(personas) {
|
|
164
|
+
return [...personas].sort((a, b) => {
|
|
165
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
166
|
+
return (a.name ?? "").localeCompare(b.name ?? "");
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/persona-resolver.ts
|
|
171
|
+
function availabilityFromResolvedAgent(resolved, options = {}) {
|
|
172
|
+
if (!resolved) return null;
|
|
173
|
+
const { toolPermissionPrefix } = options;
|
|
174
|
+
let toolCeiling;
|
|
175
|
+
if (toolPermissionPrefix) toolCeiling = Object.entries(resolved.permissions ?? {}).filter(([id, granted]) => granted && id.startsWith(toolPermissionPrefix)).map(([id]) => id.slice(toolPermissionPrefix.length)).sort();
|
|
176
|
+
return {
|
|
177
|
+
status: resolved.status,
|
|
178
|
+
toolCeiling,
|
|
179
|
+
sourceTenantId: resolved.sourceTenantId
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
var PersonaResolver = class {
|
|
183
|
+
constructor(personas) {
|
|
184
|
+
this.personas = personas;
|
|
185
|
+
}
|
|
186
|
+
personas;
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the active persona.
|
|
189
|
+
*
|
|
190
|
+
* @param tenantId - Requesting tenant.
|
|
191
|
+
* @param agentClass - Agent class (canonicalized internally).
|
|
192
|
+
* @param context - Context dimension being resolved for.
|
|
193
|
+
* @param options - Hierarchy walk, manifest defaults, and availability gate.
|
|
194
|
+
* @returns The layered {@link ResolvedPersona} — either a matched persona or
|
|
195
|
+
* the defined default fallback.
|
|
196
|
+
*/
|
|
197
|
+
async resolve(tenantId, agentClass, context = {}, options = {}) {
|
|
198
|
+
const canonical = canonicalAgentClass(agentClass);
|
|
199
|
+
const { getAncestorIds, manifestDefaults, availability } = options;
|
|
200
|
+
const levels = [tenantId, ...getAncestorIds ? await getAncestorIds(tenantId) : []];
|
|
201
|
+
for (let level = 0; level < levels.length; level++) {
|
|
202
|
+
const levelTenantId = levels[level];
|
|
203
|
+
const candidates = await this.personas.findActive(levelTenantId, canonical, context);
|
|
204
|
+
if (candidates.length === 0) continue;
|
|
205
|
+
const winner = pickWinner(candidates);
|
|
206
|
+
return this.layerPersona(winner, {
|
|
207
|
+
canonical,
|
|
208
|
+
tenantId,
|
|
209
|
+
context,
|
|
210
|
+
manifestDefaults,
|
|
211
|
+
availability,
|
|
212
|
+
personaSource: level === 0 ? "explicit" : "inherited",
|
|
213
|
+
sourceTenantId: levelTenantId
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return this.defaultResolution({
|
|
217
|
+
canonical,
|
|
218
|
+
tenantId,
|
|
219
|
+
context,
|
|
220
|
+
manifestDefaults,
|
|
221
|
+
availability
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
layerPersona(persona, ctx) {
|
|
225
|
+
const { manifestDefaults, availability } = ctx;
|
|
226
|
+
const personaTools = persona.getAllowedTools();
|
|
227
|
+
const baseTools = personaTools.length > 0 ? personaTools : manifestDefaults?.allowedTools ?? [];
|
|
228
|
+
return {
|
|
229
|
+
agentClass: ctx.canonical,
|
|
230
|
+
tenantId: ctx.tenantId,
|
|
231
|
+
personaId: persona.id ?? void 0,
|
|
232
|
+
name: persona.name,
|
|
233
|
+
instructions: persona.instructions || manifestDefaults?.instructions || "",
|
|
234
|
+
allowedTools: applyCeiling(baseTools, availability),
|
|
235
|
+
runAsUserId: persona.runAsUserId || void 0,
|
|
236
|
+
actsAsProfileId: persona.actsAsProfileId ?? null,
|
|
237
|
+
memoryScope: resolveMemoryScope(persona.memoryScope, ctx),
|
|
238
|
+
priority: persona.priority,
|
|
239
|
+
contextType: ctx.context.contextType ?? null,
|
|
240
|
+
contextId: ctx.context.contextId ?? null,
|
|
241
|
+
source: "persona",
|
|
242
|
+
personaSource: ctx.personaSource,
|
|
243
|
+
sourceTenantId: ctx.sourceTenantId,
|
|
244
|
+
available: isAvailable(availability)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
defaultResolution(ctx) {
|
|
248
|
+
const { manifestDefaults, availability } = ctx;
|
|
249
|
+
return {
|
|
250
|
+
agentClass: ctx.canonical,
|
|
251
|
+
tenantId: ctx.tenantId,
|
|
252
|
+
name: "default",
|
|
253
|
+
instructions: manifestDefaults?.instructions ?? "",
|
|
254
|
+
allowedTools: applyCeiling(manifestDefaults?.allowedTools ?? [], availability),
|
|
255
|
+
actsAsProfileId: null,
|
|
256
|
+
memoryScope: resolveMemoryScope("", ctx),
|
|
257
|
+
priority: 0,
|
|
258
|
+
contextType: ctx.context.contextType ?? null,
|
|
259
|
+
contextId: ctx.context.contextId ?? null,
|
|
260
|
+
source: "default",
|
|
261
|
+
available: isAvailable(availability)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function pickWinner(candidates) {
|
|
266
|
+
return [...candidates].sort((a, b) => {
|
|
267
|
+
const rankDelta = personaContextRank(b) - personaContextRank(a);
|
|
268
|
+
if (rankDelta !== 0) return rankDelta;
|
|
269
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
270
|
+
return (a.name ?? "").localeCompare(b.name ?? "");
|
|
271
|
+
})[0];
|
|
272
|
+
}
|
|
273
|
+
function applyCeiling(tools, availability) {
|
|
274
|
+
if (!availability?.toolCeiling) return [...tools];
|
|
275
|
+
const ceiling = new Set(availability.toolCeiling);
|
|
276
|
+
return tools.filter((tool) => ceiling.has(tool));
|
|
277
|
+
}
|
|
278
|
+
function isAvailable(availability) {
|
|
279
|
+
return availability ? availability.status === "active" : true;
|
|
280
|
+
}
|
|
281
|
+
function resolveMemoryScope(explicit, ctx) {
|
|
282
|
+
if (explicit) return explicit;
|
|
283
|
+
if (ctx.manifestDefaults?.memoryScope) return ctx.manifestDefaults.memoryScope;
|
|
284
|
+
const contextSuffix = ctx.context.contextType ? `:${ctx.context.contextType}${ctx.context.contextId ? `:${ctx.context.contextId}` : ""}` : "";
|
|
285
|
+
return `${ctx.canonical}:${ctx.tenantId}${contextSuffix}`;
|
|
286
|
+
}
|
|
287
|
+
//#endregion
|
|
288
|
+
export { AgentPersona, AgentPersonaCollection, PersonaResolver, availabilityFromResolvedAgent, canonicalAgentClass, personaAppliesToContext, personaContextRank };
|
|
289
|
+
|
|
290
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["tenantId"],"sources":["../src/__smrt-register__.ts","../src/agent-persona.ts","../src/persona-resolver.ts"],"sourcesContent":["import { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * AgentPersona - Tenant-owned, context-scoped persona for an agent class.\n *\n * A persona layers behavioral configuration on top of a {@link TenantAgent}\n * binding (from `@happyvertical/smrt-agents`). Where `TenantAgent` answers\n * \"is this agent available for the tenant, and what is its capability ceiling\",\n * an `AgentPersona` answers \"how should this agent behave for this tenant in\n * this context\" — its instructions, allowed tools, run-as principal, acting\n * identity, and memory scope.\n *\n * There can be many personas per `(tenant, agentClass)` — one per context\n * (e.g. a site, property, or chat room) plus optional tenant-wide defaults.\n * Personas are unique on `(tenant_id, agent_class, name)`.\n *\n * Resolution (context + hierarchy + priority) lives in {@link PersonaResolver};\n * this class is the persisted record and its CRUD collection only.\n */\n\nimport {\n crossPackageRef,\n field,\n getClassName,\n ObjectRegistry,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\n/**\n * Return the canonical agent type identifier for storage and lookup.\n *\n * Uses the registry's qualified name when the class is registered and falls\n * back to the raw input for dynamically defined or unregistered classes. This\n * mirrors `getAgentTypeName()` in `@happyvertical/smrt-agents` without reaching\n * into that package's internal module — personas store the same canonical form\n * that `TenantAgent` does so the two layers line up.\n */\nexport function canonicalAgentClass(name: string): string {\n const registered = ObjectRegistry.getClass(name);\n return registered?.qualifiedName || registered?.name || name;\n}\n\n/**\n * Return the meaningful aliases for an agent class — the canonical qualified\n * name plus the simple class name — deduplicated.\n *\n * Lookups match against both so a persona persisted under a simple name (e.g.\n * `Praeco`, when written through the generated create API without\n * normalization) is still found when resolving under the canonical qualified\n * name, and vice versa. Mirrors `getAgentTypeAliases()` in\n * `@happyvertical/smrt-agents`.\n */\nexport function agentClassAliases(name: string): string[] {\n const registered = ObjectRegistry.getClass(name);\n const canonical = registered?.qualifiedName || registered?.name || name;\n const simple = registered?.name || getClassName(name);\n return Array.from(new Set([canonical, simple].filter(Boolean)));\n}\n\n/**\n * Parse a JSON-array-of-strings stored as text, tolerating malformed input.\n */\nfunction parseStringArray(raw: string | null | undefined): string[] {\n if (!raw) {\n return [];\n }\n try {\n const parsed = JSON.parse(raw);\n return Array.isArray(parsed)\n ? parsed.filter((value): value is string => typeof value === 'string')\n : [];\n } catch {\n return [];\n }\n}\n\n/**\n * AgentPersona SmrtObject — a tenant/context-scoped behavioral profile for an\n * agent class.\n */\n@TenantScoped({ mode: 'required' })\n@smrt({\n tableName: 'agent_personas',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n cli: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n conflictColumns: ['tenant_id', 'agent_class', 'name'],\n})\nexport class AgentPersona extends SmrtObject {\n /** Owning tenant (required — personas are never global). */\n @tenantId()\n tenantId: string = '';\n\n /**\n * Canonical qualified agent type this persona configures\n * (e.g. `@happyvertical/smrt-agents:Praeco`). Stored in canonical form so it\n * lines up with `TenantAgent.agentClass`.\n */\n @field({ type: 'text' })\n agentClass: string = '';\n\n /**\n * Human-readable persona name, unique per `(tenant, agentClass)`\n * (see `conflictColumns`). Inherited `name` from {@link SmrtObject} is the\n * conflict column; declared here for documentation and typing clarity.\n */\n name: string = '';\n\n /**\n * Optional context dimension type this persona is scoped to\n * (e.g. `'site'`, `'property'`, `'chatRoom'`). `null` means the persona is a\n * tenant-wide default that applies to every context for its agent class.\n */\n @field({ type: 'text', nullable: true })\n contextType: string | null = null;\n\n /**\n * Optional context dimension id. Only meaningful alongside `contextType`:\n * when set, the persona targets one specific context row; when `null` (with\n * a `contextType` set) it applies to every context of that type.\n */\n @field({ type: 'text', nullable: true })\n contextId: string | null = null;\n\n /** System prompt / behavioral instructions layered onto the agent. */\n @field({ type: 'text' })\n instructions: string = '';\n\n /**\n * Allowed tool identifiers, stored as a JSON array string. Use\n * {@link getAllowedTools} / {@link setAllowedTools} rather than reading this\n * field directly. Empty array (`'[]'`) means \"no persona-specific tools\"\n * (the resolver then falls back to manifest defaults, capped by the\n * `TenantAgent` ceiling).\n */\n @field({ type: 'text' })\n allowedTools: string = '[]';\n\n /**\n * The user whose principal this persona runs as (cross-package reference to\n * `@happyvertical/smrt-users:User`). Required — a persona always runs as a\n * concrete principal. Stored as a plain id column; no DDL FK constraint is\n * emitted (cross-package).\n */\n @crossPackageRef('@happyvertical/smrt-users:User', { required: true })\n runAsUserId: string = '';\n\n /**\n * Optional `Bot` profile the persona acts as for identity/audit\n * (cross-package reference to `@happyvertical/smrt-profiles:Profile`).\n * `null` when the persona has no distinct acting identity.\n */\n @crossPackageRef('@happyvertical/smrt-profiles:Profile', { nullable: true })\n actsAsProfileId: string | null = null;\n\n /**\n * Memory scope key controlling how the agent's memory/reflection is\n * partitioned for this persona. Empty string means \"use a resolver-derived\n * default\".\n */\n @field({ type: 'text' })\n memoryScope: string = '';\n\n /**\n * Resolution priority — higher wins when several personas apply to the same\n * context at the same tenant level. Integer.\n */\n priority: number = 0;\n\n /** Whether this persona is active and eligible for resolution. */\n @field({ type: 'boolean' })\n enabled: boolean = true;\n\n /**\n * Allowed tool identifiers as an array.\n *\n * Tolerates malformed stored JSON by returning an empty array.\n */\n getAllowedTools(): string[] {\n return parseStringArray(this.allowedTools);\n }\n\n /**\n * Replace the allowed tool identifiers.\n */\n setAllowedTools(tools: string[]): void {\n this.allowedTools = JSON.stringify(tools ?? []);\n }\n}\n\n/**\n * Collection for managing {@link AgentPersona} records.\n */\nexport class AgentPersonaCollection extends SmrtCollection<AgentPersona> {\n static readonly _itemClass = AgentPersona;\n\n /**\n * List personas for a tenant + agent class, matching either the canonical\n * qualified name or the simple class name (see {@link agentClassAliases}).\n *\n * Ordered by descending priority then name.\n *\n * @param extraWhere - Additional equality filters merged into the query\n * (e.g. `{ enabled: true }`).\n */\n private async listForClass(\n tenantId: string,\n agentClass: string,\n extraWhere: Record<string, unknown> = {},\n ): Promise<AgentPersona[]> {\n const aliases = agentClassAliases(agentClass);\n const where =\n aliases.length > 1\n ? { tenantId, 'agentClass in': aliases, ...extraWhere }\n : { tenantId, agentClass: aliases[0], ...extraWhere };\n const personas = await this.list({ where });\n return sortByPriorityThenName(personas);\n }\n\n /**\n * List every persona bound to a tenant + agent class.\n *\n * Ordered by descending priority then name so callers that don't need the\n * full resolver still get a deterministic, priority-first ordering.\n */\n async byTenantAndClass(\n tenantId: string,\n agentClass: string,\n ): Promise<AgentPersona[]> {\n return this.listForClass(tenantId, agentClass);\n }\n\n /**\n * List the enabled personas for a tenant + agent class, filtered to those\n * applicable to a context.\n *\n * `enabled` is filtered in the query; the context predicate (tenant-wide vs\n * type-scoped vs exact) is applied in memory since it is an OR over several\n * columns. A persona is applicable when it is a tenant-wide default (no\n * `contextType`) or its context matches the requested one. Ordered by\n * descending priority then name.\n *\n * @param context - Optional `{ contextType, contextId }` to filter by.\n */\n async findActive(\n tenantId: string,\n agentClass: string,\n context: { contextType?: string | null; contextId?: string | null } = {},\n ): Promise<AgentPersona[]> {\n const personas = await this.listForClass(tenantId, agentClass, {\n enabled: true,\n });\n return personas.filter((persona) =>\n personaAppliesToContext(persona, context),\n );\n }\n}\n\n/**\n * Whether a persona applies to a requested context.\n *\n * - No `contextType` on the persona → tenant-wide default, applies to all.\n * - `contextType` set, `contextId` null → type-scoped, applies when the\n * requested `contextType` matches.\n * - Both set → exact, applies when both match.\n */\nexport function personaAppliesToContext(\n persona: Pick<AgentPersona, 'contextType' | 'contextId'>,\n context: { contextType?: string | null; contextId?: string | null },\n): boolean {\n if (!persona.contextType) {\n return true;\n }\n if (persona.contextType !== (context.contextType ?? null)) {\n return false;\n }\n if (persona.contextId == null) {\n return true;\n }\n return persona.contextId === (context.contextId ?? null);\n}\n\n/**\n * Context-specificity rank used for resolution ordering.\n *\n * - `2` exact `(contextType, contextId)` match\n * - `1` type-scoped match (`contextType` matches, persona `contextId` null)\n * - `0` tenant-wide default (persona has no `contextType`)\n */\nexport function personaContextRank(\n persona: Pick<AgentPersona, 'contextType' | 'contextId'>,\n): number {\n if (!persona.contextType) {\n return 0;\n }\n return persona.contextId == null ? 1 : 2;\n}\n\nfunction sortByPriorityThenName(personas: AgentPersona[]): AgentPersona[] {\n return [...personas].sort((a, b) => {\n if (b.priority !== a.priority) {\n return b.priority - a.priority;\n }\n return (a.name ?? '').localeCompare(b.name ?? '');\n });\n}\n\nexport default AgentPersona;\n","/**\n * PersonaResolver - resolves the active {@link AgentPersona} for a\n * `(tenant, agentClass, context)` request.\n *\n * Layering, bottom to top (each layer overrides the previous):\n *\n * 1. **Manifest defaults** — the agent's built-in instructions / tools /\n * memory scope (supplied by the caller as {@link ManifestPersonaDefaults}).\n * 2. **TenantAgent** — the tenant/class availability gate and capability\n * ceiling (supplied as a {@link PersonaAvailabilityGate}; build one from a\n * `ResolvedAgentAvailability` via {@link availabilityFromResolvedAgent}).\n * `TenantAgent` decides *whether* the agent runs and *caps* its tools;\n * it never sets instructions.\n * 3. **AgentPersona** — the tenant's own persona for the context: overrides\n * instructions, sets tools (within the ceiling), run-as principal, acting\n * identity, and memory scope.\n *\n * Tenant-hierarchy inheritance mirrors `TenantAgentCollection.resolveForTenant`:\n * the resolver walks from the requesting tenant up its ancestors (nearest\n * first). The nearest tenant level that has any applicable persona wins the\n * resolution (a closer level shadows a farther one); within that level the most\n * context-specific, highest-priority persona is selected. When no level has an\n * applicable persona a defined default is returned.\n *\n * The hierarchy walk reads personas across tenants, so `resolve()` is meant to\n * run in a system/admin path where cross-tenant reads are permitted (i.e. not\n * inside a strict per-tenant interceptor context) — the same expectation\n * `TenantAgentCollection.resolveForTenant` carries.\n */\n\nimport type { ResolvedAgentAvailability } from '@happyvertical/smrt-agents';\nimport {\n type AgentPersona,\n type AgentPersonaCollection,\n canonicalAgentClass,\n personaAppliesToContext,\n personaContextRank,\n} from './agent-persona.js';\n\n/**\n * The context a persona is being resolved for.\n */\nexport interface PersonaContext {\n /** Context dimension type (e.g. `'site'`). */\n contextType?: string | null;\n /** Context dimension id within `contextType`. */\n contextId?: string | null;\n}\n\n/**\n * Bottom-layer defaults for an agent class, typically derived from its build\n * manifest.\n */\nexport interface ManifestPersonaDefaults {\n /** Default system instructions when no persona overrides them. */\n instructions?: string;\n /** Default tool identifiers, capped by the availability ceiling. */\n allowedTools?: string[];\n /** Default memory scope. */\n memoryScope?: string;\n}\n\n/**\n * Middle-layer availability + capability ceiling, projected from a\n * `TenantAgent` resolution.\n */\nexport interface PersonaAvailabilityGate {\n /** Resolved `TenantAgent` status. `'disabled'` marks the persona unavailable. */\n status: 'active' | 'disabled';\n /**\n * Tool identifiers the tenant is permitted to grant. When set, resolved\n * `allowedTools` are intersected with this ceiling. `undefined` means no\n * ceiling (tools pass through unchanged).\n */\n toolCeiling?: string[];\n /** Tenant the binding was resolved from (provenance only). */\n sourceTenantId?: string;\n}\n\n/**\n * Options for {@link PersonaResolver.resolve}.\n */\nexport interface ResolvePersonaOptions {\n /**\n * Return ancestor tenant ids for a tenant, nearest parent first through to\n * the root — mirrors `TenantAgentCollection.resolveForTenant`. Omit for a\n * single-tenant resolution with no inheritance.\n */\n getAncestorIds?: (tenantId: string) => Promise<string[]>;\n /** Bottom-layer manifest defaults for the agent class. */\n manifestDefaults?: ManifestPersonaDefaults;\n /**\n * Middle-layer `TenantAgent` availability + ceiling. Omit to skip gating\n * (the result is always `available: true` with no tool ceiling).\n */\n availability?: PersonaAvailabilityGate | null;\n}\n\n/**\n * A fully resolved persona: the layered, ready-to-use configuration plus\n * provenance describing how it was resolved.\n */\nexport interface ResolvedPersona {\n /** Canonical qualified agent type the resolution was for. */\n agentClass: string;\n /** Tenant the resolution was requested for. */\n tenantId: string;\n /** Id of the selected persona, or `undefined` for the default fallback. */\n personaId?: string;\n /** Selected persona name, or `'default'` for the fallback. */\n name: string;\n /** Layered instructions (persona → manifest default). */\n instructions: string;\n /** Layered tool ids (persona/manifest tools, capped by the ceiling). */\n allowedTools: string[];\n /** Run-as user principal, if the selected persona sets one. */\n runAsUserId?: string;\n /** Acting `Bot` profile id, if the selected persona sets one. */\n actsAsProfileId?: string | null;\n /** Resolved memory scope (persona → manifest default → derived default). */\n memoryScope: string;\n /** Selected persona priority (`0` for the default fallback). */\n priority: number;\n /** Context type the resolution was requested for. */\n contextType?: string | null;\n /** Context id the resolution was requested for. */\n contextId?: string | null;\n /** Whether a persona matched (`'persona'`) or the default was used (`'default'`). */\n source: 'persona' | 'default';\n /** For a matched persona, whether it was the tenant's own or inherited. */\n personaSource?: 'explicit' | 'inherited';\n /** Tenant the matched persona came from (own tenant or an ancestor). */\n sourceTenantId?: string;\n /** Whether the `TenantAgent` gate reports the agent as available. */\n available: boolean;\n}\n\n/**\n * Project a `TenantAgent` resolution into a {@link PersonaAvailabilityGate}.\n *\n * This is the concrete bridge between the `@happyvertical/smrt-agents`\n * availability layer and the persona layer. Pass `toolPermissionPrefix` to\n * derive a tool ceiling from granted permission ids (permissions whose id\n * starts with the prefix become the ceiling, with the prefix stripped); omit it\n * to leave the ceiling open.\n *\n * @param resolved - A `ResolvedAgentAvailability` (or `null`/`undefined`).\n * @param options.toolPermissionPrefix - Permission-id prefix that marks\n * tool grants (e.g. `'tool:'`).\n * @returns A gate, or `null` when `resolved` is nullish.\n */\nexport function availabilityFromResolvedAgent(\n resolved: ResolvedAgentAvailability | null | undefined,\n options: { toolPermissionPrefix?: string } = {},\n): PersonaAvailabilityGate | null {\n if (!resolved) {\n return null;\n }\n\n const { toolPermissionPrefix } = options;\n let toolCeiling: string[] | undefined;\n if (toolPermissionPrefix) {\n toolCeiling = Object.entries(resolved.permissions ?? {})\n .filter(([id, granted]) => granted && id.startsWith(toolPermissionPrefix))\n .map(([id]) => id.slice(toolPermissionPrefix.length))\n // Sort for a stable, order-independent ceiling regardless of how the\n // permissions map was produced.\n .sort();\n }\n\n return {\n status: resolved.status,\n toolCeiling,\n sourceTenantId: resolved.sourceTenantId,\n };\n}\n\n/**\n * Resolves the active persona for a `(tenant, agentClass, context)` request.\n */\nexport class PersonaResolver {\n constructor(private readonly personas: AgentPersonaCollection) {}\n\n /**\n * Resolve the active persona.\n *\n * @param tenantId - Requesting tenant.\n * @param agentClass - Agent class (canonicalized internally).\n * @param context - Context dimension being resolved for.\n * @param options - Hierarchy walk, manifest defaults, and availability gate.\n * @returns The layered {@link ResolvedPersona} — either a matched persona or\n * the defined default fallback.\n */\n async resolve(\n tenantId: string,\n agentClass: string,\n context: PersonaContext = {},\n options: ResolvePersonaOptions = {},\n ): Promise<ResolvedPersona> {\n const canonical = canonicalAgentClass(agentClass);\n const { getAncestorIds, manifestDefaults, availability } = options;\n\n const ancestorIds = getAncestorIds ? await getAncestorIds(tenantId) : [];\n const levels = [tenantId, ...ancestorIds];\n\n for (let level = 0; level < levels.length; level++) {\n const levelTenantId = levels[level];\n const candidates = await this.personas.findActive(\n levelTenantId,\n canonical,\n context,\n );\n\n if (candidates.length === 0) {\n continue;\n }\n\n const winner = pickWinner(candidates);\n return this.layerPersona(winner, {\n canonical,\n tenantId,\n context,\n manifestDefaults,\n availability,\n personaSource: level === 0 ? 'explicit' : 'inherited',\n sourceTenantId: levelTenantId,\n });\n }\n\n return this.defaultResolution({\n canonical,\n tenantId,\n context,\n manifestDefaults,\n availability,\n });\n }\n\n private layerPersona(\n persona: AgentPersona,\n ctx: LayerContext & {\n personaSource: 'explicit' | 'inherited';\n sourceTenantId: string;\n },\n ): ResolvedPersona {\n const { manifestDefaults, availability } = ctx;\n const personaTools = persona.getAllowedTools();\n const baseTools =\n personaTools.length > 0\n ? personaTools\n : (manifestDefaults?.allowedTools ?? []);\n\n return {\n agentClass: ctx.canonical,\n tenantId: ctx.tenantId,\n personaId: persona.id ?? undefined,\n name: persona.name,\n instructions:\n persona.instructions || manifestDefaults?.instructions || '',\n allowedTools: applyCeiling(baseTools, availability),\n runAsUserId: persona.runAsUserId || undefined,\n actsAsProfileId: persona.actsAsProfileId ?? null,\n memoryScope: resolveMemoryScope(persona.memoryScope, ctx),\n priority: persona.priority,\n contextType: ctx.context.contextType ?? null,\n contextId: ctx.context.contextId ?? null,\n source: 'persona',\n personaSource: ctx.personaSource,\n sourceTenantId: ctx.sourceTenantId,\n available: isAvailable(availability),\n };\n }\n\n private defaultResolution(ctx: LayerContext): ResolvedPersona {\n const { manifestDefaults, availability } = ctx;\n return {\n agentClass: ctx.canonical,\n tenantId: ctx.tenantId,\n name: 'default',\n instructions: manifestDefaults?.instructions ?? '',\n allowedTools: applyCeiling(\n manifestDefaults?.allowedTools ?? [],\n availability,\n ),\n actsAsProfileId: null,\n memoryScope: resolveMemoryScope('', ctx),\n priority: 0,\n contextType: ctx.context.contextType ?? null,\n contextId: ctx.context.contextId ?? null,\n source: 'default',\n available: isAvailable(availability),\n };\n }\n}\n\ninterface LayerContext {\n canonical: string;\n tenantId: string;\n context: PersonaContext;\n manifestDefaults?: ManifestPersonaDefaults;\n availability?: PersonaAvailabilityGate | null;\n}\n\n/**\n * Pick the winning persona from candidates already known to apply to the\n * requested context and be enabled.\n *\n * Order: most context-specific first, then highest priority, then name for a\n * stable deterministic tie-break.\n */\nfunction pickWinner(candidates: AgentPersona[]): AgentPersona {\n return [...candidates].sort((a, b) => {\n const rankDelta = personaContextRank(b) - personaContextRank(a);\n if (rankDelta !== 0) {\n return rankDelta;\n }\n if (b.priority !== a.priority) {\n return b.priority - a.priority;\n }\n return (a.name ?? '').localeCompare(b.name ?? '');\n })[0];\n}\n\nfunction applyCeiling(\n tools: string[],\n availability?: PersonaAvailabilityGate | null,\n): string[] {\n if (!availability?.toolCeiling) {\n return [...tools];\n }\n const ceiling = new Set(availability.toolCeiling);\n return tools.filter((tool) => ceiling.has(tool));\n}\n\nfunction isAvailable(availability?: PersonaAvailabilityGate | null): boolean {\n return availability ? availability.status === 'active' : true;\n}\n\nfunction resolveMemoryScope(explicit: string, ctx: LayerContext): string {\n if (explicit) {\n return explicit;\n }\n if (ctx.manifestDefaults?.memoryScope) {\n return ctx.manifestDefaults.memoryScope;\n }\n const contextSuffix = ctx.context.contextType\n ? `:${ctx.context.contextType}${ctx.context.contextId ? `:${ctx.context.contextId}` : ''}`\n : '';\n return `${ctx.canonical}:${ctx.tenantId}${contextSuffix}`;\n}\n\n// Re-export the context applicability helper for consumers building their own\n// persona selection UIs without duplicating the semantics.\nexport { personaAppliesToContext };\n"],"mappings":"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"}
|