@dudousxd/nestjs-agent 1.20.0 → 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 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, 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';
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;
@@ -240,6 +244,13 @@ interface AgentMemoryOptions {
240
244
  maxFactChars?: number;
241
245
  }
242
246
  interface AgentModuleOptions {
247
+ actionApprovalMode?: ActionApprovalMode;
248
+ backgroundActorResolver?: BackgroundActorResolver;
249
+ actionProposalWorker?: {
250
+ pollIntervalMs?: number;
251
+ leaseMs?: number;
252
+ maxConcurrency?: number;
253
+ };
243
254
  /**
244
255
  * The LLM provider — `aiSdkModel(openai('gpt-5-mini'))`, or `aiSdkModels({ … })` for a model
245
256
  * picker (its catalog is used when `models` is omitted). The only required option.
@@ -525,7 +536,9 @@ declare class AgentDepsFactory {
525
536
  private readonly pricingStore;
526
537
  private readonly skills;
527
538
  private readonly memory;
528
- 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);
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);
529
542
  /** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
530
543
  memoryConfig(): MemoryConfig | undefined;
531
544
  /** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
@@ -558,6 +571,7 @@ declare class AgentDepsFactory {
558
571
  requested?: string;
559
572
  threadPersona?: string | null;
560
573
  }): string | undefined;
574
+ forProposal(proposal: ActionProposal, _actor: Actor): AgentDeps;
561
575
  forAgent(agentName?: string): AgentDeps;
562
576
  /**
563
577
  * A persona baked into an agent's deps — how a run journaled under an agent name that a persona
@@ -1,9 +1,13 @@
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';
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;
@@ -240,6 +244,13 @@ interface AgentMemoryOptions {
240
244
  maxFactChars?: number;
241
245
  }
242
246
  interface AgentModuleOptions {
247
+ actionApprovalMode?: ActionApprovalMode;
248
+ backgroundActorResolver?: BackgroundActorResolver;
249
+ actionProposalWorker?: {
250
+ pollIntervalMs?: number;
251
+ leaseMs?: number;
252
+ maxConcurrency?: number;
253
+ };
243
254
  /**
244
255
  * The LLM provider — `aiSdkModel(openai('gpt-5-mini'))`, or `aiSdkModels({ … })` for a model
245
256
  * picker (its catalog is used when `models` is omitted). The only required option.
@@ -525,7 +536,9 @@ declare class AgentDepsFactory {
525
536
  private readonly pricingStore;
526
537
  private readonly skills;
527
538
  private readonly memory;
528
- 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);
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);
529
542
  /** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
530
543
  memoryConfig(): MemoryConfig | undefined;
531
544
  /** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
@@ -558,6 +571,7 @@ declare class AgentDepsFactory {
558
571
  requested?: string;
559
572
  threadPersona?: string | null;
560
573
  }): string | undefined;
574
+ forProposal(proposal: ActionProposal, _actor: Actor): AgentDeps;
561
575
  forAgent(agentName?: string): AgentDeps;
562
576
  /**
563
577
  * A persona baked into an agent's deps — how a run journaled under an agent name that a persona