@dudousxd/nestjs-agent 1.24.1 → 1.25.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.
@@ -35,7 +35,7 @@ var AGENT_CHAT_QUEUE = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:chat-q
35
35
  // src/durable/agent-run.steps.ts
36
36
  import { createNegotiatedUiCollector } from "@dudousxd/nestjs-agent-core";
37
37
  import { prepareActionProposal, resolveActionProposalApproval } from "@dudousxd/nestjs-agent-core";
38
- import { AGENT_DEPS_FACTORY, createFrameBuffer, createNoopEmitUi, encodeStreamEvent, intersectAllowLists, invokeWithTransientRetry, isControlFlowSignal, isReplayIntegrityError, observeTurnFrames, publishAgentToolRetry, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolPreflightDenied, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
38
+ import { AGENT_DEPS_FACTORY, createFrameBuffer, createNoopEmitUi, encodeStreamEvent, intersectAllowLists, invokeWithTransientRetry, isControlFlowSignal, isReplayIntegrityError, observeTurnFrames, previewToolInputs, publishAgentToolRetry, registryInputPreviews, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolPreflightDenied, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
39
39
  import { Step } from "@dudousxd/nestjs-durable";
40
40
  import { Inject, Injectable } from "@nestjs/common";
41
41
 
@@ -131,7 +131,19 @@ var AgentRunSteps = class {
131
131
  const opened = await deps.sink.open(input.sinkRunId);
132
132
  writer = input.childSink ? childSinkWriter(opened) : opened;
133
133
  }
134
- const frames = observeTurnFrames(writer);
134
+ const previews = previewToolInputs(writer, registryInputPreviews(deps.registry, tools, {
135
+ actor: input.actor,
136
+ ...input.threadId !== void 0 ? {
137
+ threadId: input.threadId
138
+ } : {},
139
+ ...input.agentName !== void 0 ? {
140
+ agentName: input.agentName
141
+ } : {},
142
+ ...input.preflightContext?.uiCapabilities !== void 0 ? {
143
+ uiCapabilities: input.preflightContext.uiCapabilities
144
+ } : {}
145
+ }));
146
+ const frames = observeTurnFrames(previews.writer);
135
147
  const turn = withTurnFrames(await traceLlmTurn(input.runId, input.step, () => deps.model.runTurn({
136
148
  system: input.system,
137
149
  messages: input.messages,
@@ -203,8 +215,13 @@ var AgentRunSteps = class {
203
215
  };
204
216
  }
205
217
  }
206
- return buffer === void 0 ? stamped : {
218
+ const shown = previews.shown();
219
+ const previewed = shown.length > 0 ? {
207
220
  ...stamped,
221
+ previews: shown
222
+ } : stamped;
223
+ return buffer === void 0 ? previewed : {
224
+ ...previewed,
208
225
  bufferedFrames: buffer.frames()
209
226
  };
210
227
  }
@@ -1797,9 +1814,13 @@ var ActionProposalService = class {
1797
1814
  /** The words a text decision is made of, and what the agent answers — English unless configured. */
1798
1815
  vocabulary;
1799
1816
  replies;
1817
+ hasCapability() {
1818
+ const store = this.store;
1819
+ return typeof store.getThreadActionProposalScope === "function" && typeof store.listActionProposals === "function";
1820
+ }
1800
1821
  capability() {
1801
1822
  const store = this.store;
1802
- if (typeof store.getThreadActionProposalScope !== "function" || typeof store.listActionProposals !== "function") throw new NotImplementedException("Action proposals are unavailable");
1823
+ if (!this.hasCapability()) throw new NotImplementedException("Action proposals are unavailable");
1803
1824
  return store;
1804
1825
  }
1805
1826
  async scope(threadId, actor) {
@@ -1815,7 +1836,16 @@ var ActionProposalService = class {
1815
1836
  requesterRef: proposal.actorRef
1816
1837
  });
1817
1838
  }
1839
+ /**
1840
+ * One page of a thread's proposals this actor may see. A store without the proposal capability
1841
+ * can only run blocking approvals, so a thread there has no proposals: an empty, final page rather
1842
+ * than `501` — a client that lists by default (React's `useAgentChat`) needs no flag. Deciding one
1843
+ * still answers `501`.
1844
+ */
1818
1845
  async listPage(threadId, actor, query = {}) {
1846
+ if (!this.hasCapability()) return {
1847
+ items: []
1848
+ };
1819
1849
  const scope = await this.scope(threadId, actor);
1820
1850
  const rows = await this.capability().listActionProposals(scope, {
1821
1851
  limit: 1e3,
@@ -2549,12 +2579,13 @@ var AgentService = class {
2549
2579
  }
2550
2580
  /**
2551
2581
  * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
2552
- * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page). `404`
2553
- * when independent approvals are not on.
2582
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page).
2583
+ * Without independent approvals (the default blocking mode) there are none, so the read answers an
2584
+ * empty list rather than refusing; deciding one still refuses.
2554
2585
  */
2555
2586
  async listActionProposals(actor, threadId) {
2556
2587
  if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
2557
- throw new NotFoundException2("Proposal not found");
2588
+ return [];
2558
2589
  }
2559
2590
  return this.proposals.list(threadId, actor);
2560
2591
  }
@@ -6433,6 +6464,11 @@ var InlineAgentRunner = class _InlineAgentRunner {
6433
6464
  * running it. A cancelled id is dropped once the run settles, so the set tracks live runs only.
6434
6465
  */
6435
6466
  cancelled = /* @__PURE__ */ new Set();
6467
+ /**
6468
+ * Aborted by {@link cancel}: what cuts short the model call or tool a stopped run is in (the
6469
+ * loop's `abortSignal` hook). Keyed like {@link cancelled}, and dropped with it.
6470
+ */
6471
+ aborts = /* @__PURE__ */ new Map();
6436
6472
  /** Top-level runs this process is running now — what {@link isRunActive} answers from. */
6437
6473
  live = /* @__PURE__ */ new Set();
6438
6474
  constructor(factory, store, queue) {
@@ -6502,7 +6538,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6502
6538
  await releaseThreadRun3(this.store, input.threadId, runId);
6503
6539
  await writer.fail(streamFailure3(error));
6504
6540
  }).finally(() => {
6505
- this.cancelled.delete(runId);
6541
+ this.forgetCancel(runId);
6506
6542
  this.live.delete(runId);
6507
6543
  });
6508
6544
  return {
@@ -6521,8 +6557,10 @@ var InlineAgentRunner = class _InlineAgentRunner {
6521
6557
  * Ask a run to stop, and settle whatever it was parked on.
6522
6558
  *
6523
6559
  * The loop observes the request at its next safe point (between steps, before the turn's tools) and
6524
- * unwinds from there — so a tool already executing is left to finish, and its result is recorded
6525
- * exactly as it would have been. What this cannot wait for is a turn parked on a human: that wait
6560
+ * unwinds from there. What the run is in right now is aborted (its `abortSignal`): the model call
6561
+ * streaming the answer ends at once and the step it was writing is not persisted, and a tool that
6562
+ * passes `ctx.abortSignal` on is cut short and recorded failed; a tool that ignores the signal is
6563
+ * left to finish, and its result is recorded exactly as it would have been. What this cannot wait for is a turn parked on a human: that wait
6526
6564
  * is a promise nobody is going to resolve, so it is REJECTED here with the same
6527
6565
  * {@link RunCancelledError} the loop would have thrown, and the run unwinds through the identical
6528
6566
  * path.
@@ -6533,6 +6571,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6533
6571
  */
6534
6572
  async cancel(runId) {
6535
6573
  this.cancelled.add(runId);
6574
+ this.aborts.get(runId)?.abort(new RunCancelledError2());
6536
6575
  for (const [key, waiter] of this.pending) {
6537
6576
  if (key.startsWith(`${runId}:`)) {
6538
6577
  this.pending.delete(key);
@@ -6541,6 +6580,20 @@ var InlineAgentRunner = class _InlineAgentRunner {
6541
6580
  }
6542
6581
  await Promise.resolve();
6543
6582
  }
6583
+ /** The signal {@link cancel} aborts for `runId` — one per run, shared by its awaited delegates. */
6584
+ abortSignalFor(runId) {
6585
+ let controller = this.aborts.get(runId);
6586
+ if (controller === void 0) {
6587
+ controller = new AbortController();
6588
+ this.aborts.set(runId, controller);
6589
+ }
6590
+ return controller.signal;
6591
+ }
6592
+ /** Drop a settled run's Stop: its flag and its abort controller. */
6593
+ forgetCancel(runId) {
6594
+ this.cancelled.delete(runId);
6595
+ this.aborts.delete(runId);
6596
+ }
6544
6597
  /**
6545
6598
  * The single place a cancelled run's state is settled: recorded as its own terminal (never
6546
6599
  * `failed` — a user pressing Stop must not land in someone's error rate), the thread released, and
@@ -6660,6 +6713,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6660
6713
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
6661
6714
  parallel: settleAll2,
6662
6715
  cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
6716
+ abortSignal: this.abortSignalFor(runId),
6663
6717
  runAgent: /* @__PURE__ */ __name((agentName, task) => this.runNested({
6664
6718
  agentName,
6665
6719
  task,
@@ -6711,6 +6765,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6711
6765
  step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
6712
6766
  parallel: settleAll2,
6713
6767
  cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId), "cancelled"),
6768
+ abortSignal: this.abortSignalFor(runId),
6714
6769
  runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
6715
6770
  agentName: childName,
6716
6771
  task: childTask,
@@ -6786,7 +6841,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
6786
6841
  }
6787
6842
  });
6788
6843
  }).finally(async () => {
6789
- this.cancelled.delete(runId);
6844
+ this.forgetCancel(runId);
6790
6845
  await this.store.setActiveStream(subThread.id, null);
6791
6846
  });
6792
6847
  return {
@@ -6818,6 +6873,8 @@ var InlineAgentRunner = class _InlineAgentRunner {
6818
6873
  // A child stops when it is cancelled OR when the run a human is actually watching is: the
6819
6874
  // canceller names the top-level run, which is the only id that ever left the server.
6820
6875
  cancelled: /* @__PURE__ */ __name(async () => this.cancelled.has(runId) || this.cancelled.has(sinkRunId), "cancelled"),
6876
+ // Aborted by a Stop on the run the human is watching, which is the id a canceller names.
6877
+ abortSignal: this.abortSignalFor(sinkRunId),
6821
6878
  runAgent: /* @__PURE__ */ __name((childName, childTask) => this.runNested({
6822
6879
  agentName: childName,
6823
6880
  task: childTask,