@dudousxd/nestjs-agent 1.17.0 → 1.18.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.
@@ -2,10 +2,10 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
4
  // src/durable/agent-run.workflow.ts
5
- import { AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY2, AGENT_STORE as AGENT_STORE2, RUN_ENDED_BEFORE_TOOL_CALL, RunCancelledError, agentFailureCode, encodeStreamEvent as encodeStreamEvent3, isReplayIntegrityError, publishAgentRunFailed, releaseThreadRun, runAgentLoop, settleAll, settleDeadRun, settleUnsettledDelegation, streamFailure } from "@dudousxd/nestjs-agent-core";
5
+ import { AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY2, AGENT_STORE, RUN_ENDED_BEFORE_TOOL_CALL, RunCancelledError, agentFailureCode, encodeStreamEvent as encodeStreamEvent2, isReplayIntegrityError, publishAgentRunFailed, releaseThreadRun, runAgentLoop, settleAll, settleDeadRun, settleUnsettledDelegation, streamFailure } from "@dudousxd/nestjs-agent-core";
6
6
  import { RUN_GATEWAY, Workflow } from "@dudousxd/nestjs-durable";
7
7
  import { isWorkflowControlFlowSignal } from "@dudousxd/nestjs-durable-core";
8
- import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
8
+ import { Inject as Inject2, Injectable as Injectable2, Logger, Optional } from "@nestjs/common";
9
9
 
10
10
  // src/agent-deps.ts
11
11
  function utcDay(date = /* @__PURE__ */ new Date()) {
@@ -23,9 +23,26 @@ function childSinkWriter(writer) {
23
23
  }
24
24
  __name(childSinkWriter, "childSinkWriter");
25
25
 
26
- // src/queue/chat-queue.service.ts
27
- import { AGENT_ATTACHMENT_STAGING, AGENT_OPTIONS, AGENT_QUOTA_PROVIDER, AGENT_SINK, AGENT_STORE, encodeStreamEvent, isChatQueueStore, queuedMessageView } from "@dudousxd/nestjs-agent-core";
28
- import { Inject, Injectable, Logger, NotImplementedException, Optional } from "@nestjs/common";
26
+ // src/queue/chat-queue.token.ts
27
+ var AGENT_CHAT_QUEUE = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:chat-queue");
28
+
29
+ // src/durable/agent-run.steps.ts
30
+ import { AGENT_DEPS_FACTORY, createFrameBuffer, createUiCollector, encodeStreamEvent, invokeWithTransientRetry, observeTurnFrames, publishAgentToolRetry, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
31
+ import { Step } from "@dudousxd/nestjs-durable";
32
+ import { Inject, Injectable } from "@nestjs/common";
33
+
34
+ // src/durable/outside-workflow-ctx.ts
35
+ import { runInWorkflowCtx } from "@dudousxd/nestjs-durable-core";
36
+ function outsideWorkflowCtx(fn) {
37
+ return runInWorkflowCtx(void 0, fn);
38
+ }
39
+ __name(outsideWorkflowCtx, "outsideWorkflowCtx");
40
+ function stepOf(ctx) {
41
+ return (name, fn, ...rest) => ctx.localStep(name, (...args) => outsideWorkflowCtx(() => fn(...args)), ...rest);
42
+ }
43
+ __name(stepOf, "stepOf");
44
+
45
+ // src/durable/agent-run.steps.ts
29
46
  function _ts_decorate(decorators, target, key, desc) {
30
47
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31
48
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -52,719 +69,278 @@ function _ts_param(paramIndex, decorator) {
52
69
  };
53
70
  }
54
71
  __name(_ts_param, "_ts_param");
55
- var MAX_DRAIN_ATTEMPTS = 8;
56
- var ChatQueueService = class _ChatQueueService {
72
+ var AgentRunSteps = class {
57
73
  static {
58
- __name(this, "ChatQueueService");
74
+ __name(this, "AgentRunSteps");
75
+ }
76
+ factory;
77
+ constructor(factory) {
78
+ this.factory = factory;
59
79
  }
60
- store;
61
- sink;
62
- quotaProvider;
63
- options;
64
- staging;
65
- logger = new Logger(_ChatQueueService.name);
66
80
  /**
67
- * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
68
- * alive whatever the runner says — it simply has not registered the run yet.
81
+ * Retryable: every side effect (persist/quota/stream markers) happens loop-side under
82
+ * `hooks.step`, so a durable retry only re-runs the model call and re-streams its tokens — never
83
+ * double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
84
+ * across however many turns/retries it takes.
69
85
  */
70
- starting = /* @__PURE__ */ new Set();
71
- constructor(store, sink, quotaProvider, options, staging) {
72
- this.store = store;
73
- this.sink = sink;
74
- this.quotaProvider = quotaProvider;
75
- this.options = options;
76
- this.staging = staging;
77
- }
78
- /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
79
- get supported() {
80
- return isChatQueueStore(this.store);
86
+ llm(input) {
87
+ return outsideWorkflowCtx(() => this.runLlm(input));
81
88
  }
82
- /** The bound store as a queue store, or a 501 naming what is missing. */
83
- queueStore() {
84
- if (!isChatQueueStore(this.store)) {
85
- throw new NotImplementedException("Queueing messages requires an AgentStore that implements ChatQueueStore (enqueueMessage, listQueue, \u2026, claimActiveStream, releaseActiveStream); the bound store does not.");
89
+ async runLlm(input) {
90
+ const deps = this.factory.forAgent(input.agentName);
91
+ const tools = withMemoryTool({
92
+ tools: withSkillTool({
93
+ tools: withAskTool({
94
+ tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList, {
95
+ ...input.threadId !== void 0 ? {
96
+ threadId: input.threadId
97
+ } : {},
98
+ ...input.agentName !== void 0 ? {
99
+ agentName: input.agentName
100
+ } : {}
101
+ }),
102
+ ask: deps.ask
103
+ }),
104
+ enabled: deps.skills !== void 0
105
+ }),
106
+ enabled: deps.memory?.provider.write !== void 0
107
+ });
108
+ const buffer = input.bufferOutput === true ? createFrameBuffer() : void 0;
109
+ let writer;
110
+ if (buffer !== void 0) {
111
+ writer = buffer.writer;
112
+ } else {
113
+ const opened = await deps.sink.open(input.sinkRunId);
114
+ writer = input.childSink ? childSinkWriter(opened) : opened;
86
115
  }
87
- return this.store;
88
- }
89
- async state(threadId) {
90
- const store = this.queueStore();
91
- const [items, paused] = await Promise.all([
92
- store.listQueue(threadId),
93
- store.queuePause(threadId)
94
- ]);
95
- return {
96
- items: items.map(queuedMessageView),
97
- paused
116
+ const frames = observeTurnFrames(writer);
117
+ const turn = withTurnFrames(await traceLlmTurn(input.runId, input.step, () => deps.model.runTurn({
118
+ system: input.system,
119
+ messages: input.messages,
120
+ tools,
121
+ sink: frames.writer,
122
+ ...input.model !== void 0 ? {
123
+ model: input.model
124
+ } : {}
125
+ })), frames.summary());
126
+ const stamped = stampToolKinds(turn, deps);
127
+ return buffer === void 0 ? stamped : {
128
+ ...stamped,
129
+ bufferedFrames: buffer.frames()
98
130
  };
99
131
  }
100
132
  /**
101
- * Whether `holder` is still running. A run this process is about to start is; otherwise the
102
- * runner decides, and a runner that cannot tell says yes.
133
+ * NO DURABLE retries — a tool may not be idempotent (a bare `@Step()` carries none, unlike `llm`
134
+ * above). What it does retry, in place, is a CLASSIFIED-TRANSIENT error from the tool's own
135
+ * invocation (a DB deadlock, a lock-wait timeout — see `isTransientToolError`): the server rolled
136
+ * that work back, so re-invoking it is safe, and doing it here (inside this one step, never a new
137
+ * checkpoint) is exactly how the non-dispatched loop path handles it too. The timeout is applied
138
+ * HERE, handler-side, via `withToolTimeout` — never as a durable dispatch `timeoutMs`, which
139
+ * bounds engine liveness (retryable) rather than the tool's own business outcome — and per
140
+ * ATTEMPT, so a retried invocation gets its own fresh timeout window.
103
141
  */
104
- async isAlive(holder, runner) {
105
- if (this.starting.has(holder)) {
106
- return true;
107
- }
108
- if (typeof runner.isRunActive !== "function") {
109
- return true;
110
- }
111
- try {
112
- return await runner.isRunActive(holder);
113
- } catch {
114
- return true;
115
- }
142
+ tool(input) {
143
+ return outsideWorkflowCtx(() => this.runTool(input));
116
144
  }
117
- /**
118
- * The run holding `threadId`, or `null` when it is free — a holder the runner reports dead counts
119
- * as free, and is returned as `stale` so the caller can claim over it.
120
- */
121
- async holder(threadId, runner) {
122
- const holder = await this.queueStore().activeRunForThread(threadId);
123
- if (holder === null) {
124
- return {
125
- live: null,
126
- stale: null
127
- };
145
+ async runTool(input) {
146
+ const deps = this.factory.forAgent(input.ctx.agentName);
147
+ if (deps.registry.spec(input.toolName)?.kind === "action" && input.toolType !== "action") {
148
+ throw new Error(`tool "${input.toolName}" is an action tool but was dispatched without approval: the dispatching process resolved its kind against a registry that does not have it`);
128
149
  }
129
- return await this.isAlive(holder, runner) ? {
130
- live: holder,
131
- stale: null
132
- } : {
133
- live: null,
134
- stale: holder
150
+ const sinkRunId = input.sinkRunId;
151
+ let opened;
152
+ const ui = createUiCollector(input.toolCallId, sinkRunId === void 0 ? void 0 : async (event) => {
153
+ opened ??= Promise.resolve(deps.sink.open(sinkRunId)).then((writer) => input.childSink === true ? childSinkWriter(writer) : writer);
154
+ await (await opened).write(encodeStreamEvent(event));
155
+ });
156
+ const ctx = {
157
+ ...toolCallContext(input.ctx, input.toolCallId),
158
+ emitUi: ui.emit
159
+ };
160
+ const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
161
+ const transientRetry = input.transientRetry === false ? false : {
162
+ attempts: input.transientRetry.attempts,
163
+ backoffMs: input.transientRetry.backoffMs,
164
+ ...localClassify !== void 0 ? {
165
+ classify: localClassify
166
+ } : {}
135
167
  };
168
+ const output = await traceToolExecution(input.ctx.runId, {
169
+ toolCallId: input.toolCallId,
170
+ toolName: input.toolName,
171
+ toolType: input.toolType
172
+ }, () => invokeWithTransientRetry(() => {
173
+ ui.restart();
174
+ const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
175
+ return input.timeoutMs !== void 0 ? withToolTimeout(invocation, input.timeoutMs, input.toolName) : invocation;
176
+ }, transientRetry, {
177
+ onRetry: /* @__PURE__ */ __name((attempt, error) => {
178
+ publishAgentToolRetry({
179
+ toolName: input.toolName,
180
+ toolCallId: input.toolCallId,
181
+ attempt,
182
+ message: error instanceof Error ? error.message : String(error)
183
+ });
184
+ }, "onRetry")
185
+ }));
186
+ return input.collectUi === true ? wrapToolStepOutput(output, ui.components()) : output;
187
+ }
188
+ };
189
+ _ts_decorate([
190
+ Step({
191
+ retries: 3
192
+ }),
193
+ _ts_metadata("design:type", Function),
194
+ _ts_metadata("design:paramtypes", [
195
+ typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
196
+ ]),
197
+ _ts_metadata("design:returntype", typeof Promise === "undefined" ? Object : Promise)
198
+ ], AgentRunSteps.prototype, "llm", null);
199
+ _ts_decorate([
200
+ Step(),
201
+ _ts_metadata("design:type", Function),
202
+ _ts_metadata("design:paramtypes", [
203
+ typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
204
+ ]),
205
+ _ts_metadata("design:returntype", typeof Promise === "undefined" ? Object : Promise)
206
+ ], AgentRunSteps.prototype, "tool", null);
207
+ AgentRunSteps = _ts_decorate([
208
+ Injectable(),
209
+ _ts_param(0, Inject(AGENT_DEPS_FACTORY)),
210
+ _ts_metadata("design:type", Function),
211
+ _ts_metadata("design:paramtypes", [
212
+ typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
213
+ ])
214
+ ], AgentRunSteps);
215
+
216
+ // src/durable/agent-run.workflow.ts
217
+ function _ts_decorate2(decorators, target, key, desc) {
218
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
219
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
220
+ r = Reflect.decorate(decorators, target, key, desc);
221
+ } else {
222
+ for (var i = decorators.length - 1; i >= 0; i--) {
223
+ if (d = decorators[i]) {
224
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
225
+ }
226
+ }
227
+ }
228
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
229
+ }
230
+ __name(_ts_decorate2, "_ts_decorate");
231
+ function _ts_metadata2(metadataKey, metadataValue) {
232
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
233
+ return Reflect.metadata(metadataKey, metadataValue);
234
+ }
235
+ }
236
+ __name(_ts_metadata2, "_ts_metadata");
237
+ function _ts_param2(paramIndex, decorator) {
238
+ return function(target, key) {
239
+ decorator(target, key, paramIndex);
240
+ };
241
+ }
242
+ __name(_ts_param2, "_ts_param");
243
+ var AgentRunWorkflow = class _AgentRunWorkflow {
244
+ static {
245
+ __name(this, "AgentRunWorkflow");
246
+ }
247
+ factory;
248
+ store;
249
+ runs;
250
+ queue;
251
+ logger = new Logger(_AgentRunWorkflow.name);
252
+ constructor(factory, store, runs, queue) {
253
+ this.factory = factory;
254
+ this.store = store;
255
+ this.runs = runs;
256
+ this.queue = queue;
136
257
  }
137
258
  /**
138
- * Decide what follows a TOP-LEVEL run that is settling on `threadId`, and move the thread there:
139
- * hand it to the next queued message (claimed and popped here, started by the caller), or pause
140
- * the queue, or release the thread. Call it BEFORE the run's terminal frame, so a client reading
141
- * the stream learns what comes next from the `queue` frame, and so the thread is already free
142
- * when the client sees the run end.
259
+ * Move the thread past this settling turn — to the next queued message, started here as a
260
+ * fire-and-forget `ctx.startChild` under the message's own id, or to a paused/empty queue — and
261
+ * answer the `queue` frame to write before the turn's terminal.
143
262
  *
144
- * A no-op returning `{}` on a store without a queue.
145
- */
146
- async plan(args) {
147
- if (!isChatQueueStore(this.store)) {
148
- return {};
149
- }
150
- const store = this.store;
151
- const { threadId, outcome } = args;
152
- if (await store.activeRunForThread(threadId) !== args.runId) {
153
- return {};
154
- }
155
- let holding = args.runId;
156
- for (let attempt = 0; attempt < MAX_DRAIN_ATTEMPTS; attempt += 1) {
157
- const items = await store.listQueue(threadId);
158
- const head = items[0];
159
- if (head === void 0) {
160
- if (holding !== null) {
161
- await store.releaseActiveStream(threadId, holding);
162
- holding = null;
163
- continue;
164
- }
165
- return {};
166
- }
167
- let pause = await store.queuePause(threadId);
168
- if (pause === null) {
169
- const reason = pauseReasonFor(outcome, head);
170
- if (reason !== null) {
171
- pause = {
172
- reason,
173
- ...outcome === "failed" && args.error !== void 0 ? {
174
- message: args.error
175
- } : {},
176
- at: (/* @__PURE__ */ new Date()).toISOString()
177
- };
178
- await store.setQueuePause(threadId, pause);
179
- }
180
- }
181
- if (pause === null) {
182
- const blocked = await this.quotaBlock(head.actor);
183
- if (blocked !== null) {
184
- pause = {
185
- reason: "quota_exceeded",
186
- message: blocked,
187
- at: (/* @__PURE__ */ new Date()).toISOString()
188
- };
189
- await store.setQueuePause(threadId, pause);
190
- }
191
- }
192
- if (pause !== null) {
193
- if (holding !== null) {
194
- await store.releaseActiveStream(threadId, holding);
195
- }
196
- return {
197
- frame: queueFrame(items, pause)
198
- };
199
- }
200
- const claimed = await store.claimActiveStream(threadId, head.id, holding !== null ? {
201
- replacing: holding
202
- } : {});
203
- if (!claimed) {
204
- return {};
205
- }
206
- holding = head.id;
207
- if (!await store.removeQueuedMessage(head.id)) {
208
- await store.releaseActiveStream(threadId, head.id);
209
- holding = null;
210
- continue;
211
- }
212
- const next = {
213
- runId: head.id,
214
- messageId: head.id,
215
- input: await this.inputFor(head)
216
- };
217
- return {
218
- next,
219
- frame: queueFrame(items.slice(1), null, {
220
- messageId: head.id,
221
- runId: head.id
222
- })
223
- };
224
- }
225
- if (holding !== null) {
226
- await store.releaseActiveStream(threadId, holding);
227
- }
228
- return {};
229
- }
230
- /**
231
- * {@link plan}, then start what it picked with `start`. What an in-process settle (the inline
232
- * runner, a durable cancel issued outside a workflow) calls; answers the frame to write.
233
- */
234
- async handoff(args, start) {
235
- const plan = await this.plan(args);
236
- if (plan.next === void 0) {
237
- return plan.frame;
238
- }
239
- return await this.launch(args.threadId, plan.next, start) ? plan.frame : this.pausedFrame(args.threadId);
240
- }
241
- /**
242
- * Start the head of an IDLE thread's queue — after a send queued behind a run that turned out to
243
- * be gone, after a resume, after a stale holder. Does nothing while a live run holds the thread
244
- * (its settle drains) or while the queue is paused. Answers the started run's id, if any.
263
+ * Journaled: the decision (which message, claimed and popped) is one `localStep`, so a replay
264
+ * reads it back instead of popping a second message, and the start is the runtime's own
265
+ * replay-safe spawn. Gated by `ctx.patched`, so a run recorded before the queue existed replays
266
+ * against the history it has. A no-op for a sub-agent's run and on a store without a queue.
245
267
  */
246
- async kick(threadId, runner) {
247
- if (!isChatQueueStore(this.store)) {
268
+ async advanceQueue(ctx, input, outcome, error) {
269
+ const queue = this.queue;
270
+ if (queue === void 0 || !queue.supported || input.sinkRunId !== void 0 || input.deliverTo !== void 0) {
248
271
  return void 0;
249
272
  }
250
- const { live, stale } = await this.holder(threadId, runner);
251
- if (live !== null) {
273
+ if (!await ctx.patched("agent:chat-queue")) {
252
274
  return void 0;
253
275
  }
254
- if (stale !== null) {
255
- this.logger.warn(`thread ${threadId} was held by run ${stale}, which is no longer running`);
256
- }
257
- const plan = await this.plan({
258
- threadId,
259
- runId: stale,
260
- outcome: "completed"
276
+ const plan = await stepOf(ctx)(`queue:${outcome}`, async () => {
277
+ try {
278
+ const settled = outcome === "completed" && await this.isCancelled(ctx.runId) ? "cancelled" : outcome;
279
+ return await queue.plan({
280
+ threadId: input.threadId,
281
+ runId: ctx.runId,
282
+ outcome: settled,
283
+ ...error !== void 0 ? {
284
+ error
285
+ } : {}
286
+ });
287
+ } catch {
288
+ return {};
289
+ }
261
290
  });
262
- if (plan.next === void 0) {
263
- return void 0;
291
+ const next = plan.next;
292
+ if (next === void 0) {
293
+ return plan.frame;
264
294
  }
265
- const started = await this.launch(threadId, plan.next, (input, runId) => runner.start(input, {
266
- runId
267
- }));
268
- if (started && plan.frame !== void 0) {
269
- await this.write(plan.next.runId, plan.frame);
295
+ try {
296
+ await ctx.startChild(_AgentRunWorkflow, next.input, next.runId);
297
+ return plan.frame;
298
+ } catch (failure) {
299
+ if (isWorkflowControlFlowSignal(failure)) {
300
+ throw failure;
301
+ }
302
+ const reason = failure instanceof Error ? failure.message : String(failure);
303
+ return stepOf(ctx)("queue:restore", async () => {
304
+ await queue.restore(input.threadId, next, "start_failed", reason);
305
+ return queue.pausedFrame(input.threadId);
306
+ });
270
307
  }
271
- return started ? plan.next.runId : void 0;
272
308
  }
273
309
  /**
274
- * Start a turn the thread is already claimed for. On failure the message goes back to the head of
275
- * the queue, the queue pauses (`start_failed`), and the claim is released — nothing is lost and
276
- * nothing is left holding the thread.
310
+ * Has someone asked this run to stop? Read from the runtime's own run status, which is where
311
+ * `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
312
+ * `cancelled` once it has settled.
313
+ *
314
+ * Costs a run read at each of the loop's observation points (two per step). That is a row the
315
+ * control plane already has, against a turn that is otherwise making model calls, and it buys the
316
+ * property that matters: the answer comes from a place BOTH the canceller and every pod that might
317
+ * replay this run can see.
318
+ *
319
+ * Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
320
+ * unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
321
+ * because a status read timed out. The loop journals that answer like any other, so two processes
322
+ * can never disagree about what was seen here.
277
323
  */
278
- async launch(threadId, next, start) {
279
- this.starting.add(next.runId);
324
+ async isCancelled(runId) {
280
325
  try {
281
- await start(next.input, next.runId);
282
- return true;
283
- } catch (error) {
284
- const message = error instanceof Error ? error.message : String(error);
285
- this.logger.error(`queued message ${next.messageId} could not start: ${message}`);
286
- await this.restore(threadId, next, "start_failed", message);
326
+ const status = (await this.runs?.getRunDetail(runId))?.run.status;
327
+ return status === "cancelled" || status === "cancelling";
328
+ } catch {
287
329
  return false;
288
- } finally {
289
- this.starting.delete(next.runId);
290
330
  }
291
331
  }
292
- /** Put a turn that never started back at the head of its queue, paused, and free the thread. */
293
- async restore(threadId, next, reason, message) {
294
- const store = this.queueStore();
295
- const { input } = next;
296
- await store.enqueueMessage({
297
- threadId,
298
- actor: input.actor,
299
- content: input.userText,
300
- at: "head",
301
- ...input.attachments !== void 0 ? {
302
- attachments: input.attachments
303
- } : {},
304
- ...input.agentName !== void 0 ? {
305
- agentName: input.agentName
306
- } : {},
307
- ...input.model !== void 0 ? {
308
- model: input.model
309
- } : {},
310
- ...input.pageContext !== void 0 ? {
311
- pageContext: input.pageContext
312
- } : {}
313
- });
314
- await store.setQueuePause(threadId, {
315
- reason,
316
- message,
317
- at: (/* @__PURE__ */ new Date()).toISOString()
318
- });
319
- await store.releaseActiveStream(threadId, next.runId);
320
- }
321
- /** A `queue` frame with the thread's current state. */
322
- async pausedFrame(threadId) {
323
- return {
324
- kind: "queue",
325
- queue: await this.state(threadId)
326
- };
327
- }
328
332
  /**
329
- * Tell whoever is watching the thread that its queue changed: a `queue` frame into the stream of
330
- * the run holding it. Best-effort — a thread nobody is streaming has no one to tell, and the next
331
- * read of the thread carries the queue anyway.
333
+ * Settle the delegation a DETACHED run was started for, when that run ends without an answer.
334
+ *
335
+ * The loop delivers its own success (`deliver:detached`) but cannot catch its own crash, and the
336
+ * runtime settles the run row without knowing anything delegated it. Left alone, the card in the
337
+ * calling conversation says "started" for ever — the one state a reader can neither wait on nor
338
+ * act on. A no-op for every other run: only a detached one carries a delivery address.
332
339
  */
333
- async publish(threadId) {
334
- const state = await this.state(threadId);
335
- const holder = await this.queueStore().activeRunForThread(threadId);
336
- if (holder !== null) {
337
- await this.write(holder, {
338
- kind: "queue",
339
- queue: state
340
- }).catch((error) => {
341
- this.logger.warn(`could not publish the queue of thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
342
- });
343
- }
344
- return state;
345
- }
346
- async write(runId, frame) {
347
- const writer = await this.sink.open(runId);
348
- await writer.write(encodeStreamEvent(frame));
349
- }
350
- /** Why `actor` may not start a turn now (the quota message), or `null`. */
351
- async quotaBlock(actor) {
352
- if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
353
- return null;
354
- }
355
- try {
356
- const { blocked } = await this.quotaProvider.report({
357
- actor
358
- });
359
- if (blocked === void 0) {
360
- return null;
361
- }
362
- return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
363
- } catch {
364
- return null;
365
- }
366
- }
367
- /**
368
- * The turn a queued message starts: everything it was queued with, today's date, and its
369
- * attachments' urls minted afresh — the ones stored at enqueue time may have expired while it
370
- * waited. An attachment the staging store no longer resolves keeps its stored url.
371
- */
372
- async inputFor(message) {
373
- const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
374
- return {
375
- threadId: message.threadId,
376
- actor: message.actor,
377
- userText: message.content,
378
- day: utcDay(),
379
- ...message.agentName !== void 0 ? {
380
- agentName: message.agentName
381
- } : {},
382
- ...attachments.length > 0 ? {
383
- attachments
384
- } : {},
385
- ...message.pageContext !== void 0 ? {
386
- pageContext: message.pageContext
387
- } : {},
388
- ...message.model !== void 0 ? {
389
- model: message.model
390
- } : {}
391
- };
392
- }
393
- async freshAttachments(actor, attachments) {
394
- const staging = this.staging;
395
- if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
396
- return attachments;
397
- }
398
- return Promise.all(attachments.map(async (attachment) => {
399
- try {
400
- return await staging.resolve({
401
- mediaId: attachment.mediaId,
402
- actor
403
- }) ?? attachment;
404
- } catch {
405
- return attachment;
406
- }
407
- }));
408
- }
409
- };
410
- ChatQueueService = _ts_decorate([
411
- Injectable(),
412
- _ts_param(0, Inject(AGENT_STORE)),
413
- _ts_param(1, Inject(AGENT_SINK)),
414
- _ts_param(2, Optional()),
415
- _ts_param(2, Inject(AGENT_QUOTA_PROVIDER)),
416
- _ts_param(3, Optional()),
417
- _ts_param(3, Inject(AGENT_OPTIONS)),
418
- _ts_param(4, Optional()),
419
- _ts_param(4, Inject(AGENT_ATTACHMENT_STAGING)),
420
- _ts_metadata("design:type", Function),
421
- _ts_metadata("design:paramtypes", [
422
- typeof AgentStore === "undefined" ? Object : AgentStore,
423
- typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
424
- typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
425
- typeof Pick === "undefined" ? Object : Pick,
426
- typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
427
- ])
428
- ], ChatQueueService);
429
- function pauseReasonFor(outcome, head) {
430
- if (outcome === "failed") {
431
- return "run_failed";
432
- }
433
- if (outcome === "cancelled" && head.interrupt !== true) {
434
- return "cancelled";
435
- }
436
- return null;
437
- }
438
- __name(pauseReasonFor, "pauseReasonFor");
439
- function queueFrame(items, paused, started) {
440
- return {
441
- kind: "queue",
442
- queue: {
443
- items: items.map(queuedMessageView),
444
- paused
445
- },
446
- ...started !== void 0 ? {
447
- started
448
- } : {}
449
- };
450
- }
451
- __name(queueFrame, "queueFrame");
452
-
453
- // src/durable/agent-run.steps.ts
454
- import { AGENT_DEPS_FACTORY, createFrameBuffer, createUiCollector, encodeStreamEvent as encodeStreamEvent2, invokeWithTransientRetry, observeTurnFrames, publishAgentToolRetry, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
455
- import { Step } from "@dudousxd/nestjs-durable";
456
- import { Inject as Inject2, Injectable as Injectable2 } from "@nestjs/common";
457
-
458
- // src/durable/outside-workflow-ctx.ts
459
- import { runInWorkflowCtx } from "@dudousxd/nestjs-durable-core";
460
- function outsideWorkflowCtx(fn) {
461
- return runInWorkflowCtx(void 0, fn);
462
- }
463
- __name(outsideWorkflowCtx, "outsideWorkflowCtx");
464
- function stepOf(ctx) {
465
- return (name, fn, ...rest) => ctx.localStep(name, (...args) => outsideWorkflowCtx(() => fn(...args)), ...rest);
466
- }
467
- __name(stepOf, "stepOf");
468
-
469
- // src/durable/agent-run.steps.ts
470
- function _ts_decorate2(decorators, target, key, desc) {
471
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
472
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
473
- r = Reflect.decorate(decorators, target, key, desc);
474
- } else {
475
- for (var i = decorators.length - 1; i >= 0; i--) {
476
- if (d = decorators[i]) {
477
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
478
- }
479
- }
480
- }
481
- return c > 3 && r && Object.defineProperty(target, key, r), r;
482
- }
483
- __name(_ts_decorate2, "_ts_decorate");
484
- function _ts_metadata2(metadataKey, metadataValue) {
485
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
486
- return Reflect.metadata(metadataKey, metadataValue);
487
- }
488
- }
489
- __name(_ts_metadata2, "_ts_metadata");
490
- function _ts_param2(paramIndex, decorator) {
491
- return function(target, key) {
492
- decorator(target, key, paramIndex);
493
- };
494
- }
495
- __name(_ts_param2, "_ts_param");
496
- var AgentRunSteps = class {
497
- static {
498
- __name(this, "AgentRunSteps");
499
- }
500
- factory;
501
- constructor(factory) {
502
- this.factory = factory;
503
- }
504
- /**
505
- * Retryable: every side effect (persist/quota/stream markers) happens loop-side under
506
- * `hooks.step`, so a durable retry only re-runs the model call and re-streams its tokens — never
507
- * double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
508
- * across however many turns/retries it takes.
509
- */
510
- llm(input) {
511
- return outsideWorkflowCtx(() => this.runLlm(input));
512
- }
513
- async runLlm(input) {
514
- const deps = this.factory.forAgent(input.agentName);
515
- const tools = withMemoryTool({
516
- tools: withSkillTool({
517
- tools: withAskTool({
518
- tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList, {
519
- ...input.threadId !== void 0 ? {
520
- threadId: input.threadId
521
- } : {},
522
- ...input.agentName !== void 0 ? {
523
- agentName: input.agentName
524
- } : {}
525
- }),
526
- ask: deps.ask
527
- }),
528
- enabled: deps.skills !== void 0
529
- }),
530
- enabled: deps.memory?.provider.write !== void 0
531
- });
532
- const buffer = input.bufferOutput === true ? createFrameBuffer() : void 0;
533
- let writer;
534
- if (buffer !== void 0) {
535
- writer = buffer.writer;
536
- } else {
537
- const opened = await deps.sink.open(input.sinkRunId);
538
- writer = input.childSink ? childSinkWriter(opened) : opened;
539
- }
540
- const frames = observeTurnFrames(writer);
541
- const turn = withTurnFrames(await traceLlmTurn(input.runId, input.step, () => deps.model.runTurn({
542
- system: input.system,
543
- messages: input.messages,
544
- tools,
545
- sink: frames.writer,
546
- ...input.model !== void 0 ? {
547
- model: input.model
548
- } : {}
549
- })), frames.summary());
550
- const stamped = stampToolKinds(turn, deps);
551
- return buffer === void 0 ? stamped : {
552
- ...stamped,
553
- bufferedFrames: buffer.frames()
554
- };
555
- }
556
- /**
557
- * NO DURABLE retries — a tool may not be idempotent (a bare `@Step()` carries none, unlike `llm`
558
- * above). What it does retry, in place, is a CLASSIFIED-TRANSIENT error from the tool's own
559
- * invocation (a DB deadlock, a lock-wait timeout — see `isTransientToolError`): the server rolled
560
- * that work back, so re-invoking it is safe, and doing it here (inside this one step, never a new
561
- * checkpoint) is exactly how the non-dispatched loop path handles it too. The timeout is applied
562
- * HERE, handler-side, via `withToolTimeout` — never as a durable dispatch `timeoutMs`, which
563
- * bounds engine liveness (retryable) rather than the tool's own business outcome — and per
564
- * ATTEMPT, so a retried invocation gets its own fresh timeout window.
565
- */
566
- tool(input) {
567
- return outsideWorkflowCtx(() => this.runTool(input));
568
- }
569
- async runTool(input) {
570
- const deps = this.factory.forAgent(input.ctx.agentName);
571
- if (deps.registry.spec(input.toolName)?.kind === "action" && input.toolType !== "action") {
572
- throw new Error(`tool "${input.toolName}" is an action tool but was dispatched without approval: the dispatching process resolved its kind against a registry that does not have it`);
573
- }
574
- const sinkRunId = input.sinkRunId;
575
- let opened;
576
- const ui = createUiCollector(input.toolCallId, sinkRunId === void 0 ? void 0 : async (event) => {
577
- opened ??= Promise.resolve(deps.sink.open(sinkRunId)).then((writer) => input.childSink === true ? childSinkWriter(writer) : writer);
578
- await (await opened).write(encodeStreamEvent2(event));
579
- });
580
- const ctx = {
581
- ...toolCallContext(input.ctx, input.toolCallId),
582
- emitUi: ui.emit
583
- };
584
- const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
585
- const transientRetry = input.transientRetry === false ? false : {
586
- attempts: input.transientRetry.attempts,
587
- backoffMs: input.transientRetry.backoffMs,
588
- ...localClassify !== void 0 ? {
589
- classify: localClassify
590
- } : {}
591
- };
592
- const output = await traceToolExecution(input.ctx.runId, {
593
- toolCallId: input.toolCallId,
594
- toolName: input.toolName,
595
- toolType: input.toolType
596
- }, () => invokeWithTransientRetry(() => {
597
- ui.restart();
598
- const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
599
- return input.timeoutMs !== void 0 ? withToolTimeout(invocation, input.timeoutMs, input.toolName) : invocation;
600
- }, transientRetry, {
601
- onRetry: /* @__PURE__ */ __name((attempt, error) => {
602
- publishAgentToolRetry({
603
- toolName: input.toolName,
604
- toolCallId: input.toolCallId,
605
- attempt,
606
- message: error instanceof Error ? error.message : String(error)
607
- });
608
- }, "onRetry")
609
- }));
610
- return input.collectUi === true ? wrapToolStepOutput(output, ui.components()) : output;
611
- }
612
- };
613
- _ts_decorate2([
614
- Step({
615
- retries: 3
616
- }),
617
- _ts_metadata2("design:type", Function),
618
- _ts_metadata2("design:paramtypes", [
619
- typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
620
- ]),
621
- _ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
622
- ], AgentRunSteps.prototype, "llm", null);
623
- _ts_decorate2([
624
- Step(),
625
- _ts_metadata2("design:type", Function),
626
- _ts_metadata2("design:paramtypes", [
627
- typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
628
- ]),
629
- _ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
630
- ], AgentRunSteps.prototype, "tool", null);
631
- AgentRunSteps = _ts_decorate2([
632
- Injectable2(),
633
- _ts_param2(0, Inject2(AGENT_DEPS_FACTORY)),
634
- _ts_metadata2("design:type", Function),
635
- _ts_metadata2("design:paramtypes", [
636
- typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
637
- ])
638
- ], AgentRunSteps);
639
-
640
- // src/durable/agent-run.workflow.ts
641
- function _ts_decorate3(decorators, target, key, desc) {
642
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
643
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
644
- r = Reflect.decorate(decorators, target, key, desc);
645
- } else {
646
- for (var i = decorators.length - 1; i >= 0; i--) {
647
- if (d = decorators[i]) {
648
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
649
- }
650
- }
651
- }
652
- return c > 3 && r && Object.defineProperty(target, key, r), r;
653
- }
654
- __name(_ts_decorate3, "_ts_decorate");
655
- function _ts_metadata3(metadataKey, metadataValue) {
656
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
657
- return Reflect.metadata(metadataKey, metadataValue);
658
- }
659
- }
660
- __name(_ts_metadata3, "_ts_metadata");
661
- function _ts_param3(paramIndex, decorator) {
662
- return function(target, key) {
663
- decorator(target, key, paramIndex);
664
- };
665
- }
666
- __name(_ts_param3, "_ts_param");
667
- var AgentRunWorkflow = class _AgentRunWorkflow {
668
- static {
669
- __name(this, "AgentRunWorkflow");
670
- }
671
- factory;
672
- store;
673
- runs;
674
- queue;
675
- logger = new Logger2(_AgentRunWorkflow.name);
676
- constructor(factory, store, runs, queue) {
677
- this.factory = factory;
678
- this.store = store;
679
- this.runs = runs;
680
- this.queue = queue;
681
- }
682
- /**
683
- * Move the thread past this settling turn — to the next queued message, started here as a
684
- * fire-and-forget `ctx.startChild` under the message's own id, or to a paused/empty queue — and
685
- * answer the `queue` frame to write before the turn's terminal.
686
- *
687
- * Journaled: the decision (which message, claimed and popped) is one `localStep`, so a replay
688
- * reads it back instead of popping a second message, and the start is the runtime's own
689
- * replay-safe spawn. Gated by `ctx.patched`, so a run recorded before the queue existed replays
690
- * against the history it has. A no-op for a sub-agent's run and on a store without a queue.
691
- */
692
- async advanceQueue(ctx, input, outcome, error) {
693
- const queue = this.queue;
694
- if (queue === void 0 || !queue.supported || input.sinkRunId !== void 0 || input.deliverTo !== void 0) {
695
- return void 0;
696
- }
697
- if (!await ctx.patched("agent:chat-queue")) {
698
- return void 0;
699
- }
700
- const plan = await stepOf(ctx)(`queue:${outcome}`, async () => {
701
- try {
702
- const settled = outcome === "completed" && await this.isCancelled(ctx.runId) ? "cancelled" : outcome;
703
- return await queue.plan({
704
- threadId: input.threadId,
705
- runId: ctx.runId,
706
- outcome: settled,
707
- ...error !== void 0 ? {
708
- error
709
- } : {}
710
- });
711
- } catch {
712
- return {};
713
- }
714
- });
715
- const next = plan.next;
716
- if (next === void 0) {
717
- return plan.frame;
718
- }
719
- try {
720
- await ctx.startChild(_AgentRunWorkflow, next.input, next.runId);
721
- return plan.frame;
722
- } catch (failure) {
723
- if (isWorkflowControlFlowSignal(failure)) {
724
- throw failure;
725
- }
726
- const reason = failure instanceof Error ? failure.message : String(failure);
727
- return stepOf(ctx)("queue:restore", async () => {
728
- await queue.restore(input.threadId, next, "start_failed", reason);
729
- return queue.pausedFrame(input.threadId);
730
- });
731
- }
732
- }
733
- /**
734
- * Has someone asked this run to stop? Read from the runtime's own run status, which is where
735
- * `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
736
- * `cancelled` once it has settled.
737
- *
738
- * Costs a run read at each of the loop's observation points (two per step). That is a row the
739
- * control plane already has, against a turn that is otherwise making model calls, and it buys the
740
- * property that matters: the answer comes from a place BOTH the canceller and every pod that might
741
- * replay this run can see.
742
- *
743
- * Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
744
- * unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
745
- * because a status read timed out. The loop journals that answer like any other, so two processes
746
- * can never disagree about what was seen here.
747
- */
748
- async isCancelled(runId) {
749
- try {
750
- const status = (await this.runs?.getRunDetail(runId))?.run.status;
751
- return status === "cancelled" || status === "cancelling";
752
- } catch {
753
- return false;
754
- }
755
- }
756
- /**
757
- * Settle the delegation a DETACHED run was started for, when that run ends without an answer.
758
- *
759
- * The loop delivers its own success (`deliver:detached`) but cannot catch its own crash, and the
760
- * runtime settles the run row without knowing anything delegated it. Left alone, the card in the
761
- * calling conversation says "started" for ever — the one state a reader can neither wait on nor
762
- * act on. A no-op for every other run: only a detached one carries a delivery address.
763
- */
764
- async settleDetachedParent(ctx, input, outcome) {
765
- const delivery = input.deliverTo;
766
- if (delivery === void 0) {
767
- return;
340
+ async settleDetachedParent(ctx, input, outcome) {
341
+ const delivery = input.deliverTo;
342
+ if (delivery === void 0) {
343
+ return;
768
344
  }
769
345
  await stepOf(ctx)("deliver:detached:unsettled", () => settleUnsettledDelegation({
770
346
  store: this.store,
@@ -807,7 +383,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
807
383
  end: /* @__PURE__ */ __name(async () => {
808
384
  const frame = await this.advanceQueue(ctx, input, "completed");
809
385
  if (frame !== void 0) {
810
- await writer.write(encodeStreamEvent3(frame));
386
+ await writer.write(encodeStreamEvent2(frame));
811
387
  }
812
388
  await writer.end();
813
389
  }, "end")
@@ -995,26 +571,27 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
995
571
  });
996
572
  const writer = await hooks.openSink();
997
573
  if (queueFrame2 !== void 0) {
998
- await writer.write(encodeStreamEvent3(queueFrame2));
574
+ await writer.write(encodeStreamEvent2(queueFrame2));
999
575
  }
1000
576
  await writer.fail(streamFailure(error));
1001
577
  throw error;
1002
578
  }
1003
579
  }
1004
580
  };
1005
- AgentRunWorkflow = _ts_decorate3([
1006
- Injectable3(),
581
+ AgentRunWorkflow = _ts_decorate2([
582
+ Injectable2(),
1007
583
  Workflow({
1008
584
  name: "agent.run",
1009
585
  version: "1"
1010
586
  }),
1011
- _ts_param3(0, Inject3(AGENT_DEPS_FACTORY2)),
1012
- _ts_param3(1, Inject3(AGENT_STORE2)),
1013
- _ts_param3(2, Optional2()),
1014
- _ts_param3(2, Inject3(RUN_GATEWAY)),
1015
- _ts_param3(3, Optional2()),
1016
- _ts_metadata3("design:type", Function),
1017
- _ts_metadata3("design:paramtypes", [
587
+ _ts_param2(0, Inject2(AGENT_DEPS_FACTORY2)),
588
+ _ts_param2(1, Inject2(AGENT_STORE)),
589
+ _ts_param2(2, Optional()),
590
+ _ts_param2(2, Inject2(RUN_GATEWAY)),
591
+ _ts_param2(3, Optional()),
592
+ _ts_param2(3, Inject2(AGENT_CHAT_QUEUE)),
593
+ _ts_metadata2("design:type", Function),
594
+ _ts_metadata2("design:paramtypes", [
1018
595
  typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
1019
596
  typeof AgentStore === "undefined" ? Object : AgentStore,
1020
597
  typeof RunGateway === "undefined" ? Object : RunGateway,
@@ -1028,10 +605,10 @@ __name(isSignalTimeout, "isSignalTimeout");
1028
605
 
1029
606
  // src/durable/durable-agent-runner.ts
1030
607
  import { randomUUID } from "crypto";
1031
- import { AGENT_SINK as AGENT_SINK2, AGENT_STORE as AGENT_STORE3, encodeStreamEvent as encodeStreamEvent4, releaseThreadRun as releaseThreadRun2 } from "@dudousxd/nestjs-agent-core";
608
+ import { AGENT_SINK, AGENT_STORE as AGENT_STORE2, encodeStreamEvent as encodeStreamEvent3, releaseThreadRun as releaseThreadRun2 } from "@dudousxd/nestjs-agent-core";
1032
609
  import { RUN_GATEWAY as RUN_GATEWAY2, WorkflowService } from "@dudousxd/nestjs-durable";
1033
610
  import { isWorkflowControlFlowSignal as isWorkflowControlFlowSignal2 } from "@dudousxd/nestjs-durable-core";
1034
- import { Inject as Inject4, Injectable as Injectable4, Logger as Logger3, Optional as Optional3 } from "@nestjs/common";
611
+ import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
1035
612
 
1036
613
  // src/in-process-sink.ts
1037
614
  import { AgentStreamError } from "@dudousxd/nestjs-agent-core";
@@ -1102,7 +679,7 @@ var InProcessTokenStreamSink = class {
1102
679
  };
1103
680
 
1104
681
  // src/durable/durable-agent-runner.ts
1105
- function _ts_decorate4(decorators, target, key, desc) {
682
+ function _ts_decorate3(decorators, target, key, desc) {
1106
683
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1107
684
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1108
685
  r = Reflect.decorate(decorators, target, key, desc);
@@ -1115,19 +692,19 @@ function _ts_decorate4(decorators, target, key, desc) {
1115
692
  }
1116
693
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1117
694
  }
1118
- __name(_ts_decorate4, "_ts_decorate");
1119
- function _ts_metadata4(metadataKey, metadataValue) {
695
+ __name(_ts_decorate3, "_ts_decorate");
696
+ function _ts_metadata3(metadataKey, metadataValue) {
1120
697
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1121
698
  return Reflect.metadata(metadataKey, metadataValue);
1122
699
  }
1123
700
  }
1124
- __name(_ts_metadata4, "_ts_metadata");
1125
- function _ts_param4(paramIndex, decorator) {
701
+ __name(_ts_metadata3, "_ts_metadata");
702
+ function _ts_param3(paramIndex, decorator) {
1126
703
  return function(target, key) {
1127
704
  decorator(target, key, paramIndex);
1128
705
  };
1129
706
  }
1130
- __name(_ts_param4, "_ts_param");
707
+ __name(_ts_param3, "_ts_param");
1131
708
  function threadOfRun(input) {
1132
709
  if (typeof input !== "object" || input === null || !("threadId" in input)) {
1133
710
  return void 0;
@@ -1148,7 +725,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
1148
725
  store;
1149
726
  sink;
1150
727
  queue;
1151
- logger = new Logger3(_DurableAgentRunner.name);
728
+ logger = new Logger2(_DurableAgentRunner.name);
1152
729
  constructor(workflows, runs, store, sink, queue) {
1153
730
  this.workflows = workflows;
1154
731
  this.runs = runs;
@@ -1235,7 +812,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
1235
812
  runId: nextRunId
1236
813
  }));
1237
814
  if (frame !== void 0) {
1238
- await writer.write(encodeStreamEvent4(frame));
815
+ await writer.write(encodeStreamEvent3(frame));
1239
816
  }
1240
817
  } catch (error) {
1241
818
  this.logger.error(`could not advance the queue of thread ${threadId} after cancelling ${runId}: ${error instanceof Error ? error.message : String(error)}`);
@@ -1243,7 +820,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
1243
820
  }
1244
821
  await releaseThreadRun2(this.store, threadId, runId);
1245
822
  }
1246
- await writer.write(encodeStreamEvent4({
823
+ await writer.write(encodeStreamEvent3({
1247
824
  kind: "cancelled"
1248
825
  }));
1249
826
  await writer.end();
@@ -1270,14 +847,15 @@ var DurableAgentRunner = class _DurableAgentRunner {
1270
847
  }
1271
848
  }
1272
849
  };
1273
- DurableAgentRunner = _ts_decorate4([
1274
- Injectable4(),
1275
- _ts_param4(1, Inject4(RUN_GATEWAY2)),
1276
- _ts_param4(2, Inject4(AGENT_STORE3)),
1277
- _ts_param4(3, Inject4(AGENT_SINK2)),
1278
- _ts_param4(4, Optional3()),
1279
- _ts_metadata4("design:type", Function),
1280
- _ts_metadata4("design:paramtypes", [
850
+ DurableAgentRunner = _ts_decorate3([
851
+ Injectable3(),
852
+ _ts_param3(1, Inject3(RUN_GATEWAY2)),
853
+ _ts_param3(2, Inject3(AGENT_STORE2)),
854
+ _ts_param3(3, Inject3(AGENT_SINK)),
855
+ _ts_param3(4, Optional2()),
856
+ _ts_param3(4, Inject3(AGENT_CHAT_QUEUE)),
857
+ _ts_metadata3("design:type", Function),
858
+ _ts_metadata3("design:paramtypes", [
1281
859
  typeof WorkflowService === "undefined" ? Object : WorkflowService,
1282
860
  typeof RunGateway === "undefined" ? Object : RunGateway,
1283
861
  typeof AgentStore === "undefined" ? Object : AgentStore,
@@ -1289,7 +867,7 @@ DurableAgentRunner = _ts_decorate4([
1289
867
  // src/durable/agent-durable.module.ts
1290
868
  import { AGENT_DURABLE_RUNNER } from "@dudousxd/nestjs-agent-core";
1291
869
  import { Global, Module } from "@nestjs/common";
1292
- function _ts_decorate5(decorators, target, key, desc) {
870
+ function _ts_decorate4(decorators, target, key, desc) {
1293
871
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1294
872
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1295
873
  r = Reflect.decorate(decorators, target, key, desc);
@@ -1302,7 +880,7 @@ function _ts_decorate5(decorators, target, key, desc) {
1302
880
  }
1303
881
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1304
882
  }
1305
- __name(_ts_decorate5, "_ts_decorate");
883
+ __name(_ts_decorate4, "_ts_decorate");
1306
884
  function runnerProviders() {
1307
885
  return [
1308
886
  DurableAgentRunner,
@@ -1318,7 +896,7 @@ var AgentDurableHttpModule = class AgentDurableHttpModule2 {
1318
896
  __name(this, "AgentDurableHttpModule");
1319
897
  }
1320
898
  };
1321
- AgentDurableHttpModule = _ts_decorate5([
899
+ AgentDurableHttpModule = _ts_decorate4([
1322
900
  Global(),
1323
901
  Module({
1324
902
  providers: [
@@ -1356,7 +934,7 @@ var AgentDurableModule = class _AgentDurableModule {
1356
934
  };
1357
935
  }
1358
936
  };
1359
- AgentDurableModule = _ts_decorate5([
937
+ AgentDurableModule = _ts_decorate4([
1360
938
  Global(),
1361
939
  Module({
1362
940
  providers: [
@@ -1378,10 +956,10 @@ import { Global as Global2, Logger as Logger8, Module as Module2 } from "@nestjs
1378
956
  import { DiscoveryModule, ModulesContainer, RouterModule } from "@nestjs/core";
1379
957
 
1380
958
  // src/agent-deps.factory.ts
1381
- import { AGENT_MEMORY, AGENT_MODEL, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_REGISTRY, AGENT_ROLES_POLICY, AGENT_SINK as AGENT_SINK3, AGENT_SKILLS, AGENT_STORE as AGENT_STORE4, AGENT_TOOL_REGISTRY, AgentRegistry as AgentRegistry2, ToolRegistry as ToolRegistry2, normalizeDelegation, summarizeWithModel, windowHistory } from "@dudousxd/nestjs-agent-core";
1382
- import { AGENT_OPTIONS as AGENT_OPTIONS2 } from "@dudousxd/nestjs-agent-core";
1383
- import { Inject as Inject5, Injectable as Injectable5, Optional as Optional4 } from "@nestjs/common";
1384
- function _ts_decorate6(decorators, target, key, desc) {
959
+ import { AGENT_MEMORY, AGENT_MODEL, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_REGISTRY, AGENT_ROLES_POLICY, AGENT_SINK as AGENT_SINK2, AGENT_SKILLS, AGENT_STORE as AGENT_STORE3, AGENT_TOOL_REGISTRY, AgentRegistry as AgentRegistry2, ToolRegistry as ToolRegistry2, normalizeDelegation, summarizeWithModel, windowHistory } from "@dudousxd/nestjs-agent-core";
960
+ import { AGENT_OPTIONS } from "@dudousxd/nestjs-agent-core";
961
+ import { Inject as Inject4, Injectable as Injectable4, Optional as Optional3 } from "@nestjs/common";
962
+ function _ts_decorate5(decorators, target, key, desc) {
1385
963
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1386
964
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1387
965
  r = Reflect.decorate(decorators, target, key, desc);
@@ -1394,19 +972,19 @@ function _ts_decorate6(decorators, target, key, desc) {
1394
972
  }
1395
973
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1396
974
  }
1397
- __name(_ts_decorate6, "_ts_decorate");
1398
- function _ts_metadata5(metadataKey, metadataValue) {
975
+ __name(_ts_decorate5, "_ts_decorate");
976
+ function _ts_metadata4(metadataKey, metadataValue) {
1399
977
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1400
978
  return Reflect.metadata(metadataKey, metadataValue);
1401
979
  }
1402
980
  }
1403
- __name(_ts_metadata5, "_ts_metadata");
1404
- function _ts_param5(paramIndex, decorator) {
981
+ __name(_ts_metadata4, "_ts_metadata");
982
+ function _ts_param4(paramIndex, decorator) {
1405
983
  return function(target, key) {
1406
984
  decorator(target, key, paramIndex);
1407
985
  };
1408
986
  }
1409
- __name(_ts_param5, "_ts_param");
987
+ __name(_ts_param4, "_ts_param");
1410
988
  function delegateToolName(args) {
1411
989
  const slug = args.target.replace(/[^a-zA-Z0-9]+/g, "_");
1412
990
  return args.detached === true ? `start_${slug}` : `ask_${slug}`;
@@ -1440,204 +1018,631 @@ var AgentDepsFactory2 = class {
1440
1018
  this.skills = skills;
1441
1019
  this.memory = memory;
1442
1020
  }
1443
- /** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
1444
- memoryConfig() {
1445
- return this.memory;
1021
+ /** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
1022
+ memoryConfig() {
1023
+ return this.memory;
1024
+ }
1025
+ /** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
1026
+ skillsConfig() {
1027
+ return this.skills;
1028
+ }
1029
+ /**
1030
+ * The agent a turn uses when the caller names none: the explicit `defaultAgent` option, else the
1031
+ * sole registered `@Agent` when there is exactly one, else `'default'` (a bare assistant).
1032
+ */
1033
+ defaultAgentName() {
1034
+ if (this.options.defaultAgent !== void 0) {
1035
+ return this.options.defaultAgent;
1036
+ }
1037
+ const registered = this.agents.list();
1038
+ return registered.length === 1 ? registered[0]?.name ?? "default" : "default";
1039
+ }
1040
+ effectiveTools(definition) {
1041
+ if (definition === void 0) {
1042
+ return void 0;
1043
+ }
1044
+ const delegated = (definition.delegatesTo ?? []).map((edge) => {
1045
+ const { agent, detached } = normalizeDelegation(edge);
1046
+ return delegateToolName({
1047
+ target: agent,
1048
+ detached
1049
+ });
1050
+ });
1051
+ if (definition.tools === void 0 && delegated.length === 0) {
1052
+ return void 0;
1053
+ }
1054
+ return [
1055
+ ...definition.tools ?? [],
1056
+ ...delegated
1057
+ ];
1058
+ }
1059
+ forAgent(agentName) {
1060
+ const name = agentName ?? this.defaultAgentName();
1061
+ const definition = this.agents.get(name);
1062
+ const toolAllowList = this.effectiveTools(definition);
1063
+ const followUpsCount = this.followUpsCount();
1064
+ const retrieval = this.options.retrieval;
1065
+ const historyPolicy = this.historyPolicy(definition);
1066
+ const ask = definition?.ask ?? this.options.ask;
1067
+ return {
1068
+ model: this.model,
1069
+ store: this.store,
1070
+ sink: this.sink,
1071
+ rolesPolicy: this.rolesPolicy,
1072
+ ...this.options.approvalPolicy !== void 0 ? {
1073
+ approvalPolicy: this.options.approvalPolicy
1074
+ } : {},
1075
+ registry: this.registry,
1076
+ promptContributors: this.promptContributors,
1077
+ systemPrompt: definition?.systemPrompt ?? this.options.systemPrompt ?? "You are a helpful assistant.",
1078
+ maxSteps: definition?.maxSteps ?? 8,
1079
+ ...definition?.maxDelegationDepth !== void 0 ? {
1080
+ maxDelegationDepth: definition.maxDelegationDepth
1081
+ } : {},
1082
+ ...definition?.maxAgentAppearances !== void 0 ? {
1083
+ maxAgentAppearances: definition.maxAgentAppearances
1084
+ } : {},
1085
+ ...definition?.modelId !== void 0 ? {
1086
+ modelId: definition.modelId
1087
+ } : {},
1088
+ ...this.pricingStore !== void 0 ? {
1089
+ pricingStore: this.pricingStore
1090
+ } : {},
1091
+ ...historyPolicy !== void 0 ? {
1092
+ historyPolicy
1093
+ } : {},
1094
+ inputProcessors: this.options.inputProcessors ?? [],
1095
+ outputProcessors: this.options.outputProcessors ?? [],
1096
+ ...definition?.outputSchema !== void 0 ? {
1097
+ outputSchema: definition.outputSchema
1098
+ } : {},
1099
+ ...definition?.outputRepairAttempts !== void 0 ? {
1100
+ outputRepairAttempts: definition.outputRepairAttempts
1101
+ } : {},
1102
+ ...definition?.intake !== void 0 ? {
1103
+ intake: definition.intake
1104
+ } : {},
1105
+ // Most specific wins, exactly as `historyPolicy` resolves: the agent's own `@Agent({ ask })`,
1106
+ // else the module-wide flag. Both are module config, so every process of a deployment agrees.
1107
+ ...ask === true ? {
1108
+ ask: true
1109
+ } : {},
1110
+ // Module-wide, never per-agent: a skill is a procedure, not a persona, and an agent that could
1111
+ // opt out of one would make the catalog depend on which agent a turn happened to select — a
1112
+ // per-turn fact the worker re-deriving the tool list cannot see.
1113
+ ...this.skills !== void 0 ? {
1114
+ skills: this.skills
1115
+ } : {},
1116
+ // Module-wide, never per-agent, for the same reason skills are: which memories a turn carries
1117
+ // cannot depend on which persona answered it, or the worker re-deriving a dispatched turn's
1118
+ // tool list would disagree with the loop about whether `remember` was offered.
1119
+ ...this.memory !== void 0 ? {
1120
+ memory: this.memory
1121
+ } : {},
1122
+ ...toolAllowList !== void 0 ? {
1123
+ toolAllowList
1124
+ } : {},
1125
+ ...this.options.toolTimeoutMs !== void 0 ? {
1126
+ toolTimeoutMs: this.options.toolTimeoutMs
1127
+ } : {},
1128
+ ...this.options.toolTransientRetry !== void 0 ? {
1129
+ toolTransientRetry: this.options.toolTransientRetry
1130
+ } : {},
1131
+ ...followUpsCount !== void 0 ? {
1132
+ followUpsCount
1133
+ } : {},
1134
+ ...retrieval?.mode === "inject" ? {
1135
+ retriever: retrieval.retriever,
1136
+ ...retrieval.topK !== void 0 ? {
1137
+ retrievalTopK: retrieval.topK
1138
+ } : {}
1139
+ } : {}
1140
+ };
1141
+ }
1142
+ /**
1143
+ * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
1144
+ * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
1145
+ * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
1146
+ */
1147
+ historyPolicy(definition) {
1148
+ if (definition?.history !== void 0) {
1149
+ return this.window(definition.history);
1150
+ }
1151
+ if (this.options.historyPolicy !== void 0) {
1152
+ return this.options.historyPolicy;
1153
+ }
1154
+ return this.options.history === void 0 ? void 0 : this.window(this.options.history);
1155
+ }
1156
+ /** Build the built-in window policy from the declarative `{ maxMessages, maxTokens, summarize }`. */
1157
+ window(config) {
1158
+ return windowHistory({
1159
+ ...config.maxMessages !== void 0 ? {
1160
+ maxMessages: config.maxMessages
1161
+ } : {},
1162
+ ...config.maxTokens !== void 0 ? {
1163
+ maxTokens: config.maxTokens
1164
+ } : {},
1165
+ // The shared module model, not a per-agent one: an agent's `modelId` is an accounting label,
1166
+ // and only one provider is ever bound.
1167
+ ...config.summarize === true ? {
1168
+ summarize: summarizeWithModel(this.model)
1169
+ } : {}
1170
+ });
1171
+ }
1172
+ /** Normalize the `followUps` option (`true` → 3, `{ count }` → count) to a number, or undefined. */
1173
+ followUpsCount() {
1174
+ const followUps = this.options.followUps;
1175
+ if (followUps === true) {
1176
+ return 3;
1177
+ }
1178
+ if (typeof followUps === "object") {
1179
+ return followUps.count;
1180
+ }
1181
+ return void 0;
1182
+ }
1183
+ };
1184
+ AgentDepsFactory2 = _ts_decorate5([
1185
+ Injectable4(),
1186
+ _ts_param4(0, Inject4(AGENT_OPTIONS)),
1187
+ _ts_param4(1, Inject4(AGENT_MODEL)),
1188
+ _ts_param4(2, Inject4(AGENT_STORE3)),
1189
+ _ts_param4(3, Inject4(AGENT_SINK2)),
1190
+ _ts_param4(4, Inject4(AGENT_ROLES_POLICY)),
1191
+ _ts_param4(5, Inject4(AGENT_TOOL_REGISTRY)),
1192
+ _ts_param4(6, Inject4(AGENT_REGISTRY)),
1193
+ _ts_param4(7, Inject4(AGENT_PROMPT_CONTRIBUTORS)),
1194
+ _ts_param4(8, Optional3()),
1195
+ _ts_param4(8, Inject4(AGENT_PRICING_STORE)),
1196
+ _ts_param4(9, Optional3()),
1197
+ _ts_param4(9, Inject4(AGENT_SKILLS)),
1198
+ _ts_param4(10, Optional3()),
1199
+ _ts_param4(10, Inject4(AGENT_MEMORY)),
1200
+ _ts_metadata4("design:type", Function),
1201
+ _ts_metadata4("design:paramtypes", [
1202
+ typeof AgentModuleOptions === "undefined" ? Object : AgentModuleOptions,
1203
+ typeof ModelProvider === "undefined" ? Object : ModelProvider,
1204
+ typeof AgentStore === "undefined" ? Object : AgentStore,
1205
+ typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
1206
+ typeof RolesPolicy === "undefined" ? Object : RolesPolicy,
1207
+ typeof ToolRegistry2 === "undefined" ? Object : ToolRegistry2,
1208
+ typeof AgentRegistry2 === "undefined" ? Object : AgentRegistry2,
1209
+ Array,
1210
+ Object,
1211
+ Object,
1212
+ Object
1213
+ ])
1214
+ ], AgentDepsFactory2);
1215
+
1216
+ // src/agent.service.ts
1217
+ import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS3, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE5, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval, readElicitationQuestions, settleDeadRun as settleDeadRun2, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
1218
+ import { BadRequestException, ConflictException as ConflictException2, ForbiddenException, GoneException, HttpException, HttpStatus, Inject as Inject6, Injectable as Injectable6, NotFoundException, NotImplementedException as NotImplementedException2, Optional as Optional5 } from "@nestjs/common";
1219
+
1220
+ // src/queue/chat-queue.service.ts
1221
+ import { AGENT_ATTACHMENT_STAGING, AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_QUOTA_PROVIDER, AGENT_SINK as AGENT_SINK3, AGENT_STORE as AGENT_STORE4, encodeStreamEvent as encodeStreamEvent4, isChatQueueStore, queuedMessageView } from "@dudousxd/nestjs-agent-core";
1222
+ import { Inject as Inject5, Injectable as Injectable5, Logger as Logger3, NotImplementedException, Optional as Optional4 } from "@nestjs/common";
1223
+ function _ts_decorate6(decorators, target, key, desc) {
1224
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1225
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1226
+ r = Reflect.decorate(decorators, target, key, desc);
1227
+ } else {
1228
+ for (var i = decorators.length - 1; i >= 0; i--) {
1229
+ if (d = decorators[i]) {
1230
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1231
+ }
1232
+ }
1233
+ }
1234
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1235
+ }
1236
+ __name(_ts_decorate6, "_ts_decorate");
1237
+ function _ts_metadata5(metadataKey, metadataValue) {
1238
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1239
+ return Reflect.metadata(metadataKey, metadataValue);
1240
+ }
1241
+ }
1242
+ __name(_ts_metadata5, "_ts_metadata");
1243
+ function _ts_param5(paramIndex, decorator) {
1244
+ return function(target, key) {
1245
+ decorator(target, key, paramIndex);
1246
+ };
1247
+ }
1248
+ __name(_ts_param5, "_ts_param");
1249
+ var MAX_DRAIN_ATTEMPTS = 8;
1250
+ var ChatQueueService2 = class _ChatQueueService {
1251
+ static {
1252
+ __name(this, "ChatQueueService");
1253
+ }
1254
+ store;
1255
+ sink;
1256
+ quotaProvider;
1257
+ options;
1258
+ staging;
1259
+ logger = new Logger3(_ChatQueueService.name);
1260
+ /**
1261
+ * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
1262
+ * alive whatever the runner says — it simply has not registered the run yet.
1263
+ */
1264
+ starting = /* @__PURE__ */ new Set();
1265
+ constructor(store, sink, quotaProvider, options, staging) {
1266
+ this.store = store;
1267
+ this.sink = sink;
1268
+ this.quotaProvider = quotaProvider;
1269
+ this.options = options;
1270
+ this.staging = staging;
1271
+ }
1272
+ /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
1273
+ get supported() {
1274
+ return isChatQueueStore(this.store);
1446
1275
  }
1447
- /** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
1448
- skillsConfig() {
1449
- return this.skills;
1276
+ /** The bound store as a queue store, or a 501 naming what is missing. */
1277
+ queueStore() {
1278
+ if (!isChatQueueStore(this.store)) {
1279
+ throw new NotImplementedException("Queueing messages requires an AgentStore that implements ChatQueueStore (enqueueMessage, listQueue, \u2026, claimActiveStream, releaseActiveStream); the bound store does not.");
1280
+ }
1281
+ return this.store;
1282
+ }
1283
+ async state(threadId) {
1284
+ const store = this.queueStore();
1285
+ const [items, paused] = await Promise.all([
1286
+ store.listQueue(threadId),
1287
+ store.queuePause(threadId)
1288
+ ]);
1289
+ return {
1290
+ items: items.map(queuedMessageView),
1291
+ paused
1292
+ };
1450
1293
  }
1451
1294
  /**
1452
- * The agent a turn uses when the caller names none: the explicit `defaultAgent` option, else the
1453
- * sole registered `@Agent` when there is exactly one, else `'default'` (a bare assistant).
1295
+ * Whether `holder` is still running. A run this process is about to start is; otherwise the
1296
+ * runner decides, and a runner that cannot tell says yes.
1454
1297
  */
1455
- defaultAgentName() {
1456
- if (this.options.defaultAgent !== void 0) {
1457
- return this.options.defaultAgent;
1298
+ async isAlive(holder, runner) {
1299
+ if (this.starting.has(holder)) {
1300
+ return true;
1301
+ }
1302
+ if (typeof runner.isRunActive !== "function") {
1303
+ return true;
1304
+ }
1305
+ try {
1306
+ return await runner.isRunActive(holder);
1307
+ } catch {
1308
+ return true;
1458
1309
  }
1459
- const registered = this.agents.list();
1460
- return registered.length === 1 ? registered[0]?.name ?? "default" : "default";
1461
1310
  }
1462
- effectiveTools(definition) {
1463
- if (definition === void 0) {
1464
- return void 0;
1311
+ /**
1312
+ * The run holding `threadId`, or `null` when it is free — a holder the runner reports dead counts
1313
+ * as free, and is returned as `stale` so the caller can claim over it.
1314
+ */
1315
+ async holder(threadId, runner) {
1316
+ const holder = await this.queueStore().activeRunForThread(threadId);
1317
+ if (holder === null) {
1318
+ return {
1319
+ live: null,
1320
+ stale: null
1321
+ };
1465
1322
  }
1466
- const delegated = (definition.delegatesTo ?? []).map((edge) => {
1467
- const { agent, detached } = normalizeDelegation(edge);
1468
- return delegateToolName({
1469
- target: agent,
1470
- detached
1471
- });
1472
- });
1473
- if (definition.tools === void 0 && delegated.length === 0) {
1474
- return void 0;
1323
+ return await this.isAlive(holder, runner) ? {
1324
+ live: holder,
1325
+ stale: null
1326
+ } : {
1327
+ live: null,
1328
+ stale: holder
1329
+ };
1330
+ }
1331
+ /**
1332
+ * Decide what follows a TOP-LEVEL run that is settling on `threadId`, and move the thread there:
1333
+ * hand it to the next queued message (claimed and popped here, started by the caller), or pause
1334
+ * the queue, or release the thread. Call it BEFORE the run's terminal frame, so a client reading
1335
+ * the stream learns what comes next from the `queue` frame, and so the thread is already free
1336
+ * when the client sees the run end.
1337
+ *
1338
+ * A no-op returning `{}` on a store without a queue.
1339
+ */
1340
+ async plan(args) {
1341
+ if (!isChatQueueStore(this.store)) {
1342
+ return {};
1475
1343
  }
1476
- return [
1477
- ...definition.tools ?? [],
1478
- ...delegated
1479
- ];
1344
+ const store = this.store;
1345
+ const { threadId, outcome } = args;
1346
+ if (await store.activeRunForThread(threadId) !== args.runId) {
1347
+ return {};
1348
+ }
1349
+ let holding = args.runId;
1350
+ for (let attempt = 0; attempt < MAX_DRAIN_ATTEMPTS; attempt += 1) {
1351
+ const items = await store.listQueue(threadId);
1352
+ const head = items[0];
1353
+ if (head === void 0) {
1354
+ if (holding !== null) {
1355
+ await store.releaseActiveStream(threadId, holding);
1356
+ holding = null;
1357
+ continue;
1358
+ }
1359
+ return {};
1360
+ }
1361
+ let pause = await store.queuePause(threadId);
1362
+ if (pause === null) {
1363
+ const reason = pauseReasonFor(outcome, head);
1364
+ if (reason !== null) {
1365
+ pause = {
1366
+ reason,
1367
+ ...outcome === "failed" && args.error !== void 0 ? {
1368
+ message: args.error
1369
+ } : {},
1370
+ at: (/* @__PURE__ */ new Date()).toISOString()
1371
+ };
1372
+ await store.setQueuePause(threadId, pause);
1373
+ }
1374
+ }
1375
+ if (pause === null) {
1376
+ const blocked = await this.quotaBlock(head.actor);
1377
+ if (blocked !== null) {
1378
+ pause = {
1379
+ reason: "quota_exceeded",
1380
+ message: blocked,
1381
+ at: (/* @__PURE__ */ new Date()).toISOString()
1382
+ };
1383
+ await store.setQueuePause(threadId, pause);
1384
+ }
1385
+ }
1386
+ if (pause !== null) {
1387
+ if (holding !== null) {
1388
+ await store.releaseActiveStream(threadId, holding);
1389
+ }
1390
+ return {
1391
+ frame: queueFrame(items, pause)
1392
+ };
1393
+ }
1394
+ const claimed = await store.claimActiveStream(threadId, head.id, holding !== null ? {
1395
+ replacing: holding
1396
+ } : {});
1397
+ if (!claimed) {
1398
+ return {};
1399
+ }
1400
+ holding = head.id;
1401
+ if (!await store.removeQueuedMessage(head.id)) {
1402
+ await store.releaseActiveStream(threadId, head.id);
1403
+ holding = null;
1404
+ continue;
1405
+ }
1406
+ const next = {
1407
+ runId: head.id,
1408
+ messageId: head.id,
1409
+ input: await this.inputFor(head)
1410
+ };
1411
+ return {
1412
+ next,
1413
+ frame: queueFrame(items.slice(1), null, {
1414
+ messageId: head.id,
1415
+ runId: head.id
1416
+ })
1417
+ };
1418
+ }
1419
+ if (holding !== null) {
1420
+ await store.releaseActiveStream(threadId, holding);
1421
+ }
1422
+ return {};
1480
1423
  }
1481
- forAgent(agentName) {
1482
- const name = agentName ?? this.defaultAgentName();
1483
- const definition = this.agents.get(name);
1484
- const toolAllowList = this.effectiveTools(definition);
1485
- const followUpsCount = this.followUpsCount();
1486
- const retrieval = this.options.retrieval;
1487
- const historyPolicy = this.historyPolicy(definition);
1488
- const ask = definition?.ask ?? this.options.ask;
1489
- return {
1490
- model: this.model,
1491
- store: this.store,
1492
- sink: this.sink,
1493
- rolesPolicy: this.rolesPolicy,
1494
- ...this.options.approvalPolicy !== void 0 ? {
1495
- approvalPolicy: this.options.approvalPolicy
1496
- } : {},
1497
- registry: this.registry,
1498
- promptContributors: this.promptContributors,
1499
- systemPrompt: definition?.systemPrompt ?? this.options.systemPrompt ?? "You are a helpful assistant.",
1500
- maxSteps: definition?.maxSteps ?? 8,
1501
- ...definition?.maxDelegationDepth !== void 0 ? {
1502
- maxDelegationDepth: definition.maxDelegationDepth
1503
- } : {},
1504
- ...definition?.maxAgentAppearances !== void 0 ? {
1505
- maxAgentAppearances: definition.maxAgentAppearances
1506
- } : {},
1507
- ...definition?.modelId !== void 0 ? {
1508
- modelId: definition.modelId
1509
- } : {},
1510
- ...this.pricingStore !== void 0 ? {
1511
- pricingStore: this.pricingStore
1512
- } : {},
1513
- ...historyPolicy !== void 0 ? {
1514
- historyPolicy
1515
- } : {},
1516
- inputProcessors: this.options.inputProcessors ?? [],
1517
- outputProcessors: this.options.outputProcessors ?? [],
1518
- ...definition?.outputSchema !== void 0 ? {
1519
- outputSchema: definition.outputSchema
1520
- } : {},
1521
- ...definition?.outputRepairAttempts !== void 0 ? {
1522
- outputRepairAttempts: definition.outputRepairAttempts
1523
- } : {},
1524
- ...definition?.intake !== void 0 ? {
1525
- intake: definition.intake
1526
- } : {},
1527
- // Most specific wins, exactly as `historyPolicy` resolves: the agent's own `@Agent({ ask })`,
1528
- // else the module-wide flag. Both are module config, so every process of a deployment agrees.
1529
- ...ask === true ? {
1530
- ask: true
1531
- } : {},
1532
- // Module-wide, never per-agent: a skill is a procedure, not a persona, and an agent that could
1533
- // opt out of one would make the catalog depend on which agent a turn happened to select — a
1534
- // per-turn fact the worker re-deriving the tool list cannot see.
1535
- ...this.skills !== void 0 ? {
1536
- skills: this.skills
1537
- } : {},
1538
- // Module-wide, never per-agent, for the same reason skills are: which memories a turn carries
1539
- // cannot depend on which persona answered it, or the worker re-deriving a dispatched turn's
1540
- // tool list would disagree with the loop about whether `remember` was offered.
1541
- ...this.memory !== void 0 ? {
1542
- memory: this.memory
1543
- } : {},
1544
- ...toolAllowList !== void 0 ? {
1545
- toolAllowList
1546
- } : {},
1547
- ...this.options.toolTimeoutMs !== void 0 ? {
1548
- toolTimeoutMs: this.options.toolTimeoutMs
1424
+ /**
1425
+ * {@link plan}, then start what it picked with `start`. What an in-process settle (the inline
1426
+ * runner, a durable cancel issued outside a workflow) calls; answers the frame to write.
1427
+ */
1428
+ async handoff(args, start) {
1429
+ const plan = await this.plan(args);
1430
+ if (plan.next === void 0) {
1431
+ return plan.frame;
1432
+ }
1433
+ return await this.launch(args.threadId, plan.next, start) ? plan.frame : this.pausedFrame(args.threadId);
1434
+ }
1435
+ /**
1436
+ * Start the head of an IDLE thread's queue — after a send queued behind a run that turned out to
1437
+ * be gone, after a resume, after a stale holder. Does nothing while a live run holds the thread
1438
+ * (its settle drains) or while the queue is paused. Answers the started run's id, if any.
1439
+ */
1440
+ async kick(threadId, runner) {
1441
+ if (!isChatQueueStore(this.store)) {
1442
+ return void 0;
1443
+ }
1444
+ const { live, stale } = await this.holder(threadId, runner);
1445
+ if (live !== null) {
1446
+ return void 0;
1447
+ }
1448
+ if (stale !== null) {
1449
+ this.logger.warn(`thread ${threadId} was held by run ${stale}, which is no longer running`);
1450
+ }
1451
+ const plan = await this.plan({
1452
+ threadId,
1453
+ runId: stale,
1454
+ outcome: "completed"
1455
+ });
1456
+ if (plan.next === void 0) {
1457
+ return void 0;
1458
+ }
1459
+ const started = await this.launch(threadId, plan.next, (input, runId) => runner.start(input, {
1460
+ runId
1461
+ }));
1462
+ if (started && plan.frame !== void 0) {
1463
+ await this.write(plan.next.runId, plan.frame);
1464
+ }
1465
+ return started ? plan.next.runId : void 0;
1466
+ }
1467
+ /**
1468
+ * Start a turn the thread is already claimed for. On failure the message goes back to the head of
1469
+ * the queue, the queue pauses (`start_failed`), and the claim is released — nothing is lost and
1470
+ * nothing is left holding the thread.
1471
+ */
1472
+ async launch(threadId, next, start) {
1473
+ this.starting.add(next.runId);
1474
+ try {
1475
+ await start(next.input, next.runId);
1476
+ return true;
1477
+ } catch (error) {
1478
+ const message = error instanceof Error ? error.message : String(error);
1479
+ this.logger.error(`queued message ${next.messageId} could not start: ${message}`);
1480
+ await this.restore(threadId, next, "start_failed", message);
1481
+ return false;
1482
+ } finally {
1483
+ this.starting.delete(next.runId);
1484
+ }
1485
+ }
1486
+ /** Put a turn that never started back at the head of its queue, paused, and free the thread. */
1487
+ async restore(threadId, next, reason, message) {
1488
+ const store = this.queueStore();
1489
+ const { input } = next;
1490
+ await store.enqueueMessage({
1491
+ threadId,
1492
+ actor: input.actor,
1493
+ content: input.userText,
1494
+ at: "head",
1495
+ ...input.attachments !== void 0 ? {
1496
+ attachments: input.attachments
1549
1497
  } : {},
1550
- ...this.options.toolTransientRetry !== void 0 ? {
1551
- toolTransientRetry: this.options.toolTransientRetry
1498
+ ...input.agentName !== void 0 ? {
1499
+ agentName: input.agentName
1552
1500
  } : {},
1553
- ...followUpsCount !== void 0 ? {
1554
- followUpsCount
1501
+ ...input.model !== void 0 ? {
1502
+ model: input.model
1555
1503
  } : {},
1556
- ...retrieval?.mode === "inject" ? {
1557
- retriever: retrieval.retriever,
1558
- ...retrieval.topK !== void 0 ? {
1559
- retrievalTopK: retrieval.topK
1560
- } : {}
1504
+ ...input.pageContext !== void 0 ? {
1505
+ pageContext: input.pageContext
1561
1506
  } : {}
1507
+ });
1508
+ await store.setQueuePause(threadId, {
1509
+ reason,
1510
+ message,
1511
+ at: (/* @__PURE__ */ new Date()).toISOString()
1512
+ });
1513
+ await store.releaseActiveStream(threadId, next.runId);
1514
+ }
1515
+ /** A `queue` frame with the thread's current state. */
1516
+ async pausedFrame(threadId) {
1517
+ return {
1518
+ kind: "queue",
1519
+ queue: await this.state(threadId)
1562
1520
  };
1563
1521
  }
1564
1522
  /**
1565
- * The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
1566
- * own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
1567
- * `history` window. Undefined → unbounded, the behaviour of a module that configures none.
1523
+ * Tell whoever is watching the thread that its queue changed: a `queue` frame into the stream of
1524
+ * the run holding it. Best-effort — a thread nobody is streaming has no one to tell, and the next
1525
+ * read of the thread carries the queue anyway.
1568
1526
  */
1569
- historyPolicy(definition) {
1570
- if (definition?.history !== void 0) {
1571
- return this.window(definition.history);
1527
+ async publish(threadId) {
1528
+ const state = await this.state(threadId);
1529
+ const holder = await this.queueStore().activeRunForThread(threadId);
1530
+ if (holder !== null) {
1531
+ await this.write(holder, {
1532
+ kind: "queue",
1533
+ queue: state
1534
+ }).catch((error) => {
1535
+ this.logger.warn(`could not publish the queue of thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
1536
+ });
1572
1537
  }
1573
- if (this.options.historyPolicy !== void 0) {
1574
- return this.options.historyPolicy;
1538
+ return state;
1539
+ }
1540
+ async write(runId, frame) {
1541
+ const writer = await this.sink.open(runId);
1542
+ await writer.write(encodeStreamEvent4(frame));
1543
+ }
1544
+ /** Why `actor` may not start a turn now (the quota message), or `null`. */
1545
+ async quotaBlock(actor) {
1546
+ if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
1547
+ return null;
1548
+ }
1549
+ try {
1550
+ const { blocked } = await this.quotaProvider.report({
1551
+ actor
1552
+ });
1553
+ if (blocked === void 0) {
1554
+ return null;
1555
+ }
1556
+ return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
1557
+ } catch {
1558
+ return null;
1575
1559
  }
1576
- return this.options.history === void 0 ? void 0 : this.window(this.options.history);
1577
1560
  }
1578
- /** Build the built-in window policy from the declarative `{ maxMessages, maxTokens, summarize }`. */
1579
- window(config) {
1580
- return windowHistory({
1581
- ...config.maxMessages !== void 0 ? {
1582
- maxMessages: config.maxMessages
1561
+ /**
1562
+ * The turn a queued message starts: everything it was queued with, today's date, and its
1563
+ * attachments' urls minted afresh — the ones stored at enqueue time may have expired while it
1564
+ * waited. An attachment the staging store no longer resolves keeps its stored url.
1565
+ */
1566
+ async inputFor(message) {
1567
+ const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
1568
+ return {
1569
+ threadId: message.threadId,
1570
+ actor: message.actor,
1571
+ userText: message.content,
1572
+ day: utcDay(),
1573
+ ...message.agentName !== void 0 ? {
1574
+ agentName: message.agentName
1583
1575
  } : {},
1584
- ...config.maxTokens !== void 0 ? {
1585
- maxTokens: config.maxTokens
1576
+ ...attachments.length > 0 ? {
1577
+ attachments
1586
1578
  } : {},
1587
- // The shared module model, not a per-agent one: an agent's `modelId` is an accounting label,
1588
- // and only one provider is ever bound.
1589
- ...config.summarize === true ? {
1590
- summarize: summarizeWithModel(this.model)
1579
+ ...message.pageContext !== void 0 ? {
1580
+ pageContext: message.pageContext
1581
+ } : {},
1582
+ ...message.model !== void 0 ? {
1583
+ model: message.model
1591
1584
  } : {}
1592
- });
1585
+ };
1593
1586
  }
1594
- /** Normalize the `followUps` option (`true` → 3, `{ count }` → count) to a number, or undefined. */
1595
- followUpsCount() {
1596
- const followUps = this.options.followUps;
1597
- if (followUps === true) {
1598
- return 3;
1599
- }
1600
- if (typeof followUps === "object") {
1601
- return followUps.count;
1587
+ async freshAttachments(actor, attachments) {
1588
+ const staging = this.staging;
1589
+ if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
1590
+ return attachments;
1602
1591
  }
1603
- return void 0;
1592
+ return Promise.all(attachments.map(async (attachment) => {
1593
+ try {
1594
+ return await staging.resolve({
1595
+ mediaId: attachment.mediaId,
1596
+ actor
1597
+ }) ?? attachment;
1598
+ } catch {
1599
+ return attachment;
1600
+ }
1601
+ }));
1604
1602
  }
1605
1603
  };
1606
- AgentDepsFactory2 = _ts_decorate6([
1604
+ ChatQueueService2 = _ts_decorate6([
1607
1605
  Injectable5(),
1608
- _ts_param5(0, Inject5(AGENT_OPTIONS2)),
1609
- _ts_param5(1, Inject5(AGENT_MODEL)),
1610
- _ts_param5(2, Inject5(AGENT_STORE4)),
1611
- _ts_param5(3, Inject5(AGENT_SINK3)),
1612
- _ts_param5(4, Inject5(AGENT_ROLES_POLICY)),
1613
- _ts_param5(5, Inject5(AGENT_TOOL_REGISTRY)),
1614
- _ts_param5(6, Inject5(AGENT_REGISTRY)),
1615
- _ts_param5(7, Inject5(AGENT_PROMPT_CONTRIBUTORS)),
1616
- _ts_param5(8, Optional4()),
1617
- _ts_param5(8, Inject5(AGENT_PRICING_STORE)),
1618
- _ts_param5(9, Optional4()),
1619
- _ts_param5(9, Inject5(AGENT_SKILLS)),
1620
- _ts_param5(10, Optional4()),
1621
- _ts_param5(10, Inject5(AGENT_MEMORY)),
1606
+ _ts_param5(0, Inject5(AGENT_STORE4)),
1607
+ _ts_param5(1, Inject5(AGENT_SINK3)),
1608
+ _ts_param5(2, Optional4()),
1609
+ _ts_param5(2, Inject5(AGENT_QUOTA_PROVIDER)),
1610
+ _ts_param5(3, Optional4()),
1611
+ _ts_param5(3, Inject5(AGENT_OPTIONS2)),
1612
+ _ts_param5(4, Optional4()),
1613
+ _ts_param5(4, Inject5(AGENT_ATTACHMENT_STAGING)),
1622
1614
  _ts_metadata5("design:type", Function),
1623
1615
  _ts_metadata5("design:paramtypes", [
1624
- typeof AgentModuleOptions === "undefined" ? Object : AgentModuleOptions,
1625
- typeof ModelProvider === "undefined" ? Object : ModelProvider,
1626
1616
  typeof AgentStore === "undefined" ? Object : AgentStore,
1627
1617
  typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
1628
- typeof RolesPolicy === "undefined" ? Object : RolesPolicy,
1629
- typeof ToolRegistry2 === "undefined" ? Object : ToolRegistry2,
1630
- typeof AgentRegistry2 === "undefined" ? Object : AgentRegistry2,
1631
- Array,
1632
- Object,
1633
- Object,
1634
- Object
1618
+ typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
1619
+ typeof Pick === "undefined" ? Object : Pick,
1620
+ typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
1635
1621
  ])
1636
- ], AgentDepsFactory2);
1637
-
1638
- // src/agent.service.ts
1639
- import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS3, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE5, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval, readElicitationQuestions, settleDeadRun as settleDeadRun2, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
1640
- import { BadRequestException, ConflictException as ConflictException2, ForbiddenException, GoneException, HttpException, HttpStatus, Inject as Inject6, Injectable as Injectable6, NotFoundException, NotImplementedException as NotImplementedException2, Optional as Optional5 } from "@nestjs/common";
1622
+ ], ChatQueueService2);
1623
+ function pauseReasonFor(outcome, head) {
1624
+ if (outcome === "failed") {
1625
+ return "run_failed";
1626
+ }
1627
+ if (outcome === "cancelled" && head.interrupt !== true) {
1628
+ return "cancelled";
1629
+ }
1630
+ return null;
1631
+ }
1632
+ __name(pauseReasonFor, "pauseReasonFor");
1633
+ function queueFrame(items, paused, started) {
1634
+ return {
1635
+ kind: "queue",
1636
+ queue: {
1637
+ items: items.map(queuedMessageView),
1638
+ paused
1639
+ },
1640
+ ...started !== void 0 ? {
1641
+ started
1642
+ } : {}
1643
+ };
1644
+ }
1645
+ __name(queueFrame, "queueFrame");
1641
1646
 
1642
1647
  // src/run-not-active.exception.ts
1643
1648
  import { RUN_NOT_ACTIVE_CODE, RUN_NOT_ACTIVE_MESSAGE } from "@dudousxd/nestjs-agent-core";
@@ -1878,6 +1883,9 @@ var AgentService = class {
1878
1883
  actor: params.actor,
1879
1884
  ...params.transient === true ? {
1880
1885
  transient: true
1886
+ } : {},
1887
+ ...params.newThreadId !== void 0 ? {
1888
+ id: params.newThreadId
1881
1889
  } : {}
1882
1890
  });
1883
1891
  threadId = created.id;
@@ -2302,6 +2310,38 @@ var AgentService = class {
2302
2310
  });
2303
2311
  throw new RunNotActiveException(runId);
2304
2312
  }
2313
+ /** Who owns the thread, or `null` when there is no such (live) thread. */
2314
+ async threadOwner(threadId) {
2315
+ return this.store.ownerOfThread(threadId);
2316
+ }
2317
+ /**
2318
+ * May `actor` continue the run whose stream is `runId` — the AG-UI resume? Only the actor whose
2319
+ * thread is streaming it (`403` otherwise), and only while it still is: a run that is over is
2320
+ * waiting on no one (`409 run_not_active`), whatever an old interrupt id says.
2321
+ */
2322
+ async assertResumable(actor, runId) {
2323
+ const owner = await this.store.ownerOfActiveStream(runId);
2324
+ if (owner === null) {
2325
+ throw new RunNotActiveException(runId);
2326
+ }
2327
+ if (owner !== actor.id) {
2328
+ throw new ForbiddenException("run belongs to another actor");
2329
+ }
2330
+ }
2331
+ /**
2332
+ * Everything {@link approve} / {@link reject} check before they signal — who may decide, and
2333
+ * that the request has not lapsed — without signalling. For a caller that settles several
2334
+ * decisions at once and must refuse all of them if one is refused (an AG-UI resume).
2335
+ */
2336
+ async checkDecision(actor, toolCallId2) {
2337
+ await this.assertMayDecide(actor, toolCallId2);
2338
+ await this.assertNotExpired(toolCallId2);
2339
+ }
2340
+ /** Everything {@link answer} checks before it signals, without signalling — see {@link checkDecision}. */
2341
+ async checkAnswer(actor, toolCallId2, answers2) {
2342
+ await this.assertOwnsToolCall(actor, toolCallId2);
2343
+ await this.assertAnswersFit(toolCallId2, answers2);
2344
+ }
2305
2345
  async cancel(actor, runId) {
2306
2346
  await this.assertOwnsActiveStream(actor, runId);
2307
2347
  return this.runner.cancel(runId);
@@ -2694,7 +2734,7 @@ AgentService = _ts_decorate7([
2694
2734
  typeof ModelCatalog === "undefined" ? Object : ModelCatalog,
2695
2735
  typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
2696
2736
  typeof Pick === "undefined" ? Object : Pick,
2697
- typeof ChatQueueService === "undefined" ? Object : ChatQueueService
2737
+ typeof ChatQueueService2 === "undefined" ? Object : ChatQueueService2
2698
2738
  ])
2699
2739
  ], AgentService);
2700
2740
 
@@ -5541,7 +5581,7 @@ InlineAgentRunner = _ts_decorate25([
5541
5581
  _ts_metadata24("design:paramtypes", [
5542
5582
  typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
5543
5583
  typeof AgentStore === "undefined" ? Object : AgentStore,
5544
- typeof ChatQueueService === "undefined" ? Object : ChatQueueService
5584
+ typeof ChatQueueService2 === "undefined" ? Object : ChatQueueService2
5545
5585
  ])
5546
5586
  ], InlineAgentRunner);
5547
5587
  function isThreadTurn2(input) {
@@ -5756,7 +5796,11 @@ function sharedProviders(durable) {
5756
5796
  SkillDiscoveryService,
5757
5797
  // The thread message queue — every surface, since both runners and the durable workflow settle
5758
5798
  // into it, and the chat routes enqueue into it.
5759
- ChatQueueService,
5799
+ ChatQueueService2,
5800
+ {
5801
+ provide: AGENT_CHAT_QUEUE,
5802
+ useExisting: ChatQueueService2
5803
+ },
5760
5804
  InlineAgentRunner,
5761
5805
  AgentService,
5762
5806
  // Bound ALWAYS (durable or inline) — the console's cross-thread approvals inbox routes decisions
@@ -5818,7 +5862,8 @@ function exportsFor() {
5818
5862
  AGENT_DEPS_FACTORY7,
5819
5863
  AgentDepsFactory2,
5820
5864
  AgentService,
5821
- ChatQueueService,
5865
+ ChatQueueService2,
5866
+ AGENT_CHAT_QUEUE,
5822
5867
  InlineAgentRunner,
5823
5868
  AGENT_APPROVAL_PORT
5824
5869
  ];
@@ -5839,15 +5884,28 @@ var BASE_CONTROLLERS = [
5839
5884
  ConfigController,
5840
5885
  AttachmentsController
5841
5886
  ];
5842
- var GUARDABLE_CONTROLLERS = BASE_CONTROLLERS;
5843
- function controllersForSurface(surface) {
5844
- return surface === "engine" ? [] : BASE_CONTROLLERS;
5887
+ function adapterControllers(adapters) {
5888
+ return (adapters ?? []).flatMap((adapter) => adapter.controllers);
5889
+ }
5890
+ __name(adapterControllers, "adapterControllers");
5891
+ function adapterProviders(adapters) {
5892
+ return (adapters ?? []).flatMap((adapter) => adapter.providers ?? []);
5893
+ }
5894
+ __name(adapterProviders, "adapterProviders");
5895
+ function controllersForSurface(surface, adapters) {
5896
+ return surface === "engine" ? [] : [
5897
+ ...BASE_CONTROLLERS,
5898
+ ...adapterControllers(adapters)
5899
+ ];
5845
5900
  }
5846
5901
  __name(controllersForSurface, "controllersForSurface");
5847
5902
  var GUARDS_METADATA = "__guards__";
5848
- function applyGuards(guards) {
5903
+ function applyGuards(guards, adapters) {
5849
5904
  const resolved = guards ?? [];
5850
- for (const controller of GUARDABLE_CONTROLLERS) {
5905
+ for (const controller of [
5906
+ ...BASE_CONTROLLERS,
5907
+ ...adapterControllers(adapters)
5908
+ ]) {
5851
5909
  Reflect.defineMetadata(GUARDS_METADATA, resolved, controller);
5852
5910
  }
5853
5911
  }
@@ -5873,7 +5931,7 @@ var AgentModule = class _AgentModule {
5873
5931
  }
5874
5932
  static forRoot(options) {
5875
5933
  const path = options.path ?? DEFAULT_PATH;
5876
- applyGuards(options.guards);
5934
+ applyGuards(options.guards, options.adapters);
5877
5935
  return {
5878
5936
  module: _AgentModule,
5879
5937
  global: true,
@@ -5881,21 +5939,22 @@ var AgentModule = class _AgentModule {
5881
5939
  DiscoveryModule,
5882
5940
  routerFor(path)
5883
5941
  ],
5884
- controllers: controllersForSurface(options.surface),
5942
+ controllers: controllersForSurface(options.surface, options.adapters),
5885
5943
  providers: [
5886
5944
  {
5887
5945
  provide: AGENT_OPTIONS7,
5888
5946
  useValue: options
5889
5947
  },
5890
5948
  ...sharedProviders(options.durable ?? false),
5891
- ...guardProviders(options.guards)
5949
+ ...guardProviders(options.guards),
5950
+ ...adapterProviders(options.adapters)
5892
5951
  ],
5893
5952
  exports: exportsFor()
5894
5953
  };
5895
5954
  }
5896
5955
  static forRootAsync(options) {
5897
5956
  const path = options.path ?? DEFAULT_PATH;
5898
- applyGuards(options.guards);
5957
+ applyGuards(options.guards, options.adapters);
5899
5958
  return {
5900
5959
  module: _AgentModule,
5901
5960
  global: true,
@@ -5904,7 +5963,7 @@ var AgentModule = class _AgentModule {
5904
5963
  routerFor(path),
5905
5964
  ...options.imports ?? []
5906
5965
  ],
5907
- controllers: controllersForSurface(options.surface),
5966
+ controllers: controllersForSurface(options.surface, options.adapters),
5908
5967
  providers: [
5909
5968
  {
5910
5969
  provide: AGENT_OPTIONS7,
@@ -5922,7 +5981,8 @@ var AgentModule = class _AgentModule {
5922
5981
  inject: options.inject ?? []
5923
5982
  },
5924
5983
  ...sharedProviders(options.durable ?? false),
5925
- ...guardProviders(options.guards)
5984
+ ...guardProviders(options.guards),
5985
+ ...adapterProviders(options.adapters)
5926
5986
  ],
5927
5987
  exports: exportsFor()
5928
5988
  };