@dudousxd/nestjs-agent 1.23.0 → 1.24.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/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { DynamicModule, Type, OnApplicationBootstrap, OnModuleInit, ConflictException, OnModuleDestroy } from '@nestjs/common';
2
- import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter, e as AgentSkillsOptions } from './chat-queue.service-D1z3WjFI.cjs';
3
- export { f as AGENT_TOOL_BRAND, g as AgentAttachmentsOptions, h as AgentDeps, i as AgentEngine, j as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, L as LedgerQuotaProvider, Q as QueuePlan, k as QueueSettleOutcome, l as QueuedTurn, m as QueuedTurnStarter, n as QuotaLimits, o as QuotaWindowLimits, p as defineTool, q as delegateToolName, r as provideAgentTool, s as provideAgentTools, u as utcDay } from './chat-queue.service-D1z3WjFI.cjs';
2
+ import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter, e as AgentSkillsOptions } from './chat-queue.service-Crf9uwum.cjs';
3
+ export { f as AGENT_TOOL_BRAND, g as AgentAttachmentsOptions, h as AgentDeps, i as AgentEngine, j as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, L as LedgerQuotaProvider, Q as QueuePlan, k as QueueSettleOutcome, l as QueuedTurn, m as QueuedTurnStarter, n as QuotaLimits, o as QuotaWindowLimits, p as defineTool, q as delegateToolName, r as provideAgentTool, s as provideAgentTools, u as utcDay } from './chat-queue.service-Crf9uwum.cjs';
4
4
  import * as _dudousxd_nestjs_agent_core from '@dudousxd/nestjs-agent-core';
5
- import { AgentStore, Actor, ListActionProposals, ActionProposalMutationView, AgentRunner, AttachmentStagingStore, ModelCatalog, QuotaProvider, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver, AgentApprovalPort, MessageAttachment, AgentAttachmentConfig, AgentClientConfig, ToolSpec, ToolPresentation, AgentHistoryWindow, AgentIntake, Persona, SkillContext, ToolRegistry, AgentRegistry, PromptContributor, SkillsConfig, SkillCatalogEntry, ToolCatalogEntry, MemoryConfig, MemoryDigestEntry, SkillProvider, AgentRunInput, AgentRunStartOptions, TokenStreamSink, SinkWriter } from '@dudousxd/nestjs-agent-core';
5
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, ActionProposalView, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver, AgentApprovalPort, AgentClientConfig, ToolSpec, ToolPresentation, AgentHistoryWindow, AgentIntake, Persona, SkillContext, ToolRegistry, AgentRegistry, PromptContributor, SkillsConfig, SkillCatalogEntry, ToolCatalogEntry, MemoryConfig, MemoryDigestEntry, SkillProvider, AgentRunInput, AgentRunStartOptions, TokenStreamSink, SinkWriter, ActionProposal } from '@dudousxd/nestjs-agent-core';
6
6
  export * from '@dudousxd/nestjs-agent-core';
7
7
  export { FunctionalToolDefinition } from '@dudousxd/nestjs-agent-core';
8
8
  import { Request, Response } from 'express';
@@ -16,6 +16,20 @@ declare class AgentModule {
16
16
  static forRootAsync(options: AgentModuleAsyncOptions): DynamicModule;
17
17
  }
18
18
 
19
+ /** Default per-file size cap (20 MiB). */
20
+ declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
21
+ /** Default allowlist: what multimodal model providers commonly accept as native image/file parts. */
22
+ declare const DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES: readonly string[];
23
+ /** How many attachments one message may name. */
24
+ declare const MAX_ATTACHMENTS_PER_MESSAGE = 10;
25
+ /** The attachment rules in force — `AgentAttachmentConfig`, as `GET <base>/config` serves it. */
26
+ type AttachmentLimits = AgentAttachmentConfig;
27
+ /**
28
+ * One source for attachment limits: the staging store's own `describe()` when it declares them
29
+ * (`AgentMediaAttachmentsModule` does), else `AgentModule`'s `attachments` option, else the defaults.
30
+ */
31
+ declare function attachmentLimits(options: Pick<AgentModuleOptions, 'attachments'> | undefined, staging: AttachmentStagingStore | undefined): AttachmentLimits;
32
+
19
33
  declare class ActionProposalService {
20
34
  private readonly store;
21
35
  private readonly options;
@@ -55,8 +69,10 @@ declare class ActionProposalService {
55
69
  text: string;
56
70
  }>;
57
71
  private textReceipt;
72
+ /** The configured words a text decision is made of (`actionProposalText.vocabulary`). */
73
+ textVocabulary(): TextActionProposalVocabulary;
58
74
  /** The configured reply to a decision the store answered with `result`. */
59
- reply(result: ActionProposalMutationView, decision: 'approved' | 'rejected'): string;
75
+ reply(result: Pick<ActionProposalMutationView, 'status' | 'proposal'>, decision: 'approved' | 'rejected'): string;
60
76
  }
61
77
 
62
78
  /**
@@ -165,7 +181,7 @@ declare class AgentService {
165
181
  private readonly options?;
166
182
  private readonly queue?;
167
183
  private readonly proposals?;
168
- constructor(runner: AgentRunner, store: AgentStore, deps: AgentDepsFactory, staging?: AttachmentStagingStore | undefined, models?: ModelCatalog | undefined, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota" | "actionApprovalMode"> | undefined, queue?: ChatQueueService | undefined, proposals?: ActionProposalService | undefined);
184
+ constructor(runner: AgentRunner, store: AgentStore, deps: AgentDepsFactory, staging?: AttachmentStagingStore | undefined, models?: ModelCatalog | undefined, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota" | "actionApprovalMode" | "attachments"> | undefined, queue?: ChatQueueService | undefined, proposals?: ActionProposalService | undefined);
169
185
  /**
170
186
  * Decide one independent proposal through the scoped proposal service — what the native
171
187
  * `action-proposals/:id/approve|reject` routes call — and the reply to show for it. `404` when
@@ -194,6 +210,37 @@ declare class AgentService {
194
210
  };
195
211
  text: string;
196
212
  }>;
213
+ /** How `action` tools wait for their approval here — `AgentModule.forRoot({ actionApprovalMode })`. */
214
+ actionApprovalMode(): ActionApprovalMode;
215
+ /**
216
+ * The words a text decision is made of (`actionProposalText.vocabulary`, English by default) — what
217
+ * a surface without buttons tells the person to reply.
218
+ */
219
+ actionProposalVocabulary(): TextActionProposalVocabulary;
220
+ /** The configured reply (`actionProposalText.replies`) to a decision the store answered with `result`. */
221
+ actionProposalReply(result: Pick<ActionProposalMutationView, 'status' | 'proposal'>, decision: 'approved' | 'rejected'): string;
222
+ /**
223
+ * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
224
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page). `404`
225
+ * when independent approvals are not on.
226
+ */
227
+ listActionProposals(actor: Actor, threadId: string): Promise<ActionProposalView[]>;
228
+ /**
229
+ * The attachment rules in force — what `GET <base>/config` serves and `POST <base>/attachments`
230
+ * enforces. `enabled: false` when no `AGENT_ATTACHMENT_STAGING` provider is bound.
231
+ */
232
+ attachmentLimits(): AttachmentLimits;
233
+ /**
234
+ * Stage a file for a later turn, held to the same limits as `POST <base>/attachments` — for a
235
+ * surface that receives files itself (a messaging webhook). Refuses with `501` when attachments are
236
+ * off, `415` for a type outside the allowlist and `413` past the size cap; send the returned
237
+ * `mediaId` as `attachments: [{ mediaId }]`.
238
+ */
239
+ stageAttachment(actor: Actor, file: {
240
+ data: Buffer;
241
+ contentType: string;
242
+ filename: string;
243
+ }): Promise<MessageAttachment>;
197
244
  /** The queue service when the bound store can hold a queue, else `undefined`. */
198
245
  private queueing;
199
246
  /** The queue service, or a 501 naming what is missing. */
@@ -539,7 +586,10 @@ declare class AgUiRunHandler {
539
586
  protected readonly quietMs: number | undefined;
540
587
  constructor(agent: AgentService, actorResolver: ActorResolver, options: AgentModuleOptions, staging: AttachmentStagingStore | undefined, quietMs: number | undefined);
541
588
  handle(req: Request, res: Response, body: unknown): Promise<void>;
542
- /** Stage the turn's inline media through the bound store, within its limits; say what was not used. */
589
+ /**
590
+ * The turn's attachments: the ones already staged (by mediaId), then its inline media staged
591
+ * through the bound store — all within the store's limits; say what was not used.
592
+ */
543
593
  private stage;
544
594
  /**
545
595
  * Deliver what a resume list decided, through the same calls the native decision routes make.
@@ -674,20 +724,6 @@ declare class AttachmentsController {
674
724
  upload(req: Request, file: Express.Multer.File | undefined): Promise<MessageAttachment>;
675
725
  }
676
726
 
677
- /** Default per-file size cap (20 MiB). */
678
- declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
679
- /** Default allowlist: what multimodal model providers commonly accept as native image/file parts. */
680
- declare const DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES: readonly string[];
681
- /** How many attachments one message may name. */
682
- declare const MAX_ATTACHMENTS_PER_MESSAGE = 10;
683
- /** The attachment rules in force — `AgentAttachmentConfig`, as `GET <base>/config` serves it. */
684
- type AttachmentLimits = AgentAttachmentConfig;
685
- /**
686
- * One source for attachment limits: the staging store's own `describe()` when it declares them
687
- * (`AgentMediaAttachmentsModule` does), else `AgentModule`'s `attachments` option, else the defaults.
688
- */
689
- declare function attachmentLimits(options: Pick<AgentModuleOptions, 'attachments'> | undefined, staging: AttachmentStagingStore | undefined): AttachmentLimits;
690
-
691
727
  declare class ConfigController {
692
728
  private readonly options;
693
729
  private readonly actorResolver;
@@ -1462,15 +1498,26 @@ declare class ActionProposalController {
1462
1498
  reject(threadId: string, proposalId: string, request: Request, body: unknown): Promise<_dudousxd_nestjs_agent_core.ActionProposalMutationView>;
1463
1499
  }
1464
1500
 
1501
+ /** Called with a proposal the worker just settled (executed, or failed executing). */
1502
+ type ActionProposalSettledListener = (proposal: ActionProposal) => void | Promise<void>;
1465
1503
  declare class ActionProposalWorkerService implements OnModuleInit, OnModuleDestroy {
1466
1504
  private readonly store;
1467
1505
  private readonly options;
1468
1506
  private readonly deps;
1469
1507
  private worker;
1470
1508
  private readonly logger;
1509
+ private readonly settledListeners;
1510
+ /**
1511
+ * Run `listener` after every proposal this process's worker settles — how a module relays an
1512
+ * outcome somewhere the turn's stream no longer reaches (a text channel's conversation). Next to
1513
+ * `actionProposalWorker.onSettled`; a listener that throws is logged and does not stop the others.
1514
+ * Returns the unsubscribe. Only the replicas that run the worker (not `surface: 'http'`) call it.
1515
+ */
1516
+ onSettled(listener: ActionProposalSettledListener): () => void;
1517
+ private settled;
1471
1518
  constructor(store: AgentStore, options: AgentModuleOptions, deps: AgentDepsFactory);
1472
1519
  onModuleInit(): void;
1473
1520
  onModuleDestroy(): Promise<void>;
1474
1521
  }
1475
1522
 
1476
- export { AGENT_CHAT_QUEUE, AGENT_METADATA, AI_TOOL_METADATA, ATTACHMENT_PAGE_SIZE, ActionProposalController, ActionProposalService, ActionProposalWorkerService, type AgUiAdapterOptions, AgUiRunHandler, Agent, AgentApprovalPortAdapter, AgentDepsFactory, AgentDiscoveryService, AgentModule, AgentModuleAsyncOptions, AgentModuleOptions, type AgentOptions, AgentProtocolAdapter, AgentService, AgentSkillsOptions, AiTool, AiToolDiscoveryService, type AiToolOptions, type AnonymousActorOptions, AnonymousActorResolver, type AttachmentLimits, AttachmentsController, type ChatParams, ChatQueueService, type ChatSendMode, type ChatSendResult, ConfigController, DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES, DEFAULT_MAX_ATTACHMENT_BYTES, type DeclaredSkill, HeaderActorResolver, InProcessTokenStreamSink, InlineAgentRunner, MAX_ATTACHMENTS_PER_MESSAGE, MemoriesController, MessagesController, ModelsController, QueueController, type QueuedSend, type RequestUserMapper, type ResolvedAiToolOptions, RunNotActiveException, SKILL_METADATA, SYSTEM_PROMPT_CONTRIBUTOR_METADATA, SYSTEM_PROMPT_METADATA, Skill, type SkillBody, SkillDiscoveryService, type SkillOptions, SkillsController, SystemPrompt, SystemPromptContributor, type ThreadDefaultAgentReader, type ThreadModelReader, type ThreadPersonaReader, ToolsController, agUiAdapter, attachmentLimits, declaredSkillProvider, defaultRequestUserMapper, readAgentMetadata, readAiToolMetadata, readSkillMetadata, requestUserActorResolver, resolveSkillsConfig, skillScope, threadPersona, toolNameFromClass };
1523
+ export { AGENT_CHAT_QUEUE, AGENT_METADATA, AI_TOOL_METADATA, ATTACHMENT_PAGE_SIZE, ActionProposalController, ActionProposalService, type ActionProposalSettledListener, ActionProposalWorkerService, type AgUiAdapterOptions, AgUiRunHandler, Agent, AgentApprovalPortAdapter, AgentDepsFactory, AgentDiscoveryService, AgentModule, AgentModuleAsyncOptions, AgentModuleOptions, type AgentOptions, AgentProtocolAdapter, AgentService, AgentSkillsOptions, AiTool, AiToolDiscoveryService, type AiToolOptions, type AnonymousActorOptions, AnonymousActorResolver, type AttachmentLimits, AttachmentsController, type ChatParams, ChatQueueService, type ChatSendMode, type ChatSendResult, ConfigController, DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES, DEFAULT_MAX_ATTACHMENT_BYTES, type DeclaredSkill, HeaderActorResolver, InProcessTokenStreamSink, InlineAgentRunner, MAX_ATTACHMENTS_PER_MESSAGE, MemoriesController, MessagesController, ModelsController, QueueController, type QueuedSend, type RequestUserMapper, type ResolvedAiToolOptions, RunNotActiveException, SKILL_METADATA, SYSTEM_PROMPT_CONTRIBUTOR_METADATA, SYSTEM_PROMPT_METADATA, Skill, type SkillBody, SkillDiscoveryService, type SkillOptions, SkillsController, SystemPrompt, SystemPromptContributor, type ThreadDefaultAgentReader, type ThreadModelReader, type ThreadPersonaReader, ToolsController, agUiAdapter, attachmentLimits, declaredSkillProvider, defaultRequestUserMapper, readAgentMetadata, readAiToolMetadata, readSkillMetadata, requestUserActorResolver, resolveSkillsConfig, skillScope, threadPersona, toolNameFromClass };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { DynamicModule, Type, OnApplicationBootstrap, OnModuleInit, ConflictException, OnModuleDestroy } from '@nestjs/common';
2
- import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter, e as AgentSkillsOptions } from './chat-queue.service-CwxJDrBM.js';
3
- export { f as AGENT_TOOL_BRAND, g as AgentAttachmentsOptions, h as AgentDeps, i as AgentEngine, j as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, L as LedgerQuotaProvider, Q as QueuePlan, k as QueueSettleOutcome, l as QueuedTurn, m as QueuedTurnStarter, n as QuotaLimits, o as QuotaWindowLimits, p as defineTool, q as delegateToolName, r as provideAgentTool, s as provideAgentTools, u as utcDay } from './chat-queue.service-CwxJDrBM.js';
2
+ import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, C as ChatQueueService, d as AgentProtocolAdapter, e as AgentSkillsOptions } from './chat-queue.service-ZQkqb1Yw.js';
3
+ export { f as AGENT_TOOL_BRAND, g as AgentAttachmentsOptions, h as AgentDeps, i as AgentEngine, j as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, L as LedgerQuotaProvider, Q as QueuePlan, k as QueueSettleOutcome, l as QueuedTurn, m as QueuedTurnStarter, n as QuotaLimits, o as QuotaWindowLimits, p as defineTool, q as delegateToolName, r as provideAgentTool, s as provideAgentTools, u as utcDay } from './chat-queue.service-ZQkqb1Yw.js';
4
4
  import * as _dudousxd_nestjs_agent_core from '@dudousxd/nestjs-agent-core';
5
- import { AgentStore, Actor, ListActionProposals, ActionProposalMutationView, AgentRunner, AttachmentStagingStore, ModelCatalog, QuotaProvider, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver, AgentApprovalPort, MessageAttachment, AgentAttachmentConfig, AgentClientConfig, ToolSpec, ToolPresentation, AgentHistoryWindow, AgentIntake, Persona, SkillContext, ToolRegistry, AgentRegistry, PromptContributor, SkillsConfig, SkillCatalogEntry, ToolCatalogEntry, MemoryConfig, MemoryDigestEntry, SkillProvider, AgentRunInput, AgentRunStartOptions, TokenStreamSink, SinkWriter } from '@dudousxd/nestjs-agent-core';
5
+ import { AgentAttachmentConfig, AttachmentStagingStore, AgentStore, Actor, ListActionProposals, ActionProposalMutationView, TextActionProposalVocabulary, AgentRunner, ModelCatalog, QuotaProvider, ActionApprovalMode, ActionProposalView, MessageAttachment, QuotaReport, ModelCatalogView, AgentHostContext, AttachmentRef, PageContext, ChatQueueState, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, MessageFeedbackValue, MessageFeedback, StagedAttachment, ActorResolver, AgentApprovalPort, AgentClientConfig, ToolSpec, ToolPresentation, AgentHistoryWindow, AgentIntake, Persona, SkillContext, ToolRegistry, AgentRegistry, PromptContributor, SkillsConfig, SkillCatalogEntry, ToolCatalogEntry, MemoryConfig, MemoryDigestEntry, SkillProvider, AgentRunInput, AgentRunStartOptions, TokenStreamSink, SinkWriter, ActionProposal } from '@dudousxd/nestjs-agent-core';
6
6
  export * from '@dudousxd/nestjs-agent-core';
7
7
  export { FunctionalToolDefinition } from '@dudousxd/nestjs-agent-core';
8
8
  import { Request, Response } from 'express';
@@ -16,6 +16,20 @@ declare class AgentModule {
16
16
  static forRootAsync(options: AgentModuleAsyncOptions): DynamicModule;
17
17
  }
18
18
 
19
+ /** Default per-file size cap (20 MiB). */
20
+ declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
21
+ /** Default allowlist: what multimodal model providers commonly accept as native image/file parts. */
22
+ declare const DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES: readonly string[];
23
+ /** How many attachments one message may name. */
24
+ declare const MAX_ATTACHMENTS_PER_MESSAGE = 10;
25
+ /** The attachment rules in force — `AgentAttachmentConfig`, as `GET <base>/config` serves it. */
26
+ type AttachmentLimits = AgentAttachmentConfig;
27
+ /**
28
+ * One source for attachment limits: the staging store's own `describe()` when it declares them
29
+ * (`AgentMediaAttachmentsModule` does), else `AgentModule`'s `attachments` option, else the defaults.
30
+ */
31
+ declare function attachmentLimits(options: Pick<AgentModuleOptions, 'attachments'> | undefined, staging: AttachmentStagingStore | undefined): AttachmentLimits;
32
+
19
33
  declare class ActionProposalService {
20
34
  private readonly store;
21
35
  private readonly options;
@@ -55,8 +69,10 @@ declare class ActionProposalService {
55
69
  text: string;
56
70
  }>;
57
71
  private textReceipt;
72
+ /** The configured words a text decision is made of (`actionProposalText.vocabulary`). */
73
+ textVocabulary(): TextActionProposalVocabulary;
58
74
  /** The configured reply to a decision the store answered with `result`. */
59
- reply(result: ActionProposalMutationView, decision: 'approved' | 'rejected'): string;
75
+ reply(result: Pick<ActionProposalMutationView, 'status' | 'proposal'>, decision: 'approved' | 'rejected'): string;
60
76
  }
61
77
 
62
78
  /**
@@ -165,7 +181,7 @@ declare class AgentService {
165
181
  private readonly options?;
166
182
  private readonly queue?;
167
183
  private readonly proposals?;
168
- constructor(runner: AgentRunner, store: AgentStore, deps: AgentDepsFactory, staging?: AttachmentStagingStore | undefined, models?: ModelCatalog | undefined, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota" | "actionApprovalMode"> | undefined, queue?: ChatQueueService | undefined, proposals?: ActionProposalService | undefined);
184
+ constructor(runner: AgentRunner, store: AgentStore, deps: AgentDepsFactory, staging?: AttachmentStagingStore | undefined, models?: ModelCatalog | undefined, quotaProvider?: QuotaProvider | undefined, options?: Pick<AgentModuleOptions, "quota" | "actionApprovalMode" | "attachments"> | undefined, queue?: ChatQueueService | undefined, proposals?: ActionProposalService | undefined);
169
185
  /**
170
186
  * Decide one independent proposal through the scoped proposal service — what the native
171
187
  * `action-proposals/:id/approve|reject` routes call — and the reply to show for it. `404` when
@@ -194,6 +210,37 @@ declare class AgentService {
194
210
  };
195
211
  text: string;
196
212
  }>;
213
+ /** How `action` tools wait for their approval here — `AgentModule.forRoot({ actionApprovalMode })`. */
214
+ actionApprovalMode(): ActionApprovalMode;
215
+ /**
216
+ * The words a text decision is made of (`actionProposalText.vocabulary`, English by default) — what
217
+ * a surface without buttons tells the person to reply.
218
+ */
219
+ actionProposalVocabulary(): TextActionProposalVocabulary;
220
+ /** The configured reply (`actionProposalText.replies`) to a decision the store answered with `result`. */
221
+ actionProposalReply(result: Pick<ActionProposalMutationView, 'status' | 'proposal'>, decision: 'approved' | 'rejected'): string;
222
+ /**
223
+ * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
224
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page). `404`
225
+ * when independent approvals are not on.
226
+ */
227
+ listActionProposals(actor: Actor, threadId: string): Promise<ActionProposalView[]>;
228
+ /**
229
+ * The attachment rules in force — what `GET <base>/config` serves and `POST <base>/attachments`
230
+ * enforces. `enabled: false` when no `AGENT_ATTACHMENT_STAGING` provider is bound.
231
+ */
232
+ attachmentLimits(): AttachmentLimits;
233
+ /**
234
+ * Stage a file for a later turn, held to the same limits as `POST <base>/attachments` — for a
235
+ * surface that receives files itself (a messaging webhook). Refuses with `501` when attachments are
236
+ * off, `415` for a type outside the allowlist and `413` past the size cap; send the returned
237
+ * `mediaId` as `attachments: [{ mediaId }]`.
238
+ */
239
+ stageAttachment(actor: Actor, file: {
240
+ data: Buffer;
241
+ contentType: string;
242
+ filename: string;
243
+ }): Promise<MessageAttachment>;
197
244
  /** The queue service when the bound store can hold a queue, else `undefined`. */
198
245
  private queueing;
199
246
  /** The queue service, or a 501 naming what is missing. */
@@ -539,7 +586,10 @@ declare class AgUiRunHandler {
539
586
  protected readonly quietMs: number | undefined;
540
587
  constructor(agent: AgentService, actorResolver: ActorResolver, options: AgentModuleOptions, staging: AttachmentStagingStore | undefined, quietMs: number | undefined);
541
588
  handle(req: Request, res: Response, body: unknown): Promise<void>;
542
- /** Stage the turn's inline media through the bound store, within its limits; say what was not used. */
589
+ /**
590
+ * The turn's attachments: the ones already staged (by mediaId), then its inline media staged
591
+ * through the bound store — all within the store's limits; say what was not used.
592
+ */
543
593
  private stage;
544
594
  /**
545
595
  * Deliver what a resume list decided, through the same calls the native decision routes make.
@@ -674,20 +724,6 @@ declare class AttachmentsController {
674
724
  upload(req: Request, file: Express.Multer.File | undefined): Promise<MessageAttachment>;
675
725
  }
676
726
 
677
- /** Default per-file size cap (20 MiB). */
678
- declare const DEFAULT_MAX_ATTACHMENT_BYTES: number;
679
- /** Default allowlist: what multimodal model providers commonly accept as native image/file parts. */
680
- declare const DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES: readonly string[];
681
- /** How many attachments one message may name. */
682
- declare const MAX_ATTACHMENTS_PER_MESSAGE = 10;
683
- /** The attachment rules in force — `AgentAttachmentConfig`, as `GET <base>/config` serves it. */
684
- type AttachmentLimits = AgentAttachmentConfig;
685
- /**
686
- * One source for attachment limits: the staging store's own `describe()` when it declares them
687
- * (`AgentMediaAttachmentsModule` does), else `AgentModule`'s `attachments` option, else the defaults.
688
- */
689
- declare function attachmentLimits(options: Pick<AgentModuleOptions, 'attachments'> | undefined, staging: AttachmentStagingStore | undefined): AttachmentLimits;
690
-
691
727
  declare class ConfigController {
692
728
  private readonly options;
693
729
  private readonly actorResolver;
@@ -1462,15 +1498,26 @@ declare class ActionProposalController {
1462
1498
  reject(threadId: string, proposalId: string, request: Request, body: unknown): Promise<_dudousxd_nestjs_agent_core.ActionProposalMutationView>;
1463
1499
  }
1464
1500
 
1501
+ /** Called with a proposal the worker just settled (executed, or failed executing). */
1502
+ type ActionProposalSettledListener = (proposal: ActionProposal) => void | Promise<void>;
1465
1503
  declare class ActionProposalWorkerService implements OnModuleInit, OnModuleDestroy {
1466
1504
  private readonly store;
1467
1505
  private readonly options;
1468
1506
  private readonly deps;
1469
1507
  private worker;
1470
1508
  private readonly logger;
1509
+ private readonly settledListeners;
1510
+ /**
1511
+ * Run `listener` after every proposal this process's worker settles — how a module relays an
1512
+ * outcome somewhere the turn's stream no longer reaches (a text channel's conversation). Next to
1513
+ * `actionProposalWorker.onSettled`; a listener that throws is logged and does not stop the others.
1514
+ * Returns the unsubscribe. Only the replicas that run the worker (not `surface: 'http'`) call it.
1515
+ */
1516
+ onSettled(listener: ActionProposalSettledListener): () => void;
1517
+ private settled;
1471
1518
  constructor(store: AgentStore, options: AgentModuleOptions, deps: AgentDepsFactory);
1472
1519
  onModuleInit(): void;
1473
1520
  onModuleDestroy(): Promise<void>;
1474
1521
  }
1475
1522
 
1476
- export { AGENT_CHAT_QUEUE, AGENT_METADATA, AI_TOOL_METADATA, ATTACHMENT_PAGE_SIZE, ActionProposalController, ActionProposalService, ActionProposalWorkerService, type AgUiAdapterOptions, AgUiRunHandler, Agent, AgentApprovalPortAdapter, AgentDepsFactory, AgentDiscoveryService, AgentModule, AgentModuleAsyncOptions, AgentModuleOptions, type AgentOptions, AgentProtocolAdapter, AgentService, AgentSkillsOptions, AiTool, AiToolDiscoveryService, type AiToolOptions, type AnonymousActorOptions, AnonymousActorResolver, type AttachmentLimits, AttachmentsController, type ChatParams, ChatQueueService, type ChatSendMode, type ChatSendResult, ConfigController, DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES, DEFAULT_MAX_ATTACHMENT_BYTES, type DeclaredSkill, HeaderActorResolver, InProcessTokenStreamSink, InlineAgentRunner, MAX_ATTACHMENTS_PER_MESSAGE, MemoriesController, MessagesController, ModelsController, QueueController, type QueuedSend, type RequestUserMapper, type ResolvedAiToolOptions, RunNotActiveException, SKILL_METADATA, SYSTEM_PROMPT_CONTRIBUTOR_METADATA, SYSTEM_PROMPT_METADATA, Skill, type SkillBody, SkillDiscoveryService, type SkillOptions, SkillsController, SystemPrompt, SystemPromptContributor, type ThreadDefaultAgentReader, type ThreadModelReader, type ThreadPersonaReader, ToolsController, agUiAdapter, attachmentLimits, declaredSkillProvider, defaultRequestUserMapper, readAgentMetadata, readAiToolMetadata, readSkillMetadata, requestUserActorResolver, resolveSkillsConfig, skillScope, threadPersona, toolNameFromClass };
1523
+ export { AGENT_CHAT_QUEUE, AGENT_METADATA, AI_TOOL_METADATA, ATTACHMENT_PAGE_SIZE, ActionProposalController, ActionProposalService, type ActionProposalSettledListener, ActionProposalWorkerService, type AgUiAdapterOptions, AgUiRunHandler, Agent, AgentApprovalPortAdapter, AgentDepsFactory, AgentDiscoveryService, AgentModule, AgentModuleAsyncOptions, AgentModuleOptions, type AgentOptions, AgentProtocolAdapter, AgentService, AgentSkillsOptions, AiTool, AiToolDiscoveryService, type AiToolOptions, type AnonymousActorOptions, AnonymousActorResolver, type AttachmentLimits, AttachmentsController, type ChatParams, ChatQueueService, type ChatSendMode, type ChatSendResult, ConfigController, DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES, DEFAULT_MAX_ATTACHMENT_BYTES, type DeclaredSkill, HeaderActorResolver, InProcessTokenStreamSink, InlineAgentRunner, MAX_ATTACHMENTS_PER_MESSAGE, MemoriesController, MessagesController, ModelsController, QueueController, type QueuedSend, type RequestUserMapper, type ResolvedAiToolOptions, RunNotActiveException, SKILL_METADATA, SYSTEM_PROMPT_CONTRIBUTOR_METADATA, SYSTEM_PROMPT_METADATA, Skill, type SkillBody, SkillDiscoveryService, type SkillOptions, SkillsController, SystemPrompt, SystemPromptContributor, type ThreadDefaultAgentReader, type ThreadModelReader, type ThreadPersonaReader, ToolsController, agUiAdapter, attachmentLimits, declaredSkillProvider, defaultRequestUserMapper, readAgentMetadata, readAiToolMetadata, readSkillMetadata, requestUserActorResolver, resolveSkillsConfig, skillScope, threadPersona, toolNameFromClass };
package/dist/index.js CHANGED
@@ -565,8 +565,8 @@ AgentDepsFactory2 = _ts_decorate2([
565
565
  ], AgentDepsFactory2);
566
566
 
567
567
  // src/agent.service.ts
568
- import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY2, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS4, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE4, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval as mayDecideApproval2, readElicitationQuestions, settleDeadRun, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
569
- import { BadRequestException as BadRequestException2, ConflictException as ConflictException2, ForbiddenException as ForbiddenException2, GoneException, HttpException, HttpStatus, Inject as Inject5, Injectable as Injectable4, NotFoundException as NotFoundException2, NotImplementedException as NotImplementedException3, Optional as Optional3 } from "@nestjs/common";
568
+ import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY2, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS4, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE4, DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY as DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY2, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval as mayDecideApproval2, readElicitationQuestions, settleDeadRun, textActionProposalReply as textActionProposalReply2, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
569
+ import { BadRequestException as BadRequestException2, ConflictException as ConflictException2, ForbiddenException as ForbiddenException2, GoneException, HttpException, HttpStatus, Inject as Inject5, Injectable as Injectable4, NotFoundException as NotFoundException2, NotImplementedException as NotImplementedException3, Optional as Optional3, PayloadTooLargeException, UnsupportedMediaTypeException } from "@nestjs/common";
570
570
 
571
571
  // src/agent-deps.ts
572
572
  function utcDay(date = /* @__PURE__ */ new Date()) {
@@ -590,6 +590,31 @@ function childSinkWriter(writer) {
590
590
  }
591
591
  __name(childSinkWriter, "childSinkWriter");
592
592
 
593
+ // src/attachment-limits.ts
594
+ var DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
595
+ var DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES = [
596
+ "image/png",
597
+ "image/jpeg",
598
+ "image/gif",
599
+ "image/webp",
600
+ "application/pdf",
601
+ "text/plain",
602
+ "text/csv"
603
+ ];
604
+ var MAX_ATTACHMENTS_PER_MESSAGE = 10;
605
+ function attachmentLimits(options, staging) {
606
+ const declared = staging?.describe?.();
607
+ const own = options?.attachments;
608
+ return {
609
+ enabled: staging !== void 0,
610
+ upload: staging === void 0 ? null : declared?.upload ?? "multipart",
611
+ maxBytes: declared?.maxBytes ?? own?.maxBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES,
612
+ allowedContentTypes: declared?.allowedContentTypes ?? own?.allowedContentTypes ?? DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES,
613
+ maxPerMessage: MAX_ATTACHMENTS_PER_MESSAGE
614
+ };
615
+ }
616
+ __name(attachmentLimits, "attachmentLimits");
617
+
593
618
  // src/proposals/action-proposal.service.ts
594
619
  import { AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_STORE as AGENT_STORE2, DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES, DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY, mayDecideApproval, parseTextActionProposalCommand, resolveTextActionProposalDecision, textActionProposalReply, toActionProposalMutationView, toActionProposalView } from "@dudousxd/nestjs-agent-core";
595
620
  import { ForbiddenException, Inject as Inject3, Injectable as Injectable2, NotFoundException, NotImplementedException } from "@nestjs/common";
@@ -746,6 +771,10 @@ var ActionProposalService = class {
746
771
  text: this.reply(result, decision)
747
772
  };
748
773
  }
774
+ /** The configured words a text decision is made of (`actionProposalText.vocabulary`). */
775
+ textVocabulary() {
776
+ return this.vocabulary;
777
+ }
749
778
  /** The configured reply to a decision the store answered with `result`. */
750
779
  reply(result, decision) {
751
780
  return textActionProposalReply(result, decision, this.replies);
@@ -1372,6 +1401,65 @@ var AgentService = class {
1372
1401
  };
1373
1402
  return this.proposals.handleTextDecision(threadId, actor, text);
1374
1403
  }
1404
+ /** How `action` tools wait for their approval here — `AgentModule.forRoot({ actionApprovalMode })`. */
1405
+ actionApprovalMode() {
1406
+ return this.options?.actionApprovalMode ?? "blocking";
1407
+ }
1408
+ /**
1409
+ * The words a text decision is made of (`actionProposalText.vocabulary`, English by default) — what
1410
+ * a surface without buttons tells the person to reply.
1411
+ */
1412
+ actionProposalVocabulary() {
1413
+ return this.proposals?.textVocabulary() ?? DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY2;
1414
+ }
1415
+ /** The configured reply (`actionProposalText.replies`) to a decision the store answered with `result`. */
1416
+ actionProposalReply(result, decision) {
1417
+ return this.proposals?.reply(result, decision) ?? textActionProposalReply2(result, decision);
1418
+ }
1419
+ /**
1420
+ * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
1421
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page). `404`
1422
+ * when independent approvals are not on.
1423
+ */
1424
+ async listActionProposals(actor, threadId) {
1425
+ if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
1426
+ throw new NotFoundException2("Proposal not found");
1427
+ }
1428
+ return this.proposals.list(threadId, actor);
1429
+ }
1430
+ /**
1431
+ * The attachment rules in force — what `GET <base>/config` serves and `POST <base>/attachments`
1432
+ * enforces. `enabled: false` when no `AGENT_ATTACHMENT_STAGING` provider is bound.
1433
+ */
1434
+ attachmentLimits() {
1435
+ return attachmentLimits(this.options, this.staging);
1436
+ }
1437
+ /**
1438
+ * Stage a file for a later turn, held to the same limits as `POST <base>/attachments` — for a
1439
+ * surface that receives files itself (a messaging webhook). Refuses with `501` when attachments are
1440
+ * off, `415` for a type outside the allowlist and `413` past the size cap; send the returned
1441
+ * `mediaId` as `attachments: [{ mediaId }]`.
1442
+ */
1443
+ async stageAttachment(actor, file) {
1444
+ if (this.staging === void 0) {
1445
+ throw new NotImplementedException3("Attachments are not configured on this server: bind AGENT_ATTACHMENT_STAGING (e.g. import AgentMediaAttachmentsModule).");
1446
+ }
1447
+ const { allowedContentTypes, maxBytes } = this.attachmentLimits();
1448
+ const contentType = file.contentType.split(";")[0]?.trim().toLowerCase() ?? "";
1449
+ if (!allowedContentTypes.includes(contentType)) {
1450
+ throw new UnsupportedMediaTypeException(`content type "${contentType}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
1451
+ }
1452
+ if (file.data.byteLength > maxBytes) {
1453
+ throw new PayloadTooLargeException(`file exceeds the ${maxBytes}-byte limit`);
1454
+ }
1455
+ return this.staging.stage({
1456
+ data: file.data,
1457
+ filename: file.filename,
1458
+ contentType,
1459
+ sizeBytes: file.data.byteLength,
1460
+ actor
1461
+ });
1462
+ }
1375
1463
  /** The queue service when the bound store can hold a queue, else `undefined`. */
1376
1464
  queueing() {
1377
1465
  return this.queue?.supported === true ? this.queue : void 0;
@@ -2653,35 +2741,8 @@ AgentsController = _ts_decorate7([
2653
2741
 
2654
2742
  // src/controller/attachments.controller.ts
2655
2743
  import { AGENT_ACTOR_RESOLVER, AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING3, AGENT_OPTIONS as AGENT_OPTIONS5 } from "@dudousxd/nestjs-agent-core";
2656
- import { BadRequestException as BadRequestException3, Controller as Controller2, Get as Get2, Inject as Inject7, NotImplementedException as NotImplementedException5, Optional as Optional5, PayloadTooLargeException, Post, Req, UnsupportedMediaTypeException, UploadedFile, UseInterceptors } from "@nestjs/common";
2744
+ import { BadRequestException as BadRequestException3, Controller as Controller2, Get as Get2, Inject as Inject7, NotImplementedException as NotImplementedException5, Optional as Optional5, PayloadTooLargeException as PayloadTooLargeException2, Post, Req, UnsupportedMediaTypeException as UnsupportedMediaTypeException2, UploadedFile, UseInterceptors } from "@nestjs/common";
2657
2745
  import { FileInterceptor } from "@nestjs/platform-express";
2658
-
2659
- // src/attachment-limits.ts
2660
- var DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
2661
- var DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES = [
2662
- "image/png",
2663
- "image/jpeg",
2664
- "image/gif",
2665
- "image/webp",
2666
- "application/pdf",
2667
- "text/plain",
2668
- "text/csv"
2669
- ];
2670
- var MAX_ATTACHMENTS_PER_MESSAGE = 10;
2671
- function attachmentLimits(options, staging) {
2672
- const declared = staging?.describe?.();
2673
- const own = options?.attachments;
2674
- return {
2675
- enabled: staging !== void 0,
2676
- upload: staging === void 0 ? null : declared?.upload ?? "multipart",
2677
- maxBytes: declared?.maxBytes ?? own?.maxBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES,
2678
- allowedContentTypes: declared?.allowedContentTypes ?? own?.allowedContentTypes ?? DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES,
2679
- maxPerMessage: MAX_ATTACHMENTS_PER_MESSAGE
2680
- };
2681
- }
2682
- __name(attachmentLimits, "attachmentLimits");
2683
-
2684
- // src/controller/attachments.controller.ts
2685
2746
  function _ts_decorate8(decorators, target, key, desc) {
2686
2747
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2687
2748
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -2748,10 +2809,10 @@ var AttachmentsController = class {
2748
2809
  }
2749
2810
  const { allowedContentTypes, maxBytes } = attachmentLimits(this.options, this.staging);
2750
2811
  if (!allowedContentTypes.includes(file.mimetype)) {
2751
- throw new UnsupportedMediaTypeException(`content type "${file.mimetype}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
2812
+ throw new UnsupportedMediaTypeException2(`content type "${file.mimetype}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
2752
2813
  }
2753
2814
  if (file.size > maxBytes) {
2754
- throw new PayloadTooLargeException(`file exceeds the ${maxBytes}-byte limit`);
2815
+ throw new PayloadTooLargeException2(`file exceeds the ${maxBytes}-byte limit`);
2755
2816
  }
2756
2817
  const actor = await this.actorResolver.resolve(req);
2757
2818
  return this.staging.stage({
@@ -5084,6 +5145,34 @@ var ActionProposalWorkerService = class _ActionProposalWorkerService {
5084
5145
  deps;
5085
5146
  worker;
5086
5147
  logger = new Logger5(_ActionProposalWorkerService.name);
5148
+ settledListeners = /* @__PURE__ */ new Set();
5149
+ /**
5150
+ * Run `listener` after every proposal this process's worker settles — how a module relays an
5151
+ * outcome somewhere the turn's stream no longer reaches (a text channel's conversation). Next to
5152
+ * `actionProposalWorker.onSettled`; a listener that throws is logged and does not stop the others.
5153
+ * Returns the unsubscribe. Only the replicas that run the worker (not `surface: 'http'`) call it.
5154
+ */
5155
+ onSettled(listener) {
5156
+ this.settledListeners.add(listener);
5157
+ return () => {
5158
+ this.settledListeners.delete(listener);
5159
+ };
5160
+ }
5161
+ async settled(proposal) {
5162
+ const configured = this.options.actionProposalWorker?.onSettled;
5163
+ for (const listener of [
5164
+ ...configured ? [
5165
+ configured
5166
+ ] : [],
5167
+ ...this.settledListeners
5168
+ ]) {
5169
+ try {
5170
+ await listener(proposal);
5171
+ } catch (error) {
5172
+ this.logger.error(error);
5173
+ }
5174
+ }
5175
+ }
5087
5176
  constructor(store, options, deps) {
5088
5177
  this.store = store;
5089
5178
  this.options = options;
@@ -5099,6 +5188,7 @@ var ActionProposalWorkerService = class _ActionProposalWorkerService {
5099
5188
  store: this.store,
5100
5189
  workerId: randomUUID(),
5101
5190
  ...this.options.actionProposalWorker,
5191
+ onSettled: /* @__PURE__ */ __name((proposal) => this.settled(proposal), "onSettled"),
5102
5192
  executor: new ActionProposalExecutor({
5103
5193
  resolver,
5104
5194
  resolveExecution: /* @__PURE__ */ __name(async (proposal, actor) => {
@@ -6369,7 +6459,10 @@ var AgUiRunHandler = class {
6369
6459
  if (owner !== null && owner !== actor.id) {
6370
6460
  throw new ForbiddenException4("thread belongs to another actor");
6371
6461
  }
6372
- if (owner !== null && turn.media.length === 0) {
6462
+ if (forwarded.regenerate === true && owner === null) {
6463
+ throw invalid("invalid_input", "regenerate needs a thread that already has an exchange");
6464
+ }
6465
+ if (owner !== null && forwarded.regenerate !== true && turn.media.length === 0 && turn.staged.length === 0) {
6373
6466
  const decision = await this.agent.handleTextDecision(input.threadId, actor, turn.text);
6374
6467
  if ("proposalDecision" in decision) {
6375
6468
  this.writeEvents(res, [
@@ -6384,7 +6477,7 @@ var AgUiRunHandler = class {
6384
6477
  return;
6385
6478
  }
6386
6479
  }
6387
- const refs = await this.stage(actor, turn.media, warnings);
6480
+ const refs = await this.stage(actor, turn.media, warnings, turn.staged);
6388
6481
  if (turn.text.trim().length === 0 && refs.length === 0) {
6389
6482
  throw invalid("no_user_message", "the user message to answer is empty");
6390
6483
  }
@@ -6420,6 +6513,9 @@ var AgUiRunHandler = class {
6420
6513
  } : {},
6421
6514
  ...refs.length > 0 ? {
6422
6515
  attachments: refs
6516
+ } : {},
6517
+ ...forwarded.regenerate === true ? {
6518
+ regenerate: true
6423
6519
  } : {}
6424
6520
  });
6425
6521
  await this.pipe(res, started.runId, {
@@ -6431,10 +6527,25 @@ var AgUiRunHandler = class {
6431
6527
  preamble: warningEvents(warnings)
6432
6528
  });
6433
6529
  }
6434
- /** Stage the turn's inline media through the bound store, within its limits; say what was not used. */
6435
- async stage(actor, media, warnings) {
6530
+ /**
6531
+ * The turn's attachments: the ones already staged (by mediaId), then its inline media staged
6532
+ * through the bound store — all within the store's limits; say what was not used.
6533
+ */
6534
+ async stage(actor, media, warnings, staged = []) {
6436
6535
  const refs = [];
6437
6536
  const limits = attachmentLimits(this.options, this.staging);
6537
+ for (const ref of staged) {
6538
+ const label = `The attachment ${ref.mediaId} was not used`;
6539
+ if (this.staging === void 0) {
6540
+ warnings.push(`${label}: attachments are not enabled on this agent.`);
6541
+ } else if (refs.length >= limits.maxPerMessage) {
6542
+ warnings.push(`${label}: a message carries at most ${limits.maxPerMessage} attachments.`);
6543
+ } else {
6544
+ refs.push({
6545
+ mediaId: ref.mediaId
6546
+ });
6547
+ }
6548
+ }
6438
6549
  for (const item of media) {
6439
6550
  const label = `The ${item.kind} "${item.filename}" was not used`;
6440
6551
  if (this.staging === void 0) {
@@ -6447,7 +6558,7 @@ var AgUiRunHandler = class {
6447
6558
  warnings.push(`${label}: it is larger than ${limits.maxBytes} bytes.`);
6448
6559
  } else {
6449
6560
  try {
6450
- const staged = await this.staging.stage({
6561
+ const staged2 = await this.staging.stage({
6451
6562
  data: item.data,
6452
6563
  filename: item.filename,
6453
6564
  contentType: item.contentType,
@@ -6455,7 +6566,7 @@ var AgUiRunHandler = class {
6455
6566
  actor
6456
6567
  });
6457
6568
  refs.push({
6458
- mediaId: staged.mediaId
6569
+ mediaId: staged2.mediaId
6459
6570
  });
6460
6571
  } catch {
6461
6572
  warnings.push(`${label}: it could not be stored.`);
@@ -6573,11 +6684,17 @@ var AgUiRunHandler = class {
6573
6684
  res.setHeader("X-Agent-Run-Id", runId);
6574
6685
  res.flushHeaders();
6575
6686
  let terminal = false;
6687
+ const cursor = {
6688
+ seq: 0
6689
+ };
6576
6690
  try {
6577
6691
  const frames = agUiFramesFromNdjson(this.agent.subscribe(runId));
6578
- for await (const event of agUiEvents(frames, options)) {
6692
+ for await (const event of agUiEvents(frames, {
6693
+ ...options,
6694
+ cursor
6695
+ })) {
6579
6696
  if (event.type === "RUN_FINISHED" || event.type === "RUN_ERROR") terminal = true;
6580
- res.write(agUiSse(event));
6697
+ res.write(agUiSse(event, cursor.seq));
6581
6698
  }
6582
6699
  } catch {
6583
6700
  if (!terminal) {