@dudousxd/nestjs-agent 1.19.2 → 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.
@@ -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) => {
@@ -282,12 +291,60 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
282
291
  store;
283
292
  runs;
284
293
  queue;
294
+ workflows;
285
295
  logger = new import_common2.Logger(_AgentRunWorkflow.name);
286
- constructor(factory, store, runs, queue) {
296
+ constructor(factory, store, runs, queue, workflows) {
287
297
  this.factory = factory;
288
298
  this.store = store;
289
299
  this.runs = runs;
290
300
  this.queue = queue;
301
+ this.workflows = workflows;
302
+ }
303
+ /**
304
+ * Start a detached delegation as a TOP-LEVEL run, under an id derived from this run and the call.
305
+ *
306
+ * Not `ctx.startChild`, because the runtime treats a `spawn:` child as part of its parent: a Stop
307
+ * on the parent cascades to every run it spawned. A detached run is exactly the one the person did
308
+ * NOT ask to stop — they stopped the turn they were watching, and the work it handed off is meant
309
+ * to outlive that turn (it is meant to outlive it finishing, after all). Cascaded, it died parked
310
+ * on its approval, where its body never runs again, so nothing settled the delegation and the card
311
+ * said "started" for ever. The agent-level edge stays: the child's input still carries
312
+ * `parentRunId`, which is what the run rows record.
313
+ *
314
+ * Journaled as one `detach:<toolCallId>` localStep whose body runs outside the ambient workflow
315
+ * ctx (so the start goes to the engine and writes no position in this run). Its recorded result
316
+ * keeps a replay from starting it again, and a body re-run after a crash between the start and
317
+ * the checkpoint asks for the same id, which the runtime answers with the run it already has.
318
+ * The child is stamped with this run's namespace, as `ctx.startChild` would have stamped it, so an
319
+ * operator re-driving a tenant's turn does not move its delegate off that tenant's workers.
320
+ */
321
+ startDetached(ctx, toolCallId2, childInput) {
322
+ return stepOf(ctx)(`detach:${toolCallId2}`, async () => {
323
+ const workflows = this.workflows;
324
+ if (workflows === void 0) {
325
+ throw new Error("A detached delegation needs WorkflowService (DurableModule) to start its run, and none is bound.");
326
+ }
327
+ const childRunId = `${ctx.runId}.detached.${toolCallId2}`;
328
+ const namespace = await this.namespaceOf(ctx.runId);
329
+ try {
330
+ await workflows.start(_AgentRunWorkflow, childInput, childRunId, namespace !== void 0 ? {
331
+ namespace
332
+ } : void 0);
333
+ } catch (error) {
334
+ if (!(0, import_nestjs_durable_core2.isWorkflowControlFlowSignal)(error)) {
335
+ throw error;
336
+ }
337
+ }
338
+ return childRunId;
339
+ });
340
+ }
341
+ /** The namespace this run is stamped with, where the runtime can say. */
342
+ async namespaceOf(runId) {
343
+ try {
344
+ return (await this.runs?.getRunDetail(runId))?.run.namespace;
345
+ } catch {
346
+ return void 0;
347
+ }
291
348
  }
292
349
  /**
293
350
  * Move the thread past this settling turn — to the next queued message, started here as a
@@ -488,6 +545,10 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
488
545
  // OWN runId, and that is what `runForToolCall` answers with.
489
546
  // - `deliverTo` instead. The parent's tool result is a receipt, so the answer needs an
490
547
  // address of its own, and by the time it exists nobody is holding one.
548
+ // - no runtime parent. A Stop on this turn must not reach the work it handed off, so the
549
+ // run is started on its own (`startDetached`), not as a `spawn:` child. A run that
550
+ // journaled the `spawn:` before this release replays it: `ctx.patched` answers false at a
551
+ // position that already holds one.
491
552
  startAgent: /* @__PURE__ */ __name(async ({ agentName, task, toolCallId: toolCallId2 }) => {
492
553
  const subThreadId = await stepOf(ctx)(`subthread:${agentName}`, async () => {
493
554
  const thread = await this.store.createThread({
@@ -496,7 +557,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
496
557
  });
497
558
  return thread.id;
498
559
  });
499
- const childRunId = await ctx.startChild(_AgentRunWorkflow, {
560
+ const childInput = {
500
561
  agentName,
501
562
  threadId: subThreadId,
502
563
  actor: input.actor,
@@ -509,9 +570,14 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
509
570
  threadId: input.threadId,
510
571
  toolCallId: toolCallId2
511
572
  }
512
- });
573
+ };
574
+ if (!await ctx.patched("agent:detached-unlinked")) {
575
+ return {
576
+ runId: await ctx.startChild(_AgentRunWorkflow, childInput)
577
+ };
578
+ }
513
579
  return {
514
- runId: childRunId
580
+ runId: await this.startDetached(ctx, toolCallId2, childInput)
515
581
  };
516
582
  }, "startAgent"),
517
583
  // The two long steps as engine-dispatched `ctx.step`s, so a turn isn't pinned to this
@@ -624,12 +690,14 @@ AgentRunWorkflow = _ts_decorate2([
624
690
  _ts_param2(2, (0, import_common2.Inject)(import_nestjs_durable2.RUN_GATEWAY)),
625
691
  _ts_param2(3, (0, import_common2.Optional)()),
626
692
  _ts_param2(3, (0, import_common2.Inject)(AGENT_CHAT_QUEUE)),
693
+ _ts_param2(4, (0, import_common2.Optional)()),
627
694
  _ts_metadata2("design:type", Function),
628
695
  _ts_metadata2("design:paramtypes", [
629
696
  typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
630
697
  typeof AgentStore === "undefined" ? Object : AgentStore,
631
698
  typeof RunGateway === "undefined" ? Object : RunGateway,
632
- typeof ChatQueueService === "undefined" ? Object : ChatQueueService
699
+ typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
700
+ typeof import_nestjs_durable2.WorkflowService === "undefined" ? Object : import_nestjs_durable2.WorkflowService
633
701
  ])
634
702
  ], AgentRunWorkflow);
635
703
  function isSignalTimeout(error) {
@@ -750,6 +818,20 @@ function isThreadTurn(input) {
750
818
  return typeof input === "object" && input !== null && input.sinkRunId === void 0 && input.deliverTo === void 0;
751
819
  }
752
820
  __name(isThreadTurn, "isThreadTurn");
821
+ function deliveryOf(input) {
822
+ if (typeof input !== "object" || input === null) {
823
+ return void 0;
824
+ }
825
+ const { deliverTo, agentName } = input;
826
+ if (typeof deliverTo !== "object" || deliverTo === null || typeof deliverTo.threadId !== "string" || typeof deliverTo.toolCallId !== "string") {
827
+ return void 0;
828
+ }
829
+ return {
830
+ delivery: deliverTo,
831
+ agent: typeof agentName === "string" ? agentName : "default"
832
+ };
833
+ }
834
+ __name(deliveryOf, "deliveryOf");
753
835
  var DurableAgentRunner = class _DurableAgentRunner {
754
836
  static {
755
837
  __name(this, "DurableAgentRunner");
@@ -829,11 +911,16 @@ var DurableAgentRunner = class _DurableAgentRunner {
829
911
  */
830
912
  async cancel(runId) {
831
913
  this.logger.log(`cancelling agent run ${runId}`);
832
- const input = await this.inputOf(runId);
914
+ const detail = await this.detailOf(runId);
915
+ const input = detail?.run.input;
833
916
  const threadId = threadOfRun(input);
834
917
  await this.runs.cancel(runId, {
835
918
  compensate: true
836
919
  });
920
+ await this.settleCancelledDelegation(runId, input);
921
+ for (const childId of detail?.children ?? []) {
922
+ await this.settleCascadedDetached(childId);
923
+ }
837
924
  const writer = await this.sink.open(runId);
838
925
  if (threadId !== void 0) {
839
926
  if (this.queue?.supported === true && isThreadTurn(input)) {
@@ -864,7 +951,57 @@ var DurableAgentRunner = class _DurableAgentRunner {
864
951
  });
865
952
  }
866
953
  /**
867
- * The input a run was started with (which thread it was streaming), or `undefined` where the gateway cannot say.
954
+ * Settle the delegation a DETACHED run was started for, once it has been stopped. A run parked on
955
+ * a human never runs its body again — the runtime settles it from outside — so the body's own
956
+ * `deliver:detached:unsettled` is not reached, and without this the card in the calling thread says
957
+ * "started" for ever. Settles once (see `settleUnsettledDelegation`), so a body that did observe
958
+ * the Stop and a run that had already answered are both left as they are. A no-op for any run
959
+ * that does not deliver into another thread.
960
+ */
961
+ async settleCancelledDelegation(runId, input) {
962
+ const target = deliveryOf(input);
963
+ if (target === void 0) {
964
+ return;
965
+ }
966
+ try {
967
+ await (0, import_nestjs_agent_core4.settleUnsettledDelegation)({
968
+ store: this.store,
969
+ delivery: target.delivery,
970
+ agent: target.agent,
971
+ runId,
972
+ status: "cancelled"
973
+ });
974
+ } catch (error) {
975
+ this.logger.error(`could not settle the delegation of cancelled run ${runId}: ${error instanceof Error ? error.message : String(error)}`);
976
+ }
977
+ }
978
+ /**
979
+ * A detached child the runtime cancelled along with its parent (a `spawn:` journaled before
980
+ * detached runs were started on their own): end it the way a Stop on its own id would have.
981
+ */
982
+ async settleCascadedDetached(childId) {
983
+ const child = await this.detailOf(childId);
984
+ const status = child?.run.status;
985
+ if (child === void 0 || deliveryOf(child.run.input) === void 0 || status !== "cancelled" && status !== "cancelling") {
986
+ return;
987
+ }
988
+ const subThreadId = threadOfRun(child.run.input);
989
+ if (subThreadId !== void 0) {
990
+ await (0, import_nestjs_agent_core4.releaseThreadRun)(this.store, subThreadId, childId);
991
+ }
992
+ await this.settleCancelledDelegation(childId, child.run.input);
993
+ const writer = await this.sink.open(childId);
994
+ await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)({
995
+ kind: "cancelled"
996
+ }));
997
+ await writer.end();
998
+ await this.store.recordRunEnd?.({
999
+ runId: childId,
1000
+ status: "cancelled"
1001
+ });
1002
+ }
1003
+ /**
1004
+ * What the runtime recorded for a run — the input it was started with (which thread it was streaming), its status and the runs it spawned — or `undefined` where the gateway cannot say.
868
1005
  *
869
1006
  * A read failure answers `undefined` instead of propagating, the same posture `AgentRunWorkflow`
870
1007
  * takes for its own cancel observation: failing to ask is not an answer worth failing a cancel
@@ -873,9 +1010,9 @@ var DurableAgentRunner = class _DurableAgentRunner {
873
1010
  * with a subscriber holding a stream that never settles, from the one call whose entire job is to
874
1011
  * make a run stop.
875
1012
  */
876
- async inputOf(runId) {
1013
+ async detailOf(runId) {
877
1014
  try {
878
- return (await this.runs.getRunDetail(runId))?.run.input;
1015
+ return await this.runs.getRunDetail(runId) ?? void 0;
879
1016
  } catch {
880
1017
  return void 0;
881
1018
  }
@@ -1090,8 +1227,64 @@ var AgentDepsFactory2 = class {
1090
1227
  ...delegated
1091
1228
  ];
1092
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
+ }
1093
1279
  forAgent(agentName) {
1094
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
+ }
1095
1288
  const definition = this.agents.get(name);
1096
1289
  const toolAllowList = this.effectiveTools(definition);
1097
1290
  const followUpsCount = this.followUpsCount();
@@ -1156,6 +1349,9 @@ var AgentDepsFactory2 = class {
1156
1349
  ...toolAllowList !== void 0 ? {
1157
1350
  toolAllowList
1158
1351
  } : {},
1352
+ ...definition?.personas !== void 0 ? {
1353
+ personas: definition.personas
1354
+ } : {},
1159
1355
  ...this.options.toolTimeoutMs !== void 0 ? {
1160
1356
  toolTimeoutMs: this.options.toolTimeoutMs
1161
1357
  } : {},
@@ -1174,6 +1370,45 @@ var AgentDepsFactory2 = class {
1174
1370
  };
1175
1371
  }
1176
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
+ /**
1177
1412
  * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
1178
1413
  * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
1179
1414
  * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
@@ -1254,6 +1489,18 @@ var import_common8 = require("@nestjs/common");
1254
1489
  // src/queue/chat-queue.service.ts
1255
1490
  var import_nestjs_agent_core8 = require("@dudousxd/nestjs-agent-core");
1256
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
1257
1504
  function _ts_decorate6(decorators, target, key, desc) {
1258
1505
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1259
1506
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -1290,18 +1537,20 @@ var ChatQueueService2 = class _ChatQueueService {
1290
1537
  quotaProvider;
1291
1538
  options;
1292
1539
  staging;
1540
+ deps;
1293
1541
  logger = new import_common6.Logger(_ChatQueueService.name);
1294
1542
  /**
1295
1543
  * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
1296
1544
  * alive whatever the runner says — it simply has not registered the run yet.
1297
1545
  */
1298
1546
  starting = /* @__PURE__ */ new Set();
1299
- constructor(store, sink, quotaProvider, options, staging) {
1547
+ constructor(store, sink, quotaProvider, options, staging, deps) {
1300
1548
  this.store = store;
1301
1549
  this.sink = sink;
1302
1550
  this.quotaProvider = quotaProvider;
1303
1551
  this.options = options;
1304
1552
  this.staging = staging;
1553
+ this.deps = deps;
1305
1554
  }
1306
1555
  /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
1307
1556
  get supported() {
@@ -1599,13 +1848,17 @@ var ChatQueueService2 = class _ChatQueueService {
1599
1848
  */
1600
1849
  async inputFor(message) {
1601
1850
  const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
1851
+ const { agentName, persona } = await this.targetFor(message);
1602
1852
  return {
1603
1853
  threadId: message.threadId,
1604
1854
  actor: message.actor,
1605
1855
  userText: message.content,
1606
1856
  day: utcDay(),
1607
- ...message.agentName !== void 0 ? {
1608
- agentName: message.agentName
1857
+ ...agentName !== void 0 ? {
1858
+ agentName
1859
+ } : {},
1860
+ ...persona !== void 0 ? {
1861
+ persona
1609
1862
  } : {},
1610
1863
  ...attachments.length > 0 ? {
1611
1864
  attachments
@@ -1618,6 +1871,37 @@ var ChatQueueService2 = class _ChatQueueService {
1618
1871
  } : {}
1619
1872
  };
1620
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
+ }
1621
1905
  async freshAttachments(actor, attachments) {
1622
1906
  const staging = this.staging;
1623
1907
  if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
@@ -1645,13 +1929,16 @@ ChatQueueService2 = _ts_decorate6([
1645
1929
  _ts_param5(3, (0, import_common6.Inject)(import_nestjs_agent_core8.AGENT_OPTIONS)),
1646
1930
  _ts_param5(4, (0, import_common6.Optional)()),
1647
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)),
1648
1934
  _ts_metadata5("design:type", Function),
1649
1935
  _ts_metadata5("design:paramtypes", [
1650
1936
  typeof AgentStore === "undefined" ? Object : AgentStore,
1651
1937
  typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
1652
1938
  typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
1653
1939
  typeof Pick === "undefined" ? Object : Pick,
1654
- typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
1940
+ typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore,
1941
+ typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
1655
1942
  ])
1656
1943
  ], ChatQueueService2);
1657
1944
  function pauseReasonFor(outcome, head) {
@@ -1905,7 +2192,9 @@ var AgentService = class {
1905
2192
  */
1906
2193
  async send(params) {
1907
2194
  await this.assertWithinQuota(params.actor);
1908
- 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);
1909
2198
  const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId);
1910
2199
  const attachments = await this.resolveAttachments(params.actor, params.attachments ?? []);
1911
2200
  let threadId = params.threadId;
@@ -1920,11 +2209,17 @@ var AgentService = class {
1920
2209
  } : {},
1921
2210
  ...params.newThreadId !== void 0 ? {
1922
2211
  id: params.newThreadId
2212
+ } : {},
2213
+ ...params.personaId !== void 0 ? {
2214
+ persona: params.personaId
1923
2215
  } : {}
1924
2216
  });
1925
2217
  threadId = created.id;
1926
2218
  } else {
1927
2219
  await this.assertOwnsThread(params.actor, threadId);
2220
+ if (params.personaId !== void 0) {
2221
+ await this.pinThreadPersona(threadId, params.personaId);
2222
+ }
1928
2223
  }
1929
2224
  const input = {
1930
2225
  threadId,
@@ -1932,6 +2227,9 @@ var AgentService = class {
1932
2227
  userText: params.message,
1933
2228
  day: utcDay(),
1934
2229
  agentName,
2230
+ ...persona !== void 0 ? {
2231
+ persona
2232
+ } : {},
1935
2233
  ...params.regenerate === true ? {
1936
2234
  regenerate: true
1937
2235
  } : {},
@@ -2018,6 +2316,9 @@ var AgentService = class {
2018
2316
  ...input.agentName !== void 0 ? {
2019
2317
  agentName: input.agentName
2020
2318
  } : {},
2319
+ ...input.persona !== void 0 ? {
2320
+ persona: input.persona
2321
+ } : {},
2021
2322
  ...input.model !== void 0 ? {
2022
2323
  model: input.model
2023
2324
  } : {},
@@ -2412,16 +2713,22 @@ var AgentService = class {
2412
2713
  async updateThread(actor, threadId, patch) {
2413
2714
  const title = patch.title !== void 0 ? this.validateTitle(patch.title) : void 0;
2414
2715
  await this.assertOwnsThread(actor, threadId);
2415
- if (patch.defaultAgent === void 0 && patch.model === void 0) {
2716
+ if (patch.defaultAgent === void 0 && patch.model === void 0 && patch.persona === void 0) {
2416
2717
  if (title !== void 0) {
2417
2718
  await this.store.setTitle(threadId, title);
2418
2719
  }
2419
2720
  return;
2420
2721
  }
2421
2722
  if (this.store.updateThread === void 0) {
2422
- 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.");
2423
2724
  }
2424
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
+ }
2425
2732
  await this.store.updateThread(threadId, {
2426
2733
  ...title !== void 0 ? {
2427
2734
  title
@@ -2431,9 +2738,39 @@ var AgentService = class {
2431
2738
  } : {},
2432
2739
  ...model !== void 0 ? {
2433
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)
2434
2760
  } : {}
2435
2761
  });
2436
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
+ }
2437
2774
  validateTitle(title) {
2438
2775
  const trimmed = title.trim();
2439
2776
  if (trimmed.length === 0) {
@@ -2479,6 +2816,7 @@ var AgentService = class {
2479
2816
  ...thread,
2480
2817
  defaultAgent: thread.defaultAgent ?? null,
2481
2818
  model: thread.model ?? null,
2819
+ persona: thread.persona ?? null,
2482
2820
  activeRunId: await this.store.activeRunForThread?.(thread.id) ?? thread.activeRunId ?? null
2483
2821
  };
2484
2822
  }
@@ -2880,13 +3218,22 @@ var AgentsController = class {
2880
3218
  }
2881
3219
  list() {
2882
3220
  const defaultAgentName = this.depsFactory.defaultAgentName();
2883
- return this.agents.list().map((definition) => ({
2884
- name: definition.name,
2885
- description: definition.description ?? "",
2886
- ...definition.name === defaultAgentName ? {
2887
- isDefault: true
2888
- } : {}
2889
- }));
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
+ });
2890
3237
  }
2891
3238
  };
2892
3239
  _ts_decorate9([
@@ -3156,6 +3503,9 @@ var ChatController = class {
3156
3503
  ...body.agent !== void 0 ? {
3157
3504
  agentName: body.agent
3158
3505
  } : {},
3506
+ ...typeof body.persona === "string" && body.persona.length > 0 ? {
3507
+ personaId: body.persona
3508
+ } : {},
3159
3509
  ...attachments.length > 0 ? {
3160
3510
  attachments
3161
3511
  } : {},
@@ -4155,6 +4505,9 @@ var ThreadsController = class {
4155
4505
  } : {},
4156
4506
  ..."model" in body ? {
4157
4507
  model: body.model ?? null
4508
+ } : {},
4509
+ ..."persona" in body ? {
4510
+ persona: body.persona ?? null
4158
4511
  } : {}
4159
4512
  });
4160
4513
  return {
@@ -4650,6 +5003,7 @@ var AgentDiscoveryService = class _AgentDiscoveryService {
4650
5003
  }
4651
5004
  this.collectContributors(instance);
4652
5005
  }
5006
+ this.assertPersonas(this.registry.list());
4653
5007
  }
4654
5008
  /** Build the internal {@link AgentDefinition} from an `@Agent` instance and its options. */
4655
5009
  definitionFrom(instance, meta) {
@@ -4693,10 +5047,48 @@ var AgentDiscoveryService = class _AgentDiscoveryService {
4693
5047
  } : {},
4694
5048
  ...meta.handoff !== void 0 ? {
4695
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
4696
5056
  } : {}
4697
5057
  };
4698
5058
  }
4699
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
+ /**
4700
5092
  * Resolve each handoff entry to an {@link AgentDelegation} — its target's registered name, plus
4701
5093
  * whether that edge detaches (skipping non-agent classes). A bare name is kept for a plain edge
4702
5094
  * rather than an always-object form, so a definition an older host built by hand still reads.