@dudousxd/nestjs-agent 1.2.0 → 1.4.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,6 +1,6 @@
1
1
  import { DynamicModule, OnModuleInit, Type, OnApplicationBootstrap } from '@nestjs/common';
2
- import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, d as AgentSkillsOptions } from './agent-deps.factory-OjrLyAZo.cjs';
3
- export { e as AGENT_TOOL_BRAND, f as AgentAttachmentsOptions, g as AgentDeps, h as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, i as delegateToolName, p as provideAgentTool, u as utcDay } from './agent-deps.factory-OjrLyAZo.cjs';
2
+ import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, d as AgentSkillsOptions } from './agent-deps.factory-CUyVPsa1.cjs';
3
+ export { e as AGENT_TOOL_BRAND, f as AgentAttachmentsOptions, g as AgentDeps, h as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, i as delegateToolName, p as provideAgentTool, u as utcDay } from './agent-deps.factory-CUyVPsa1.cjs';
4
4
  import { AgentRunner, AgentStore, QuotaStore, AttachmentStagingStore, Actor, AttachmentRef, PageContext, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, StagedAttachment, QuotaView, AgentApprovalPort, ActorResolver, MessageAttachment, ToolPresentation, AgentHistoryWindow, AgentIntake, SkillContext, ToolRegistry, AgentRegistry, PromptContributor, SkillsConfig, SkillCatalogEntry, ToolCatalogEntry, MemoryConfig, MemoryDigestEntry, SkillProvider, AgentRunInput, TokenStreamSink, SinkWriter, QuotaState } from '@dudousxd/nestjs-agent-core';
5
5
  export * from '@dudousxd/nestjs-agent-core';
6
6
  import { Request } from 'express';
@@ -80,8 +80,19 @@ declare class AgentService {
80
80
  * ownership is derived from.
81
81
  */
82
82
  subscribeAs(actor: Actor, runId: string): Promise<AsyncIterable<Uint8Array>>;
83
- approve(actor: Actor, toolCallId: string): Promise<void>;
84
- reject(actor: Actor, toolCallId: string, reason?: string): Promise<void>;
83
+ /**
84
+ * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
85
+ * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
86
+ * thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
87
+ * persisted with the call.
88
+ */
89
+ approve(actor: Actor, toolCallId: string, opts?: {
90
+ remember?: boolean;
91
+ via?: string;
92
+ }): Promise<void>;
93
+ reject(actor: Actor, toolCallId: string, reason?: string, opts?: {
94
+ via?: string;
95
+ }): Promise<void>;
85
96
  /**
86
97
  * Answer a parked question set. An omitted question takes the request's own pre-picked default —
87
98
  * resolved by the LOOP against the request it already holds, so "the user just submitted" and
@@ -193,6 +204,29 @@ declare class AgentService {
193
204
  * adapter can resolve+signal a decision WITHOUT this check — it is authorized upstream instead.
194
205
  */
195
206
  private assertOwnsToolCall;
207
+ /**
208
+ * Check a reply against the questions it answers before it is signalled: every submitted value a
209
+ * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
210
+ * options), and every `required` question answered — by the reply, or by the defaults an omitted
211
+ * question falls back to. A reply the loop would have to drop parts of is refused here instead, so
212
+ * the person who typed it hears why. Needs the store to hand back the call's recorded questions;
213
+ * without that seam the loop's own filtering is the only check.
214
+ */
215
+ private assertAnswersFit;
216
+ /**
217
+ * Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
218
+ * the requester (the default, and every call recorded before approvers existed) is the thread's
219
+ * own actor — the ownership check this always was; any other approver goes through the policy's
220
+ * `canDecide`, which by default asks whether the actor holds that role.
221
+ */
222
+ private assertMayDecide;
223
+ /**
224
+ * Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
225
+ * `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
226
+ * a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
227
+ * later replay of the run reads back.
228
+ */
229
+ private assertNotExpired;
196
230
  /**
197
231
  * Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
198
232
  * never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
@@ -223,10 +257,13 @@ declare class AgentApprovalPortAdapter implements AgentApprovalPort {
223
257
  constructor(agent: AgentService);
224
258
  approve(toolCallId: string, opts?: {
225
259
  executedByRef?: string;
260
+ remember?: boolean;
261
+ decidedVia?: string;
226
262
  }): Promise<void>;
227
263
  reject(toolCallId: string, opts?: {
228
264
  executedByRef?: string;
229
265
  reason?: string;
266
+ decidedVia?: string;
230
267
  }): Promise<void>;
231
268
  }
232
269
 
@@ -754,6 +791,12 @@ declare class InlineAgentRunner implements AgentRunner {
754
791
  * shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
755
792
  * cancel settles the wait too, by rejecting it.
756
793
  */
794
+ /**
795
+ * {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
796
+ * settles the wait as `expired` — the same Decision the durable runner builds from its signal
797
+ * timeout — and a decision that arrives first disarms it.
798
+ */
799
+ private parkApproval;
757
800
  private park;
758
801
  private topLevelHooks;
759
802
  /**
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DynamicModule, OnModuleInit, Type, OnApplicationBootstrap } from '@nestjs/common';
2
- import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, d as AgentSkillsOptions } from './agent-deps.factory-OjrLyAZo.js';
3
- export { e as AGENT_TOOL_BRAND, f as AgentAttachmentsOptions, g as AgentDeps, h as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, i as delegateToolName, p as provideAgentTool, u as utcDay } from './agent-deps.factory-OjrLyAZo.js';
2
+ import { b as AgentModuleOptions, c as AgentModuleAsyncOptions, A as AgentDepsFactory, d as AgentSkillsOptions } from './agent-deps.factory-CUyVPsa1.js';
3
+ export { e as AGENT_TOOL_BRAND, f as AgentAttachmentsOptions, g as AgentDeps, h as AgentMemoryOptions, a as AgentSurface, F as FunctionalTool, i as delegateToolName, p as provideAgentTool, u as utcDay } from './agent-deps.factory-CUyVPsa1.js';
4
4
  import { AgentRunner, AgentStore, QuotaStore, AttachmentStagingStore, Actor, AttachmentRef, PageContext, HumanReply, ThreadSummary, ThreadDetail, UpdateThreadInput, StagedAttachment, QuotaView, AgentApprovalPort, ActorResolver, MessageAttachment, ToolPresentation, AgentHistoryWindow, AgentIntake, SkillContext, ToolRegistry, AgentRegistry, PromptContributor, SkillsConfig, SkillCatalogEntry, ToolCatalogEntry, MemoryConfig, MemoryDigestEntry, SkillProvider, AgentRunInput, TokenStreamSink, SinkWriter, QuotaState } from '@dudousxd/nestjs-agent-core';
5
5
  export * from '@dudousxd/nestjs-agent-core';
6
6
  import { Request } from 'express';
@@ -80,8 +80,19 @@ declare class AgentService {
80
80
  * ownership is derived from.
81
81
  */
82
82
  subscribeAs(actor: Actor, runId: string): Promise<AsyncIterable<Uint8Array>>;
83
- approve(actor: Actor, toolCallId: string): Promise<void>;
84
- reject(actor: Actor, toolCallId: string, reason?: string): Promise<void>;
83
+ /**
84
+ * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
85
+ * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
86
+ * thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
87
+ * persisted with the call.
88
+ */
89
+ approve(actor: Actor, toolCallId: string, opts?: {
90
+ remember?: boolean;
91
+ via?: string;
92
+ }): Promise<void>;
93
+ reject(actor: Actor, toolCallId: string, reason?: string, opts?: {
94
+ via?: string;
95
+ }): Promise<void>;
85
96
  /**
86
97
  * Answer a parked question set. An omitted question takes the request's own pre-picked default —
87
98
  * resolved by the LOOP against the request it already holds, so "the user just submitted" and
@@ -193,6 +204,29 @@ declare class AgentService {
193
204
  * adapter can resolve+signal a decision WITHOUT this check — it is authorized upstream instead.
194
205
  */
195
206
  private assertOwnsToolCall;
207
+ /**
208
+ * Check a reply against the questions it answers before it is signalled: every submitted value a
209
+ * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
210
+ * options), and every `required` question answered — by the reply, or by the defaults an omitted
211
+ * question falls back to. A reply the loop would have to drop parts of is refused here instead, so
212
+ * the person who typed it hears why. Needs the store to hand back the call's recorded questions;
213
+ * without that seam the loop's own filtering is the only check.
214
+ */
215
+ private assertAnswersFit;
216
+ /**
217
+ * Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
218
+ * the requester (the default, and every call recorded before approvers existed) is the thread's
219
+ * own actor — the ownership check this always was; any other approver goes through the policy's
220
+ * `canDecide`, which by default asks whether the actor holds that role.
221
+ */
222
+ private assertMayDecide;
223
+ /**
224
+ * Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
225
+ * `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
226
+ * a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
227
+ * later replay of the run reads back.
228
+ */
229
+ private assertNotExpired;
196
230
  /**
197
231
  * Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
198
232
  * never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
@@ -223,10 +257,13 @@ declare class AgentApprovalPortAdapter implements AgentApprovalPort {
223
257
  constructor(agent: AgentService);
224
258
  approve(toolCallId: string, opts?: {
225
259
  executedByRef?: string;
260
+ remember?: boolean;
261
+ decidedVia?: string;
226
262
  }): Promise<void>;
227
263
  reject(toolCallId: string, opts?: {
228
264
  executedByRef?: string;
229
265
  reason?: string;
266
+ decidedVia?: string;
230
267
  }): Promise<void>;
231
268
  }
232
269
 
@@ -754,6 +791,12 @@ declare class InlineAgentRunner implements AgentRunner {
754
791
  * shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
755
792
  * cancel settles the wait too, by rejecting it.
756
793
  */
794
+ /**
795
+ * {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
796
+ * settles the wait as `expired` — the same Decision the durable runner builds from its signal
797
+ * timeout — and a decision that arrives first disarms it.
798
+ */
799
+ private parkApproval;
757
800
  private park;
758
801
  private topLevelHooks;
759
802
  /**
package/dist/index.js CHANGED
@@ -122,6 +122,9 @@ var AgentDepsFactory2 = class {
122
122
  store: this.store,
123
123
  sink: this.sink,
124
124
  rolesPolicy: this.rolesPolicy,
125
+ ...this.options.approvalPolicy !== void 0 ? {
126
+ approvalPolicy: this.options.approvalPolicy
127
+ } : {},
125
128
  registry: this.registry,
126
129
  promptContributors: this.promptContributors,
127
130
  systemPrompt: definition?.systemPrompt ?? "You are a helpful assistant.",
@@ -269,8 +272,8 @@ AgentDepsFactory2 = _ts_decorate([
269
272
  ], AgentDepsFactory2);
270
273
 
271
274
  // src/agent.service.ts
272
- import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY, AGENT_QUOTA_STORE as AGENT_QUOTA_STORE2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE2 } from "@dudousxd/nestjs-agent-core";
273
- import { BadRequestException, ForbiddenException, Inject as Inject2, Injectable as Injectable2, NotFoundException, NotImplementedException, Optional as Optional2 } from "@nestjs/common";
275
+ import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY, AGENT_QUOTA_STORE as AGENT_QUOTA_STORE2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE2, REQUESTER_APPROVER, mayDecideApproval, readElicitationQuestions, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
276
+ import { BadRequestException, ForbiddenException, GoneException, Inject as Inject2, Injectable as Injectable2, NotFoundException, NotImplementedException, Optional as Optional2 } from "@nestjs/common";
274
277
 
275
278
  // src/agent-deps.ts
276
279
  function utcDay(date = /* @__PURE__ */ new Date()) {
@@ -423,18 +426,35 @@ var AgentService = class {
423
426
  await this.assertOwnsActiveStream(actor, runId);
424
427
  return this.subscribe(runId);
425
428
  }
426
- async approve(actor, toolCallId2) {
427
- await this.assertOwnsToolCall(actor, toolCallId2);
429
+ /**
430
+ * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
431
+ * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
432
+ * thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
433
+ * persisted with the call.
434
+ */
435
+ async approve(actor, toolCallId2, opts = {}) {
436
+ await this.assertMayDecide(actor, toolCallId2);
428
437
  return this.signalToolCall(toolCallId2, {
429
- approved: true
438
+ approved: true,
439
+ executedByRef: actor.id,
440
+ ...opts.remember === true ? {
441
+ remember: true
442
+ } : {},
443
+ ...opts.via !== void 0 ? {
444
+ decidedVia: opts.via
445
+ } : {}
430
446
  });
431
447
  }
432
- async reject(actor, toolCallId2, reason) {
433
- await this.assertOwnsToolCall(actor, toolCallId2);
448
+ async reject(actor, toolCallId2, reason, opts = {}) {
449
+ await this.assertMayDecide(actor, toolCallId2);
434
450
  return this.signalToolCall(toolCallId2, {
435
451
  approved: false,
452
+ executedByRef: actor.id,
436
453
  ...reason !== void 0 ? {
437
454
  reason
455
+ } : {},
456
+ ...opts.via !== void 0 ? {
457
+ decidedVia: opts.via
438
458
  } : {}
439
459
  });
440
460
  }
@@ -446,6 +466,7 @@ var AgentService = class {
446
466
  */
447
467
  async answer(actor, toolCallId2, answers2 = {}) {
448
468
  await this.assertOwnsToolCall(actor, toolCallId2);
469
+ await this.assertAnswersFit(toolCallId2, answers2);
449
470
  const reply = {
450
471
  answers: answers2,
451
472
  answeredByRef: actor.id
@@ -475,6 +496,7 @@ var AgentService = class {
475
496
  * not the thread's own actor).
476
497
  */
477
498
  async signalToolCall(toolCallId2, reply) {
499
+ await this.assertNotExpired(toolCallId2);
478
500
  const runId = await this.resolveRunForToolCall(toolCallId2);
479
501
  return this.runner.signal(runId, toolCallId2, reply);
480
502
  }
@@ -713,6 +735,68 @@ var AgentService = class {
713
735
  }
714
736
  }
715
737
  /**
738
+ * Check a reply against the questions it answers before it is signalled: every submitted value a
739
+ * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
740
+ * options), and every `required` question answered — by the reply, or by the defaults an omitted
741
+ * question falls back to. A reply the loop would have to drop parts of is refused here instead, so
742
+ * the person who typed it hears why. Needs the store to hand back the call's recorded questions;
743
+ * without that seam the loop's own filtering is the only check.
744
+ */
745
+ async assertAnswersFit(toolCallId2, answers2) {
746
+ if (this.store.toolCallInput === void 0) {
747
+ return;
748
+ }
749
+ const questions = readElicitationQuestions(await this.store.toolCallInput(toolCallId2));
750
+ for (const question of questions) {
751
+ const submitted = Object.hasOwn(answers2, question.id) ? answers2[question.id] : void 0;
752
+ const problem = validateElicitationAnswer(question, submitted ?? question.defaults ?? []);
753
+ if (problem !== null) {
754
+ throw new BadRequestException(`answers["${question.id}"] ${problem}`);
755
+ }
756
+ }
757
+ }
758
+ /**
759
+ * Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
760
+ * the requester (the default, and every call recorded before approvers existed) is the thread's
761
+ * own actor — the ownership check this always was; any other approver goes through the policy's
762
+ * `canDecide`, which by default asks whether the actor holds that role.
763
+ */
764
+ async assertMayDecide(actor, toolCallId2) {
765
+ const approval = await this.store.toolCallApproval?.(toolCallId2);
766
+ const approver = approval?.approver ?? REQUESTER_APPROVER;
767
+ if (approver === REQUESTER_APPROVER) {
768
+ return this.assertOwnsToolCall(actor, toolCallId2);
769
+ }
770
+ const requesterRef = await this.store.ownerOfToolCall(toolCallId2);
771
+ if (requesterRef === null) {
772
+ throw new NotFoundException(`tool call ${toolCallId2} not found`);
773
+ }
774
+ const allowed = await mayDecideApproval(this.deps.forAgent().approvalPolicy, actor, {
775
+ toolCallId: toolCallId2,
776
+ approver,
777
+ requesterRef
778
+ });
779
+ if (!allowed) {
780
+ throw new ForbiddenException(`this approval is for ${approver}`);
781
+ }
782
+ }
783
+ /**
784
+ * Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
785
+ * `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
786
+ * a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
787
+ * later replay of the run reads back.
788
+ */
789
+ async assertNotExpired(toolCallId2) {
790
+ const approval = await this.store.toolCallApproval?.(toolCallId2);
791
+ if (approval === null || approval === void 0) {
792
+ return;
793
+ }
794
+ const lapsed = approval.status === "expired" || approval.status === "pending_approval" && approval.expiresAt !== null && Date.parse(approval.expiresAt) <= Date.now();
795
+ if (lapsed) {
796
+ throw new GoneException(`the approval request for tool call ${toolCallId2} has expired`);
797
+ }
798
+ }
799
+ /**
716
800
  * Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
717
801
  * never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
718
802
  * call belongs to a delegated agent. The store answers from the CALL'S OWN `runId` where it has
@@ -790,6 +874,12 @@ var AgentApprovalPortAdapter = class {
790
874
  approved: true,
791
875
  ...opts?.executedByRef !== void 0 ? {
792
876
  executedByRef: opts.executedByRef
877
+ } : {},
878
+ ...opts?.remember === true ? {
879
+ remember: true
880
+ } : {},
881
+ ...opts?.decidedVia !== void 0 ? {
882
+ decidedVia: opts.decidedVia
793
883
  } : {}
794
884
  });
795
885
  }
@@ -801,6 +891,9 @@ var AgentApprovalPortAdapter = class {
801
891
  } : {},
802
892
  ...opts?.reason !== void 0 ? {
803
893
  reason: opts.reason
894
+ } : {},
895
+ ...opts?.decidedVia !== void 0 ? {
896
+ decidedVia: opts.decidedVia
804
897
  } : {}
805
898
  });
806
899
  }
@@ -1720,6 +1813,8 @@ var MAX_ANSWERED_QUESTIONS = 100;
1720
1813
  var MAX_VALUES_PER_QUESTION = 100;
1721
1814
  var MAX_ANSWER_VALUE_LENGTH = 4096;
1722
1815
  var MAX_REJECT_REASON_LENGTH = 4096;
1816
+ var MAX_VIA_LENGTH = 64;
1817
+ var DEFAULT_VIA = "web";
1723
1818
  function toolCallId(claimed) {
1724
1819
  if (typeof claimed !== "string" || claimed.length === 0) {
1725
1820
  throw new BadRequestException4("toolCallId must be a non-empty string");
@@ -1740,6 +1835,26 @@ function rejectReason(claimed) {
1740
1835
  return claimed;
1741
1836
  }
1742
1837
  __name(rejectReason, "rejectReason");
1838
+ function remember(claimed) {
1839
+ if (claimed === void 0) {
1840
+ return false;
1841
+ }
1842
+ if (typeof claimed !== "boolean") {
1843
+ throw new BadRequestException4("remember must be a boolean");
1844
+ }
1845
+ return claimed;
1846
+ }
1847
+ __name(remember, "remember");
1848
+ function via(claimed) {
1849
+ if (claimed === void 0) {
1850
+ return DEFAULT_VIA;
1851
+ }
1852
+ if (typeof claimed !== "string" || claimed.length === 0 || claimed.length > MAX_VIA_LENGTH) {
1853
+ throw new BadRequestException4(`via must be a string of 1-${MAX_VIA_LENGTH} characters`);
1854
+ }
1855
+ return claimed;
1856
+ }
1857
+ __name(via, "via");
1743
1858
  function answers(claimed) {
1744
1859
  if (claimed === void 0) {
1745
1860
  return void 0;
@@ -1784,14 +1899,19 @@ var ToolCallController = class {
1784
1899
  }
1785
1900
  async approve(req, body) {
1786
1901
  const actor = await this.actorResolver.resolve(req);
1787
- await this.agent.approve(actor, toolCallId(body.toolCallId));
1902
+ await this.agent.approve(actor, toolCallId(body.toolCallId), {
1903
+ remember: remember(body.remember),
1904
+ via: via(body.via)
1905
+ });
1788
1906
  return {
1789
1907
  ok: true
1790
1908
  };
1791
1909
  }
1792
1910
  async reject(req, body) {
1793
1911
  const actor = await this.actorResolver.resolve(req);
1794
- await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason));
1912
+ await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason), {
1913
+ via: via(body.via)
1914
+ });
1795
1915
  return {
1796
1916
  ok: true
1797
1917
  };
@@ -1857,7 +1977,7 @@ _ts_decorate11([
1857
1977
  _ts_metadata11("design:type", Function),
1858
1978
  _ts_metadata11("design:paramtypes", [
1859
1979
  typeof Request === "undefined" ? Object : Request,
1860
- typeof ApproveBody === "undefined" ? Object : ApproveBody
1980
+ Object
1861
1981
  ]),
1862
1982
  _ts_metadata11("design:returntype", Promise)
1863
1983
  ], ToolCallController.prototype, "skip", null);
@@ -2723,6 +2843,36 @@ var InlineAgentRunner = class _InlineAgentRunner {
2723
2843
  * shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
2724
2844
  * cancel settles the wait too, by rejecting it.
2725
2845
  */
2846
+ /**
2847
+ * {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
2848
+ * settles the wait as `expired` — the same Decision the durable runner builds from its signal
2849
+ * timeout — and a decision that arrives first disarms it.
2850
+ */
2851
+ parkApproval(runId, toolCallId2, timeoutMs) {
2852
+ const waiting = this.park(runId, toolCallId2);
2853
+ if (timeoutMs === void 0) {
2854
+ return waiting;
2855
+ }
2856
+ const key = `${runId}:${toolCallId2}`;
2857
+ let timer;
2858
+ const lapse = new Promise((resolve) => {
2859
+ timer = setTimeout(() => {
2860
+ const entry = this.pending.get(key);
2861
+ if (entry !== void 0) {
2862
+ this.pending.delete(key);
2863
+ resolve({
2864
+ approved: false,
2865
+ expired: true
2866
+ });
2867
+ }
2868
+ }, timeoutMs);
2869
+ timer.unref?.();
2870
+ });
2871
+ return Promise.race([
2872
+ waiting,
2873
+ lapse
2874
+ ]).finally(() => clearTimeout(timer));
2875
+ }
2726
2876
  park(runId, toolCallId2) {
2727
2877
  return new Promise((resolve, reject) => {
2728
2878
  this.pending.set(`${runId}:${toolCallId2}`, {
@@ -2736,7 +2886,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
2736
2886
  return {
2737
2887
  runId,
2738
2888
  openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
2739
- awaitApproval: /* @__PURE__ */ __name((call) => this.park(runId, call.id), "awaitApproval"),
2889
+ awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
2740
2890
  awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
2741
2891
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
2742
2892
  parallel: settleAll,
@@ -2787,7 +2937,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
2787
2937
  const hooks = {
2788
2938
  runId,
2789
2939
  openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
2790
- awaitApproval: /* @__PURE__ */ __name((call) => this.park(runId, call.id), "awaitApproval"),
2940
+ awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
2791
2941
  awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
2792
2942
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
2793
2943
  parallel: settleAll,
@@ -2891,7 +3041,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
2891
3041
  runId,
2892
3042
  // Forward into the top-level stream; the top-level run owns end/fail on that shared sink.
2893
3043
  openSink: /* @__PURE__ */ __name(async () => childSinkWriter(await deps.sink.open(sinkRunId)), "openSink"),
2894
- awaitApproval: /* @__PURE__ */ __name((call) => this.park(runId, call.id), "awaitApproval"),
3044
+ awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
2895
3045
  awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
2896
3046
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
2897
3047
  parallel: settleAll,