@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/README.md CHANGED
@@ -130,6 +130,36 @@ nobody is streaming (including one that has already finished). In-process caller
130
130
  authorized elsewhere use `AgentService.subscribe(runId)`; anything reachable from a request must go
131
131
  through `AgentService.subscribeAs(actor, runId)`.
132
132
 
133
+ ### Who approves an action, and for how long
134
+
135
+ Every `action` tool call waits on the person chatting by default. `approvalPolicy` changes that per
136
+ call:
137
+
138
+ ```ts
139
+ AgentModule.forRoot({
140
+ // …
141
+ approvalPolicy: {
142
+ requirementFor: (tool, actor) =>
143
+ tool.name === 'issueRefund'
144
+ ? { required: true, approver: 'finance', ttlMs: 15 * 60_000 }
145
+ : { required: true, approver: 'requester' },
146
+ // Optional. Default: 'requester' = the thread's own actor, anything else = a role the decider holds.
147
+ // canDecide: (actor, { approver, requesterRef }) => gate.forUser(actor).allows('approve', approver),
148
+ },
149
+ });
150
+ ```
151
+
152
+ - `POST /agent/tool-call/approve` `{ toolCallId, remember?, via? }` and `…/reject` `{ toolCallId,
153
+ reason?, via? }` enforce the recorded approver (`403` otherwise) and refuse a request that has
154
+ lapsed (`410`). The decider is recorded as `executedByRef`, `via` as `decided_via` (`'web'` when
155
+ omitted; the console approvals port takes `decidedVia` too).
156
+ - `remember: true` approves later calls of the same tool in the same thread without asking.
157
+ - A `ttlMs` becomes the approval wait's timeout — `ctx.waitForSignal(…, { timeoutMs })` on the
158
+ durable runner, a timer inline. On expiry the call settles `expired`, the model is told nobody
159
+ approved it in time, and the stream carries `tool-output-denied` + `approval-settled`.
160
+ - The tool-call row gains `approver`, `expires_at`, `remember` and `decided_via`; a reloaded thread
161
+ carries them as `StoredMessage.approvals`.
162
+
133
163
  ### A durable turn's model call and tools run in a worker
134
164
 
135
165
  Under `durable: true` the turn's model call and each of its tool executions are **dispatched steps**
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
4
4
 
@@ -8,6 +8,8 @@ interface AgentDeps {
8
8
  store: AgentStore;
9
9
  registry: ToolRegistry;
10
10
  rolesPolicy: RolesPolicy;
11
+ /** Who approves an action call, and for how long (from `forRoot({ approvalPolicy })`). Undefined → the requester, no expiry. */
12
+ approvalPolicy?: ApprovalPolicy;
11
13
  quota?: QuotaStore;
12
14
  sink: TokenStreamSink;
13
15
  /** Fallback accounting label; the provider's turn result overrides it when set. */
@@ -205,6 +207,13 @@ interface AgentModuleOptions {
205
207
  quotaLimitTokens?: number;
206
208
  /** Tool authorization gate. Defaults to role-in-`defaultRoles`. */
207
209
  rolesPolicy?: RolesPolicy;
210
+ /**
211
+ * Who has to approve an `action` tool call, and how long the request stays open. Defaults to the
212
+ * requester (the thread's own actor) with no expiry. A requirement naming another approver (a
213
+ * role) is enforced by `POST tool-call/approve|reject` through `canDecide` — by default, the
214
+ * deciding actor must hold that role. See `ApprovalPolicy`.
215
+ */
216
+ approvalPolicy?: ApprovalPolicy;
208
217
  /** Default roles a tool requires when its `roles` is omitted. Defaults to `['ADMIN']`. */
209
218
  defaultRoles?: string[];
210
219
  /**
@@ -1,4 +1,4 @@
1
- import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
1
+ import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
3
  import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
4
4
 
@@ -8,6 +8,8 @@ interface AgentDeps {
8
8
  store: AgentStore;
9
9
  registry: ToolRegistry;
10
10
  rolesPolicy: RolesPolicy;
11
+ /** Who approves an action call, and for how long (from `forRoot({ approvalPolicy })`). Undefined → the requester, no expiry. */
12
+ approvalPolicy?: ApprovalPolicy;
11
13
  quota?: QuotaStore;
12
14
  sink: TokenStreamSink;
13
15
  /** Fallback accounting label; the provider's turn result overrides it when set. */
@@ -205,6 +207,13 @@ interface AgentModuleOptions {
205
207
  quotaLimitTokens?: number;
206
208
  /** Tool authorization gate. Defaults to role-in-`defaultRoles`. */
207
209
  rolesPolicy?: RolesPolicy;
210
+ /**
211
+ * Who has to approve an `action` tool call, and how long the request stays open. Defaults to the
212
+ * requester (the thread's own actor) with no expiry. A requirement naming another approver (a
213
+ * role) is enforced by `POST tool-call/approve|reject` through `canDecide` — by default, the
214
+ * deciding actor must hold that role. See `ApprovalPolicy`.
215
+ */
216
+ approvalPolicy?: ApprovalPolicy;
208
217
  /** Default roles a tool requires when its `roles` is omitted. Defaults to `['ADMIN']`. */
209
218
  defaultRoles?: string[];
210
219
  /**
@@ -304,7 +304,21 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
304
304
  runId: ctx.runId,
305
305
  // A child forwards into the top-level sink but must not end/fail it (the top-level run owns it).
306
306
  openSink: /* @__PURE__ */ __name(async () => input.sinkRunId !== void 0 ? childSinkWriter(await deps.sink.open(sinkRunId)) : deps.sink.open(ctx.runId), "openSink"),
307
- awaitApproval: /* @__PURE__ */ __name((call) => ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`), "awaitApproval"),
307
+ // A policy's time to live becomes the signal wait's own timeout: the runtime journals the
308
+ // deadline on the first call (a position of its own, reached only for a call the journal says
309
+ // has a ttl) and wakes the run when it passes. The lapse comes back as a Decision rather than a
310
+ // throw, so the loop settles the call `expired` on its ordinary rejection checkpoint.
311
+ awaitApproval: /* @__PURE__ */ __name((call, _toolCtx, opts) => opts?.timeoutMs === void 0 ? ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`) : ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`, {
312
+ timeoutMs: opts.timeoutMs
313
+ }).catch((error) => {
314
+ if (isSignalTimeout(error)) {
315
+ return {
316
+ approved: false,
317
+ expired: true
318
+ };
319
+ }
320
+ throw error;
321
+ }), "awaitApproval"),
308
322
  // The same wait, on the same signal key, carrying a typed answer instead of a yes/no — so a
309
323
  // question set and an approval reach a parked run through one path, and the HTTP surface that
310
324
  // settles either one is the same `POST /agent/tool-call/*` family.
@@ -478,6 +492,10 @@ AgentRunWorkflow = _ts_decorate2([
478
492
  typeof RunGateway === "undefined" ? Object : RunGateway
479
493
  ])
480
494
  ], AgentRunWorkflow);
495
+ function isSignalTimeout(error) {
496
+ return error instanceof Error && error.name === "SignalTimeoutError";
497
+ }
498
+ __name(isSignalTimeout, "isSignalTimeout");
481
499
 
482
500
  // src/durable/durable-agent-runner.ts
483
501
  var import_node_crypto = require("crypto");
@@ -908,6 +926,9 @@ var AgentDepsFactory2 = class {
908
926
  store: this.store,
909
927
  sink: this.sink,
910
928
  rolesPolicy: this.rolesPolicy,
929
+ ...this.options.approvalPolicy !== void 0 ? {
930
+ approvalPolicy: this.options.approvalPolicy
931
+ } : {},
911
932
  registry: this.registry,
912
933
  promptContributors: this.promptContributors,
913
934
  systemPrompt: definition?.systemPrompt ?? "You are a helpful assistant.",
@@ -1191,18 +1212,35 @@ var AgentService = class {
1191
1212
  await this.assertOwnsActiveStream(actor, runId);
1192
1213
  return this.subscribe(runId);
1193
1214
  }
1194
- async approve(actor, toolCallId2) {
1195
- await this.assertOwnsToolCall(actor, toolCallId2);
1215
+ /**
1216
+ * Approve a parked action call as `actor`. Who may is the call's recorded approver's business
1217
+ * (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
1218
+ * thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
1219
+ * persisted with the call.
1220
+ */
1221
+ async approve(actor, toolCallId2, opts = {}) {
1222
+ await this.assertMayDecide(actor, toolCallId2);
1196
1223
  return this.signalToolCall(toolCallId2, {
1197
- approved: true
1224
+ approved: true,
1225
+ executedByRef: actor.id,
1226
+ ...opts.remember === true ? {
1227
+ remember: true
1228
+ } : {},
1229
+ ...opts.via !== void 0 ? {
1230
+ decidedVia: opts.via
1231
+ } : {}
1198
1232
  });
1199
1233
  }
1200
- async reject(actor, toolCallId2, reason) {
1201
- await this.assertOwnsToolCall(actor, toolCallId2);
1234
+ async reject(actor, toolCallId2, reason, opts = {}) {
1235
+ await this.assertMayDecide(actor, toolCallId2);
1202
1236
  return this.signalToolCall(toolCallId2, {
1203
1237
  approved: false,
1238
+ executedByRef: actor.id,
1204
1239
  ...reason !== void 0 ? {
1205
1240
  reason
1241
+ } : {},
1242
+ ...opts.via !== void 0 ? {
1243
+ decidedVia: opts.via
1206
1244
  } : {}
1207
1245
  });
1208
1246
  }
@@ -1214,6 +1252,7 @@ var AgentService = class {
1214
1252
  */
1215
1253
  async answer(actor, toolCallId2, answers2 = {}) {
1216
1254
  await this.assertOwnsToolCall(actor, toolCallId2);
1255
+ await this.assertAnswersFit(toolCallId2, answers2);
1217
1256
  const reply = {
1218
1257
  answers: answers2,
1219
1258
  answeredByRef: actor.id
@@ -1243,6 +1282,7 @@ var AgentService = class {
1243
1282
  * not the thread's own actor).
1244
1283
  */
1245
1284
  async signalToolCall(toolCallId2, reply) {
1285
+ await this.assertNotExpired(toolCallId2);
1246
1286
  const runId = await this.resolveRunForToolCall(toolCallId2);
1247
1287
  return this.runner.signal(runId, toolCallId2, reply);
1248
1288
  }
@@ -1481,6 +1521,68 @@ var AgentService = class {
1481
1521
  }
1482
1522
  }
1483
1523
  /**
1524
+ * Check a reply against the questions it answers before it is signalled: every submitted value a
1525
+ * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
1526
+ * options), and every `required` question answered — by the reply, or by the defaults an omitted
1527
+ * question falls back to. A reply the loop would have to drop parts of is refused here instead, so
1528
+ * the person who typed it hears why. Needs the store to hand back the call's recorded questions;
1529
+ * without that seam the loop's own filtering is the only check.
1530
+ */
1531
+ async assertAnswersFit(toolCallId2, answers2) {
1532
+ if (this.store.toolCallInput === void 0) {
1533
+ return;
1534
+ }
1535
+ const questions = (0, import_nestjs_agent_core8.readElicitationQuestions)(await this.store.toolCallInput(toolCallId2));
1536
+ for (const question of questions) {
1537
+ const submitted = Object.hasOwn(answers2, question.id) ? answers2[question.id] : void 0;
1538
+ const problem = (0, import_nestjs_agent_core8.validateElicitationAnswer)(question, submitted ?? question.defaults ?? []);
1539
+ if (problem !== null) {
1540
+ throw new import_common6.BadRequestException(`answers["${question.id}"] ${problem}`);
1541
+ }
1542
+ }
1543
+ }
1544
+ /**
1545
+ * Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
1546
+ * the requester (the default, and every call recorded before approvers existed) is the thread's
1547
+ * own actor — the ownership check this always was; any other approver goes through the policy's
1548
+ * `canDecide`, which by default asks whether the actor holds that role.
1549
+ */
1550
+ async assertMayDecide(actor, toolCallId2) {
1551
+ const approval = await this.store.toolCallApproval?.(toolCallId2);
1552
+ const approver = approval?.approver ?? import_nestjs_agent_core8.REQUESTER_APPROVER;
1553
+ if (approver === import_nestjs_agent_core8.REQUESTER_APPROVER) {
1554
+ return this.assertOwnsToolCall(actor, toolCallId2);
1555
+ }
1556
+ const requesterRef = await this.store.ownerOfToolCall(toolCallId2);
1557
+ if (requesterRef === null) {
1558
+ throw new import_common6.NotFoundException(`tool call ${toolCallId2} not found`);
1559
+ }
1560
+ const allowed = await (0, import_nestjs_agent_core8.mayDecideApproval)(this.deps.forAgent().approvalPolicy, actor, {
1561
+ toolCallId: toolCallId2,
1562
+ approver,
1563
+ requesterRef
1564
+ });
1565
+ if (!allowed) {
1566
+ throw new import_common6.ForbiddenException(`this approval is for ${approver}`);
1567
+ }
1568
+ }
1569
+ /**
1570
+ * Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
1571
+ * `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
1572
+ * a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
1573
+ * later replay of the run reads back.
1574
+ */
1575
+ async assertNotExpired(toolCallId2) {
1576
+ const approval = await this.store.toolCallApproval?.(toolCallId2);
1577
+ if (approval === null || approval === void 0) {
1578
+ return;
1579
+ }
1580
+ const lapsed = approval.status === "expired" || approval.status === "pending_approval" && approval.expiresAt !== null && Date.parse(approval.expiresAt) <= Date.now();
1581
+ if (lapsed) {
1582
+ throw new import_common6.GoneException(`the approval request for tool call ${toolCallId2} has expired`);
1583
+ }
1584
+ }
1585
+ /**
1484
1586
  * Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
1485
1587
  * never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
1486
1588
  * call belongs to a delegated agent. The store answers from the CALL'S OWN `runId` where it has
@@ -1558,6 +1660,12 @@ var AgentApprovalPortAdapter = class {
1558
1660
  approved: true,
1559
1661
  ...opts?.executedByRef !== void 0 ? {
1560
1662
  executedByRef: opts.executedByRef
1663
+ } : {},
1664
+ ...opts?.remember === true ? {
1665
+ remember: true
1666
+ } : {},
1667
+ ...opts?.decidedVia !== void 0 ? {
1668
+ decidedVia: opts.decidedVia
1561
1669
  } : {}
1562
1670
  });
1563
1671
  }
@@ -1569,6 +1677,9 @@ var AgentApprovalPortAdapter = class {
1569
1677
  } : {},
1570
1678
  ...opts?.reason !== void 0 ? {
1571
1679
  reason: opts.reason
1680
+ } : {},
1681
+ ...opts?.decidedVia !== void 0 ? {
1682
+ decidedVia: opts.decidedVia
1572
1683
  } : {}
1573
1684
  });
1574
1685
  }
@@ -2488,6 +2599,8 @@ var MAX_ANSWERED_QUESTIONS = 100;
2488
2599
  var MAX_VALUES_PER_QUESTION = 100;
2489
2600
  var MAX_ANSWER_VALUE_LENGTH = 4096;
2490
2601
  var MAX_REJECT_REASON_LENGTH = 4096;
2602
+ var MAX_VIA_LENGTH = 64;
2603
+ var DEFAULT_VIA = "web";
2491
2604
  function toolCallId(claimed) {
2492
2605
  if (typeof claimed !== "string" || claimed.length === 0) {
2493
2606
  throw new import_common15.BadRequestException("toolCallId must be a non-empty string");
@@ -2508,6 +2621,26 @@ function rejectReason(claimed) {
2508
2621
  return claimed;
2509
2622
  }
2510
2623
  __name(rejectReason, "rejectReason");
2624
+ function remember(claimed) {
2625
+ if (claimed === void 0) {
2626
+ return false;
2627
+ }
2628
+ if (typeof claimed !== "boolean") {
2629
+ throw new import_common15.BadRequestException("remember must be a boolean");
2630
+ }
2631
+ return claimed;
2632
+ }
2633
+ __name(remember, "remember");
2634
+ function via(claimed) {
2635
+ if (claimed === void 0) {
2636
+ return DEFAULT_VIA;
2637
+ }
2638
+ if (typeof claimed !== "string" || claimed.length === 0 || claimed.length > MAX_VIA_LENGTH) {
2639
+ throw new import_common15.BadRequestException(`via must be a string of 1-${MAX_VIA_LENGTH} characters`);
2640
+ }
2641
+ return claimed;
2642
+ }
2643
+ __name(via, "via");
2511
2644
  function answers(claimed) {
2512
2645
  if (claimed === void 0) {
2513
2646
  return void 0;
@@ -2552,14 +2685,19 @@ var ToolCallController = class {
2552
2685
  }
2553
2686
  async approve(req, body) {
2554
2687
  const actor = await this.actorResolver.resolve(req);
2555
- await this.agent.approve(actor, toolCallId(body.toolCallId));
2688
+ await this.agent.approve(actor, toolCallId(body.toolCallId), {
2689
+ remember: remember(body.remember),
2690
+ via: via(body.via)
2691
+ });
2556
2692
  return {
2557
2693
  ok: true
2558
2694
  };
2559
2695
  }
2560
2696
  async reject(req, body) {
2561
2697
  const actor = await this.actorResolver.resolve(req);
2562
- await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason));
2698
+ await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason), {
2699
+ via: via(body.via)
2700
+ });
2563
2701
  return {
2564
2702
  ok: true
2565
2703
  };
@@ -2625,7 +2763,7 @@ _ts_decorate15([
2625
2763
  _ts_metadata14("design:type", Function),
2626
2764
  _ts_metadata14("design:paramtypes", [
2627
2765
  typeof Request === "undefined" ? Object : Request,
2628
- typeof ApproveBody === "undefined" ? Object : ApproveBody
2766
+ Object
2629
2767
  ]),
2630
2768
  _ts_metadata14("design:returntype", Promise)
2631
2769
  ], ToolCallController.prototype, "skip", null);
@@ -3370,6 +3508,36 @@ var InlineAgentRunner = class _InlineAgentRunner {
3370
3508
  * shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
3371
3509
  * cancel settles the wait too, by rejecting it.
3372
3510
  */
3511
+ /**
3512
+ * {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
3513
+ * settles the wait as `expired` — the same Decision the durable runner builds from its signal
3514
+ * timeout — and a decision that arrives first disarms it.
3515
+ */
3516
+ parkApproval(runId, toolCallId2, timeoutMs) {
3517
+ const waiting = this.park(runId, toolCallId2);
3518
+ if (timeoutMs === void 0) {
3519
+ return waiting;
3520
+ }
3521
+ const key = `${runId}:${toolCallId2}`;
3522
+ let timer;
3523
+ const lapse = new Promise((resolve) => {
3524
+ timer = setTimeout(() => {
3525
+ const entry = this.pending.get(key);
3526
+ if (entry !== void 0) {
3527
+ this.pending.delete(key);
3528
+ resolve({
3529
+ approved: false,
3530
+ expired: true
3531
+ });
3532
+ }
3533
+ }, timeoutMs);
3534
+ timer.unref?.();
3535
+ });
3536
+ return Promise.race([
3537
+ waiting,
3538
+ lapse
3539
+ ]).finally(() => clearTimeout(timer));
3540
+ }
3373
3541
  park(runId, toolCallId2) {
3374
3542
  return new Promise((resolve, reject) => {
3375
3543
  this.pending.set(`${runId}:${toolCallId2}`, {
@@ -3383,7 +3551,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
3383
3551
  return {
3384
3552
  runId,
3385
3553
  openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
3386
- awaitApproval: /* @__PURE__ */ __name((call) => this.park(runId, call.id), "awaitApproval"),
3554
+ awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
3387
3555
  awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
3388
3556
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
3389
3557
  parallel: import_nestjs_agent_core22.settleAll,
@@ -3434,7 +3602,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
3434
3602
  const hooks = {
3435
3603
  runId,
3436
3604
  openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
3437
- awaitApproval: /* @__PURE__ */ __name((call) => this.park(runId, call.id), "awaitApproval"),
3605
+ awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
3438
3606
  awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
3439
3607
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
3440
3608
  parallel: import_nestjs_agent_core22.settleAll,
@@ -3538,7 +3706,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
3538
3706
  runId,
3539
3707
  // Forward into the top-level stream; the top-level run owns end/fail on that shared sink.
3540
3708
  openSink: /* @__PURE__ */ __name(async () => childSinkWriter(await deps.sink.open(sinkRunId)), "openSink"),
3541
- awaitApproval: /* @__PURE__ */ __name((call) => this.park(runId, call.id), "awaitApproval"),
3709
+ awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
3542
3710
  awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
3543
3711
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
3544
3712
  parallel: import_nestjs_agent_core22.settleAll,