@dudousxd/nestjs-agent 1.19.3 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ModelCatalog, EmptyRoles, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry, AttachmentStagingStore, ChatQueueStore, ChatQueueState, AgentRunner, AgentRunInput, AgentStreamEvent, QueuePauseReason, Actor } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, Persona, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ModelCatalog, EmptyRoles, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry, PersonaCatalogEntry, AttachmentStagingStore, ChatQueueStore, ChatQueueState, AgentRunner, AgentRunInput, AgentStreamEvent, QueuePauseReason, Actor } from '@dudousxd/nestjs-agent-core';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
4
4
 
@@ -24,6 +24,8 @@ interface AgentDeps {
24
24
  maxAgentAppearances?: number;
25
25
  /** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
26
26
  toolAllowList?: string[];
27
+ /** The agent's personas (`@Agent({ personas })`) — read by the loop only for a run that names one. */
28
+ personas?: Persona[];
27
29
  /** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
28
30
  toolTimeoutMs?: number;
29
31
  /**
@@ -534,7 +536,36 @@ declare class AgentDepsFactory {
534
536
  */
535
537
  defaultAgentName(): string;
536
538
  private effectiveTools;
539
+ /**
540
+ * The agent a name stands for: itself when it is a registered agent (or unknown — the bare
541
+ * assistant), else the agent whose persona took the name over (`Persona.aliases`) and that persona.
542
+ */
543
+ resolveAgent(name: string): {
544
+ agentName: string;
545
+ persona?: string;
546
+ };
547
+ /** `agentName`'s personas as a picker reads them. Empty when it declares none. */
548
+ personaCatalog(agentName: string): PersonaCatalogEntry[];
549
+ /** The persona `agentName` runs under when nothing names one. Undefined → none. */
550
+ defaultPersona(agentName: string): string | undefined;
551
+ /**
552
+ * The persona a turn of `agentName` runs under, most specific first: the one the send names
553
+ * (refused with `400 persona_not_found` when the agent does not declare it), else the thread's
554
+ * pinned one when this agent declares it, else the agent's default. Undefined → none.
555
+ */
556
+ resolvePersona(args: {
557
+ agentName: string;
558
+ requested?: string;
559
+ threadPersona?: string | null;
560
+ }): string | undefined;
537
561
  forAgent(agentName?: string): AgentDeps;
562
+ /**
563
+ * A persona baked into an agent's deps — how a run journaled under an agent name that a persona
564
+ * has since taken over (`Persona.aliases`) is served. That run names no persona of its own (it
565
+ * predates them, and its input cannot change), so the persona applies the way the old agent's own
566
+ * config did: as config, re-read on every replay, with no checkpoint of its own.
567
+ */
568
+ private withPersona;
538
569
  /**
539
570
  * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
540
571
  * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
@@ -591,13 +622,14 @@ declare class ChatQueueService {
591
622
  private readonly quotaProvider?;
592
623
  private readonly options?;
593
624
  private readonly staging?;
625
+ private readonly deps?;
594
626
  private readonly logger;
595
627
  /**
596
628
  * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
597
629
  * alive whatever the runner says — it simply has not registered the run yet.
598
630
  */
599
631
  private readonly starting;
600
- constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined);
632
+ constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined, deps?: AgentDepsFactory | undefined);
601
633
  /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
602
634
  get supported(): boolean;
603
635
  /** The bound store as a queue store, or a 501 naming what is missing. */
@@ -673,6 +705,13 @@ declare class ChatQueueService {
673
705
  * waited. An attachment the staging store no longer resolves keeps its stored url.
674
706
  */
675
707
  private inputFor;
708
+ /**
709
+ * The agent and persona a queued message starts as. A message carries the persona its send
710
+ * resolved; one that carries none — queued before personas existed, or by a host that sets none —
711
+ * resolves it now, exactly as a send would: an agent name a persona took over, then the thread's
712
+ * pinned persona, then the agent's default. Never refused here: nobody is waiting on a 400.
713
+ */
714
+ private targetFor;
676
715
  private freshAttachments;
677
716
  }
678
717
 
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ModelCatalog, EmptyRoles, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry, AttachmentStagingStore, ChatQueueStore, ChatQueueState, AgentRunner, AgentRunInput, AgentStreamEvent, QueuePauseReason, Actor } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, Persona, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ModelCatalog, EmptyRoles, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry, PersonaCatalogEntry, AttachmentStagingStore, ChatQueueStore, ChatQueueState, AgentRunner, AgentRunInput, AgentStreamEvent, QueuePauseReason, Actor } from '@dudousxd/nestjs-agent-core';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
4
4
 
@@ -24,6 +24,8 @@ interface AgentDeps {
24
24
  maxAgentAppearances?: number;
25
25
  /** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
26
26
  toolAllowList?: string[];
27
+ /** The agent's personas (`@Agent({ personas })`) — read by the loop only for a run that names one. */
28
+ personas?: Persona[];
27
29
  /** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
28
30
  toolTimeoutMs?: number;
29
31
  /**
@@ -534,7 +536,36 @@ declare class AgentDepsFactory {
534
536
  */
535
537
  defaultAgentName(): string;
536
538
  private effectiveTools;
539
+ /**
540
+ * The agent a name stands for: itself when it is a registered agent (or unknown — the bare
541
+ * assistant), else the agent whose persona took the name over (`Persona.aliases`) and that persona.
542
+ */
543
+ resolveAgent(name: string): {
544
+ agentName: string;
545
+ persona?: string;
546
+ };
547
+ /** `agentName`'s personas as a picker reads them. Empty when it declares none. */
548
+ personaCatalog(agentName: string): PersonaCatalogEntry[];
549
+ /** The persona `agentName` runs under when nothing names one. Undefined → none. */
550
+ defaultPersona(agentName: string): string | undefined;
551
+ /**
552
+ * The persona a turn of `agentName` runs under, most specific first: the one the send names
553
+ * (refused with `400 persona_not_found` when the agent does not declare it), else the thread's
554
+ * pinned one when this agent declares it, else the agent's default. Undefined → none.
555
+ */
556
+ resolvePersona(args: {
557
+ agentName: string;
558
+ requested?: string;
559
+ threadPersona?: string | null;
560
+ }): string | undefined;
537
561
  forAgent(agentName?: string): AgentDeps;
562
+ /**
563
+ * A persona baked into an agent's deps — how a run journaled under an agent name that a persona
564
+ * has since taken over (`Persona.aliases`) is served. That run names no persona of its own (it
565
+ * predates them, and its input cannot change), so the persona applies the way the old agent's own
566
+ * config did: as config, re-read on every replay, with no checkpoint of its own.
567
+ */
568
+ private withPersona;
538
569
  /**
539
570
  * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
540
571
  * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
@@ -591,13 +622,14 @@ declare class ChatQueueService {
591
622
  private readonly quotaProvider?;
592
623
  private readonly options?;
593
624
  private readonly staging?;
625
+ private readonly deps?;
594
626
  private readonly logger;
595
627
  /**
596
628
  * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
597
629
  * alive whatever the runner says — it simply has not registered the run yet.
598
630
  */
599
631
  private readonly starting;
600
- constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined);
632
+ constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined, deps?: AgentDepsFactory | undefined);
601
633
  /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
602
634
  get supported(): boolean;
603
635
  /** The bound store as a queue store, or a 501 naming what is missing. */
@@ -673,6 +705,13 @@ declare class ChatQueueService {
673
705
  * waited. An attachment the staging store no longer resolves keeps its stored url.
674
706
  */
675
707
  private inputFor;
708
+ /**
709
+ * The agent and persona a queued message starts as. A message carries the persona its send
710
+ * resolved; one that carries none — queued before personas existed, or by a host that sets none —
711
+ * resolves it now, exactly as a send would: an agent name a persona took over, then the thread's
712
+ * pinned persona, then the agent's default. Never refused here: nobody is waiting on a 400.
713
+ */
714
+ private targetFor;
676
715
  private freshAttachments;
677
716
  }
678
717
 
@@ -125,14 +125,21 @@ var AgentRunSteps = class {
125
125
  const tools = (0, import_nestjs_agent_core.withMemoryTool)({
126
126
  tools: (0, import_nestjs_agent_core.withSkillTool)({
127
127
  tools: (0, import_nestjs_agent_core.withAskTool)({
128
- tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList, {
129
- ...input.threadId !== void 0 ? {
130
- threadId: input.threadId
131
- } : {},
132
- ...input.agentName !== void 0 ? {
133
- agentName: input.agentName
134
- } : {}
135
- }),
128
+ tools: await deps.registry.definitionsFor(
129
+ input.actor,
130
+ deps.rolesPolicy,
131
+ // The persona narrowing the loop journaled in `persona:resolve` rides the envelope:
132
+ // this worker's own persona config may already say something else.
133
+ (0, import_nestjs_agent_core.intersectAllowLists)(deps.toolAllowList, input.personaAllowedTools),
134
+ {
135
+ ...input.threadId !== void 0 ? {
136
+ threadId: input.threadId
137
+ } : {},
138
+ ...input.agentName !== void 0 ? {
139
+ agentName: input.agentName
140
+ } : {}
141
+ }
142
+ ),
136
143
  ask: deps.ask
137
144
  }),
138
145
  enabled: deps.skills !== void 0
@@ -205,7 +212,9 @@ var AgentRunSteps = class {
205
212
  toolType: input.toolType
206
213
  }, () => (0, import_nestjs_agent_core.invokeWithTransientRetry)(() => {
207
214
  ui.restart();
208
- const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
215
+ const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy, input.allowedTools !== void 0 ? {
216
+ allowedTools: input.allowedTools
217
+ } : {});
209
218
  return input.timeoutMs !== void 0 ? (0, import_nestjs_agent_core.withToolTimeout)(invocation, input.timeoutMs, input.toolName) : invocation;
210
219
  }, transientRetry, {
211
220
  onRetry: /* @__PURE__ */ __name((attempt, error) => {
@@ -1218,8 +1227,64 @@ var AgentDepsFactory2 = class {
1218
1227
  ...delegated
1219
1228
  ];
1220
1229
  }
1230
+ /**
1231
+ * The agent a name stands for: itself when it is a registered agent (or unknown — the bare
1232
+ * assistant), else the agent whose persona took the name over (`Persona.aliases`) and that persona.
1233
+ */
1234
+ resolveAgent(name) {
1235
+ if (this.agents.has(name)) {
1236
+ return {
1237
+ agentName: name
1238
+ };
1239
+ }
1240
+ const alias = (0, import_nestjs_agent_core6.resolvePersonaAlias)(this.agents.list(), name);
1241
+ return alias === void 0 ? {
1242
+ agentName: name
1243
+ } : {
1244
+ agentName: alias.agent,
1245
+ persona: alias.persona
1246
+ };
1247
+ }
1248
+ /** `agentName`'s personas as a picker reads them. Empty when it declares none. */
1249
+ personaCatalog(agentName) {
1250
+ return (this.agents.get(agentName)?.personas ?? []).map(import_nestjs_agent_core6.personaCatalogEntry);
1251
+ }
1252
+ /** The persona `agentName` runs under when nothing names one. Undefined → none. */
1253
+ defaultPersona(agentName) {
1254
+ return this.agents.get(agentName)?.defaultPersona;
1255
+ }
1256
+ /**
1257
+ * The persona a turn of `agentName` runs under, most specific first: the one the send names
1258
+ * (refused with `400 persona_not_found` when the agent does not declare it), else the thread's
1259
+ * pinned one when this agent declares it, else the agent's default. Undefined → none.
1260
+ */
1261
+ resolvePersona(args) {
1262
+ const definition = this.agents.get(args.agentName);
1263
+ if (args.requested !== void 0) {
1264
+ if ((0, import_nestjs_agent_core6.findPersona)(definition, args.requested) === void 0) {
1265
+ throw new import_common5.BadRequestException({
1266
+ statusCode: 400,
1267
+ code: "persona_not_found",
1268
+ message: `agent "${args.agentName}" has no persona "${args.requested}"`
1269
+ });
1270
+ }
1271
+ return args.requested;
1272
+ }
1273
+ const pinned = args.threadPersona ?? void 0;
1274
+ if (pinned !== void 0 && (0, import_nestjs_agent_core6.findPersona)(definition, pinned) !== void 0) {
1275
+ return pinned;
1276
+ }
1277
+ return definition?.defaultPersona;
1278
+ }
1221
1279
  forAgent(agentName) {
1222
1280
  const name = agentName ?? this.defaultAgentName();
1281
+ if (agentName !== void 0 && !this.agents.has(agentName)) {
1282
+ const alias = (0, import_nestjs_agent_core6.resolvePersonaAlias)(this.agents.list(), agentName);
1283
+ const persona = alias === void 0 ? void 0 : (0, import_nestjs_agent_core6.findPersona)(this.agents.get(alias.agent), alias.persona);
1284
+ if (alias !== void 0 && persona !== void 0) {
1285
+ return this.withPersona(this.forAgent(alias.agent), persona);
1286
+ }
1287
+ }
1223
1288
  const definition = this.agents.get(name);
1224
1289
  const toolAllowList = this.effectiveTools(definition);
1225
1290
  const followUpsCount = this.followUpsCount();
@@ -1284,6 +1349,9 @@ var AgentDepsFactory2 = class {
1284
1349
  ...toolAllowList !== void 0 ? {
1285
1350
  toolAllowList
1286
1351
  } : {},
1352
+ ...definition?.personas !== void 0 ? {
1353
+ personas: definition.personas
1354
+ } : {},
1287
1355
  ...this.options.toolTimeoutMs !== void 0 ? {
1288
1356
  toolTimeoutMs: this.options.toolTimeoutMs
1289
1357
  } : {},
@@ -1302,6 +1370,45 @@ var AgentDepsFactory2 = class {
1302
1370
  };
1303
1371
  }
1304
1372
  /**
1373
+ * A persona baked into an agent's deps — how a run journaled under an agent name that a persona
1374
+ * has since taken over (`Persona.aliases`) is served. That run names no persona of its own (it
1375
+ * predates them, and its input cannot change), so the persona applies the way the old agent's own
1376
+ * config did: as config, re-read on every replay, with no checkpoint of its own.
1377
+ */
1378
+ withPersona(deps, persona) {
1379
+ const base = deps.systemPrompt;
1380
+ const ref = {
1381
+ id: persona.id,
1382
+ label: persona.label
1383
+ };
1384
+ const resolve = /* @__PURE__ */ __name(async (prompt, ctx) => typeof prompt === "function" ? prompt(ctx) : prompt, "resolve");
1385
+ const own = persona.systemPrompt;
1386
+ const systemPrompt = /* @__PURE__ */ __name(async (ctx) => {
1387
+ const scoped = {
1388
+ ...ctx,
1389
+ persona: ref
1390
+ };
1391
+ if (own === void 0) {
1392
+ return resolve(base, scoped);
1393
+ }
1394
+ const basePrompt = typeof own === "function" ? await resolve(base, scoped) : void 0;
1395
+ return resolve(own, {
1396
+ ...scoped,
1397
+ ...basePrompt !== void 0 ? {
1398
+ basePrompt
1399
+ } : {}
1400
+ });
1401
+ }, "systemPrompt");
1402
+ const toolAllowList = (0, import_nestjs_agent_core6.intersectAllowLists)(deps.toolAllowList, persona.allowedTools);
1403
+ return {
1404
+ ...deps,
1405
+ systemPrompt,
1406
+ ...toolAllowList !== void 0 ? {
1407
+ toolAllowList
1408
+ } : {}
1409
+ };
1410
+ }
1411
+ /**
1305
1412
  * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
1306
1413
  * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
1307
1414
  * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
@@ -1382,6 +1489,18 @@ var import_common8 = require("@nestjs/common");
1382
1489
  // src/queue/chat-queue.service.ts
1383
1490
  var import_nestjs_agent_core8 = require("@dudousxd/nestjs-agent-core");
1384
1491
  var import_common6 = require("@nestjs/common");
1492
+
1493
+ // src/thread-persona.ts
1494
+ async function threadPersona(store, threadId) {
1495
+ const projecting = store;
1496
+ if (typeof projecting.personaForThread === "function") {
1497
+ return projecting.personaForThread(threadId);
1498
+ }
1499
+ return (await store.getThread(threadId))?.persona ?? null;
1500
+ }
1501
+ __name(threadPersona, "threadPersona");
1502
+
1503
+ // src/queue/chat-queue.service.ts
1385
1504
  function _ts_decorate6(decorators, target, key, desc) {
1386
1505
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1387
1506
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -1418,18 +1537,20 @@ var ChatQueueService2 = class _ChatQueueService {
1418
1537
  quotaProvider;
1419
1538
  options;
1420
1539
  staging;
1540
+ deps;
1421
1541
  logger = new import_common6.Logger(_ChatQueueService.name);
1422
1542
  /**
1423
1543
  * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
1424
1544
  * alive whatever the runner says — it simply has not registered the run yet.
1425
1545
  */
1426
1546
  starting = /* @__PURE__ */ new Set();
1427
- constructor(store, sink, quotaProvider, options, staging) {
1547
+ constructor(store, sink, quotaProvider, options, staging, deps) {
1428
1548
  this.store = store;
1429
1549
  this.sink = sink;
1430
1550
  this.quotaProvider = quotaProvider;
1431
1551
  this.options = options;
1432
1552
  this.staging = staging;
1553
+ this.deps = deps;
1433
1554
  }
1434
1555
  /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
1435
1556
  get supported() {
@@ -1727,13 +1848,17 @@ var ChatQueueService2 = class _ChatQueueService {
1727
1848
  */
1728
1849
  async inputFor(message) {
1729
1850
  const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
1851
+ const { agentName, persona } = await this.targetFor(message);
1730
1852
  return {
1731
1853
  threadId: message.threadId,
1732
1854
  actor: message.actor,
1733
1855
  userText: message.content,
1734
1856
  day: utcDay(),
1735
- ...message.agentName !== void 0 ? {
1736
- agentName: message.agentName
1857
+ ...agentName !== void 0 ? {
1858
+ agentName
1859
+ } : {},
1860
+ ...persona !== void 0 ? {
1861
+ persona
1737
1862
  } : {},
1738
1863
  ...attachments.length > 0 ? {
1739
1864
  attachments
@@ -1746,6 +1871,37 @@ var ChatQueueService2 = class _ChatQueueService {
1746
1871
  } : {}
1747
1872
  };
1748
1873
  }
1874
+ /**
1875
+ * The agent and persona a queued message starts as. A message carries the persona its send
1876
+ * resolved; one that carries none — queued before personas existed, or by a host that sets none —
1877
+ * resolves it now, exactly as a send would: an agent name a persona took over, then the thread's
1878
+ * pinned persona, then the agent's default. Never refused here: nobody is waiting on a 400.
1879
+ */
1880
+ async targetFor(message) {
1881
+ const deps = this.deps;
1882
+ if (message.persona !== void 0 || deps === void 0 || message.agentName === void 0) {
1883
+ return {
1884
+ ...message.agentName !== void 0 ? {
1885
+ agentName: message.agentName
1886
+ } : {},
1887
+ ...message.persona !== void 0 ? {
1888
+ persona: message.persona
1889
+ } : {}
1890
+ };
1891
+ }
1892
+ const target = deps.resolveAgent(message.agentName);
1893
+ const hasPersonas = deps.personaCatalog(target.agentName).length > 0;
1894
+ const persona = target.persona ?? (hasPersonas ? deps.resolvePersona({
1895
+ agentName: target.agentName,
1896
+ threadPersona: await threadPersona(this.store, message.threadId)
1897
+ }) : void 0);
1898
+ return {
1899
+ agentName: target.agentName,
1900
+ ...persona !== void 0 ? {
1901
+ persona
1902
+ } : {}
1903
+ };
1904
+ }
1749
1905
  async freshAttachments(actor, attachments) {
1750
1906
  const staging = this.staging;
1751
1907
  if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
@@ -1773,13 +1929,16 @@ ChatQueueService2 = _ts_decorate6([
1773
1929
  _ts_param5(3, (0, import_common6.Inject)(import_nestjs_agent_core8.AGENT_OPTIONS)),
1774
1930
  _ts_param5(4, (0, import_common6.Optional)()),
1775
1931
  _ts_param5(4, (0, import_common6.Inject)(import_nestjs_agent_core8.AGENT_ATTACHMENT_STAGING)),
1932
+ _ts_param5(5, (0, import_common6.Optional)()),
1933
+ _ts_param5(5, (0, import_common6.Inject)(import_nestjs_agent_core8.AGENT_DEPS_FACTORY)),
1776
1934
  _ts_metadata5("design:type", Function),
1777
1935
  _ts_metadata5("design:paramtypes", [
1778
1936
  typeof AgentStore === "undefined" ? Object : AgentStore,
1779
1937
  typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
1780
1938
  typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
1781
1939
  typeof Pick === "undefined" ? Object : Pick,
1782
- typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
1940
+ typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore,
1941
+ typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
1783
1942
  ])
1784
1943
  ], ChatQueueService2);
1785
1944
  function pauseReasonFor(outcome, head) {
@@ -2033,7 +2192,9 @@ var AgentService = class {
2033
2192
  */
2034
2193
  async send(params) {
2035
2194
  await this.assertWithinQuota(params.actor);
2036
- const agentName = await this.resolveAgentName(params.agentName, params.threadId);
2195
+ const target = this.deps.resolveAgent(await this.resolveAgentName(params.agentName, params.threadId));
2196
+ const agentName = target.agentName;
2197
+ const persona = await this.resolvePersona(agentName, params.personaId ?? target.persona, params.threadId);
2037
2198
  const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId);
2038
2199
  const attachments = await this.resolveAttachments(params.actor, params.attachments ?? []);
2039
2200
  let threadId = params.threadId;
@@ -2048,11 +2209,17 @@ var AgentService = class {
2048
2209
  } : {},
2049
2210
  ...params.newThreadId !== void 0 ? {
2050
2211
  id: params.newThreadId
2212
+ } : {},
2213
+ ...params.personaId !== void 0 ? {
2214
+ persona: params.personaId
2051
2215
  } : {}
2052
2216
  });
2053
2217
  threadId = created.id;
2054
2218
  } else {
2055
2219
  await this.assertOwnsThread(params.actor, threadId);
2220
+ if (params.personaId !== void 0) {
2221
+ await this.pinThreadPersona(threadId, params.personaId);
2222
+ }
2056
2223
  }
2057
2224
  const input = {
2058
2225
  threadId,
@@ -2060,6 +2227,9 @@ var AgentService = class {
2060
2227
  userText: params.message,
2061
2228
  day: utcDay(),
2062
2229
  agentName,
2230
+ ...persona !== void 0 ? {
2231
+ persona
2232
+ } : {},
2063
2233
  ...params.regenerate === true ? {
2064
2234
  regenerate: true
2065
2235
  } : {},
@@ -2146,6 +2316,9 @@ var AgentService = class {
2146
2316
  ...input.agentName !== void 0 ? {
2147
2317
  agentName: input.agentName
2148
2318
  } : {},
2319
+ ...input.persona !== void 0 ? {
2320
+ persona: input.persona
2321
+ } : {},
2149
2322
  ...input.model !== void 0 ? {
2150
2323
  model: input.model
2151
2324
  } : {},
@@ -2540,16 +2713,22 @@ var AgentService = class {
2540
2713
  async updateThread(actor, threadId, patch) {
2541
2714
  const title = patch.title !== void 0 ? this.validateTitle(patch.title) : void 0;
2542
2715
  await this.assertOwnsThread(actor, threadId);
2543
- if (patch.defaultAgent === void 0 && patch.model === void 0) {
2716
+ if (patch.defaultAgent === void 0 && patch.model === void 0 && patch.persona === void 0) {
2544
2717
  if (title !== void 0) {
2545
2718
  await this.store.setTitle(threadId, title);
2546
2719
  }
2547
2720
  return;
2548
2721
  }
2549
2722
  if (this.store.updateThread === void 0) {
2550
- throw new import_common8.NotImplementedException("Setting a thread's defaultAgent or model requires an AgentStore that implements updateThread(); the bound store does not support it.");
2723
+ throw new import_common8.NotImplementedException("Setting a thread's defaultAgent, model or persona requires an AgentStore that implements updateThread(); the bound store does not support it.");
2551
2724
  }
2552
2725
  const model = patch.model === void 0 || patch.model === null ? patch.model : await this.assertModelAllowed(actor, patch.defaultAgent ?? await this.resolveAgentName(void 0, threadId), patch.model);
2726
+ if (patch.persona !== void 0 && patch.persona !== null) {
2727
+ this.deps.resolvePersona({
2728
+ agentName: this.deps.resolveAgent(patch.defaultAgent ?? await this.resolveAgentName(void 0, threadId)).agentName,
2729
+ requested: patch.persona
2730
+ });
2731
+ }
2553
2732
  await this.store.updateThread(threadId, {
2554
2733
  ...title !== void 0 ? {
2555
2734
  title
@@ -2559,9 +2738,39 @@ var AgentService = class {
2559
2738
  } : {},
2560
2739
  ...model !== void 0 ? {
2561
2740
  model
2741
+ } : {},
2742
+ ...patch.persona !== void 0 ? {
2743
+ persona: patch.persona
2744
+ } : {}
2745
+ });
2746
+ }
2747
+ /**
2748
+ * The persona this send runs under — see {@link AgentDepsFactory.resolvePersona}. The thread's
2749
+ * pinned persona is read only when the send names none AND the agent has personas to pick from.
2750
+ */
2751
+ async resolvePersona(agentName, requested, threadId) {
2752
+ const needsThread = requested === void 0 && threadId !== void 0 && this.deps.personaCatalog(agentName).length > 0;
2753
+ return this.deps.resolvePersona({
2754
+ agentName,
2755
+ ...requested !== void 0 ? {
2756
+ requested
2757
+ } : {},
2758
+ ...needsThread ? {
2759
+ threadPersona: await threadPersona(this.store, threadId)
2562
2760
  } : {}
2563
2761
  });
2564
2762
  }
2763
+ /** Pin `persona` on the thread when it is not already — a no-op on a store that cannot. */
2764
+ async pinThreadPersona(threadId, persona) {
2765
+ if (this.store.updateThread === void 0) {
2766
+ return;
2767
+ }
2768
+ if (await threadPersona(this.store, threadId) !== persona) {
2769
+ await this.store.updateThread(threadId, {
2770
+ persona
2771
+ });
2772
+ }
2773
+ }
2565
2774
  validateTitle(title) {
2566
2775
  const trimmed = title.trim();
2567
2776
  if (trimmed.length === 0) {
@@ -2607,6 +2816,7 @@ var AgentService = class {
2607
2816
  ...thread,
2608
2817
  defaultAgent: thread.defaultAgent ?? null,
2609
2818
  model: thread.model ?? null,
2819
+ persona: thread.persona ?? null,
2610
2820
  activeRunId: await this.store.activeRunForThread?.(thread.id) ?? thread.activeRunId ?? null
2611
2821
  };
2612
2822
  }
@@ -3008,13 +3218,22 @@ var AgentsController = class {
3008
3218
  }
3009
3219
  list() {
3010
3220
  const defaultAgentName = this.depsFactory.defaultAgentName();
3011
- return this.agents.list().map((definition) => ({
3012
- name: definition.name,
3013
- description: definition.description ?? "",
3014
- ...definition.name === defaultAgentName ? {
3015
- isDefault: true
3016
- } : {}
3017
- }));
3221
+ return this.agents.list().map((definition) => {
3222
+ const personas = this.depsFactory.personaCatalog(definition.name);
3223
+ return {
3224
+ name: definition.name,
3225
+ description: definition.description ?? "",
3226
+ ...definition.name === defaultAgentName ? {
3227
+ isDefault: true
3228
+ } : {},
3229
+ ...personas.length > 0 ? {
3230
+ personas
3231
+ } : {},
3232
+ ...definition.defaultPersona !== void 0 ? {
3233
+ defaultPersona: definition.defaultPersona
3234
+ } : {}
3235
+ };
3236
+ });
3018
3237
  }
3019
3238
  };
3020
3239
  _ts_decorate9([
@@ -3284,6 +3503,9 @@ var ChatController = class {
3284
3503
  ...body.agent !== void 0 ? {
3285
3504
  agentName: body.agent
3286
3505
  } : {},
3506
+ ...typeof body.persona === "string" && body.persona.length > 0 ? {
3507
+ personaId: body.persona
3508
+ } : {},
3287
3509
  ...attachments.length > 0 ? {
3288
3510
  attachments
3289
3511
  } : {},
@@ -4283,6 +4505,9 @@ var ThreadsController = class {
4283
4505
  } : {},
4284
4506
  ..."model" in body ? {
4285
4507
  model: body.model ?? null
4508
+ } : {},
4509
+ ..."persona" in body ? {
4510
+ persona: body.persona ?? null
4286
4511
  } : {}
4287
4512
  });
4288
4513
  return {
@@ -4778,6 +5003,7 @@ var AgentDiscoveryService = class _AgentDiscoveryService {
4778
5003
  }
4779
5004
  this.collectContributors(instance);
4780
5005
  }
5006
+ this.assertPersonas(this.registry.list());
4781
5007
  }
4782
5008
  /** Build the internal {@link AgentDefinition} from an `@Agent` instance and its options. */
4783
5009
  definitionFrom(instance, meta) {
@@ -4821,10 +5047,48 @@ var AgentDiscoveryService = class _AgentDiscoveryService {
4821
5047
  } : {},
4822
5048
  ...meta.handoff !== void 0 ? {
4823
5049
  delegatesTo: this.handoffEdges(meta)
5050
+ } : {},
5051
+ ...meta.personas !== void 0 ? {
5052
+ personas: meta.personas
5053
+ } : {},
5054
+ ...meta.defaultPersona !== void 0 ? {
5055
+ defaultPersona: meta.defaultPersona
4824
5056
  } : {}
4825
5057
  };
4826
5058
  }
4827
5059
  /**
5060
+ * Refuse to boot on a persona declaration that could only fail later, per request: a default that
5061
+ * names no persona, an id declared twice, an alias two personas claim, or an alias that is a real
5062
+ * agent's name (which would never be reached — a registered agent always wins).
5063
+ */
5064
+ assertPersonas(definitions) {
5065
+ const agentNames = new Set(definitions.map((definition) => definition.name));
5066
+ const aliasOwner = /* @__PURE__ */ new Map();
5067
+ for (const definition of definitions) {
5068
+ const ids = /* @__PURE__ */ new Set();
5069
+ for (const persona of definition.personas ?? []) {
5070
+ if (ids.has(persona.id)) {
5071
+ throw new Error(`Agent "${definition.name}" declares persona "${persona.id}" twice.`);
5072
+ }
5073
+ ids.add(persona.id);
5074
+ for (const alias of persona.aliases ?? []) {
5075
+ const owner = `${definition.name}/${persona.id}`;
5076
+ if (agentNames.has(alias)) {
5077
+ throw new Error(`Persona ${owner} claims the alias "${alias}", which is a registered agent's name.`);
5078
+ }
5079
+ const previous = aliasOwner.get(alias);
5080
+ if (previous !== void 0) {
5081
+ throw new Error(`Personas ${previous} and ${owner} both claim the alias "${alias}".`);
5082
+ }
5083
+ aliasOwner.set(alias, owner);
5084
+ }
5085
+ }
5086
+ if (definition.defaultPersona !== void 0 && !ids.has(definition.defaultPersona)) {
5087
+ throw new Error(`Agent "${definition.name}" names "${definition.defaultPersona}" as its default persona, but declares no persona with that id.`);
5088
+ }
5089
+ }
5090
+ }
5091
+ /**
4828
5092
  * Resolve each handoff entry to an {@link AgentDelegation} — its target's registered name, plus
4829
5093
  * whether that edge detaches (skipping non-agent classes). A bare name is kept for a plain edge
4830
5094
  * rather than an always-object form, so a definition an older host built by hand still reads.