@dudousxd/nestjs-agent 1.19.3 → 1.21.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.
- package/README.md +51 -0
- package/dist/{chat-queue.service-CKZkjlMK.d.ts → chat-queue.service-CGsR40Ct.d.cts} +56 -3
- package/dist/{chat-queue.service-CKZkjlMK.d.cts → chat-queue.service-XG9RQCjU.d.ts} +56 -3
- package/dist/durable/index.cjs +1826 -836
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +3 -1
- package/dist/durable/index.d.ts +3 -1
- package/dist/durable/index.js +1604 -614
- package/dist/durable/index.js.map +1 -1
- package/dist/genui/index.d.cts +1 -1
- package/dist/genui/index.d.ts +1 -1
- package/dist/index.cjs +1752 -830
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +157 -9
- package/dist/index.d.ts +157 -9
- package/dist/index.js +1562 -644
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -405,6 +405,57 @@ In anonymous mode the limits apply per browser (each is its own actor).
|
|
|
405
405
|
|
|
406
406
|
### Who approves an action, and for how long
|
|
407
407
|
|
|
408
|
+
Action handlers can implement an optional read-only `preflight(input, ctx, { phase })`. The
|
|
409
|
+
registry authorizes and validates the input first. `prepare` runs before an approval is requested;
|
|
410
|
+
`execute` rechecks current state inside the tool checkpoint immediately before its side effect.
|
|
411
|
+
Automatic and remembered approvals still run both checks; direct registry and MCP invocations
|
|
412
|
+
run the execution check too.
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
class RemoveSessionsTool implements ToolHandler<{ accountId: string }, { removed: number }> {
|
|
416
|
+
constructor(private readonly sessions: SessionsService) {}
|
|
417
|
+
|
|
418
|
+
async preflight(input: { accountId: string }, ctx: AiToolCtx, { phase }: ToolPreflightOptions): Promise<ToolPreflightResult<{ removed: number }>> {
|
|
419
|
+
const count = await this.sessions.countForActor(input.accountId, ctx.actor);
|
|
420
|
+
if (count === 0) return { status: 'completed', output: { removed: 0 } };
|
|
421
|
+
if (await this.sessions.isLocked(input.accountId)) {
|
|
422
|
+
return { status: 'denied', reason: 'This account is locked.' };
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
status: 'ready',
|
|
426
|
+
confirmation: { title: `Remove ${count} sessions?`, verb: 'Remove' },
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async execute(input: { accountId: string }, ctx: AiToolCtx) {
|
|
431
|
+
return this.sessions.removeForActor(input.accountId, ctx.actor, ctx.idempotencyKey);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
`ready` may supply fully resolved `ToolConfirmation` strings (`title`, `verb`, optional `detail`)
|
|
437
|
+
that override the tool's presentation templates for this call, including after a history reload.
|
|
438
|
+
`denied` returns a domain refusal to the model, and `completed` returns an existing result. Both
|
|
439
|
+
skip approval and execution during preparation. A completed terminal action ends the turn.
|
|
440
|
+
A refusal after a person approves preserves that approval while reporting that the mutation did
|
|
441
|
+
not run. An exception becomes a failed tool result. Read tools ignore this hook.
|
|
442
|
+
|
|
443
|
+
Functional tools use the same handler contract:
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
provideAgentTool({
|
|
447
|
+
spec: removeSessionsSpec,
|
|
448
|
+
handler: { preflight: checkSessions, execute: removeSessions },
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Preflight must not perform writes or emit UI. Its preparation result is journaled with the existing
|
|
453
|
+
call checkpoint (and, for remote execution, first with the model worker's turn); replay uses that
|
|
454
|
+
result without rerunning the check or changing its approval branch. The execution check is
|
|
455
|
+
journaled inside the existing tool checkpoint. Keep domain checks inside `execute` or an atomic
|
|
456
|
+
transaction as needed: a preflight check cannot eliminate concurrent changes between reads and
|
|
457
|
+
writes, and it does not replace idempotency.
|
|
458
|
+
|
|
408
459
|
Every `action` tool call waits on the person chatting by default. `approvalPolicy` changes that per
|
|
409
460
|
call:
|
|
410
461
|
|
|
@@ -1,9 +1,13 @@
|
|
|
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
|
|
1
|
+
import { ResolveToolUiCatalog, ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, Persona, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ActionApprovalMode, BackgroundActorResolver, ModelCatalog, EmptyRoles, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry, PersonaCatalogEntry, ActionProposal, Actor, AttachmentStagingStore, ChatQueueStore, ChatQueueState, AgentRunner, AgentRunInput, AgentStreamEvent, QueuePauseReason } from '@dudousxd/nestjs-agent-core';
|
|
2
|
+
import { Catalog } from '@dudousxd/nestjs-agent-core/genui';
|
|
2
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
4
|
import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
5
|
+
import { GenuiCatalogResolver } from './genui/index.cjs';
|
|
4
6
|
|
|
5
7
|
/** Everything `runAgentLoop` needs, minus the per-run `day` the runner stamps. */
|
|
6
8
|
interface AgentDeps {
|
|
9
|
+
resolveUiCatalog?: ResolveToolUiCatalog;
|
|
10
|
+
actionApprovalMode?: 'blocking' | 'independent';
|
|
7
11
|
model: ModelProvider;
|
|
8
12
|
store: AgentStore;
|
|
9
13
|
registry: ToolRegistry;
|
|
@@ -24,6 +28,8 @@ interface AgentDeps {
|
|
|
24
28
|
maxAgentAppearances?: number;
|
|
25
29
|
/** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
|
|
26
30
|
toolAllowList?: string[];
|
|
31
|
+
/** The agent's personas (`@Agent({ personas })`) — read by the loop only for a run that names one. */
|
|
32
|
+
personas?: Persona[];
|
|
27
33
|
/** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
|
|
28
34
|
toolTimeoutMs?: number;
|
|
29
35
|
/**
|
|
@@ -238,6 +244,13 @@ interface AgentMemoryOptions {
|
|
|
238
244
|
maxFactChars?: number;
|
|
239
245
|
}
|
|
240
246
|
interface AgentModuleOptions {
|
|
247
|
+
actionApprovalMode?: ActionApprovalMode;
|
|
248
|
+
backgroundActorResolver?: BackgroundActorResolver;
|
|
249
|
+
actionProposalWorker?: {
|
|
250
|
+
pollIntervalMs?: number;
|
|
251
|
+
leaseMs?: number;
|
|
252
|
+
maxConcurrency?: number;
|
|
253
|
+
};
|
|
241
254
|
/**
|
|
242
255
|
* The LLM provider — `aiSdkModel(openai('gpt-5-mini'))`, or `aiSdkModels({ … })` for a model
|
|
243
256
|
* picker (its catalog is used when `models` is omitted). The only required option.
|
|
@@ -523,7 +536,9 @@ declare class AgentDepsFactory {
|
|
|
523
536
|
private readonly pricingStore;
|
|
524
537
|
private readonly skills;
|
|
525
538
|
private readonly memory;
|
|
526
|
-
|
|
539
|
+
private readonly uiCatalog?;
|
|
540
|
+
private readonly uiCatalogResolver?;
|
|
541
|
+
constructor(options: AgentModuleOptions, model: ModelProvider, store: AgentStore, sink: TokenStreamSink, rolesPolicy: RolesPolicy, registry: ToolRegistry, agents: AgentRegistry, promptContributors: PromptContributor[], pricingStore: AgentPricingStore | undefined, skills: SkillsConfig | undefined, memory: MemoryConfig | undefined, uiCatalog?: Catalog | undefined, uiCatalogResolver?: GenuiCatalogResolver | undefined);
|
|
527
542
|
/** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
|
|
528
543
|
memoryConfig(): MemoryConfig | undefined;
|
|
529
544
|
/** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
|
|
@@ -534,7 +549,37 @@ declare class AgentDepsFactory {
|
|
|
534
549
|
*/
|
|
535
550
|
defaultAgentName(): string;
|
|
536
551
|
private effectiveTools;
|
|
552
|
+
/**
|
|
553
|
+
* The agent a name stands for: itself when it is a registered agent (or unknown — the bare
|
|
554
|
+
* assistant), else the agent whose persona took the name over (`Persona.aliases`) and that persona.
|
|
555
|
+
*/
|
|
556
|
+
resolveAgent(name: string): {
|
|
557
|
+
agentName: string;
|
|
558
|
+
persona?: string;
|
|
559
|
+
};
|
|
560
|
+
/** `agentName`'s personas as a picker reads them. Empty when it declares none. */
|
|
561
|
+
personaCatalog(agentName: string): PersonaCatalogEntry[];
|
|
562
|
+
/** The persona `agentName` runs under when nothing names one. Undefined → none. */
|
|
563
|
+
defaultPersona(agentName: string): string | undefined;
|
|
564
|
+
/**
|
|
565
|
+
* The persona a turn of `agentName` runs under, most specific first: the one the send names
|
|
566
|
+
* (refused with `400 persona_not_found` when the agent does not declare it), else the thread's
|
|
567
|
+
* pinned one when this agent declares it, else the agent's default. Undefined → none.
|
|
568
|
+
*/
|
|
569
|
+
resolvePersona(args: {
|
|
570
|
+
agentName: string;
|
|
571
|
+
requested?: string;
|
|
572
|
+
threadPersona?: string | null;
|
|
573
|
+
}): string | undefined;
|
|
574
|
+
forProposal(proposal: ActionProposal, _actor: Actor): AgentDeps;
|
|
537
575
|
forAgent(agentName?: string): AgentDeps;
|
|
576
|
+
/**
|
|
577
|
+
* A persona baked into an agent's deps — how a run journaled under an agent name that a persona
|
|
578
|
+
* has since taken over (`Persona.aliases`) is served. That run names no persona of its own (it
|
|
579
|
+
* predates them, and its input cannot change), so the persona applies the way the old agent's own
|
|
580
|
+
* config did: as config, re-read on every replay, with no checkpoint of its own.
|
|
581
|
+
*/
|
|
582
|
+
private withPersona;
|
|
538
583
|
/**
|
|
539
584
|
* The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
|
|
540
585
|
* own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
|
|
@@ -591,13 +636,14 @@ declare class ChatQueueService {
|
|
|
591
636
|
private readonly quotaProvider?;
|
|
592
637
|
private readonly options?;
|
|
593
638
|
private readonly staging?;
|
|
639
|
+
private readonly deps?;
|
|
594
640
|
private readonly logger;
|
|
595
641
|
/**
|
|
596
642
|
* Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
|
|
597
643
|
* alive whatever the runner says — it simply has not registered the run yet.
|
|
598
644
|
*/
|
|
599
645
|
private readonly starting;
|
|
600
|
-
constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined);
|
|
646
|
+
constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined, deps?: AgentDepsFactory | undefined);
|
|
601
647
|
/** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
|
|
602
648
|
get supported(): boolean;
|
|
603
649
|
/** The bound store as a queue store, or a 501 naming what is missing. */
|
|
@@ -673,6 +719,13 @@ declare class ChatQueueService {
|
|
|
673
719
|
* waited. An attachment the staging store no longer resolves keeps its stored url.
|
|
674
720
|
*/
|
|
675
721
|
private inputFor;
|
|
722
|
+
/**
|
|
723
|
+
* The agent and persona a queued message starts as. A message carries the persona its send
|
|
724
|
+
* resolved; one that carries none — queued before personas existed, or by a host that sets none —
|
|
725
|
+
* resolves it now, exactly as a send would: an agent name a persona took over, then the thread's
|
|
726
|
+
* pinned persona, then the agent's default. Never refused here: nobody is waiting on a 400.
|
|
727
|
+
*/
|
|
728
|
+
private targetFor;
|
|
676
729
|
private freshAttachments;
|
|
677
730
|
}
|
|
678
731
|
|
|
@@ -1,9 +1,13 @@
|
|
|
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
|
|
1
|
+
import { ResolveToolUiCatalog, ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, Persona, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, QuotaProvider, QuotaQuery, QuotaReport, ActionApprovalMode, BackgroundActorResolver, ModelCatalog, EmptyRoles, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry, PersonaCatalogEntry, ActionProposal, Actor, AttachmentStagingStore, ChatQueueStore, ChatQueueState, AgentRunner, AgentRunInput, AgentStreamEvent, QueuePauseReason } from '@dudousxd/nestjs-agent-core';
|
|
2
|
+
import { Catalog } from '@dudousxd/nestjs-agent-core/genui';
|
|
2
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
4
|
import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
5
|
+
import { GenuiCatalogResolver } from './genui/index.js';
|
|
4
6
|
|
|
5
7
|
/** Everything `runAgentLoop` needs, minus the per-run `day` the runner stamps. */
|
|
6
8
|
interface AgentDeps {
|
|
9
|
+
resolveUiCatalog?: ResolveToolUiCatalog;
|
|
10
|
+
actionApprovalMode?: 'blocking' | 'independent';
|
|
7
11
|
model: ModelProvider;
|
|
8
12
|
store: AgentStore;
|
|
9
13
|
registry: ToolRegistry;
|
|
@@ -24,6 +28,8 @@ interface AgentDeps {
|
|
|
24
28
|
maxAgentAppearances?: number;
|
|
25
29
|
/** Agent-level tool allow-list. Undefined → all tools (after role filtering). */
|
|
26
30
|
toolAllowList?: string[];
|
|
31
|
+
/** The agent's personas (`@Agent({ personas })`) — read by the loop only for a run that names one. */
|
|
32
|
+
personas?: Persona[];
|
|
27
33
|
/** Per-tool execution timeout in ms (from module options). Undefined → no timeout. */
|
|
28
34
|
toolTimeoutMs?: number;
|
|
29
35
|
/**
|
|
@@ -238,6 +244,13 @@ interface AgentMemoryOptions {
|
|
|
238
244
|
maxFactChars?: number;
|
|
239
245
|
}
|
|
240
246
|
interface AgentModuleOptions {
|
|
247
|
+
actionApprovalMode?: ActionApprovalMode;
|
|
248
|
+
backgroundActorResolver?: BackgroundActorResolver;
|
|
249
|
+
actionProposalWorker?: {
|
|
250
|
+
pollIntervalMs?: number;
|
|
251
|
+
leaseMs?: number;
|
|
252
|
+
maxConcurrency?: number;
|
|
253
|
+
};
|
|
241
254
|
/**
|
|
242
255
|
* The LLM provider — `aiSdkModel(openai('gpt-5-mini'))`, or `aiSdkModels({ … })` for a model
|
|
243
256
|
* picker (its catalog is used when `models` is omitted). The only required option.
|
|
@@ -523,7 +536,9 @@ declare class AgentDepsFactory {
|
|
|
523
536
|
private readonly pricingStore;
|
|
524
537
|
private readonly skills;
|
|
525
538
|
private readonly memory;
|
|
526
|
-
|
|
539
|
+
private readonly uiCatalog?;
|
|
540
|
+
private readonly uiCatalogResolver?;
|
|
541
|
+
constructor(options: AgentModuleOptions, model: ModelProvider, store: AgentStore, sink: TokenStreamSink, rolesPolicy: RolesPolicy, registry: ToolRegistry, agents: AgentRegistry, promptContributors: PromptContributor[], pricingStore: AgentPricingStore | undefined, skills: SkillsConfig | undefined, memory: MemoryConfig | undefined, uiCatalog?: Catalog | undefined, uiCatalogResolver?: GenuiCatalogResolver | undefined);
|
|
527
542
|
/** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
|
|
528
543
|
memoryConfig(): MemoryConfig | undefined;
|
|
529
544
|
/** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
|
|
@@ -534,7 +549,37 @@ declare class AgentDepsFactory {
|
|
|
534
549
|
*/
|
|
535
550
|
defaultAgentName(): string;
|
|
536
551
|
private effectiveTools;
|
|
552
|
+
/**
|
|
553
|
+
* The agent a name stands for: itself when it is a registered agent (or unknown — the bare
|
|
554
|
+
* assistant), else the agent whose persona took the name over (`Persona.aliases`) and that persona.
|
|
555
|
+
*/
|
|
556
|
+
resolveAgent(name: string): {
|
|
557
|
+
agentName: string;
|
|
558
|
+
persona?: string;
|
|
559
|
+
};
|
|
560
|
+
/** `agentName`'s personas as a picker reads them. Empty when it declares none. */
|
|
561
|
+
personaCatalog(agentName: string): PersonaCatalogEntry[];
|
|
562
|
+
/** The persona `agentName` runs under when nothing names one. Undefined → none. */
|
|
563
|
+
defaultPersona(agentName: string): string | undefined;
|
|
564
|
+
/**
|
|
565
|
+
* The persona a turn of `agentName` runs under, most specific first: the one the send names
|
|
566
|
+
* (refused with `400 persona_not_found` when the agent does not declare it), else the thread's
|
|
567
|
+
* pinned one when this agent declares it, else the agent's default. Undefined → none.
|
|
568
|
+
*/
|
|
569
|
+
resolvePersona(args: {
|
|
570
|
+
agentName: string;
|
|
571
|
+
requested?: string;
|
|
572
|
+
threadPersona?: string | null;
|
|
573
|
+
}): string | undefined;
|
|
574
|
+
forProposal(proposal: ActionProposal, _actor: Actor): AgentDeps;
|
|
537
575
|
forAgent(agentName?: string): AgentDeps;
|
|
576
|
+
/**
|
|
577
|
+
* A persona baked into an agent's deps — how a run journaled under an agent name that a persona
|
|
578
|
+
* has since taken over (`Persona.aliases`) is served. That run names no persona of its own (it
|
|
579
|
+
* predates them, and its input cannot change), so the persona applies the way the old agent's own
|
|
580
|
+
* config did: as config, re-read on every replay, with no checkpoint of its own.
|
|
581
|
+
*/
|
|
582
|
+
private withPersona;
|
|
538
583
|
/**
|
|
539
584
|
* The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
|
|
540
585
|
* own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
|
|
@@ -591,13 +636,14 @@ declare class ChatQueueService {
|
|
|
591
636
|
private readonly quotaProvider?;
|
|
592
637
|
private readonly options?;
|
|
593
638
|
private readonly staging?;
|
|
639
|
+
private readonly deps?;
|
|
594
640
|
private readonly logger;
|
|
595
641
|
/**
|
|
596
642
|
* Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
|
|
597
643
|
* alive whatever the runner says — it simply has not registered the run yet.
|
|
598
644
|
*/
|
|
599
645
|
private readonly starting;
|
|
600
|
-
constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined);
|
|
646
|
+
constructor(store: AgentStore, sink: TokenStreamSink, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota"> | undefined, staging?: AttachmentStagingStore | undefined, deps?: AgentDepsFactory | undefined);
|
|
601
647
|
/** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
|
|
602
648
|
get supported(): boolean;
|
|
603
649
|
/** The bound store as a queue store, or a 501 naming what is missing. */
|
|
@@ -673,6 +719,13 @@ declare class ChatQueueService {
|
|
|
673
719
|
* waited. An attachment the staging store no longer resolves keeps its stored url.
|
|
674
720
|
*/
|
|
675
721
|
private inputFor;
|
|
722
|
+
/**
|
|
723
|
+
* The agent and persona a queued message starts as. A message carries the persona its send
|
|
724
|
+
* resolved; one that carries none — queued before personas existed, or by a host that sets none —
|
|
725
|
+
* resolves it now, exactly as a send would: an agent name a persona took over, then the thread's
|
|
726
|
+
* pinned persona, then the agent's default. Never refused here: nobody is waiting on a 400.
|
|
727
|
+
*/
|
|
728
|
+
private targetFor;
|
|
676
729
|
private freshAttachments;
|
|
677
730
|
}
|
|
678
731
|
|